This will be toggled as well
+```
diff --git a/frontend/src/utils/tooltips/tooltips.js b/frontend/src/utils/tooltips/tooltips.js
index 1bf275e43..b773eef39 100644
--- a/frontend/src/utils/tooltips/tooltips.js
+++ b/frontend/src/utils/tooltips/tooltips.js
@@ -1,7 +1,10 @@
+import { Utility } from '../../core/utility';
import './tooltips.scss';
-export default {
- name: 'tooltips',
+// empty 'shell' to be able to load styles
+@Utility({
selector: '[not-something-that-would-be-found]',
- setup: () => {},
+})
+export class Tooltip {
+ destroy() {}
};
diff --git a/frontend/src/utils/tooltips/tooltips.scss b/frontend/src/utils/tooltips/tooltips.scss
index ed6b5fd5d..5e745bc5b 100644
--- a/frontend/src/utils/tooltips/tooltips.scss
+++ b/frontend/src/utils/tooltips/tooltips.scss
@@ -28,7 +28,7 @@
position: absolute;
top: 0;
left: 0;
- font-family: "Font Awesome 5 Free";
+ font-family: 'Font Awesome 5 Free';
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js
index 05c430456..04e77ec38 100644
--- a/frontend/src/utils/utils.js
+++ b/frontend/src/utils/utils.js
@@ -1,38 +1,26 @@
-import alerts from './alerts/alerts';
-import asidenav from './asidenav/asidenav';
-import asyncForm from './async-form/async-form';
-import asyncTable from './async-table/async-table';
-import checkAll from './check-all/check-all';
-import massInput from './mass-input/mass-input';
-import { fileInput, checkbox } from './inputs/inputs';
-import modal from './modal/modal';
-import showHide from './show-hide/show-hide';
-import {
- interactiveFieldset,
- navigateAwayPrompt,
- autoSubmitButton,
- autoSubmitInput,
- formErrorRemover,
- datepicker,
-} from './form/form';
-import tooltips from './tooltips/tooltips';
+import { Alerts } from './alerts/alerts';
+import { Asidenav } from './asidenav/asidenav';
+import { AsyncForm } from './async-form/async-form';
+import { ShowHide } from './show-hide/show-hide';
+import { AsyncTable } from './async-table/async-table';
+import { CheckAll } from './check-all/check-all';
+import { FormUtils } from './form/form';
+import { InputUtils } from './inputs/inputs';
+import { MassInput } from './mass-input/mass-input';
+import { Modal } from './modal/modal';
+import { Tooltip } from './tooltips/tooltips';
export const Utils = [
- alerts,
- asidenav,
- asyncForm,
- asyncTable,
- checkAll,
- massInput,
- fileInput,
- checkbox,
- modal,
- showHide,
- interactiveFieldset,
- navigateAwayPrompt,
- autoSubmitButton,
- autoSubmitInput,
- formErrorRemover,
- datepicker,
- tooltips,
+ Alerts,
+ Asidenav,
+ AsyncForm,
+ AsyncTable,
+ CheckAll,
+ ShowHide,
+ ...FormUtils,
+ ...InputUtils,
+ MassInput,
+ Modal,
+ ShowHide,
+ Tooltip,
];
diff --git a/frontend/vendor/flatpickr.css b/frontend/vendor/flatpickr.css
index 2e9c517e9..64e91f696 100644
--- a/frontend/vendor/flatpickr.css
+++ b/frontend/vendor/flatpickr.css
@@ -254,7 +254,7 @@
}
.numInputWrapper span:after {
display: block;
- content: "";
+ content: '';
position: absolute;
}
.numInputWrapper span.arrowUp {
@@ -628,7 +628,7 @@ span.flatpickr-weekday {
display: flex;
}
.flatpickr-time:after {
- content: "";
+ content: '';
display: table;
clear: both;
}
diff --git a/frontend/vendor/fontawesome.css b/frontend/vendor/fontawesome.css
index 68b26ef9b..70c4ba2dd 100644
--- a/frontend/vendor/fontawesome.css
+++ b/frontend/vendor/fontawesome.css
@@ -2,4 +2,4 @@
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
-.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-moving:before{content:"\f4df"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}
\ No newline at end of file
+.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';transform:rotate(90deg)}.fa-rotate-180{-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';transform:rotate(180deg)}.fa-rotate-270{-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)'}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:'\f26e'}.fa-accessible-icon:before{content:'\f368'}.fa-accusoft:before{content:'\f369'}.fa-address-book:before{content:'\f2b9'}.fa-address-card:before{content:'\f2bb'}.fa-adjust:before{content:'\f042'}.fa-adn:before{content:'\f170'}.fa-adversal:before{content:'\f36a'}.fa-affiliatetheme:before{content:'\f36b'}.fa-algolia:before{content:'\f36c'}.fa-align-center:before{content:'\f037'}.fa-align-justify:before{content:'\f039'}.fa-align-left:before{content:'\f036'}.fa-align-right:before{content:'\f038'}.fa-allergies:before{content:'\f461'}.fa-amazon:before{content:'\f270'}.fa-amazon-pay:before{content:'\f42c'}.fa-ambulance:before{content:'\f0f9'}.fa-american-sign-language-interpreting:before{content:'\f2a3'}.fa-amilia:before{content:'\f36d'}.fa-anchor:before{content:'\f13d'}.fa-android:before{content:'\f17b'}.fa-angellist:before{content:'\f209'}.fa-angle-double-down:before{content:'\f103'}.fa-angle-double-left:before{content:'\f100'}.fa-angle-double-right:before{content:'\f101'}.fa-angle-double-up:before{content:'\f102'}.fa-angle-down:before{content:'\f107'}.fa-angle-left:before{content:'\f104'}.fa-angle-right:before{content:'\f105'}.fa-angle-up:before{content:'\f106'}.fa-angry:before{content:'\f556'}.fa-angrycreative:before{content:'\f36e'}.fa-angular:before{content:'\f420'}.fa-app-store:before{content:'\f36f'}.fa-app-store-ios:before{content:'\f370'}.fa-apper:before{content:'\f371'}.fa-apple:before{content:'\f179'}.fa-apple-pay:before{content:'\f415'}.fa-archive:before{content:'\f187'}.fa-archway:before{content:'\f557'}.fa-arrow-alt-circle-down:before{content:'\f358'}.fa-arrow-alt-circle-left:before{content:'\f359'}.fa-arrow-alt-circle-right:before{content:'\f35a'}.fa-arrow-alt-circle-up:before{content:'\f35b'}.fa-arrow-circle-down:before{content:'\f0ab'}.fa-arrow-circle-left:before{content:'\f0a8'}.fa-arrow-circle-right:before{content:'\f0a9'}.fa-arrow-circle-up:before{content:'\f0aa'}.fa-arrow-down:before{content:'\f063'}.fa-arrow-left:before{content:'\f060'}.fa-arrow-right:before{content:'\f061'}.fa-arrow-up:before{content:'\f062'}.fa-arrows-alt:before{content:'\f0b2'}.fa-arrows-alt-h:before{content:'\f337'}.fa-arrows-alt-v:before{content:'\f338'}.fa-assistive-listening-systems:before{content:'\f2a2'}.fa-asterisk:before{content:'\f069'}.fa-asymmetrik:before{content:'\f372'}.fa-at:before{content:'\f1fa'}.fa-atlas:before{content:'\f558'}.fa-audible:before{content:'\f373'}.fa-audio-description:before{content:'\f29e'}.fa-autoprefixer:before{content:'\f41c'}.fa-avianex:before{content:'\f374'}.fa-aviato:before{content:'\f421'}.fa-award:before{content:'\f559'}.fa-aws:before{content:'\f375'}.fa-backspace:before{content:'\f55a'}.fa-backward:before{content:'\f04a'}.fa-balance-scale:before{content:'\f24e'}.fa-ban:before{content:'\f05e'}.fa-band-aid:before{content:'\f462'}.fa-bandcamp:before{content:'\f2d5'}.fa-barcode:before{content:'\f02a'}.fa-bars:before{content:'\f0c9'}.fa-baseball-ball:before{content:'\f433'}.fa-basketball-ball:before{content:'\f434'}.fa-bath:before{content:'\f2cd'}.fa-battery-empty:before{content:'\f244'}.fa-battery-full:before{content:'\f240'}.fa-battery-half:before{content:'\f242'}.fa-battery-quarter:before{content:'\f243'}.fa-battery-three-quarters:before{content:'\f241'}.fa-bed:before{content:'\f236'}.fa-beer:before{content:'\f0fc'}.fa-behance:before{content:'\f1b4'}.fa-behance-square:before{content:'\f1b5'}.fa-bell:before{content:'\f0f3'}.fa-bell-slash:before{content:'\f1f6'}.fa-bezier-curve:before{content:'\f55b'}.fa-bicycle:before{content:'\f206'}.fa-bimobject:before{content:'\f378'}.fa-binoculars:before{content:'\f1e5'}.fa-birthday-cake:before{content:'\f1fd'}.fa-bitbucket:before{content:'\f171'}.fa-bitcoin:before{content:'\f379'}.fa-bity:before{content:'\f37a'}.fa-black-tie:before{content:'\f27e'}.fa-blackberry:before{content:'\f37b'}.fa-blender:before{content:'\f517'}.fa-blind:before{content:'\f29d'}.fa-blogger:before{content:'\f37c'}.fa-blogger-b:before{content:'\f37d'}.fa-bluetooth:before{content:'\f293'}.fa-bluetooth-b:before{content:'\f294'}.fa-bold:before{content:'\f032'}.fa-bolt:before{content:'\f0e7'}.fa-bomb:before{content:'\f1e2'}.fa-bong:before{content:'\f55c'}.fa-book:before{content:'\f02d'}.fa-book-open:before{content:'\f518'}.fa-bookmark:before{content:'\f02e'}.fa-bowling-ball:before{content:'\f436'}.fa-box:before{content:'\f466'}.fa-box-open:before{content:'\f49e'}.fa-boxes:before{content:'\f468'}.fa-braille:before{content:'\f2a1'}.fa-briefcase:before{content:'\f0b1'}.fa-briefcase-medical:before{content:'\f469'}.fa-broadcast-tower:before{content:'\f519'}.fa-broom:before{content:'\f51a'}.fa-brush:before{content:'\f55d'}.fa-btc:before{content:'\f15a'}.fa-bug:before{content:'\f188'}.fa-building:before{content:'\f1ad'}.fa-bullhorn:before{content:'\f0a1'}.fa-bullseye:before{content:'\f140'}.fa-burn:before{content:'\f46a'}.fa-buromobelexperte:before{content:'\f37f'}.fa-bus:before{content:'\f207'}.fa-bus-alt:before{content:'\f55e'}.fa-buysellads:before{content:'\f20d'}.fa-calculator:before{content:'\f1ec'}.fa-calendar:before{content:'\f133'}.fa-calendar-alt:before{content:'\f073'}.fa-calendar-check:before{content:'\f274'}.fa-calendar-minus:before{content:'\f272'}.fa-calendar-plus:before{content:'\f271'}.fa-calendar-times:before{content:'\f273'}.fa-camera:before{content:'\f030'}.fa-camera-retro:before{content:'\f083'}.fa-cannabis:before{content:'\f55f'}.fa-capsules:before{content:'\f46b'}.fa-car:before{content:'\f1b9'}.fa-caret-down:before{content:'\f0d7'}.fa-caret-left:before{content:'\f0d9'}.fa-caret-right:before{content:'\f0da'}.fa-caret-square-down:before{content:'\f150'}.fa-caret-square-left:before{content:'\f191'}.fa-caret-square-right:before{content:'\f152'}.fa-caret-square-up:before{content:'\f151'}.fa-caret-up:before{content:'\f0d8'}.fa-cart-arrow-down:before{content:'\f218'}.fa-cart-plus:before{content:'\f217'}.fa-cc-amazon-pay:before{content:'\f42d'}.fa-cc-amex:before{content:'\f1f3'}.fa-cc-apple-pay:before{content:'\f416'}.fa-cc-diners-club:before{content:'\f24c'}.fa-cc-discover:before{content:'\f1f2'}.fa-cc-jcb:before{content:'\f24b'}.fa-cc-mastercard:before{content:'\f1f1'}.fa-cc-paypal:before{content:'\f1f4'}.fa-cc-stripe:before{content:'\f1f5'}.fa-cc-visa:before{content:'\f1f0'}.fa-centercode:before{content:'\f380'}.fa-certificate:before{content:'\f0a3'}.fa-chalkboard:before{content:'\f51b'}.fa-chalkboard-teacher:before{content:'\f51c'}.fa-chart-area:before{content:'\f1fe'}.fa-chart-bar:before{content:'\f080'}.fa-chart-line:before{content:'\f201'}.fa-chart-pie:before{content:'\f200'}.fa-check:before{content:'\f00c'}.fa-check-circle:before{content:'\f058'}.fa-check-double:before{content:'\f560'}.fa-check-square:before{content:'\f14a'}.fa-chess:before{content:'\f439'}.fa-chess-bishop:before{content:'\f43a'}.fa-chess-board:before{content:'\f43c'}.fa-chess-king:before{content:'\f43f'}.fa-chess-knight:before{content:'\f441'}.fa-chess-pawn:before{content:'\f443'}.fa-chess-queen:before{content:'\f445'}.fa-chess-rook:before{content:'\f447'}.fa-chevron-circle-down:before{content:'\f13a'}.fa-chevron-circle-left:before{content:'\f137'}.fa-chevron-circle-right:before{content:'\f138'}.fa-chevron-circle-up:before{content:'\f139'}.fa-chevron-down:before{content:'\f078'}.fa-chevron-left:before{content:'\f053'}.fa-chevron-right:before{content:'\f054'}.fa-chevron-up:before{content:'\f077'}.fa-child:before{content:'\f1ae'}.fa-chrome:before{content:'\f268'}.fa-church:before{content:'\f51d'}.fa-circle:before{content:'\f111'}.fa-circle-notch:before{content:'\f1ce'}.fa-clipboard:before{content:'\f328'}.fa-clipboard-check:before{content:'\f46c'}.fa-clipboard-list:before{content:'\f46d'}.fa-clock:before{content:'\f017'}.fa-clone:before{content:'\f24d'}.fa-closed-captioning:before{content:'\f20a'}.fa-cloud:before{content:'\f0c2'}.fa-cloud-download-alt:before{content:'\f381'}.fa-cloud-upload-alt:before{content:'\f382'}.fa-cloudscale:before{content:'\f383'}.fa-cloudsmith:before{content:'\f384'}.fa-cloudversify:before{content:'\f385'}.fa-cocktail:before{content:'\f561'}.fa-code:before{content:'\f121'}.fa-code-branch:before{content:'\f126'}.fa-codepen:before{content:'\f1cb'}.fa-codiepie:before{content:'\f284'}.fa-coffee:before{content:'\f0f4'}.fa-cog:before{content:'\f013'}.fa-cogs:before{content:'\f085'}.fa-coins:before{content:'\f51e'}.fa-columns:before{content:'\f0db'}.fa-comment:before{content:'\f075'}.fa-comment-alt:before{content:'\f27a'}.fa-comment-dots:before{content:'\f4ad'}.fa-comment-slash:before{content:'\f4b3'}.fa-comments:before{content:'\f086'}.fa-compact-disc:before{content:'\f51f'}.fa-compass:before{content:'\f14e'}.fa-compress:before{content:'\f066'}.fa-concierge-bell:before{content:'\f562'}.fa-connectdevelop:before{content:'\f20e'}.fa-contao:before{content:'\f26d'}.fa-cookie:before{content:'\f563'}.fa-cookie-bite:before{content:'\f564'}.fa-copy:before{content:'\f0c5'}.fa-copyright:before{content:'\f1f9'}.fa-couch:before{content:'\f4b8'}.fa-cpanel:before{content:'\f388'}.fa-creative-commons:before{content:'\f25e'}.fa-creative-commons-by:before{content:'\f4e7'}.fa-creative-commons-nc:before{content:'\f4e8'}.fa-creative-commons-nc-eu:before{content:'\f4e9'}.fa-creative-commons-nc-jp:before{content:'\f4ea'}.fa-creative-commons-nd:before{content:'\f4eb'}.fa-creative-commons-pd:before{content:'\f4ec'}.fa-creative-commons-pd-alt:before{content:'\f4ed'}.fa-creative-commons-remix:before{content:'\f4ee'}.fa-creative-commons-sa:before{content:'\f4ef'}.fa-creative-commons-sampling:before{content:'\f4f0'}.fa-creative-commons-sampling-plus:before{content:'\f4f1'}.fa-creative-commons-share:before{content:'\f4f2'}.fa-credit-card:before{content:'\f09d'}.fa-crop:before{content:'\f125'}.fa-crop-alt:before{content:'\f565'}.fa-crosshairs:before{content:'\f05b'}.fa-crow:before{content:'\f520'}.fa-crown:before{content:'\f521'}.fa-css3:before{content:'\f13c'}.fa-css3-alt:before{content:'\f38b'}.fa-cube:before{content:'\f1b2'}.fa-cubes:before{content:'\f1b3'}.fa-cut:before{content:'\f0c4'}.fa-cuttlefish:before{content:'\f38c'}.fa-d-and-d:before{content:'\f38d'}.fa-dashcube:before{content:'\f210'}.fa-database:before{content:'\f1c0'}.fa-deaf:before{content:'\f2a4'}.fa-delicious:before{content:'\f1a5'}.fa-deploydog:before{content:'\f38e'}.fa-deskpro:before{content:'\f38f'}.fa-desktop:before{content:'\f108'}.fa-deviantart:before{content:'\f1bd'}.fa-diagnoses:before{content:'\f470'}.fa-dice:before{content:'\f522'}.fa-dice-five:before{content:'\f523'}.fa-dice-four:before{content:'\f524'}.fa-dice-one:before{content:'\f525'}.fa-dice-six:before{content:'\f526'}.fa-dice-three:before{content:'\f527'}.fa-dice-two:before{content:'\f528'}.fa-digg:before{content:'\f1a6'}.fa-digital-ocean:before{content:'\f391'}.fa-digital-tachograph:before{content:'\f566'}.fa-discord:before{content:'\f392'}.fa-discourse:before{content:'\f393'}.fa-divide:before{content:'\f529'}.fa-dizzy:before{content:'\f567'}.fa-dna:before{content:'\f471'}.fa-dochub:before{content:'\f394'}.fa-docker:before{content:'\f395'}.fa-dollar-sign:before{content:'\f155'}.fa-dolly:before{content:'\f472'}.fa-dolly-flatbed:before{content:'\f474'}.fa-donate:before{content:'\f4b9'}.fa-door-closed:before{content:'\f52a'}.fa-door-open:before{content:'\f52b'}.fa-dot-circle:before{content:'\f192'}.fa-dove:before{content:'\f4ba'}.fa-download:before{content:'\f019'}.fa-draft2digital:before{content:'\f396'}.fa-drafting-compass:before{content:'\f568'}.fa-dribbble:before{content:'\f17d'}.fa-dribbble-square:before{content:'\f397'}.fa-dropbox:before{content:'\f16b'}.fa-drum:before{content:'\f569'}.fa-drum-steelpan:before{content:'\f56a'}.fa-drupal:before{content:'\f1a9'}.fa-dumbbell:before{content:'\f44b'}.fa-dyalog:before{content:'\f399'}.fa-earlybirds:before{content:'\f39a'}.fa-ebay:before{content:'\f4f4'}.fa-edge:before{content:'\f282'}.fa-edit:before{content:'\f044'}.fa-eject:before{content:'\f052'}.fa-elementor:before{content:'\f430'}.fa-ellipsis-h:before{content:'\f141'}.fa-ellipsis-v:before{content:'\f142'}.fa-ember:before{content:'\f423'}.fa-empire:before{content:'\f1d1'}.fa-envelope:before{content:'\f0e0'}.fa-envelope-open:before{content:'\f2b6'}.fa-envelope-square:before{content:'\f199'}.fa-envira:before{content:'\f299'}.fa-equals:before{content:'\f52c'}.fa-eraser:before{content:'\f12d'}.fa-erlang:before{content:'\f39d'}.fa-ethereum:before{content:'\f42e'}.fa-etsy:before{content:'\f2d7'}.fa-euro-sign:before{content:'\f153'}.fa-exchange-alt:before{content:'\f362'}.fa-exclamation:before{content:'\f12a'}.fa-exclamation-circle:before{content:'\f06a'}.fa-exclamation-triangle:before{content:'\f071'}.fa-expand:before{content:'\f065'}.fa-expand-arrows-alt:before{content:'\f31e'}.fa-expeditedssl:before{content:'\f23e'}.fa-external-link-alt:before{content:'\f35d'}.fa-external-link-square-alt:before{content:'\f360'}.fa-eye:before{content:'\f06e'}.fa-eye-dropper:before{content:'\f1fb'}.fa-eye-slash:before{content:'\f070'}.fa-facebook:before{content:'\f09a'}.fa-facebook-f:before{content:'\f39e'}.fa-facebook-messenger:before{content:'\f39f'}.fa-facebook-square:before{content:'\f082'}.fa-fast-backward:before{content:'\f049'}.fa-fast-forward:before{content:'\f050'}.fa-fax:before{content:'\f1ac'}.fa-feather:before{content:'\f52d'}.fa-feather-alt:before{content:'\f56b'}.fa-female:before{content:'\f182'}.fa-fighter-jet:before{content:'\f0fb'}.fa-file:before{content:'\f15b'}.fa-file-alt:before{content:'\f15c'}.fa-file-archive:before{content:'\f1c6'}.fa-file-audio:before{content:'\f1c7'}.fa-file-code:before{content:'\f1c9'}.fa-file-contract:before{content:'\f56c'}.fa-file-download:before{content:'\f56d'}.fa-file-excel:before{content:'\f1c3'}.fa-file-export:before{content:'\f56e'}.fa-file-image:before{content:'\f1c5'}.fa-file-import:before{content:'\f56f'}.fa-file-invoice:before{content:'\f570'}.fa-file-invoice-dollar:before{content:'\f571'}.fa-file-medical:before{content:'\f477'}.fa-file-medical-alt:before{content:'\f478'}.fa-file-pdf:before{content:'\f1c1'}.fa-file-powerpoint:before{content:'\f1c4'}.fa-file-prescription:before{content:'\f572'}.fa-file-signature:before{content:'\f573'}.fa-file-upload:before{content:'\f574'}.fa-file-video:before{content:'\f1c8'}.fa-file-word:before{content:'\f1c2'}.fa-fill:before{content:'\f575'}.fa-fill-drip:before{content:'\f576'}.fa-film:before{content:'\f008'}.fa-filter:before{content:'\f0b0'}.fa-fingerprint:before{content:'\f577'}.fa-fire:before{content:'\f06d'}.fa-fire-extinguisher:before{content:'\f134'}.fa-firefox:before{content:'\f269'}.fa-first-aid:before{content:'\f479'}.fa-first-order:before{content:'\f2b0'}.fa-first-order-alt:before{content:'\f50a'}.fa-firstdraft:before{content:'\f3a1'}.fa-fish:before{content:'\f578'}.fa-flag:before{content:'\f024'}.fa-flag-checkered:before{content:'\f11e'}.fa-flask:before{content:'\f0c3'}.fa-flickr:before{content:'\f16e'}.fa-flipboard:before{content:'\f44d'}.fa-flushed:before{content:'\f579'}.fa-fly:before{content:'\f417'}.fa-folder:before{content:'\f07b'}.fa-folder-open:before{content:'\f07c'}.fa-font:before{content:'\f031'}.fa-font-awesome:before{content:'\f2b4'}.fa-font-awesome-alt:before{content:'\f35c'}.fa-font-awesome-flag:before{content:'\f425'}.fa-font-awesome-logo-full:before{content:'\f4e6'}.fa-fonticons:before{content:'\f280'}.fa-fonticons-fi:before{content:'\f3a2'}.fa-football-ball:before{content:'\f44e'}.fa-fort-awesome:before{content:'\f286'}.fa-fort-awesome-alt:before{content:'\f3a3'}.fa-forumbee:before{content:'\f211'}.fa-forward:before{content:'\f04e'}.fa-foursquare:before{content:'\f180'}.fa-free-code-camp:before{content:'\f2c5'}.fa-freebsd:before{content:'\f3a4'}.fa-frog:before{content:'\f52e'}.fa-frown:before{content:'\f119'}.fa-frown-open:before{content:'\f57a'}.fa-fulcrum:before{content:'\f50b'}.fa-futbol:before{content:'\f1e3'}.fa-galactic-republic:before{content:'\f50c'}.fa-galactic-senate:before{content:'\f50d'}.fa-gamepad:before{content:'\f11b'}.fa-gas-pump:before{content:'\f52f'}.fa-gavel:before{content:'\f0e3'}.fa-gem:before{content:'\f3a5'}.fa-genderless:before{content:'\f22d'}.fa-get-pocket:before{content:'\f265'}.fa-gg:before{content:'\f260'}.fa-gg-circle:before{content:'\f261'}.fa-gift:before{content:'\f06b'}.fa-git:before{content:'\f1d3'}.fa-git-square:before{content:'\f1d2'}.fa-github:before{content:'\f09b'}.fa-github-alt:before{content:'\f113'}.fa-github-square:before{content:'\f092'}.fa-gitkraken:before{content:'\f3a6'}.fa-gitlab:before{content:'\f296'}.fa-gitter:before{content:'\f426'}.fa-glass-martini:before{content:'\f000'}.fa-glass-martini-alt:before{content:'\f57b'}.fa-glasses:before{content:'\f530'}.fa-glide:before{content:'\f2a5'}.fa-glide-g:before{content:'\f2a6'}.fa-globe:before{content:'\f0ac'}.fa-globe-africa:before{content:'\f57c'}.fa-globe-americas:before{content:'\f57d'}.fa-globe-asia:before{content:'\f57e'}.fa-gofore:before{content:'\f3a7'}.fa-golf-ball:before{content:'\f450'}.fa-goodreads:before{content:'\f3a8'}.fa-goodreads-g:before{content:'\f3a9'}.fa-google:before{content:'\f1a0'}.fa-google-drive:before{content:'\f3aa'}.fa-google-play:before{content:'\f3ab'}.fa-google-plus:before{content:'\f2b3'}.fa-google-plus-g:before{content:'\f0d5'}.fa-google-plus-square:before{content:'\f0d4'}.fa-google-wallet:before{content:'\f1ee'}.fa-graduation-cap:before{content:'\f19d'}.fa-gratipay:before{content:'\f184'}.fa-grav:before{content:'\f2d6'}.fa-greater-than:before{content:'\f531'}.fa-greater-than-equal:before{content:'\f532'}.fa-grimace:before{content:'\f57f'}.fa-grin:before{content:'\f580'}.fa-grin-alt:before{content:'\f581'}.fa-grin-beam:before{content:'\f582'}.fa-grin-beam-sweat:before{content:'\f583'}.fa-grin-hearts:before{content:'\f584'}.fa-grin-squint:before{content:'\f585'}.fa-grin-squint-tears:before{content:'\f586'}.fa-grin-stars:before{content:'\f587'}.fa-grin-tears:before{content:'\f588'}.fa-grin-tongue:before{content:'\f589'}.fa-grin-tongue-squint:before{content:'\f58a'}.fa-grin-tongue-wink:before{content:'\f58b'}.fa-grin-wink:before{content:'\f58c'}.fa-grip-horizontal:before{content:'\f58d'}.fa-grip-vertical:before{content:'\f58e'}.fa-gripfire:before{content:'\f3ac'}.fa-grunt:before{content:'\f3ad'}.fa-gulp:before{content:'\f3ae'}.fa-h-square:before{content:'\f0fd'}.fa-hacker-news:before{content:'\f1d4'}.fa-hacker-news-square:before{content:'\f3af'}.fa-hand-holding:before{content:'\f4bd'}.fa-hand-holding-heart:before{content:'\f4be'}.fa-hand-holding-usd:before{content:'\f4c0'}.fa-hand-lizard:before{content:'\f258'}.fa-hand-paper:before{content:'\f256'}.fa-hand-peace:before{content:'\f25b'}.fa-hand-point-down:before{content:'\f0a7'}.fa-hand-point-left:before{content:'\f0a5'}.fa-hand-point-right:before{content:'\f0a4'}.fa-hand-point-up:before{content:'\f0a6'}.fa-hand-pointer:before{content:'\f25a'}.fa-hand-rock:before{content:'\f255'}.fa-hand-scissors:before{content:'\f257'}.fa-hand-spock:before{content:'\f259'}.fa-hands:before{content:'\f4c2'}.fa-hands-helping:before{content:'\f4c4'}.fa-handshake:before{content:'\f2b5'}.fa-hashtag:before{content:'\f292'}.fa-hdd:before{content:'\f0a0'}.fa-heading:before{content:'\f1dc'}.fa-headphones:before{content:'\f025'}.fa-headphones-alt:before{content:'\f58f'}.fa-headset:before{content:'\f590'}.fa-heart:before{content:'\f004'}.fa-heartbeat:before{content:'\f21e'}.fa-helicopter:before{content:'\f533'}.fa-highlighter:before{content:'\f591'}.fa-hips:before{content:'\f452'}.fa-hire-a-helper:before{content:'\f3b0'}.fa-history:before{content:'\f1da'}.fa-hockey-puck:before{content:'\f453'}.fa-home:before{content:'\f015'}.fa-hooli:before{content:'\f427'}.fa-hornbill:before{content:'\f592'}.fa-hospital:before{content:'\f0f8'}.fa-hospital-alt:before{content:'\f47d'}.fa-hospital-symbol:before{content:'\f47e'}.fa-hot-tub:before{content:'\f593'}.fa-hotel:before{content:'\f594'}.fa-hotjar:before{content:'\f3b1'}.fa-hourglass:before{content:'\f254'}.fa-hourglass-end:before{content:'\f253'}.fa-hourglass-half:before{content:'\f252'}.fa-hourglass-start:before{content:'\f251'}.fa-houzz:before{content:'\f27c'}.fa-html5:before{content:'\f13b'}.fa-hubspot:before{content:'\f3b2'}.fa-i-cursor:before{content:'\f246'}.fa-id-badge:before{content:'\f2c1'}.fa-id-card:before{content:'\f2c2'}.fa-id-card-alt:before{content:'\f47f'}.fa-image:before{content:'\f03e'}.fa-images:before{content:'\f302'}.fa-imdb:before{content:'\f2d8'}.fa-inbox:before{content:'\f01c'}.fa-indent:before{content:'\f03c'}.fa-industry:before{content:'\f275'}.fa-infinity:before{content:'\f534'}.fa-info:before{content:'\f129'}.fa-info-circle:before{content:'\f05a'}.fa-instagram:before{content:'\f16d'}.fa-internet-explorer:before{content:'\f26b'}.fa-ioxhost:before{content:'\f208'}.fa-italic:before{content:'\f033'}.fa-itunes:before{content:'\f3b4'}.fa-itunes-note:before{content:'\f3b5'}.fa-java:before{content:'\f4e4'}.fa-jedi-order:before{content:'\f50e'}.fa-jenkins:before{content:'\f3b6'}.fa-joget:before{content:'\f3b7'}.fa-joint:before{content:'\f595'}.fa-joomla:before{content:'\f1aa'}.fa-js:before{content:'\f3b8'}.fa-js-square:before{content:'\f3b9'}.fa-jsfiddle:before{content:'\f1cc'}.fa-key:before{content:'\f084'}.fa-keybase:before{content:'\f4f5'}.fa-keyboard:before{content:'\f11c'}.fa-keycdn:before{content:'\f3ba'}.fa-kickstarter:before{content:'\f3bb'}.fa-kickstarter-k:before{content:'\f3bc'}.fa-kiss:before{content:'\f596'}.fa-kiss-beam:before{content:'\f597'}.fa-kiss-wink-heart:before{content:'\f598'}.fa-kiwi-bird:before{content:'\f535'}.fa-korvue:before{content:'\f42f'}.fa-language:before{content:'\f1ab'}.fa-laptop:before{content:'\f109'}.fa-laravel:before{content:'\f3bd'}.fa-lastfm:before{content:'\f202'}.fa-lastfm-square:before{content:'\f203'}.fa-laugh:before{content:'\f599'}.fa-laugh-beam:before{content:'\f59a'}.fa-laugh-squint:before{content:'\f59b'}.fa-laugh-wink:before{content:'\f59c'}.fa-leaf:before{content:'\f06c'}.fa-leanpub:before{content:'\f212'}.fa-lemon:before{content:'\f094'}.fa-less:before{content:'\f41d'}.fa-less-than:before{content:'\f536'}.fa-less-than-equal:before{content:'\f537'}.fa-level-down-alt:before{content:'\f3be'}.fa-level-up-alt:before{content:'\f3bf'}.fa-life-ring:before{content:'\f1cd'}.fa-lightbulb:before{content:'\f0eb'}.fa-line:before{content:'\f3c0'}.fa-link:before{content:'\f0c1'}.fa-linkedin:before{content:'\f08c'}.fa-linkedin-in:before{content:'\f0e1'}.fa-linode:before{content:'\f2b8'}.fa-linux:before{content:'\f17c'}.fa-lira-sign:before{content:'\f195'}.fa-list:before{content:'\f03a'}.fa-list-alt:before{content:'\f022'}.fa-list-ol:before{content:'\f0cb'}.fa-list-ul:before{content:'\f0ca'}.fa-location-arrow:before{content:'\f124'}.fa-lock:before{content:'\f023'}.fa-lock-open:before{content:'\f3c1'}.fa-long-arrow-alt-down:before{content:'\f309'}.fa-long-arrow-alt-left:before{content:'\f30a'}.fa-long-arrow-alt-right:before{content:'\f30b'}.fa-long-arrow-alt-up:before{content:'\f30c'}.fa-low-vision:before{content:'\f2a8'}.fa-luggage-cart:before{content:'\f59d'}.fa-lyft:before{content:'\f3c3'}.fa-magento:before{content:'\f3c4'}.fa-magic:before{content:'\f0d0'}.fa-magnet:before{content:'\f076'}.fa-mailchimp:before{content:'\f59e'}.fa-male:before{content:'\f183'}.fa-mandalorian:before{content:'\f50f'}.fa-map:before{content:'\f279'}.fa-map-marked:before{content:'\f59f'}.fa-map-marked-alt:before{content:'\f5a0'}.fa-map-marker:before{content:'\f041'}.fa-map-marker-alt:before{content:'\f3c5'}.fa-map-pin:before{content:'\f276'}.fa-map-signs:before{content:'\f277'}.fa-marker:before{content:'\f5a1'}.fa-mars:before{content:'\f222'}.fa-mars-double:before{content:'\f227'}.fa-mars-stroke:before{content:'\f229'}.fa-mars-stroke-h:before{content:'\f22b'}.fa-mars-stroke-v:before{content:'\f22a'}.fa-mastodon:before{content:'\f4f6'}.fa-maxcdn:before{content:'\f136'}.fa-medal:before{content:'\f5a2'}.fa-medapps:before{content:'\f3c6'}.fa-medium:before{content:'\f23a'}.fa-medium-m:before{content:'\f3c7'}.fa-medkit:before{content:'\f0fa'}.fa-medrt:before{content:'\f3c8'}.fa-meetup:before{content:'\f2e0'}.fa-megaport:before{content:'\f5a3'}.fa-meh:before{content:'\f11a'}.fa-meh-blank:before{content:'\f5a4'}.fa-meh-rolling-eyes:before{content:'\f5a5'}.fa-memory:before{content:'\f538'}.fa-mercury:before{content:'\f223'}.fa-microchip:before{content:'\f2db'}.fa-microphone:before{content:'\f130'}.fa-microphone-alt:before{content:'\f3c9'}.fa-microphone-alt-slash:before{content:'\f539'}.fa-microphone-slash:before{content:'\f131'}.fa-microsoft:before{content:'\f3ca'}.fa-minus:before{content:'\f068'}.fa-minus-circle:before{content:'\f056'}.fa-minus-square:before{content:'\f146'}.fa-mix:before{content:'\f3cb'}.fa-mixcloud:before{content:'\f289'}.fa-mizuni:before{content:'\f3cc'}.fa-mobile:before{content:'\f10b'}.fa-mobile-alt:before{content:'\f3cd'}.fa-modx:before{content:'\f285'}.fa-monero:before{content:'\f3d0'}.fa-money-bill:before{content:'\f0d6'}.fa-money-bill-alt:before{content:'\f3d1'}.fa-money-bill-wave:before{content:'\f53a'}.fa-money-bill-wave-alt:before{content:'\f53b'}.fa-money-check:before{content:'\f53c'}.fa-money-check-alt:before{content:'\f53d'}.fa-monument:before{content:'\f5a6'}.fa-moon:before{content:'\f186'}.fa-mortar-pestle:before{content:'\f5a7'}.fa-motorcycle:before{content:'\f21c'}.fa-mouse-pointer:before{content:'\f245'}.fa-music:before{content:'\f001'}.fa-napster:before{content:'\f3d2'}.fa-neuter:before{content:'\f22c'}.fa-newspaper:before{content:'\f1ea'}.fa-nimblr:before{content:'\f5a8'}.fa-nintendo-switch:before{content:'\f418'}.fa-node:before{content:'\f419'}.fa-node-js:before{content:'\f3d3'}.fa-not-equal:before{content:'\f53e'}.fa-notes-medical:before{content:'\f481'}.fa-npm:before{content:'\f3d4'}.fa-ns8:before{content:'\f3d5'}.fa-nutritionix:before{content:'\f3d6'}.fa-object-group:before{content:'\f247'}.fa-object-ungroup:before{content:'\f248'}.fa-odnoklassniki:before{content:'\f263'}.fa-odnoklassniki-square:before{content:'\f264'}.fa-old-republic:before{content:'\f510'}.fa-opencart:before{content:'\f23d'}.fa-openid:before{content:'\f19b'}.fa-opera:before{content:'\f26a'}.fa-optin-monster:before{content:'\f23c'}.fa-osi:before{content:'\f41a'}.fa-outdent:before{content:'\f03b'}.fa-page4:before{content:'\f3d7'}.fa-pagelines:before{content:'\f18c'}.fa-paint-brush:before{content:'\f1fc'}.fa-paint-roller:before{content:'\f5aa'}.fa-palette:before{content:'\f53f'}.fa-palfed:before{content:'\f3d8'}.fa-pallet:before{content:'\f482'}.fa-paper-plane:before{content:'\f1d8'}.fa-paperclip:before{content:'\f0c6'}.fa-parachute-box:before{content:'\f4cd'}.fa-paragraph:before{content:'\f1dd'}.fa-parking:before{content:'\f540'}.fa-passport:before{content:'\f5ab'}.fa-paste:before{content:'\f0ea'}.fa-patreon:before{content:'\f3d9'}.fa-pause:before{content:'\f04c'}.fa-pause-circle:before{content:'\f28b'}.fa-paw:before{content:'\f1b0'}.fa-paypal:before{content:'\f1ed'}.fa-pen:before{content:'\f304'}.fa-pen-alt:before{content:'\f305'}.fa-pen-fancy:before{content:'\f5ac'}.fa-pen-nib:before{content:'\f5ad'}.fa-pen-square:before{content:'\f14b'}.fa-pencil-alt:before{content:'\f303'}.fa-pencil-ruler:before{content:'\f5ae'}.fa-people-carry:before{content:'\f4ce'}.fa-percent:before{content:'\f295'}.fa-percentage:before{content:'\f541'}.fa-periscope:before{content:'\f3da'}.fa-phabricator:before{content:'\f3db'}.fa-phoenix-framework:before{content:'\f3dc'}.fa-phoenix-squadron:before{content:'\f511'}.fa-phone:before{content:'\f095'}.fa-phone-slash:before{content:'\f3dd'}.fa-phone-square:before{content:'\f098'}.fa-phone-volume:before{content:'\f2a0'}.fa-php:before{content:'\f457'}.fa-pied-piper:before{content:'\f2ae'}.fa-pied-piper-alt:before{content:'\f1a8'}.fa-pied-piper-hat:before{content:'\f4e5'}.fa-pied-piper-pp:before{content:'\f1a7'}.fa-piggy-bank:before{content:'\f4d3'}.fa-pills:before{content:'\f484'}.fa-pinterest:before{content:'\f0d2'}.fa-pinterest-p:before{content:'\f231'}.fa-pinterest-square:before{content:'\f0d3'}.fa-plane:before{content:'\f072'}.fa-plane-arrival:before{content:'\f5af'}.fa-plane-departure:before{content:'\f5b0'}.fa-play:before{content:'\f04b'}.fa-play-circle:before{content:'\f144'}.fa-playstation:before{content:'\f3df'}.fa-plug:before{content:'\f1e6'}.fa-plus:before{content:'\f067'}.fa-plus-circle:before{content:'\f055'}.fa-plus-square:before{content:'\f0fe'}.fa-podcast:before{content:'\f2ce'}.fa-poo:before{content:'\f2fe'}.fa-portrait:before{content:'\f3e0'}.fa-pound-sign:before{content:'\f154'}.fa-power-off:before{content:'\f011'}.fa-prescription:before{content:'\f5b1'}.fa-prescription-bottle:before{content:'\f485'}.fa-prescription-bottle-alt:before{content:'\f486'}.fa-print:before{content:'\f02f'}.fa-procedures:before{content:'\f487'}.fa-product-hunt:before{content:'\f288'}.fa-project-diagram:before{content:'\f542'}.fa-pushed:before{content:'\f3e1'}.fa-puzzle-piece:before{content:'\f12e'}.fa-python:before{content:'\f3e2'}.fa-qq:before{content:'\f1d6'}.fa-qrcode:before{content:'\f029'}.fa-question:before{content:'\f128'}.fa-question-circle:before{content:'\f059'}.fa-quidditch:before{content:'\f458'}.fa-quinscape:before{content:'\f459'}.fa-quora:before{content:'\f2c4'}.fa-quote-left:before{content:'\f10d'}.fa-quote-right:before{content:'\f10e'}.fa-r-project:before{content:'\f4f7'}.fa-random:before{content:'\f074'}.fa-ravelry:before{content:'\f2d9'}.fa-react:before{content:'\f41b'}.fa-readme:before{content:'\f4d5'}.fa-rebel:before{content:'\f1d0'}.fa-receipt:before{content:'\f543'}.fa-recycle:before{content:'\f1b8'}.fa-red-river:before{content:'\f3e3'}.fa-reddit:before{content:'\f1a1'}.fa-reddit-alien:before{content:'\f281'}.fa-reddit-square:before{content:'\f1a2'}.fa-redo:before{content:'\f01e'}.fa-redo-alt:before{content:'\f2f9'}.fa-registered:before{content:'\f25d'}.fa-rendact:before{content:'\f3e4'}.fa-renren:before{content:'\f18b'}.fa-reply:before{content:'\f3e5'}.fa-reply-all:before{content:'\f122'}.fa-replyd:before{content:'\f3e6'}.fa-researchgate:before{content:'\f4f8'}.fa-resolving:before{content:'\f3e7'}.fa-retweet:before{content:'\f079'}.fa-ribbon:before{content:'\f4d6'}.fa-road:before{content:'\f018'}.fa-robot:before{content:'\f544'}.fa-rocket:before{content:'\f135'}.fa-rocketchat:before{content:'\f3e8'}.fa-rockrms:before{content:'\f3e9'}.fa-rss:before{content:'\f09e'}.fa-rss-square:before{content:'\f143'}.fa-ruble-sign:before{content:'\f158'}.fa-ruler:before{content:'\f545'}.fa-ruler-combined:before{content:'\f546'}.fa-ruler-horizontal:before{content:'\f547'}.fa-ruler-vertical:before{content:'\f548'}.fa-rupee-sign:before{content:'\f156'}.fa-sad-cry:before{content:'\f5b3'}.fa-sad-tear:before{content:'\f5b4'}.fa-safari:before{content:'\f267'}.fa-sass:before{content:'\f41e'}.fa-save:before{content:'\f0c7'}.fa-schlix:before{content:'\f3ea'}.fa-school:before{content:'\f549'}.fa-screwdriver:before{content:'\f54a'}.fa-scribd:before{content:'\f28a'}.fa-search:before{content:'\f002'}.fa-search-minus:before{content:'\f010'}.fa-search-plus:before{content:'\f00e'}.fa-searchengin:before{content:'\f3eb'}.fa-seedling:before{content:'\f4d8'}.fa-sellcast:before{content:'\f2da'}.fa-sellsy:before{content:'\f213'}.fa-server:before{content:'\f233'}.fa-servicestack:before{content:'\f3ec'}.fa-share:before{content:'\f064'}.fa-share-alt:before{content:'\f1e0'}.fa-share-alt-square:before{content:'\f1e1'}.fa-share-square:before{content:'\f14d'}.fa-shekel-sign:before{content:'\f20b'}.fa-shield-alt:before{content:'\f3ed'}.fa-ship:before{content:'\f21a'}.fa-shipping-fast:before{content:'\f48b'}.fa-shirtsinbulk:before{content:'\f214'}.fa-shoe-prints:before{content:'\f54b'}.fa-shopping-bag:before{content:'\f290'}.fa-shopping-basket:before{content:'\f291'}.fa-shopping-cart:before{content:'\f07a'}.fa-shopware:before{content:'\f5b5'}.fa-shower:before{content:'\f2cc'}.fa-shuttle-van:before{content:'\f5b6'}.fa-sign:before{content:'\f4d9'}.fa-sign-in-alt:before{content:'\f2f6'}.fa-sign-language:before{content:'\f2a7'}.fa-sign-out-alt:before{content:'\f2f5'}.fa-signal:before{content:'\f012'}.fa-signature:before{content:'\f5b7'}.fa-simplybuilt:before{content:'\f215'}.fa-sistrix:before{content:'\f3ee'}.fa-sitemap:before{content:'\f0e8'}.fa-sith:before{content:'\f512'}.fa-skull:before{content:'\f54c'}.fa-skyatlas:before{content:'\f216'}.fa-skype:before{content:'\f17e'}.fa-slack:before{content:'\f198'}.fa-slack-hash:before{content:'\f3ef'}.fa-sliders-h:before{content:'\f1de'}.fa-slideshare:before{content:'\f1e7'}.fa-smile:before{content:'\f118'}.fa-smile-beam:before{content:'\f5b8'}.fa-smile-wink:before{content:'\f4da'}.fa-smoking:before{content:'\f48d'}.fa-smoking-ban:before{content:'\f54d'}.fa-snapchat:before{content:'\f2ab'}.fa-snapchat-ghost:before{content:'\f2ac'}.fa-snapchat-square:before{content:'\f2ad'}.fa-snowflake:before{content:'\f2dc'}.fa-solar-panel:before{content:'\f5ba'}.fa-sort:before{content:'\f0dc'}.fa-sort-alpha-down:before{content:'\f15d'}.fa-sort-alpha-up:before{content:'\f15e'}.fa-sort-amount-down:before{content:'\f160'}.fa-sort-amount-up:before{content:'\f161'}.fa-sort-down:before{content:'\f0dd'}.fa-sort-numeric-down:before{content:'\f162'}.fa-sort-numeric-up:before{content:'\f163'}.fa-sort-up:before{content:'\f0de'}.fa-soundcloud:before{content:'\f1be'}.fa-spa:before{content:'\f5bb'}.fa-space-shuttle:before{content:'\f197'}.fa-speakap:before{content:'\f3f3'}.fa-spinner:before{content:'\f110'}.fa-splotch:before{content:'\f5bc'}.fa-spotify:before{content:'\f1bc'}.fa-spray-can:before{content:'\f5bd'}.fa-square:before{content:'\f0c8'}.fa-square-full:before{content:'\f45c'}.fa-squarespace:before{content:'\f5be'}.fa-stack-exchange:before{content:'\f18d'}.fa-stack-overflow:before{content:'\f16c'}.fa-stamp:before{content:'\f5bf'}.fa-star:before{content:'\f005'}.fa-star-half:before{content:'\f089'}.fa-star-half-alt:before{content:'\f5c0'}.fa-staylinked:before{content:'\f3f5'}.fa-steam:before{content:'\f1b6'}.fa-steam-square:before{content:'\f1b7'}.fa-steam-symbol:before{content:'\f3f6'}.fa-step-backward:before{content:'\f048'}.fa-step-forward:before{content:'\f051'}.fa-stethoscope:before{content:'\f0f1'}.fa-sticker-mule:before{content:'\f3f7'}.fa-sticky-note:before{content:'\f249'}.fa-stop:before{content:'\f04d'}.fa-stop-circle:before{content:'\f28d'}.fa-stopwatch:before{content:'\f2f2'}.fa-store:before{content:'\f54e'}.fa-store-alt:before{content:'\f54f'}.fa-strava:before{content:'\f428'}.fa-stream:before{content:'\f550'}.fa-street-view:before{content:'\f21d'}.fa-strikethrough:before{content:'\f0cc'}.fa-stripe:before{content:'\f429'}.fa-stripe-s:before{content:'\f42a'}.fa-stroopwafel:before{content:'\f551'}.fa-studiovinari:before{content:'\f3f8'}.fa-stumbleupon:before{content:'\f1a4'}.fa-stumbleupon-circle:before{content:'\f1a3'}.fa-subscript:before{content:'\f12c'}.fa-subway:before{content:'\f239'}.fa-suitcase:before{content:'\f0f2'}.fa-suitcase-rolling:before{content:'\f5c1'}.fa-sun:before{content:'\f185'}.fa-superpowers:before{content:'\f2dd'}.fa-superscript:before{content:'\f12b'}.fa-supple:before{content:'\f3f9'}.fa-surprise:before{content:'\f5c2'}.fa-swatchbook:before{content:'\f5c3'}.fa-swimmer:before{content:'\f5c4'}.fa-swimming-pool:before{content:'\f5c5'}.fa-sync:before{content:'\f021'}.fa-sync-alt:before{content:'\f2f1'}.fa-syringe:before{content:'\f48e'}.fa-table:before{content:'\f0ce'}.fa-table-tennis:before{content:'\f45d'}.fa-tablet:before{content:'\f10a'}.fa-tablet-alt:before{content:'\f3fa'}.fa-tablets:before{content:'\f490'}.fa-tachometer-alt:before{content:'\f3fd'}.fa-tag:before{content:'\f02b'}.fa-tags:before{content:'\f02c'}.fa-tape:before{content:'\f4db'}.fa-tasks:before{content:'\f0ae'}.fa-taxi:before{content:'\f1ba'}.fa-teamspeak:before{content:'\f4f9'}.fa-telegram:before{content:'\f2c6'}.fa-telegram-plane:before{content:'\f3fe'}.fa-tencent-weibo:before{content:'\f1d5'}.fa-terminal:before{content:'\f120'}.fa-text-height:before{content:'\f034'}.fa-text-width:before{content:'\f035'}.fa-th:before{content:'\f00a'}.fa-th-large:before{content:'\f009'}.fa-th-list:before{content:'\f00b'}.fa-themeco:before{content:'\f5c6'}.fa-themeisle:before{content:'\f2b2'}.fa-thermometer:before{content:'\f491'}.fa-thermometer-empty:before{content:'\f2cb'}.fa-thermometer-full:before{content:'\f2c7'}.fa-thermometer-half:before{content:'\f2c9'}.fa-thermometer-quarter:before{content:'\f2ca'}.fa-thermometer-three-quarters:before{content:'\f2c8'}.fa-thumbs-down:before{content:'\f165'}.fa-thumbs-up:before{content:'\f164'}.fa-thumbtack:before{content:'\f08d'}.fa-ticket-alt:before{content:'\f3ff'}.fa-times:before{content:'\f00d'}.fa-times-circle:before{content:'\f057'}.fa-tint:before{content:'\f043'}.fa-tint-slash:before{content:'\f5c7'}.fa-tired:before{content:'\f5c8'}.fa-toggle-off:before{content:'\f204'}.fa-toggle-on:before{content:'\f205'}.fa-toolbox:before{content:'\f552'}.fa-tooth:before{content:'\f5c9'}.fa-trade-federation:before{content:'\f513'}.fa-trademark:before{content:'\f25c'}.fa-train:before{content:'\f238'}.fa-transgender:before{content:'\f224'}.fa-transgender-alt:before{content:'\f225'}.fa-trash:before{content:'\f1f8'}.fa-trash-alt:before{content:'\f2ed'}.fa-tree:before{content:'\f1bb'}.fa-trello:before{content:'\f181'}.fa-tripadvisor:before{content:'\f262'}.fa-trophy:before{content:'\f091'}.fa-truck:before{content:'\f0d1'}.fa-truck-loading:before{content:'\f4de'}.fa-truck-moving:before{content:'\f4df'}.fa-tshirt:before{content:'\f553'}.fa-tty:before{content:'\f1e4'}.fa-tumblr:before{content:'\f173'}.fa-tumblr-square:before{content:'\f174'}.fa-tv:before{content:'\f26c'}.fa-twitch:before{content:'\f1e8'}.fa-twitter:before{content:'\f099'}.fa-twitter-square:before{content:'\f081'}.fa-typo3:before{content:'\f42b'}.fa-uber:before{content:'\f402'}.fa-uikit:before{content:'\f403'}.fa-umbrella:before{content:'\f0e9'}.fa-umbrella-beach:before{content:'\f5ca'}.fa-underline:before{content:'\f0cd'}.fa-undo:before{content:'\f0e2'}.fa-undo-alt:before{content:'\f2ea'}.fa-uniregistry:before{content:'\f404'}.fa-universal-access:before{content:'\f29a'}.fa-university:before{content:'\f19c'}.fa-unlink:before{content:'\f127'}.fa-unlock:before{content:'\f09c'}.fa-unlock-alt:before{content:'\f13e'}.fa-untappd:before{content:'\f405'}.fa-upload:before{content:'\f093'}.fa-usb:before{content:'\f287'}.fa-user:before{content:'\f007'}.fa-user-alt:before{content:'\f406'}.fa-user-alt-slash:before{content:'\f4fa'}.fa-user-astronaut:before{content:'\f4fb'}.fa-user-check:before{content:'\f4fc'}.fa-user-circle:before{content:'\f2bd'}.fa-user-clock:before{content:'\f4fd'}.fa-user-cog:before{content:'\f4fe'}.fa-user-edit:before{content:'\f4ff'}.fa-user-friends:before{content:'\f500'}.fa-user-graduate:before{content:'\f501'}.fa-user-lock:before{content:'\f502'}.fa-user-md:before{content:'\f0f0'}.fa-user-minus:before{content:'\f503'}.fa-user-ninja:before{content:'\f504'}.fa-user-plus:before{content:'\f234'}.fa-user-secret:before{content:'\f21b'}.fa-user-shield:before{content:'\f505'}.fa-user-slash:before{content:'\f506'}.fa-user-tag:before{content:'\f507'}.fa-user-tie:before{content:'\f508'}.fa-user-times:before{content:'\f235'}.fa-users:before{content:'\f0c0'}.fa-users-cog:before{content:'\f509'}.fa-ussunnah:before{content:'\f407'}.fa-utensil-spoon:before{content:'\f2e5'}.fa-utensils:before{content:'\f2e7'}.fa-vaadin:before{content:'\f408'}.fa-vector-square:before{content:'\f5cb'}.fa-venus:before{content:'\f221'}.fa-venus-double:before{content:'\f226'}.fa-venus-mars:before{content:'\f228'}.fa-viacoin:before{content:'\f237'}.fa-viadeo:before{content:'\f2a9'}.fa-viadeo-square:before{content:'\f2aa'}.fa-vial:before{content:'\f492'}.fa-vials:before{content:'\f493'}.fa-viber:before{content:'\f409'}.fa-video:before{content:'\f03d'}.fa-video-slash:before{content:'\f4e2'}.fa-vimeo:before{content:'\f40a'}.fa-vimeo-square:before{content:'\f194'}.fa-vimeo-v:before{content:'\f27d'}.fa-vine:before{content:'\f1ca'}.fa-vk:before{content:'\f189'}.fa-vnv:before{content:'\f40b'}.fa-volleyball-ball:before{content:'\f45f'}.fa-volume-down:before{content:'\f027'}.fa-volume-off:before{content:'\f026'}.fa-volume-up:before{content:'\f028'}.fa-vuejs:before{content:'\f41f'}.fa-walking:before{content:'\f554'}.fa-wallet:before{content:'\f555'}.fa-warehouse:before{content:'\f494'}.fa-weebly:before{content:'\f5cc'}.fa-weibo:before{content:'\f18a'}.fa-weight:before{content:'\f496'}.fa-weight-hanging:before{content:'\f5cd'}.fa-weixin:before{content:'\f1d7'}.fa-whatsapp:before{content:'\f232'}.fa-whatsapp-square:before{content:'\f40c'}.fa-wheelchair:before{content:'\f193'}.fa-whmcs:before{content:'\f40d'}.fa-wifi:before{content:'\f1eb'}.fa-wikipedia-w:before{content:'\f266'}.fa-window-close:before{content:'\f410'}.fa-window-maximize:before{content:'\f2d0'}.fa-window-minimize:before{content:'\f2d1'}.fa-window-restore:before{content:'\f2d2'}.fa-windows:before{content:'\f17a'}.fa-wine-glass:before{content:'\f4e3'}.fa-wine-glass-alt:before{content:'\f5ce'}.fa-wix:before{content:'\f5cf'}.fa-wolf-pack-battalion:before{content:'\f514'}.fa-won-sign:before{content:'\f159'}.fa-wordpress:before{content:'\f19a'}.fa-wordpress-simple:before{content:'\f411'}.fa-wpbeginner:before{content:'\f297'}.fa-wpexplorer:before{content:'\f2de'}.fa-wpforms:before{content:'\f298'}.fa-wrench:before{content:'\f0ad'}.fa-x-ray:before{content:'\f497'}.fa-xbox:before{content:'\f412'}.fa-xing:before{content:'\f168'}.fa-xing-square:before{content:'\f169'}.fa-y-combinator:before{content:'\f23b'}.fa-yahoo:before{content:'\f19e'}.fa-yandex:before{content:'\f413'}.fa-yandex-international:before{content:'\f414'}.fa-yelp:before{content:'\f1e9'}.fa-yen-sign:before{content:'\f157'}.fa-yoast:before{content:'\f2b1'}.fa-youtube:before{content:'\f167'}.fa-youtube-square:before{content:'\f431'}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}
diff --git a/package-lock.json b/package-lock.json
index b757dbbcd..ef0a855d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -338,6 +338,17 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
+ "@babel/plugin-proposal-decorators": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz",
+ "integrity": "sha512-z7MpQz3XC/iQJWXH9y+MaWcLPNSMY9RQSthrLzak8R8hCj0fuyNk+Dzi9kfNe/JxxlWQ2g7wkABbgWjW36MTcw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.4.4",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-syntax-decorators": "^7.2.0"
+ }
+ },
"@babel/plugin-proposal-json-strings": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
@@ -388,6 +399,15 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
+ "@babel/plugin-syntax-decorators": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz",
+ "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
"@babel/plugin-syntax-json-strings": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
@@ -1655,12 +1675,29 @@
"babel-runtime": "^6.22.0"
}
},
+ "babel-plugin-syntax-decorators": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
+ "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
+ "dev": true
+ },
"babel-plugin-syntax-dynamic-import": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
"integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
"dev": true
},
+ "babel-plugin-transform-decorators-legacy": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.5.tgz",
+ "integrity": "sha512-jYHwjzRXRelYQ1uGm353zNzf3QmtdCfvJbuYTZ4gKveK7M9H1fs3a5AKdY1JUDl0z97E30ukORW1dzhWvsabtA==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-decorators": "^6.1.18",
+ "babel-runtime": "^6.2.0",
+ "babel-template": "^6.3.0"
+ }
+ },
"babel-plugin-transform-es2015-arrow-functions": {
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
@@ -6295,6 +6332,12 @@
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+ "dev": true
+ },
"lodash.tail": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
diff --git a/package.json b/package.json
index 10ab7582a..a8744aec4 100644
--- a/package.json
+++ b/package.json
@@ -37,12 +37,14 @@
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
+ "@babel/plugin-proposal-decorators": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"autoprefixer": "^9.5.1",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
+ "babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.1.1",
"eslint": "^5.16.0",
@@ -57,6 +59,7 @@
"karma-mocha-reporter": "^2.2.5",
"karma-webpack": "^3.0.5",
"lint-staged": "^8.1.7",
+ "lodash.debounce": "^4.0.8",
"node-sass": "^4.12.0",
"npm-run-all": "^4.1.5",
"null-loader": "^2.0.0",
diff --git a/src/Application.hs b/src/Application.hs
index bf7927e51..ab612883c 100644
--- a/src/Application.hs
+++ b/src/Application.hs
@@ -36,6 +36,8 @@ import System.Log.FastLogger ( defaultBufSize, newStderrLoggerSet
, toLogStr, rmLoggerSet
)
+import Handler.Utils (runAppLoggingT)
+
import qualified Data.Map.Strict as Map
import Foreign.Store
@@ -222,13 +224,6 @@ makeFoundation appSettings'@AppSettings{..} = do
$logDebugS "setup" "Done"
return foundation
-runAppLoggingT :: UniWorX -> LoggingT m a -> m a
-runAppLoggingT app@(appLogger -> (_, loggerTVar)) = flip runLoggingT logFunc
- where
- logFunc loc src lvl str = do
- f <- messageLoggerSource app <$> readTVarIO loggerTVar
- f loc src lvl str
-
clusterSetting :: forall key m p.
( MonadIO m
, ClusterSetting key
diff --git a/src/Data/CaseInsensitive/Instances.hs b/src/Data/CaseInsensitive/Instances.hs
index 3986e3cc7..b6b69fa02 100644
--- a/src/Data/CaseInsensitive/Instances.hs
+++ b/src/Data/CaseInsensitive/Instances.hs
@@ -26,6 +26,9 @@ import qualified Database.Esqueleto as E
import Web.HttpApiData
+import Data.Binary (Binary)
+import qualified Data.Binary as Binary
+
instance PersistField (CI Text) where
toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 $ CI.original ciText
@@ -92,5 +95,9 @@ instance FromHttpApiData (CI Text) where
instance (CI.FoldCase s, PathMultiPiece s) => PathMultiPiece (CI s) where
fromPathMultiPiece = fmap CI.mk . fromPathMultiPiece
- toPathMultiPiece = toPathMultiPiece . CI.foldedCase
+ toPathMultiPiece = toPathMultiPiece . CI.original
+instance (CI.FoldCase s, Binary s) => Binary (CI s) where
+ get = CI.mk <$> Binary.get
+ put = Binary.put . CI.original
+ putList = Binary.putList . map CI.original
diff --git a/src/Foundation.hs b/src/Foundation.hs
index 20d6464aa..675d6638b 100644
--- a/src/Foundation.hs
+++ b/src/Foundation.hs
@@ -46,7 +46,7 @@ import Data.Map (Map, (!?))
import qualified Data.Map as Map
import qualified Data.HashSet as HashSet
-import Data.List (nubBy, (!!))
+import Data.List (nubBy, (!!), findIndex)
import Data.Monoid (Any(..))
@@ -493,7 +493,7 @@ askTokenUnsafe = $cachedHere $ do
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalid
validateToken :: Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> BearerToken UniWorX -> DB AuthResult
-validateToken mAuthId' route' isWrite' token' = runCachedMemoT $ for4 memo validateToken' mAuthId' route' isWrite' token'
+validateToken mAuthId' route' isWrite' token' = $runCachedMemoT $ for4 memo validateToken' mAuthId' route' isWrite' token'
where
validateToken' :: _ -> _ -> _ -> _ -> CachedMemoT (Maybe (AuthId UniWorX), Route UniWorX, Bool, BearerToken UniWorX) AuthResult DB AuthResult
validateToken' mAuthId route isWrite BearerToken{..} = lift . exceptT return return $ do
@@ -524,7 +524,7 @@ tagAccessPredicate :: AuthTag -> AccessPredicate
tagAccessPredicate AuthFree = trueAP
tagAccessPredicate AuthAdmin = APDB $ \mAuthId route _ -> case route of
-- Courses: access only to school admins
- CourseR tid ssh csh _ -> exceptT return return $ do
+ CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
[E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` userAdmin) -> do
E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserAdminSchool
@@ -536,7 +536,7 @@ tagAccessPredicate AuthAdmin = APDB $ \mAuthId route _ -> case route of
guardMExceptT (c > 0) (unauthorizedI MsgUnauthorizedSchoolAdmin)
return Authorized
-- other routes: access to any admin is granted here
- _other -> exceptT return return $ do
+ _other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
adrights <- lift $ selectFirst [UserAdminUser ==. authId] []
guardMExceptT (isJust adrights) (unauthorizedI MsgUnauthorizedSiteAdmin)
@@ -566,7 +566,7 @@ tagAccessPredicate AuthDevelopment = APHandler $ \_ r _ -> do
return $ Unauthorized "Route under development"
#endif
tagAccessPredicate AuthLecturer = APDB $ \mAuthId route _ -> case route of
- CourseR tid ssh csh _ -> exceptT return return $ do
+ CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
[E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
@@ -578,13 +578,13 @@ tagAccessPredicate AuthLecturer = APDB $ \mAuthId route _ -> case route of
guardMExceptT (c>0) (unauthorizedI MsgUnauthorizedLecturer)
return Authorized
-- lecturer for any school will do
- _ -> exceptT return return $ do
+ _ -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSchoolLecturer) $ selectFirst [UserLecturerUser ==. authId] []
return Authorized
tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
- resList <- lift . E.select . E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
+ resList <- $cachedHereBinary (mAuthId) . lift . E.select . E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
E.on $ sheetCorrector E.^. SheetCorrectorSheet E.==. sheet E.^. SheetId
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val authId
@@ -593,17 +593,17 @@ tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return ret
resMap :: Map CourseId (Set SheetId)
resMap = Map.fromListWith Set.union [ (cid, Set.singleton sid) | (E.Value cid, E.Value sid) <- resList ]
case route of
- CSubmissionR _ _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionCorrector) $ do
+ CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary (mAuthId, cID) . maybeT (unauthorizedI MsgUnauthorizedSubmissionCorrector) $ do
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
Submission{..} <- MaybeT . lift $ get sid
guard $ maybe False (== authId) submissionRatingBy
return Authorized
- CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedSheetCorrector) $ do
+ CSheetR tid ssh csh shn _ -> $cachedHereBinary (mAuthId, tid, ssh, csh, shn) . maybeT (unauthorizedI MsgUnauthorizedSheetCorrector) $ do
Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
Entity sid _ <- MaybeT . lift . getBy $ CourseSheet cid shn
guard $ sid `Set.member` fromMaybe Set.empty (resMap !? cid)
return Authorized
- CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnauthorizedCorrector) $ do
+ CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . maybeT (unauthorizedI MsgUnauthorizedCorrector) $ do
Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
guard $ cid `Set.member` Map.keysSet resMap
return Authorized
@@ -612,7 +612,7 @@ tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return ret
return Authorized
tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
- resList <- lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
+ resList <- $cachedHereBinary authId . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
E.on $ tutor E.^. TutorTutorial E.==. tutorial E.^. TutorialId
E.on $ tutorial E.^. TutorialCourse E.==. course E.^. CourseId
E.where_ $ tutor E.^. TutorUser E.==. E.val authId
@@ -622,12 +622,12 @@ tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return
resMap = Map.fromListWith Set.union [ (cid, Set.singleton tutid) | (E.Value cid, E.Value tutid) <- resList ]
case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgUnauthorizedTutorialTutor) $ do
- Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
- Entity tutid _ <- MaybeT . lift . getBy $ UniqueTutorial cid tutn
+ Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
+ Entity tutid _ <- $cachedHereBinary (cid, tutn) . MaybeT . lift . getBy $ UniqueTutorial cid tutn
guard $ tutid `Set.member` fromMaybe Set.empty (resMap !? cid)
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnauthorizedCourseTutor) $ do
- Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
+ Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
guard $ cid `Set.member` Map.keysSet resMap
return Authorized
_ -> do
@@ -636,10 +636,10 @@ tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return
tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
CTutorialR tid ssh csh tutn TRegisterR -> maybeT (unauthorizedI MsgUnauthorizedTutorialTime) $ do
now <- liftIO getCurrentTime
- course <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- Entity tutId Tutorial{..} <- MaybeT . getBy $ UniqueTutorial course tutn
+ course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ Entity tutId Tutorial{..} <- $cachedHereBinary (course, tutn) . MaybeT . getBy $ UniqueTutorial course tutn
registered <- case mAuthId of
- Just uid -> lift . existsBy $ UniqueTutorialParticipant tutId uid
+ Just uid -> $cachedHereBinary (tutId, uid) . lift . existsBy $ UniqueTutorialParticipant tutId uid
Nothing -> return False
if
@@ -654,8 +654,8 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
-> mzero
CSheetR tid ssh csh shn subRoute -> maybeT (unauthorizedI MsgUnauthorizedSheetTime) $ do
- cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- Entity _sid Sheet{..} <- MaybeT . getBy $ CourseSheet cid shn
+ cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ Entity _sid Sheet{..} <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn
cTime <- liftIO getCurrentTime
let
visible = NTop sheetVisibleFrom <= NTop (Just cTime)
@@ -685,8 +685,8 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
return Authorized
CourseR tid ssh csh (MaterialR mnm _) -> maybeT (unauthorizedI MsgUnauthorizedMaterialTime) $ do
- cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- Entity _mid Material{materialVisibleFrom} <- MaybeT . getBy $ UniqueMaterial cid mnm
+ cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ Entity _mid Material{materialVisibleFrom} <- $cachedHereBinary (cid, mnm) . MaybeT . getBy $ UniqueMaterial cid mnm
cTime <- liftIO getCurrentTime
let visible = NTop materialVisibleFrom <= NTop (Just cTime)
guard visible
@@ -694,9 +694,9 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
CourseR tid ssh csh CRegisterR -> do
now <- liftIO getCurrentTime
- mbc <- getBy $ TermSchoolCourseShort tid ssh csh
+ mbc <- $cachedHereBinary (tid, ssh, csh) . getBy $ TermSchoolCourseShort tid ssh csh
registered <- case (mbc,mAuthId) of
- (Just (Entity cid _), Just uid) -> isJust <$> (getBy $ UniqueParticipant uid cid)
+ (Just (Entity cid _), Just uid) -> $cachedHereBinary (uid, cid) $ isJust <$> (getBy $ UniqueParticipant uid cid)
_ -> return False
case mbc of
(Just (Entity _ Course{courseRegisterFrom, courseRegisterTo}))
@@ -710,7 +710,7 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
smId <- decrypt cID
- SystemMessage{systemMessageFrom, systemMessageTo} <- MaybeT $ get smId
+ SystemMessage{systemMessageFrom, systemMessageTo} <- $cachedHereBinary smId . MaybeT $ get smId
cTime <- (NTop . Just) <$> liftIO getCurrentTime
guard $ NTop systemMessageFrom <= cTime
&& NTop systemMessageTo >= cTime
@@ -720,7 +720,7 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
tagAccessPredicate AuthCourseRegistered = APDB $ \mAuthId route _ -> case route of
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
- [E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
+ [E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.select . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
@@ -733,7 +733,7 @@ tagAccessPredicate AuthCourseRegistered = APDB $ \mAuthId route _ -> case route
tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case route of
CTutorialR tid ssh csh tutn _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
- [E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
+ [E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh, tutn) . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
@@ -746,7 +746,7 @@ tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case rout
return Authorized
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
- [E.Value c] <- lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
+ [E.Value c] <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
@@ -764,14 +764,14 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
whenExceptT ok Authorized
participant <- decrypt cID
-- participant is currently registered
- authorizedIfExists $ \(course `E.InnerJoin` courseParticipant) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant has at least one submission
- authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser) -> do
E.on $ submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmission
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
@@ -780,7 +780,7 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is member of a submissionGroup
- authorizedIfExists $ \(course `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser) -> do
E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup
E.on $ course E.^. CourseId E.==. submissionGroup E.^. SubmissionGroupCourse
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val participant
@@ -788,7 +788,7 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is a sheet corrector
- authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val participant
@@ -796,7 +796,7 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is a tutorial user
- authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialUser) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialUser) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialUser E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialUser E.^. TutorialParticipantUser E.==. E.val participant
@@ -804,7 +804,7 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is tutor for this course
- authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
E.on $ tutorial E.^. TutorialId E.==. tutor E.^. TutorTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutor E.^. TutorUser E.==. E.val participant
@@ -812,7 +812,7 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is lecturer for this course
- authorizedIfExists $ \(course `E.InnerJoin` lecturer) -> do
+ $cachedHereBinary (participant, tid, ssh, csh) . authorizedIfExists $ \(course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
E.where_ $ lecturer E.^. LecturerUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
@@ -822,26 +822,26 @@ tagAccessPredicate AuthParticipant = APDB $ \_ route _ -> case route of
r -> $unsupportedAuthPredicate AuthParticipant r
tagAccessPredicate AuthCapacity = APDB $ \_ route _ -> case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgTutorialNoCapacity) $ do
- cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- Entity tutId Tutorial{..} <- MaybeT . getBy $ UniqueTutorial cid tutn
- registered <- lift $ fromIntegral <$> count [ TutorialParticipantTutorial ==. tutId ]
+ cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ Entity tutId Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
+ registered <- $cachedHereBinary tutId . lift $ fromIntegral <$> count [ TutorialParticipantTutorial ==. tutId ]
guard $ NTop tutorialCapacity > NTop (Just registered)
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNoCapacity) $ do
- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
- registered <- lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
+ Entity cid Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
+ registered <- $cachedHereBinary cid . lift $ fromIntegral <$> count [ CourseParticipantCourse ==. cid ]
guard $ NTop courseCapacity > NTop (Just registered)
return Authorized
r -> $unsupportedAuthPredicate AuthCapacity r
tagAccessPredicate AuthRegisterGroup = APDB $ \mAuthId route _ -> case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgUnauthorizedTutorialRegisterGroup) $ do
- cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- Entity _ Tutorial{..} <- MaybeT . getBy $ UniqueTutorial cid tutn
+ cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ Entity _ Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
case (tutorialRegGroup, mAuthId) of
(Nothing, _) -> return Authorized
(_, Nothing) -> return AuthenticationRequired
(Just rGroup, Just uid) -> do
- [E.Value hasOther] <- lift . E.select . return . E.exists . E.from $ \(tutorial `E.InnerJoin` participant) -> do
+ [E.Value hasOther] <- $cachedHereBinary (uid, rGroup) . lift . E.select . return . E.exists . E.from $ \(tutorial `E.InnerJoin` participant) -> do
E.on $ tutorial E.^. TutorialId E.==. participant E.^. TutorialParticipantTutorial
E.where_ $ participant E.^. TutorialParticipantUser E.==. E.val uid
E.&&. tutorial E.^. TutorialRegGroup E.==. E.just (E.val rGroup)
@@ -851,9 +851,9 @@ tagAccessPredicate AuthRegisterGroup = APDB $ \mAuthId route _ -> case route of
tagAccessPredicate AuthEmpty = APDB $ \_ route _ -> case route of
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
- cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
- assertM_ (<= 0) . lift $ count [ CourseParticipantCourse ==. cid ]
- assertM_ ((<= 0) :: Int -> Bool) . lift . fmap (E.unValue . unsafeHead) $ E.select . E.from $ \(sheet `E.InnerJoin` submission) -> do
+ cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
+ assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ]
+ assertM_ ((<= 0) :: Int -> Bool) . $cachedHereBinary cid . lift . fmap (E.unValue . unsafeHead) $ E.select . E.from $ \(sheet `E.InnerJoin` submission) -> do
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
return E.countRows
@@ -861,26 +861,26 @@ tagAccessPredicate AuthEmpty = APDB $ \_ route _ -> case route of
r -> $unsupportedAuthPredicate AuthEmpty r
tagAccessPredicate AuthMaterials = APDB $ \_ route _ -> case route of
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
- Entity _ Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
+ Entity _ Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
guard courseMaterialFree
return Authorized
r -> $unsupportedAuthPredicate AuthMaterials r
tagAccessPredicate AuthOwner = APDB $ \mAuthId route _ -> case route of
- CSubmissionR _ _ _ _ cID _ -> exceptT return return $ do
+ CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary (mAuthId, cID) . exceptT return return $ do
sid <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSubmissionOwner) (const True :: CryptoIDError -> Bool) $ decrypt cID
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSubmissionOwner) . getBy $ UniqueSubmissionUser authId sid
return Authorized
r -> $unsupportedAuthPredicate AuthOwner r
tagAccessPredicate AuthRated = APDB $ \_ route _ -> case route of
- CSubmissionR _ _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionRated) $ do
+ CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary cID . maybeT (unauthorizedI MsgUnauthorizedSubmissionRated) $ do
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
sub <- MaybeT $ get sid
guard $ submissionRatingDone sub
return Authorized
r -> $unsupportedAuthPredicate AuthRated r
tagAccessPredicate AuthUserSubmissions = APDB $ \_ route _ -> case route of
- CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedUserSubmission) $ do
+ CSheetR tid ssh csh shn _ -> $cachedHereBinary (tid, ssh, csh, shn) . maybeT (unauthorizedI MsgUnauthorizedUserSubmission) $ do
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{ sheetSubmissionMode = SubmissionMode{..} } <- MaybeT . getBy $ CourseSheet cid shn
guard $ is _Just submissionModeUser
@@ -888,8 +888,8 @@ tagAccessPredicate AuthUserSubmissions = APDB $ \_ route _ -> case route of
r -> $unsupportedAuthPredicate AuthUserSubmissions r
tagAccessPredicate AuthCorrectorSubmissions = APDB $ \_ route _ -> case route of
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectorSubmission) $ do
- Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
- Entity _ Sheet{ sheetSubmissionMode = SubmissionMode{..} } <- MaybeT . getBy $ CourseSheet cid shn
+ Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
+ Entity _ Sheet{ sheetSubmissionMode = SubmissionMode{..} } <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn
guard submissionModeCorrector
return Authorized
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
@@ -910,7 +910,7 @@ tagAccessPredicate AuthSelf = APHandler $ \mAuthId route _ -> exceptT return ret
tagAccessPredicate AuthAuthentication = APDB $ \mAuthId route _ -> case route of
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
smId <- decrypt cID
- SystemMessage{..} <- MaybeT $ get smId
+ SystemMessage{..} <- $cachedHereBinary smId . MaybeT $ get smId
let isAuthenticated = isJust mAuthId
guard $ not systemMessageAuthenticatedOnly || isAuthenticated
return Authorized
@@ -919,6 +919,21 @@ tagAccessPredicate AuthRead = APHandler . const . const $ bool (return Authorize
tagAccessPredicate AuthWrite = APHandler . const . const $ bool (unauthorizedI MsgUnauthorized) (return Authorized)
+authTagSpecificity :: AuthTag -> AuthTag -> Ordering
+-- ^ Heuristic for which `AuthTag`s to evaluate first
+authTagSpecificity = comparing $ NTop . flip findIndex eqClasses . elem
+ where
+ eqClasses :: [[AuthTag]]
+ -- ^ Constructors of `AuthTag` ordered (increasing) by execution order
+ eqClasses =
+ [ [ AuthFree, AuthDeprecated, AuthDevelopment ] -- Route wide
+ , [ AuthRead, AuthWrite, AuthToken ] -- Request wide
+ , [ AuthAdmin ] -- Site wide
+ , [ AuthLecturer, AuthCourseRegistered, AuthParticipant, AuthTime, AuthMaterials, AuthUserSubmissions, AuthCorrectorSubmissions, AuthCapacity, AuthEmpty ] ++ [ AuthSelf, AuthNoEscalation ] ++ [ AuthAuthentication ] -- Course/User/SystemMessage wide
+ , [ AuthCorrector ] ++ [ AuthTutor ] ++ [ AuthTutorialRegistered, AuthRegisterGroup ] -- Tutorial/Material/Sheet wide
+ , [ AuthOwner, AuthRated ] -- Submission wide
+ ]
+
defaultAuthDNF :: AuthDNF
defaultAuthDNF = PredDNF $ Set.fromList
[ impureNonNull . Set.singleton $ PLVariable AuthAdmin
@@ -946,16 +961,19 @@ routeAuthTags = fmap (PredDNF . Set.mapMonotonic impureNonNull) . ofoldM partiti
evalAuthTags :: forall m. (MonadAP m, MonadLogger m) => AuthTagActive -> AuthDNF -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> WriterT (Set AuthTag) m AuthResult
-- ^ `tell`s disabled predicates, identified as pivots
-evalAuthTags AuthTagActive{..} (map (Set.toList . toNullable) . Set.toList . dnfTerms -> authDNF) mAuthId route isWrite
+evalAuthTags AuthTagActive{..} (map (Set.toList . toNullable) . Set.toList . dnfTerms -> authDNF') mAuthId route isWrite
= do
mr <- getMsgRenderer
let
+ authVarSpecificity = authTagSpecificity `on` plVar
+ authDNF = sortBy (authVarSpecificity `on` maximumBy authVarSpecificity . impureNonNull) $ map (sortBy authVarSpecificity) authDNF'
+
authTagIsInactive = not . authTagIsActive
evalAuthTag :: AuthTag -> WriterT (Set AuthTag) m AuthResult
- evalAuthTag authTag = lift . (runCachedMemoT :: CachedMemoT (AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for4 memo evalAccessPred' authTag mAuthId route isWrite
+ evalAuthTag authTag = lift . ($runCachedMemoT :: CachedMemoT (AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for4 memo evalAccessPred' authTag mAuthId route isWrite
where
- evalAccessPred' authTag' mAuthId' route' isWrite' = CachedMemoT $ do
+ evalAccessPred' authTag' mAuthId' route' isWrite' = lift $ do
$logDebugS "evalAccessPred" $ tshow (authTag', mAuthId', route', isWrite')
evalAccessPred (tagAccessPredicate authTag') mAuthId' route' isWrite'
diff --git a/src/Handler/Utils.hs b/src/Handler/Utils.hs
index 0384c83e5..8877dc8de 100644
--- a/src/Handler/Utils.hs
+++ b/src/Handler/Utils.hs
@@ -37,6 +37,8 @@ import System.FilePath.Posix (takeBaseName, takeFileName)
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
+import Control.Monad.Logger
+
-- | Check whether the user's preference for files is inline-viewing or downloading
downloadFiles :: (MonadHandler m, HandlerSite m ~ UniWorX) => m Bool
@@ -247,3 +249,12 @@ guardAuthorizedFor :: ( HandlerSite h ~ UniWorX, MonadHandler h, MonadLogger h
=> Route UniWorX -> a -> m (ReaderT SqlBackend h) a
guardAuthorizedFor link val =
val <$ guardM (lift $ (== Authorized) <$> evalAccessDB link False)
+
+
+runAppLoggingT :: UniWorX -> LoggingT m a -> m a
+runAppLoggingT app@(appLogger -> (_, loggerTVar)) = flip runLoggingT logFunc
+ where
+ logFunc loc src lvl str = do
+ f <- messageLoggerSource app <$> readTVarIO loggerTVar
+ f loc src lvl str
+
diff --git a/src/Import/NoModel.hs b/src/Import/NoModel.hs
index 639eca131..d2ba81705 100644
--- a/src/Import/NoModel.hs
+++ b/src/Import/NoModel.hs
@@ -14,7 +14,7 @@ import Yesod.Auth as Import
import Yesod.Core.Types as Import (loggerSet)
import Yesod.Default.Config2 as Import
import Yesod.Core.Json as Import (provideJson)
-import Yesod.Core.Types.Instances as Import (CachedMemoT(..))
+import Yesod.Core.Types.Instances as Import
import Utils as Import
import Utils.Frontend.I18n as Import
diff --git a/src/Jobs.hs b/src/Jobs.hs
index 5ba9f1fa4..867718bab 100644
--- a/src/Jobs.hs
+++ b/src/Jobs.hs
@@ -7,6 +7,7 @@ module Jobs
import Import
import Utils.Lens
+import Handler.Utils
import Jobs.Types as Types hiding (JobCtl(JobCtlQueue))
import Jobs.Types (JobCtl(JobCtlQueue))
@@ -93,7 +94,7 @@ handleJobs foundation@UniWorX{..} = do
logStart = $logDebugS ("Jobs #" <> tshow n) "Starting"
logStop = $logDebugS ("Jobs #" <> tshow n) "Stopping"
removeChan = atomically . modifyTVar' appJobCtl . Map.delete =<< myThreadId
- doFork = flip forkFinally (\_ -> removeChan) . unsafeHandler foundation . bracket_ logStart logStop . flip runReaderT JobContext{..} . runConduit $ sourceTMChan chan .| handleJobs' n
+ doFork = flip forkFinally (\_ -> removeChan) . runAppLoggingT foundation . bracket_ logStart logStop . flip runReaderT JobContext{..} . runConduit $ sourceTMChan chan .| handleJobs' foundation n
(_, tId) <- allocate (liftIO doFork) (\_ -> liftIO . atomically $ closeTMChan chan)
atomically . modifyTVar' appJobCtl $ Map.insert tId bChan
@@ -101,7 +102,7 @@ handleJobs foundation@UniWorX{..} = do
when (num > 0) $ do
registeredCron <- liftIO newEmptyTMVarIO
let execCrontab' = whenM (atomically $ readTMVar registeredCron) $
- unsafeHandler foundation $ runReaderT execCrontab JobContext{..}
+ runReaderT (execCrontab foundation) JobContext{..}
unregister = atomically . whenM (fromMaybe False <$> tryReadTMVar registeredCron) . void $ tryTakeTMVar appCronThread
cData <- allocate (liftIO . forkFinally execCrontab' $ \_ -> unregister) (\_ -> liftIO . atomically . void $ tryTakeTMVar jobCrontab)
registeredCron' <- atomically $ do
@@ -126,73 +127,75 @@ stopJobCtl UniWorX{appJobCtl, appCronThread} = do
guard . none (`Map.member` wMap') $ Map.keysSet wMap
-execCrontab :: ReaderT JobContext (HandlerT UniWorX IO) ()
+execCrontab :: MonadIO m => UniWorX -> ReaderT JobContext m ()
-- ^ Keeping a `HashMap` of the latest execution times of `JobCtl`s we have
-- seen, wait for the time of the next job and fire it
-execCrontab = evalStateT go HashMap.empty
+execCrontab foundation = evalStateT go HashMap.empty
where
go = do
- mapStateT (liftHandlerT . runDB . setSerializable) $ do
- let
- merge (Entity leId CronLastExec{..})
- | Just job <- Aeson.parseMaybe parseJSON cronLastExecJob
- = State.modify $ HashMap.insertWith (<>) (JobCtlQueue job) (Max cronLastExecTime)
- | otherwise = lift $ delete leId
- runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
+ cont <- mapStateT (mapReaderT $ liftIO . unsafeHandler foundation) $ do
+ mapStateT (liftHandlerT . runDB . setSerializable) $ do
+ let
+ merge (Entity leId CronLastExec{..})
+ | Just job <- Aeson.parseMaybe parseJSON cronLastExecJob
+ = State.modify $ HashMap.insertWith (<>) (JobCtlQueue job) (Max cronLastExecTime)
+ | otherwise = lift $ delete leId
+ runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ merge
- refT <- liftIO getCurrentTime
- settings <- getsYesod appSettings'
- currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
- crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
- case crontab' of
- Nothing -> return Nothing
- Just crontab -> Just <$> do
- State.modify . HashMap.filterWithKey $ \k _ -> HashMap.member k crontab
- prevExec <- State.get
- case earliestJob settings prevExec crontab refT of
- Nothing -> liftBase retry
- Just (_, MatchNone) -> liftBase retry
- Just x -> return (crontab, x)
+ refT <- liftIO getCurrentTime
+ settings <- getsYesod appSettings'
+ currentState <- mapStateT (mapReaderT $ liftIO . atomically) $ do
+ crontab' <- liftBase . tryReadTMVar =<< asks jobCrontab
+ case crontab' of
+ Nothing -> return Nothing
+ Just crontab -> Just <$> do
+ State.modify . HashMap.filterWithKey $ \k _ -> HashMap.member k crontab
+ prevExec <- State.get
+ case earliestJob settings prevExec crontab refT of
+ Nothing -> liftBase retry
+ Just (_, MatchNone) -> liftBase retry
+ Just x -> return (crontab, x)
- case currentState of
- Nothing -> return ()
- Just (currentCrontab, (jobCtl, nextMatch)) -> do
- let doJob = mapStateT (mapReaderT $ liftHandlerT . runDBJobs . setSerializable) $ do
- newCrontab <- lift . lift . hoist lift $ determineCrontab'
- if
- | ((==) `on` HashMap.lookup jobCtl) newCrontab currentCrontab
- -> do
- now <- liftIO $ getCurrentTime
- instanceID' <- getsYesod appInstanceID
- State.modify $ HashMap.alter (Just . ($ Max now) . maybe id (<>)) jobCtl
- case jobCtl of
- JobCtlQueue job -> do
- void . lift . lift $ upsertBy
- (UniqueCronLastExec $ toJSON job)
- CronLastExec
- { cronLastExecJob = toJSON job
- , cronLastExecTime = now
- , cronLastExecInstance = instanceID'
- }
- [ CronLastExecTime =. now ]
- lift . lift $ queueDBJob job
- other -> writeJobCtl other
- | otherwise
- -> lift . mapReaderT (liftIO . atomically) $
- lift . void . flip swapTMVar newCrontab =<< asks jobCrontab
+ case currentState of
+ Nothing -> return False
+ Just (currentCrontab, (jobCtl, nextMatch)) -> do
+ let doJob = mapStateT (mapReaderT $ liftHandlerT . runDBJobs . setSerializable) $ do
+ newCrontab <- lift . lift . hoist lift $ determineCrontab'
+ if
+ | ((==) `on` HashMap.lookup jobCtl) newCrontab currentCrontab
+ -> do
+ now <- liftIO $ getCurrentTime
+ instanceID' <- getsYesod appInstanceID
+ State.modify $ HashMap.alter (Just . ($ Max now) . maybe id (<>)) jobCtl
+ case jobCtl of
+ JobCtlQueue job -> do
+ void . lift . lift $ upsertBy
+ (UniqueCronLastExec $ toJSON job)
+ CronLastExec
+ { cronLastExecJob = toJSON job
+ , cronLastExecTime = now
+ , cronLastExecInstance = instanceID'
+ }
+ [ CronLastExecTime =. now ]
+ lift . lift $ queueDBJob job
+ other -> writeJobCtl other
+ | otherwise
+ -> lift . mapReaderT (liftIO . atomically) $
+ lift . void . flip swapTMVar newCrontab =<< asks jobCrontab
- case nextMatch of
- MatchAsap -> doJob
- MatchNone -> return ()
- MatchAt nextTime -> do
- JobContext{jobCrontab} <- ask
- nextTime' <- applyJitter jobCtl nextTime
- $logDebugS "Cron" [st|Waiting until #{tshow (utcToLocalTimeTZ appTZ nextTime')} to execute #{tshow jobCtl}|]
- logFunc <- askLoggerIO
- whenM (liftIO . flip runLoggingT logFunc $ waitUntil jobCrontab currentCrontab nextTime')
- doJob
+ case nextMatch of
+ MatchAsap -> doJob
+ MatchNone -> return ()
+ MatchAt nextTime -> do
+ JobContext{jobCrontab} <- ask
+ nextTime' <- applyJitter jobCtl nextTime
+ $logDebugS "Cron" [st|Waiting until #{tshow (utcToLocalTimeTZ appTZ nextTime')} to execute #{tshow jobCtl}|]
+ logFunc <- askLoggerIO
+ whenM (liftIO . flip runLoggingT logFunc $ waitUntil jobCrontab currentCrontab nextTime')
+ doJob
- go
+ return True
+ when cont go
where
acc :: NominalDiffTime
acc = 1e-3
@@ -244,12 +247,12 @@ execCrontab = evalStateT go HashMap.empty
bool (waitUntil crontabTV crontab nextTime) (return False) crontabChanged
-handleJobs' :: Natural -> Sink JobCtl (ReaderT JobContext Handler) ()
-handleJobs' wNum = C.mapM_ $ \jctl -> do
+handleJobs' :: (MonadIO m, MonadLogger m, MonadCatch m) => UniWorX -> Natural -> Sink JobCtl (ReaderT JobContext m) ()
+handleJobs' foundation wNum = C.mapM_ $ \jctl -> do
$logDebugS logIdent $ tshow jctl
resVars <- mapReaderT (liftIO . atomically) $
HashMap.lookup jctl <$> (lift . readTVar =<< asks jobConfirm)
- res <- fmap (either Just $ const Nothing) . try $ handleCmd jctl
+ res <- fmap (either Just $ const Nothing) . try . (mapReaderT $ liftIO . unsafeHandler foundation) $ handleCmd jctl
sentRes <- liftIO . atomically $ foldrM (\resVar -> bool (tryPutTMVar resVar res) $ return True) False (maybe [] NonEmpty.toList resVars)
case res of
Just err
diff --git a/src/Utils.hs b/src/Utils.hs
index bd9364299..b9239d9e3 100644
--- a/src/Utils.hs
+++ b/src/Utils.hs
@@ -69,6 +69,7 @@ import qualified Crypto.Data.PKCS7 as PKCS7
import Data.Fixed
import Data.Ratio ((%))
+import Data.Binary (Binary)
import qualified Data.Binary as Binary
import Network.Wai (requestMethod)
@@ -921,10 +922,18 @@ encodedSecretBoxOpen ciphertext = do
-- Caching --
-------------
+cachedByBinary :: (Binary a, Typeable b, MonadHandler m) => a -> m b -> m b
+cachedByBinary k = cachedBy (toStrict $ Binary.encode k)
+
cachedHere :: Q Exp
cachedHere = do
loc <- location
- [e| cachedBy (toStrict $ Binary.encode loc) |]
+ [e| cachedByBinary loc |]
+
+cachedHereBinary :: Q Exp
+cachedHereBinary = do
+ loc <- location
+ [e| \k -> cachedByBinary (loc, k) |]
hashToText :: Hashable a => a -> Text
hashToText = decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
diff --git a/src/Yesod/Core/Types/Instances.hs b/src/Yesod/Core/Types/Instances.hs
index 5402ce3ba..2f03d0e94 100644
--- a/src/Yesod/Core/Types/Instances.hs
+++ b/src/Yesod/Core/Types/Instances.hs
@@ -2,7 +2,8 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances #-}
module Yesod.Core.Types.Instances
- ( CachedMemoT(..)
+ ( CachedMemoT
+ , runCachedMemoT
) where
import ClassyPrelude.Yesod
@@ -13,9 +14,15 @@ import Control.Monad.Fix
import Control.Monad.Memo
import Data.Binary (Binary)
-import qualified Data.Binary as Binary
import Control.Monad.Logger (MonadLoggerIO)
+
+import Utils
+
+import Language.Haskell.TH
+
+import Control.Monad.Reader (MonadReader(..))
+import Control.Monad.Trans.Reader (ReaderT, mapReaderT, runReaderT)
instance MonadFix m => MonadFix (HandlerT site m) where
@@ -26,23 +33,31 @@ instance MonadFix m => MonadFix (WidgetT site m) where
-- | Type-level tags for compatability of Yesod `cached`-System with `MonadMemo`
-newtype CachedMemoT k v m a = CachedMemoT { runCachedMemoT :: m a }
+newtype CachedMemoT k v m a = CachedMemoT { runCachedMemoT' :: ReaderT Loc m a }
deriving newtype ( Functor, Applicative, Alternative, Monad, MonadPlus, MonadFix
, MonadIO
, MonadThrow, MonadCatch, MonadMask, MonadLogger, MonadLoggerIO
, MonadResource, MonadHandler, MonadWidget
- , IsString, Semigroup, Monoid
)
deriving newtype instance MonadBase b m => MonadBase b (CachedMemoT k v m)
deriving newtype instance MonadBaseControl b m => MonadBaseControl b (CachedMemoT k v m)
-deriving newtype instance MonadReader r m => MonadReader r (CachedMemoT k v m)
+instance MonadReader r m => MonadReader r (CachedMemoT k v m) where
+ reader = CachedMemoT . lift . reader
+ local f (CachedMemoT act) = CachedMemoT $ mapReaderT (local f) act
instance MonadTrans (CachedMemoT k v) where
- lift = CachedMemoT
+ lift = CachedMemoT . lift
-- | Uses `cachedBy` with a `Binary`-encoded @k@
instance (Typeable v, Binary k, MonadHandler m) => MonadMemo k v (CachedMemoT k v m) where
- memo act key = cachedBy (toStrict $ Binary.encode key) $ act key
+ memo act key = do
+ loc <- CachedMemoT ask
+ cachedByBinary (loc, key) $ act key
+
+runCachedMemoT :: Q Exp
+runCachedMemoT = do
+ loc <- location
+ [e| flip runReaderT loc . runCachedMemoT' |]
diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js
deleted file mode 100644
index ccc441038..000000000
--- a/static/js/utils/asyncTable.js
+++ /dev/null
@@ -1,430 +0,0 @@
-(function collonadeClosure() {
- 'use strict';
-
- /**
- *
- * Async Table Utility
- * makes table filters, sorting and pagination behave asynchronously via AJAX calls
- *
- * Attribute: uw-async-table
- *
- * Example usage:
- * (regular table)
- */
-
- var INPUT_DEBOUNCE = 600;
- var HEADER_HEIGHT = 80;
-
- var ASYNC_TABLE_UTIL_NAME = 'asyncTable';
- var ASYNC_TABLE_UTIL_SELECTOR = '[uw-async-table]';
-
- var ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE';
- var ASYNC_TABLE_SCROLLTABLE_SELECTOR = '.scrolltable';
- var ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized';
- var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
-
- var ASYNC_TABLE_FILTER_FORM_SELECTOR = '.table-filter-form';
- var ASYNC_TABLE_FILTER_FORM_ID_SELECTOR = '[name="form-identifier"]';
-
-
- var asyncTableUtil = function(element) {
- var asyncTableHeader;
- var asyncTableId;
-
- var ths = [];
- var pageLinks = [];
- var pagesizeForm;
- var scrollTable;
- var cssIdPrefix = '';
-
- var tableFilterInputs = {
- search: [],
- input: [],
- change: [],
- select: [],
- }
-
- function init() {
- if (!element) {
- throw new Error('Async Table utility cannot be setup without an element!');
- }
-
- if (element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) {
- return false;
- }
-
- // param asyncTableDbHeader
- if (element.dataset.asyncTableDbHeader !== undefined) {
- asyncTableHeader = element.dataset.asyncTableDbHeader;
- }
-
- var rawTableId = element.querySelector('table').id;
- cssIdPrefix = findCssIdPrefix(rawTableId);
- asyncTableId = rawTableId.replace(cssIdPrefix, '');
-
- // find scrolltable wrapper
- scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
- if (!scrollTable) {
- throw new Error('Async Table cannot be set up without a scrolltable element!');
- }
-
- setupSortableHeaders();
- setupPagination();
- setupPageSizeSelect();
- setupTableFilter();
-
- processLocalStorage();
-
- // clear currentTableUrl from previous requests
- setLocalStorageParameter('currentTableUrl', null);
-
- // mark initialized
- element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
-
- return {
- name: ASYNC_TABLE_UTIL_NAME,
- element: element,
- destroy: function() {},
- };
- }
-
- function setupSortableHeaders() {
- ths = Array.from(scrollTable.querySelectorAll('th.sortable')).map(function(th) {
- return { element: th };
- });
-
- ths.forEach(function(th) {
- th.clickHandler = function(event) {
- setLocalStorageParameter('horizPos', (scrollTable || {}).scrollLeft);
- linkClickHandler(event);
- };
- th.element.addEventListener('click', th.clickHandler);
- });
- }
-
- function setupPagination() {
- var pagination = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagination');
- if (pagination) {
- pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) {
- return { element: link };
- });
-
- pageLinks.forEach(function(link) {
- link.clickHandler = function(event) {
- var tableBoundingRect = scrollTable.getBoundingClientRect();
- if (tableBoundingRect.top < HEADER_HEIGHT) {
- var scrollTo = {
- top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT,
- left: scrollTable.offsetLeft || 0,
- behavior: 'smooth',
- };
- setLocalStorageParameter('scrollTo', scrollTo);
- }
- linkClickHandler(event);
- }
- link.element.addEventListener('click', link.clickHandler);
- });
- }
- }
-
- function setupPageSizeSelect() {
- // pagesize form
- pagesizeForm = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagesize-form');
-
- if (pagesizeForm) {
- var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]');
- pagesizeSelect.addEventListener('change', changePagesizeHandler);
- }
- }
-
- function setupTableFilter() {
- var tableFilterForm = element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);
-
- if (tableFilterForm) {
- gatherTableFilterInputs(tableFilterForm);
- addTableFilterEventListeners(tableFilterForm);
- }
- }
-
- function gatherTableFilterInputs(tableFilterForm) {
- Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach(function(input) {
- tableFilterInputs.search.push(input);
- });
-
- Array.from(tableFilterForm.querySelectorAll('input[type="text"]')).forEach(function(input) {
- tableFilterInputs.input.push(input);
- });
-
- Array.from(tableFilterForm.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach(function(input) {
- tableFilterInputs.change.push(input);
- });
-
- Array.from(tableFilterForm.querySelectorAll('select')).forEach(function(input) {
- tableFilterInputs.select.push(input);
- });
- }
-
- function addTableFilterEventListeners(tableFilterForm) {
- tableFilterInputs.search.forEach(function(input) {
- var debouncedInput = debounce(function() {
- if (input.value.length === 0 || input.value.length > 2) {
- updateFromTableFilter(tableFilterForm);
- }
- }, INPUT_DEBOUNCE);
- input.addEventListener('input', debouncedInput);
- });
-
- tableFilterInputs.input.forEach(function(input) {
- var debouncedInput = debounce(function() {
- if (input.value.length === 0 || input.value.length > 2) {
- updateFromTableFilter(tableFilterForm);
- }
- }, INPUT_DEBOUNCE);
- input.addEventListener('input', debouncedInput);
- });
-
- tableFilterInputs.change.forEach(function(input) {
- input.addEventListener('change', function() {
- updateFromTableFilter(tableFilterForm);
- });
- });
-
- tableFilterInputs.select.forEach(function(input) {
- input.addEventListener('change', function() {
- updateFromTableFilter(tableFilterForm);
- });
- });
-
- tableFilterForm.addEventListener('submit', function(event) {
- event.preventDefault();
- updateFromTableFilter(tableFilterForm);
- });
- }
-
- function updateFromTableFilter(tableFilterForm) {
- var url = serializeTableFilterToURL();
- var callback = null;
-
- var focusedInput = tableFilterForm.querySelector(':focus, :active');
- // focus previously focused input
- if (focusedInput && focusedInput.selectionStart !== null) {
- var selectionStart = focusedInput.selectionStart;
- // remove the following part of the id to get rid of the random
- // (yet somewhat structured) prefix we got from nudging.
- var prefix = findCssIdPrefix(focusedInput.id);
- var focusId = focusedInput.id.replace(prefix, '');
- callback = function(wrapper) {
- var idPrefix = getLocalStorageParameter('cssIdPrefix');
- var toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
- if (toBeFocused) {
- toBeFocused.focus();
- toBeFocused.selectionStart = selectionStart;
- }
- };
- }
- updateTableFrom(url, callback);
- }
-
- function serializeTableFilterToURL() {
- var url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href);
-
- var formIdElement = element.querySelector(ASYNC_TABLE_FILTER_FORM_ID_SELECTOR);
- if (!formIdElement) {
- // cannot serialize the filter form without an identifier
- return;
- }
-
- url.searchParams.set('form-identifier', formIdElement.value);
- url.searchParams.set('_hasdata', 'true');
- url.searchParams.set(asyncTableId + '-page', '0');
-
- tableFilterInputs.search.forEach(function(input) {
- url.searchParams.set(input.name, input.value);
- });
-
- tableFilterInputs.input.forEach(function(input) {
- url.searchParams.set(input.name, input.value);
- });
-
- tableFilterInputs.change.forEach(function(input) {
- if (input.checked) {
- url.searchParams.set(input.name, input.value);
- }
- });
-
- tableFilterInputs.select.forEach(function(select) {
- var options = Array.from(select.querySelectorAll('option'));
- var selected = options.find(function(option) { return option.selected });
- if (selected) {
- url.searchParams.set(select.name, selected.value);
- }
- });
-
- return url;
- }
-
- function processLocalStorage() {
- var scrollTo = getLocalStorageParameter('scrollTo');
- if (scrollTo && scrollTable) {
- window.scrollTo(scrollTo);
- }
- setLocalStorageParameter('scrollTo', null);
-
- var horizPos = getLocalStorageParameter('horizPos');
- if (horizPos && scrollTable) {
- scrollTable.scrollLeft = horizPos;
- }
- setLocalStorageParameter('horizPos', null);
- }
-
- function removeListeners() {
- ths.forEach(function(th) {
- th.element.removeEventListener('click', th.clickHandler);
- });
-
- pageLinks.forEach(function(link) {
- link.element.removeEventListener('click', link.clickHandler);
- });
-
- if (pagesizeForm) {
- var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]')
- pagesizeSelect.removeEventListener('change', changePagesizeHandler);
- }
- }
-
- function linkClickHandler(event) {
- event.preventDefault();
- var url = getClickDestination(event.target);
- if (!url.match(/^http/)) {
- url = window.location.origin + window.location.pathname + url;
- }
- updateTableFrom(url);
- }
-
- function getClickDestination(el) {
- if (!el.matches('a') && !el.querySelector('a')) {
- return '';
- }
- return el.getAttribute('href') || el.querySelector('a').getAttribute('href');
- }
-
- function changePagesizeHandler(event) {
- var paginationParamKey = asyncTableId + '-pagination';
- var pagesizeParamKey = asyncTableId + '-pagesize';
- var pageParamKey = asyncTableId + '-page';
-
- var paginationParamEl = pagesizeForm.querySelector('[name="' + paginationParamKey + '"]');
- var url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href);
- url.searchParams.set(pagesizeParamKey, event.target.value);
- url.searchParams.set(pageParamKey, 0);
-
- if (paginationParamEl) {
- var encodedValue = encodeURIComponent(paginationParamEl.value);
- url.searchParams.set(paginationParamKey, encodedValue);
- }
- updateTableFrom(url.href);
- }
-
- // fetches new sorted element from url with params and replaces contents of current element
- function updateTableFrom(url, callback) {
- if (!HttpClient) {
- throw new Error('HttpClient not found!');
- }
-
- element.classList.add(ASYNC_TABLE_LOADING_CLASS);
-
- var headers = {
- 'Accept': 'text/html',
- [asyncTableHeader]: asyncTableId
- };
-
- HttpClient.get({
- url: url,
- headers: headers,
- accept: HttpClient.ACCEPT.TEXT_HTML,
- }).then(function(response) {
- return HtmlHelpers.parseResponse(response);
- }).then(function(response) {
- setLocalStorageParameter('currentTableUrl', url.href);
- // reset table
- removeListeners();
- element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
- // update table with new
- updateWrapperContents(response);
-
- if (UtilRegistry) {
- UtilRegistry.setupAll(element);
- }
-
- if (callback && typeof callback === 'function') {
- setLocalStorageParameter('cssIdPrefix', response.idPrefix);
- callback(element);
- setLocalStorageParameter('cssIdPrefix', '');
- }
- }).catch(function(err) {
- console.error(err);
- }).finally(function() {
- element.classList.remove(ASYNC_TABLE_LOADING_CLASS);
- });
- }
-
- function updateWrapperContents(response) {
- var newPage = document.createElement('div');
- newPage.appendChild(response.element);
- var newWrapperContents = newPage.querySelector('#' + response.idPrefix + element.id);
- element.innerHTML = newWrapperContents.innerHTML;
- }
-
- return init();
- };
-
- // returns any random nudged prefix found in the given id
- function findCssIdPrefix(id) {
- var matcher = /r\d*?__/;
- var maybePrefix = id.match(matcher);
- if (maybePrefix && maybePrefix[0]) {
- return maybePrefix[0]
- }
- return '';
- }
-
- function setLocalStorageParameter(key, value) {
- var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
- if (value !== null) {
- currentLSState[key] = value;
- } else {
- delete currentLSState[key];
- }
- window.localStorage.setItem(ASYNC_TABLE_LOCAL_STORAGE_KEY, JSON.stringify(currentLSState));
- }
-
- function getLocalStorageParameter(key) {
- var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
- return currentLSState[key];
- }
-
- // debounce function, taken from Underscore.js
- function debounce(func, wait, immediate) {
- var timeout;
- return function() {
- var context = this, args = arguments;
- var later = function() {
- timeout = null;
- if (!immediate) func.apply(context, args);
- };
- var callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) func.apply(context, args);
- };
- }
-
- // register async table utility
- if (UtilRegistry) {
- UtilRegistry.register({
- name: ASYNC_TABLE_UTIL_NAME,
- selector: ASYNC_TABLE_UTIL_SELECTOR,
- setup: asyncTableUtil,
- });
- }
-})();
diff --git a/templates/table/colonnade.hamlet b/templates/table/colonnade.hamlet
index 71938bc4a..9ccea4ef8 100644
--- a/templates/table/colonnade.hamlet
+++ b/templates/table/colonnade.hamlet
@@ -1,6 +1,6 @@
$newline never
-
+
$maybe wHeaders' <- wHeaders
diff --git a/templates/table/layout-wrapper.hamlet b/templates/table/layout-wrapper.hamlet
index fc144c1ed..1092c126f 100644
--- a/templates/table/layout-wrapper.hamlet
+++ b/templates/table/layout-wrapper.hamlet
@@ -1,3 +1,3 @@
$newline never
-