From d9edb40cd5c963850998a21689527b45b1bbd7ad Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 13 Mar 2020 00:34:27 -0400 Subject: [PATCH 01/48] Added basic id3 tagging functionality and simplified mp3 downloading logic --- backend/app.js | 24 +++++++++++++++++++----- backend/package.json | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/backend/app.js b/backend/app.js index 027dd1d..b95ecb0 100644 --- a/backend/app.js +++ b/backend/app.js @@ -9,6 +9,7 @@ var express = require("express"); var bodyParser = require("body-parser"); var archiver = require('archiver'); const low = require('lowdb') +const NodeID3 = require('node-id3') var URL = require('url').URL; const shortid = require('shortid') const url_api = require('url'); @@ -658,11 +659,24 @@ app.post('/api/tomp3', function(req, res) { // if invalid, continue onto the next continue; } - var file_name = output_json['_filename'].replace(/^.*[\\\/]/, ''); - var file_path = output_json['_filename'].substring(audioFolderPath.length, output_json['_filename'].length); - var alternate_file_path = file_path.substring(0, file_path.length-4); - var alternate_file_name = file_name.substring(0, file_name.length-4); - if (alternate_file_path) file_names.push(alternate_file_path); + + var full_file_path = output_json['_filename'].substring(0, output_json['_filename'].length-5) + '.mp3'; + console.log(full_file_path); + if (fs.existsSync(full_file_path)) { + let tags = { + title: output_json['title'], + artist: output_json['artist'] ? output_json['artist'] : output_json['uploader'] + } + // NodeID3.create(tags, function(frame) { }) + let success = NodeID3.write(tags, full_file_path); + if (success) console.log('successfully tagged file'); + else console.log('failed to tag file'); + } else { + console.log('Output mp3 does not exist'); + } + + var file_path = output_json['_filename'].substring(audioFolderPath.length, output_json['_filename'].length-5); + if (file_path) file_names.push(file_path); } let is_playlist = file_names.length > 1; diff --git a/backend/package.json b/backend/package.json index 6da5631..f4c042f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "exe": "^1.0.2", "express": "^4.17.1", "lowdb": "^1.0.0", + "node-id3": "^0.1.14", "shortid": "^2.2.15", "uuidv4": "^6.0.6", "youtube-dl": "^3.0.2" From 21797f39018e5ccfa6cfdb5cc17d2c466d9c28e6 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sat, 14 Mar 2020 17:32:52 -0400 Subject: [PATCH 02/48] Added preliminary localization support to almost all strings in the program --- package.json | 1 + src/app/app.component.html | 8 +- .../create-playlist.component.html | 7 +- .../subscribe-dialog.component.html | 16 +-- .../subscription-info-dialog.component.html | 14 +- .../download-item.component.html | 2 +- src/app/file-card/file-card.component.html | 4 +- .../input-dialog/input-dialog.component.html | 1 + src/app/main/main.component.html | 122 +++++++++++++----- src/app/player/player.component.html | 2 +- src/app/settings/settings.component.html | 98 +++++++------- .../subscription-file-card.component.html | 6 +- .../subscription/subscription.component.html | 4 +- .../subscriptions.component.html | 20 +-- src/polyfills.ts | 4 + 15 files changed, 189 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 73966f1..2801163 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@angular/core": "^8.2.11", "@angular/forms": "^8.2.11", "@angular/http": "^7.2.15", + "@angular/localize": "^9.0.6", "@angular/material": "^8.2.3", "@angular/platform-browser": "^8.2.11", "@angular/platform-browser-dynamic": "^8.2.11", diff --git a/src/app/app.component.html b/src/app/app.component.html index fb2927b..9403aeb 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -14,12 +14,12 @@ @@ -30,8 +30,8 @@ - Home - Subscriptions + Home + Subscriptions diff --git a/src/app/create-playlist/create-playlist.component.html b/src/app/create-playlist/create-playlist.component.html index 5447200..c361625 100644 --- a/src/app/create-playlist/create-playlist.component.html +++ b/src/app/create-playlist/create-playlist.component.html @@ -1,13 +1,14 @@ -

Create a playlist

+

Create a playlist

- +
- {{(type === 'audio') ? 'Audio files' : 'Videos'}} + Audio files + Videos {{file.id}} diff --git a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html index 7e8d004..10f81b7 100644 --- a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html +++ b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html @@ -1,25 +1,25 @@ -

Subscribe to playlist or channel

+

Subscribe to playlist or channel

- - The playlist or channel URL + + The playlist or channel URL
- - This is optional + + This is optional
- Download all uploads + Download all uploads
- Download videos uploaded in the last + Download videos uploaded in the last @@ -34,7 +34,7 @@ - +
diff --git a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html index eec94eb..1ebff5a 100644 --- a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html +++ b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html @@ -2,26 +2,26 @@
- Type: + Type:  {{(sub.isPlaylist ? 'Playlist' : 'Channel')}}
- URL: + URL:  {{sub.url}}
- ID: + ID:  {{sub.id}}
- Archive: + Archive:  {{sub.archive}}
- - + + - + \ No newline at end of file diff --git a/src/app/download-item/download-item.component.html b/src/app/download-item/download-item.component.html index 3ca4144..5c5a02f 100644 --- a/src/app/download-item/download-item.component.html +++ b/src/app/download-item/download-item.component.html @@ -4,7 +4,7 @@
{{queueNumber}}.
-
ID: {{url_id}}
+
ID: {{url_id}}
diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index 5dc8773..39184ce 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -3,8 +3,8 @@
{{title}}
- ID: {{name}} -
Count: {{count}}
+ ID: {{name}} +
Count: {{count}}
Thumbnail diff --git a/src/app/input-dialog/input-dialog.component.html b/src/app/input-dialog/input-dialog.component.html index a636cf6..09acfb5 100644 --- a/src/app/input-dialog/input-dialog.component.html +++ b/src/app/input-dialog/input-dialog.component.html @@ -7,6 +7,7 @@
+ diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 4c6807a..110b1d0 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -2,7 +2,7 @@
- Youtube Downloader + Youtube Downloader
@@ -12,13 +12,19 @@
- Please enter a valid URL! + + Please enter a valid URL! +
- Quality + + + Quality + + @@ -42,22 +48,43 @@
{{result.uploaded}}
- - + +

- Only Audio - Multi-download mode + + + Only Audio + + + + + Multi-download Mode + +
- - + +
@@ -66,35 +93,60 @@ - Advanced + + Advanced + -

Simulated command: {{this.simulatedOutput}}

+

+ + Simulated command: + +  {{this.simulatedOutput}}

- Use custom args + + + Use custom args + + - - No need to include URL, just everything after. + + + + No need to include URL, just everything after. + +
- Use custom output + + + Use custom output + + - - Documentation. Path is relative to the config download path. Don't include extension. + + + Documentation. + Path is relative to the config download path. Don't include extension. +
- Use authentication + + + Use authentication + + - +
- +
@@ -138,10 +190,14 @@ - Audio + + Audio + - Your audio files are here + + Your audio files are here +
@@ -154,7 +210,7 @@
-
Playlists
+
Playlists
@@ -165,7 +221,9 @@
- No playlists available. Create one from your downloading audio files by clicking the blue plus button. + + No playlists available. Create one from your downloading audio files by clicking the blue plus button. +
@@ -173,10 +231,14 @@ - Video + + Video + - Your video files are here + + Your video files are here +
@@ -190,7 +252,7 @@
-
Playlists
+
Playlists
@@ -203,7 +265,9 @@
- No playlists available. Create one from your downloading video files by clicking the blue plus button. + + No playlists available. Create one from your downloading video files by clicking the blue plus button. +
diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index f424988..10b9f56 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -19,7 +19,7 @@
- +
diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 6594c44..ec76f1c 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -1,25 +1,25 @@ -

Settings

- +

Settings

+ - Host + Host
- - Base URL this app will be accessed from, without the port. + + URL this app will be accessed from, without the port.
- - The desired port. Default is 17442. + + The desired port. Default is 17442.
@@ -31,24 +31,24 @@ - Encryption + Encryption
- Use encryption + Use encryption
- +
- +
@@ -59,29 +59,29 @@ - Downloader + Downloader
- - Path for audio only downloads. It is relative to YTDL-Material's root folder. + + Path for audio only downloads. It is relative to YTDL-Material's root folder.
- - Path for video downloads. It is relative to YTDL-Material's root folder. + + Path for video downloads. It is relative to YTDL-Material's root folder.
- - Global custom args for downloads on the home page. + + Global custom args for downloads on the home page.
@@ -92,28 +92,28 @@ - Extra + Extra
- +
- File manager enabled + File manager enabled
- Allow quality select + Allow quality select
- Download only mode + Download only mode
- Allow multi-download mode + Allow multi-download mode
@@ -123,18 +123,18 @@ - API + API
- Use YouTube API + Use YouTube API
@@ -145,20 +145,20 @@ - Themes + Themes
- Default - Dark + Default + Dark
- Allow theme change + Allow theme change
@@ -168,30 +168,30 @@ - Subscriptions + Subscriptions
- Allow subscriptions + Allow subscriptions
- - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder.
- - Unit is seconds, only include numbers. + + Unit is seconds, only include numbers.
- Use youtube-dl archive -

With youtube-dl's archive feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory.

-

This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss.

+ Use youtube-dl archive +

With youtube-dl's archive feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory.

+

This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss.

@@ -201,22 +201,22 @@ - Advanced + Advanced
- Use default downloading agent + Use default downloading agent
- +
- Allow advanced download + Allow advanced download
@@ -225,7 +225,11 @@
- - + +
\ No newline at end of file diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.html b/src/app/subscription/subscription-file-card/subscription-file-card.component.html index 32ab2c7..4537dd6 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -1,11 +1,11 @@
- Length: {{formattedDuration}} + Length: {{formattedDuration}}
- - + +
diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index bb4c5d5..d94a0a4 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -12,11 +12,11 @@
-

Videos

+

Videos

- + search
diff --git a/src/app/subscriptions/subscriptions.component.html b/src/app/subscriptions/subscriptions.component.html index 80572fd..5bf76bb 100644 --- a/src/app/subscriptions/subscriptions.component.html +++ b/src/app/subscriptions/subscriptions.component.html @@ -1,20 +1,17 @@
-

Your subscriptions

+

Your subscriptions


-

Channels

+

Channels

{{ sub.name }}
- Name not available. Channel retrieval in progress. - - - + Name not available. Channel retrieval in progress.
diff --git a/src/assets/default.json b/src/assets/default.json index 94aad55..7a41fcc 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -1,44 +1,45 @@ { - "YoutubeDLMaterial": { - "Host": { - "url": "http://localhost", - "port": "17442" - }, - "Encryption": { - "use-encryption": false, - "cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem", - "key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem" - }, - "Downloader": { - "path-audio": "audio/", - "path-video": "video/", - "custom_args": "" - }, - "Extra": { - "title_top": "Youtube Downloader", - "file_manager_enabled": true, - "allow_quality_select": true, - "download_only_mode": false, - "allow_multi_download_mode": true - }, - "API": { - "use_youtube_API": false, - "youtube_API_key": "" - }, - "Themes": { - "default_theme": "default", - "allow_theme_change": true - }, - "Subscriptions": { - "allow_subscriptions": true, - "subscriptions_base_path": "subscriptions/", - "subscriptions_check_interval": "300", - "subscriptions_use_youtubedl_archive": true - }, - "Advanced": { - "use_default_downloading_agent": true, - "custom_downloading_agent": "", - "allow_advanced_download": true - } + "YoutubeDLMaterial": { + "Host": { + "url": "http://localhost", + "port": "17442" + }, + "Encryption": { + "use-encryption": false, + "cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem", + "key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem" + }, + "Downloader": { + "path-audio": "audio/", + "path-video": "video/", + "use_youtubedl_archive": true, + "custom_args": "" + }, + "Extra": { + "title_top": "Youtube Downloader", + "file_manager_enabled": true, + "allow_quality_select": true, + "download_only_mode": false, + "allow_multi_download_mode": true + }, + "API": { + "use_youtube_API": false, + "youtube_API_key": "" + }, + "Themes": { + "default_theme": "default", + "allow_theme_change": true + }, + "Subscriptions": { + "allow_subscriptions": true, + "subscriptions_base_path": "subscriptions/", + "subscriptions_check_interval": "300", + "subscriptions_use_youtubedl_archive": true + }, + "Advanced": { + "use_default_downloading_agent": true, + "custom_downloading_agent": "", + "allow_advanced_download": true } } +} \ No newline at end of file From d39f6f7a177f58894214dc5e005d1b2bccbba154 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:28:18 -0400 Subject: [PATCH 16/48] File cards modified to support blacklisting videos when using youtube-dl archive --- src/app/app.module.ts | 5 +---- src/app/file-card/file-card.component.html | 7 ++++++- src/app/file-card/file-card.component.ts | 5 +++-- src/app/main/main.component.html | 8 ++++---- src/app/main/main.component.ts | 6 ++++++ .../subscription-file-card.component.html | 2 +- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8be4234..e6b6152 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule, LOCALE_ID } from '@angular/core'; import { registerLocaleData } from '@angular/common'; -import { LocaleService } from '@soluling/angular'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; @@ -111,9 +110,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible AppRoutingModule, ], providers: [ - PostsService, - LocaleService, - { provide: LOCALE_ID, deps: [LocaleService], useFactory: (service: LocaleService) => service.localeId }, + PostsService ], bootstrap: [AppComponent] }) diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index 39184ce..1c796c5 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -15,5 +15,10 @@
- + + + + + + diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 7e39fc7..770976e 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -21,6 +21,7 @@ export class FileCardComponent implements OnInit { @Output() removeFile: EventEmitter = new EventEmitter(); @Input() isPlaylist = false; @Input() count = null; + @Input() use_youtubedl_archive = false; type; image_loaded = false; image_errored = false; @@ -40,9 +41,9 @@ export class FileCardComponent implements OnInit { this.type = this.isAudio ? 'audio' : 'video'; } - deleteFile() { + deleteFile(blacklistMode = false) { if (!this.isPlaylist) { - this.postsService.deleteFile(this.name, this.isAudio).subscribe(result => { + this.postsService.deleteFile(this.name, this.isAudio, blacklistMode).subscribe(result => { if (result === true) { this.openSnackBar('Delete success!', 'OK.'); this.removeFile.emit(this.name); diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 110b1d0..dc11d79 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -204,7 +204,7 @@ + [length]="file.duration" [isAudio]="true" [use_youtubedl_archive]="use_youtubedl_archive"> @@ -215,7 +215,7 @@ + [length]="null" [isAudio]="true" [isPlaylist]="true" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"> @@ -245,7 +245,7 @@ + [length]="file.duration" [isAudio]="false" [use_youtubedl_archive]="use_youtubedl_archive"> @@ -257,7 +257,7 @@ + [length]="null" [isAudio]="false" [isPlaylist]="true" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"> diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index ba36282..975da5e 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -72,6 +72,7 @@ export class MainComponent implements OnInit { allowMultiDownloadMode = false; audioFolderPath; videoFolderPath; + use_youtubedl_archive = false; globalCustomArgs = null; allowAdvancedDownload = false; useDefaultDownloadingAgent = true; @@ -241,6 +242,7 @@ export class MainComponent implements OnInit { this.allowMultiDownloadMode = result['YoutubeDLMaterial']['Extra']['allow_multi_download_mode']; this.audioFolderPath = result['YoutubeDLMaterial']['Downloader']['path-audio']; this.videoFolderPath = result['YoutubeDLMaterial']['Downloader']['path-video']; + this.use_youtubedl_archive = result['YoutubeDLMaterial']['Downloader']['use_youtubedl_archive']; this.globalCustomArgs = result['YoutubeDLMaterial']['Downloader']['custom_args']; this.youtubeSearchEnabled = result['YoutubeDLMaterial']['API'] && result['YoutubeDLMaterial']['API']['use_youtube_API'] && result['YoutubeDLMaterial']['API']['youtube_API_key']; @@ -594,6 +596,8 @@ export class MainComponent implements OnInit { } }, error => { // can't access server this.downloadingfile = false; + this.current_download = null; + new_download['downloading'] = false; this.openSnackBar('Download failed!', 'OK.'); }); } else { @@ -626,6 +630,8 @@ export class MainComponent implements OnInit { } }, error => { // can't access server this.downloadingfile = false; + this.current_download = null; + new_download['downloading'] = false; this.openSnackBar('Download failed!', 'OK.'); }); } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.html b/src/app/subscription/subscription-file-card/subscription-file-card.component.html index 4537dd6..a2972a9 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -3,7 +3,7 @@ Length: {{formattedDuration}}
- + From f1c09a5fa918e0f4dcb06df392bcd6785efa717b Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:30:48 -0400 Subject: [PATCH 17/48] Added firefox extension zip to repo --- .../youtubedl-material-firefox-extension.zip | Bin 0 -> 4487 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 chrome-extension/youtubedl-material-firefox-extension.zip diff --git a/chrome-extension/youtubedl-material-firefox-extension.zip b/chrome-extension/youtubedl-material-firefox-extension.zip new file mode 100644 index 0000000000000000000000000000000000000000..54d612014896cbd2020c5353c08f04981dc8ca6e GIT binary patch literal 4487 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 literal 0 HcmV?d00001 From 84c9ec1300dfb056274de35cc7e54ce23f4c8a92 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:31:04 -0400 Subject: [PATCH 18/48] Updated file card image appearance --- src/app/file-card/file-card.component.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/file-card/file-card.component.css b/src/app/file-card/file-card.component.css index f17823f..7376b16 100644 --- a/src/app/file-card/file-card.component.css +++ b/src/app/file-card/file-card.component.css @@ -17,8 +17,9 @@ } .image { - max-width:100%; - max-height:100%; + width: 100%; + height: 100%; + border-radius: 0px 0px 4px 4px; } .example-full-width-height { From 44bff55a88f472bd64d5a7cc8c1d2594ebeb2319 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:35:51 -0400 Subject: [PATCH 19/48] Adds youtube dl args to simulated output --- src/app/main/main.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 975da5e..9c356ce 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -885,6 +885,10 @@ export class MainComponent implements OnInit { full_string_array.push(...additional_params); } + if (this.use_youtubedl_archive) { + full_string_array.push('--download-archive', 'archive.txt'); + } + if (globalArgsExists) { full_string_array = full_string_array.concat(this.globalCustomArgs.split(' ')); } From b765830584acd6cead9455cb9453dcf671b67555 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:37:26 -0400 Subject: [PATCH 20/48] Using youtubedl archive for downloads defaults to false --- src/assets/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/default.json b/src/assets/default.json index 7a41fcc..6a7e5b8 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -12,7 +12,7 @@ "Downloader": { "path-audio": "audio/", "path-video": "video/", - "use_youtubedl_archive": true, + "use_youtubedl_archive": false, "custom_args": "" }, "Extra": { From da399601e105572ac3d123450dc05ff612472822 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 20:59:21 -0400 Subject: [PATCH 21/48] Added ability to select any supported custom downloader --- backend/app.js | 18 +++++++++++++----- src/app/settings/settings.component.html | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/backend/app.js b/backend/app.js index b50dd99..0c08a84 100644 --- a/backend/app.js +++ b/backend/app.js @@ -58,7 +58,13 @@ let debugMode = process.env.YTDL_MODE === 'debug'; if (debugMode) console.log('YTDL-Material in debug mode!'); var validDownloadingAgents = [ - 'aria2c' + 'aria2c', + 'avconv', + 'axel', + 'curl', + 'ffmpeg', + 'httpie', + 'wget' ] // don't overwrite config if it already happened.. NOT @@ -143,6 +149,8 @@ async function loadConfig() { if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) { console.log(`INFO: Using non-default downloading agent \'${customDownloadingAgent}\'`) + } else { + customDownloadingAgent = null; } if (usingEncryption) @@ -659,8 +667,8 @@ app.post('/api/tomp3', async function(req, res) { downloadConfig.splice(3, 0, qualityPath); } - if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') { - downloadConfig.splice(0, 0, '--external-downloader', 'aria2c'); + if (!useDefaultDownloadingAgent && customDownloadingAgent) { + downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent); } if (useYoutubeDLArchive) { @@ -783,8 +791,8 @@ app.post('/api/tomp4', async function(req, res) { downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword); } - if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') { - downloadConfig.splice(0, 0, '--external-downloader', 'aria2c'); + if (!useDefaultDownloadingAgent && customDownloadingAgent) { + downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent); } if (useYoutubeDLArchive) { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 1b0dd3b..8c0135d 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -223,13 +223,19 @@
Use default downloading agent
-
- - - - +
+ + Select a downloader + aria2c + avconv + axel + curl + ffmpeg + httpie + wget +
-
+
Allow advanced download
From 2cd35cccd1da675f331d961c8e4d82a57dd16221 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sun, 15 Mar 2020 22:05:00 -0400 Subject: [PATCH 22/48] Added about page --- src/app/app.component.html | 4 +++ src/app/app.component.ts | 7 +++++ src/app/app.module.ts | 4 ++- .../about-dialog/about-dialog.component.html | 23 ++++++++++++++++ .../about-dialog/about-dialog.component.scss | 3 +++ .../about-dialog.component.spec.ts | 25 ++++++++++++++++++ .../about-dialog/about-dialog.component.ts | 21 +++++++++++++++ src/assets/images/GitHub-64px.png | Bin 0 -> 2625 bytes 8 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/app/dialogs/about-dialog/about-dialog.component.html create mode 100644 src/app/dialogs/about-dialog/about-dialog.component.scss create mode 100644 src/app/dialogs/about-dialog/about-dialog.component.spec.ts create mode 100644 src/app/dialogs/about-dialog/about-dialog.component.ts create mode 100644 src/assets/images/GitHub-64px.png diff --git a/src/app/app.component.html b/src/app/app.component.html index 9403aeb..8291ae3 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -21,6 +21,10 @@ settings Settings +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b5cb7b9..6f01687 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -21,6 +21,7 @@ import { Router, NavigationStart, NavigationEnd } from '@angular/router'; import { OverlayContainer } from '@angular/cdk/overlay'; import { THEMES_CONFIG } from '../themes'; import { SettingsComponent } from './settings/settings.component'; +import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; @Component({ selector: 'app-root', @@ -165,5 +166,11 @@ onSetTheme(theme, old_theme) { }); } + openAboutDialog() { + const dialogRef = this.dialog.open(AboutDialogComponent, { + width: '80vw' + }); + } + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e6b6152..a430fff 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,7 @@ import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dia import { SettingsComponent } from './settings/settings.component'; import es from '@angular/common/locales/es'; +import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; registerLocaleData(es, 'es'); export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps) { @@ -68,7 +69,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible SubscriptionComponent, SubscriptionFileCardComponent, SubscriptionInfoDialogComponent, - SettingsComponent + SettingsComponent, + AboutDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/dialogs/about-dialog/about-dialog.component.html b/src/app/dialogs/about-dialog/about-dialog.component.html new file mode 100644 index 0000000..dde7daf --- /dev/null +++ b/src/app/dialogs/about-dialog/about-dialog.component.html @@ -0,0 +1,23 @@ +

About YoutubeDL-Material

+ + +
+

+ YoutubeDL-Material is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. +

+

+ YoutubeDL-Material has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon below. +

+

+ Found a bug or have a suggestion? Click here to create an issue! +

+ + + +

Installed version: {{version}} - View latest update

+
+
+ + + + \ No newline at end of file diff --git a/src/app/dialogs/about-dialog/about-dialog.component.scss b/src/app/dialogs/about-dialog/about-dialog.component.scss new file mode 100644 index 0000000..b1d084c --- /dev/null +++ b/src/app/dialogs/about-dialog/about-dialog.component.scss @@ -0,0 +1,3 @@ +i { + margin-right: 1px; +} \ No newline at end of file diff --git a/src/app/dialogs/about-dialog/about-dialog.component.spec.ts b/src/app/dialogs/about-dialog/about-dialog.component.spec.ts new file mode 100644 index 0000000..7fc637d --- /dev/null +++ b/src/app/dialogs/about-dialog/about-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AboutDialogComponent } from './about-dialog.component'; + +describe('AboutDialogComponent', () => { + let component: AboutDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AboutDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AboutDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/about-dialog/about-dialog.component.ts b/src/app/dialogs/about-dialog/about-dialog.component.ts new file mode 100644 index 0000000..475328a --- /dev/null +++ b/src/app/dialogs/about-dialog/about-dialog.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-about-dialog', + templateUrl: './about-dialog.component.html', + styleUrls: ['./about-dialog.component.scss'] +}) +export class AboutDialogComponent implements OnInit { + + projectLink = 'https://github.com/Tzahi12345/YoutubeDL-Material'; + issuesLink = 'https://github.com/Tzahi12345/YoutubeDL-Material/issues'; + latestUpdateLink = 'https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest' + + version = 'v3.5'; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/assets/images/GitHub-64px.png b/src/assets/images/GitHub-64px.png new file mode 100644 index 0000000000000000000000000000000000000000..182a1a3f734fc1b7d712c68b04c29bad9460d6cd GIT binary patch literal 2625 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 W4I>>0bz>}E>ge79v Date: Mon, 16 Mar 2020 01:22:09 -0400 Subject: [PATCH 23/48] Changed location of db and config to one unified directory, 'appdata' Archive files now get generated if nonexistent during deletions Simplified docker-compose.yml to not require environment variables. Added volume for appdata folder which will be automatically shipped with docker builds --- backend/app.js | 26 ++++++++++++++----- backend/{config => appdata}/default.json | 0 backend/{config => appdata}/encrypted.json | 0 backend/config.js | 2 +- backend/consts.js | 2 -- backend/subscriptions.js | 2 +- docker-compose.yml | 30 +++------------------- 7 files changed, 24 insertions(+), 38 deletions(-) rename backend/{config => appdata}/default.json (100%) rename backend/{config => appdata}/encrypted.json (100%) diff --git a/backend/app.js b/backend/app.js index 0c08a84..d616e57 100644 --- a/backend/app.js +++ b/backend/app.js @@ -19,7 +19,7 @@ var subscriptions_api = require('./subscriptions') var app = express(); const FileSync = require('lowdb/adapters/FileSync') -const adapter = new FileSync('db.json'); +const adapter = new FileSync('./appdata/db.json'); const db = low(adapter) // Set some defaults @@ -41,7 +41,6 @@ var usingEncryption = null; var basePath = null; var audioFolderPath = null; var videoFolderPath = null; -var useYoutubeDLArchive = null; var downloadOnlyMode = null; var useDefaultDownloadingAgent = null; var customDownloadingAgent = null; @@ -140,7 +139,6 @@ async function loadConfig() { usingEncryption = config_api.getConfigItem('ytdl_use_encryption'); audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path'); videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path'); - useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); downloadOnlyMode = config_api.getConfigItem('ytdl_download_only_mode'); useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent'); customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent'); @@ -414,6 +412,7 @@ async function deleteAudioFile(name, blacklistMode = false) { } } + let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { const archive_path = audioFolderPath + 'archive.txt'; @@ -424,8 +423,13 @@ async function deleteAudioFile(name, blacklistMode = false) { if (jsonobj) id = jsonobj.id; // use subscriptions API to remove video from the archive file, and write it to the blacklist - const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null; - if (blacklistMode && line) writeToBlacklist('audio', line); + if (fs.existsSync(archive_path)) { + const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null; + if (blacklistMode && line) writeToBlacklist('audio', line); + } else { + console.log('Could not find archive file for audio files. Creating...'); + fs.closeSync(fs.openSync(archive_path, 'w')); + } } if (jsonExists) fs.unlinkSync(jsonPath); @@ -466,6 +470,7 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) { } } + let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { const archive_path = videoFolderPath + 'archive.txt'; @@ -476,8 +481,13 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) { if (jsonobj) id = jsonobj.id; // use subscriptions API to remove video from the archive file, and write it to the blacklist - const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null; - if (blacklistMode && line) writeToBlacklist('video', line); + if (fs.existsSync(archive_path)) { + const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null; + if (blacklistMode && line) writeToBlacklist('video', line); + } else { + console.log('Could not find archive file for videos. Creating...'); + fs.closeSync(fs.openSync(archive_path, 'w')); + } } if (jsonExists) fs.unlinkSync(jsonPath); @@ -671,6 +681,7 @@ app.post('/api/tomp3', async function(req, res) { downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent); } + let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { let archive_path = audioFolderPath + 'archive.txt'; // create archive file if it doesn't exist @@ -795,6 +806,7 @@ app.post('/api/tomp4', async function(req, res) { downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent); } + let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { let archive_path = videoFolderPath + 'archive.txt'; // create archive file if it doesn't exist diff --git a/backend/config/default.json b/backend/appdata/default.json similarity index 100% rename from backend/config/default.json rename to backend/appdata/default.json diff --git a/backend/config/encrypted.json b/backend/appdata/encrypted.json similarity index 100% rename from backend/config/encrypted.json rename to backend/appdata/encrypted.json diff --git a/backend/config.js b/backend/config.js index f61d321..fd6d854 100644 --- a/backend/config.js +++ b/backend/config.js @@ -3,7 +3,7 @@ const fs = require('fs'); let CONFIG_ITEMS = require('./consts.js')['CONFIG_ITEMS']; const debugMode = process.env.YTDL_MODE === 'debug'; -let configPath = debugMode ? '../src/assets/default.json' : 'config/default.json'; +let configPath = debugMode ? '../src/assets/default.json' : 'appdata/default.json'; // https://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key Object.byString = function(o, s) { diff --git a/backend/consts.js b/backend/consts.js index 73e4087..767dc0a 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -1,5 +1,3 @@ -var config = require('config'); - let CONFIG_ITEMS = { // Host 'ytdl_url': { diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 8fa9ce6..d324d74 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -8,7 +8,7 @@ var path = require('path'); var youtubedl = require('youtube-dl'); const config_api = require('./config'); -const adapter = new FileSync('db.json'); +const adapter = new FileSync('./appdata/db.json'); const db = low(adapter) const debugMode = process.env.YTDL_MODE === 'debug'; diff --git a/docker-compose.yml b/docker-compose.yml index 6db5ceb..f03bdea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,35 +4,11 @@ services: ytdl_material: build: . environment: - # config items - ytdl_url: http://localhost:8998 - ytdl_port: '17442' - ytdl_use_encryption: 'false' - ytdl_cert_file_path: /etc/letsencrypt/live/example.com/fullchain.pem - ytdl_key_file_path: /etc/letsencrypt/live/example.com/privkey.pem - ytdl_audio_folder_path: audio/ - ytdl_video_folder_path: video/ - ytdl_custom_args: '' - ytdl_title_top: Youtube Downloader - ytdl_file_manager_enabled: 'true' - ytdl_allow_quality_select: 'true' - ytdl_download_only_mode: 'false' - ytdl_allow_multi_download_mode: 'true' - ytdl_use_youtube_api: 'false' - ytdl_youtube_api_key: 'false' - ytdl_default_theme: default - ytdl_allow_theme_change: 'true' - ytdl_allow_subscriptions: 'true' - ytdl_subscriptions_base_path: subscriptions/ - ytdl_subscriptions_check_interval: '300' - ytdl_subscriptions_use_youtubedl_archive: 'true' - ytdl_use_default_downloading_agent: 'true' - ytdl_custom_downloading_agent: 'false' - ytdl_allow_advanced_download: 'false' - # do not touch this write_ytdl_config: 'true' ALLOW_CONFIG_MUTATIONS: 'true' restart: always + volumes: + - ./appdata:/app/config ports: - "8998:17442" - image: tzahi12345/youtubedl-material:3.4 \ No newline at end of file + image: tzahi12345/youtubedl-material:experimental \ No newline at end of file From 25b953cebd3edb2ea149cb1356b01741c0aa29f1 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Mon, 16 Mar 2020 01:22:48 -0400 Subject: [PATCH 24/48] Updated look of file cards again to prevent aspect ratio from being changed --- src/app/file-card/file-card.component.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/file-card/file-card.component.css b/src/app/file-card/file-card.component.css index 7376b16..342885f 100644 --- a/src/app/file-card/file-card.component.css +++ b/src/app/file-card/file-card.component.css @@ -18,8 +18,6 @@ .image { width: 100%; - height: 100%; - border-radius: 0px 0px 4px 4px; } .example-full-width-height { @@ -38,6 +36,8 @@ padding: 0px; margin: 8px 0px 0px -5px; width: calc(100% + 5px + 5px); + overflow: hidden; + border-radius: 0px 0px 4px 4px; } .max-two-lines { From 1d29dd8df4ee2ee18f0ed08356277e64336ab8f5 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Mon, 16 Mar 2020 01:23:08 -0400 Subject: [PATCH 25/48] Updated translations --- src/assets/i18n/messages.es.json | 26 ++-- src/locale/messages.xlf | 210 +++++++++++++++++++++---------- 2 files changed, 165 insertions(+), 71 deletions(-) diff --git a/src/assets/i18n/messages.es.json b/src/assets/i18n/messages.es.json index c197eaa..e72f728 100644 --- a/src/assets/i18n/messages.es.json +++ b/src/assets/i18n/messages.es.json @@ -5,18 +5,18 @@ "a52dae09be10ca3a65da918533ced3d3f4992238": "Archivos de video", "038ebcb2a89155d90c24fa1c17bfe83dbadc3c20": "Descargador de Youtube", "6d2ec8898344c8955542b0542c942038ef28bb80": "Por favor entre una URL válida", - "a38ae1082fec79ba1f379978337385a539a28e73": "Calidad", + "a38ae1082fec79ba1f379978337385a539a28e73": "Calidad:", "4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Usa URL", - "d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ver", + "d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ver:", "4a9889d36910edc8323d7bab60858ab3da6d91df": "Solo audio", "96a01fafe135afc58b0f8071a4ab00234495ce18": "Descarga múltiple", "6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Descarga", - "6a3777f913cf3f288664f0632b9f24794fdcc24e": "Cancela", + "6a3777f913cf3f288664f0632b9f24794fdcc24e": "Cancelar", "322ed150e02666fe2259c5b4614eac7066f4ffa0": "Avanzado", "b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Commando simulado:", "4e4c721129466be9c3862294dc40241b64045998": "Usar argumentos personalizados", "ad2f8ac8b7de7945b80c8e424484da94e597125f": "Argumentos personalizados", - "ccc7e92cbdd35e901acf9ad80941abee07bd8f60": "No es necesario incluir URL, solo todo después", + "ccc7e92cbdd35e901acf9ad80941abee07bd8f60": "No es necesario incluir URL, solo todo después ", "3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Usar salida personalizada", "d9c02face477f2f9cdaae318ccee5f89856851fb": "Salida personalizada", "fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentación", @@ -32,7 +32,9 @@ "960582a8b9d7942716866ecfb7718309728f2916": "Tus archivos de video son aquí", "0f59c46ca29e9725898093c9ea6b586730d0624e": "No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más.", "ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:", - "e684046d73bcee88e82f7ff01e2852789a05fc32": "Cuenta:", + "e684046d73bcee88e82f7ff01e2852789a05fc32": "Count:", + "826b25211922a1b46436589233cb6f1a163d89b7": "Eliminar", + "34504b488c24c27e68089be549f0eeae6ebaf30b": "Eliminar y ", "121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Configuraciones", "fe22ca53e651df951dac25b67c17894b0980f767": "Host", "801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL", @@ -49,6 +51,7 @@ "46826331da1949bd6fb74624447057099c9d20cd": "Ruta de la carpeta de video", "17c92e6d47a213fa95b5aa344b3f258147123f93": "Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material.", "f41145afc02fd47ef0576ac79acd2c47ebbf4901": "Argumentos personalizados globales para descargas en la página de inicio.", + "78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Usa el archivo de youtube-dl", "d5f69691f9f05711633128b5a3db696783266b58": "Extra", "61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Título superior", "78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Administrador de archivos habilitado", @@ -69,16 +72,23 @@ "bc9892814ee2d119ae94378c905ea440a249b84a": "Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material.", "5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Intervalo de comprobación", "0f56a7449b77630c114615395bbda4cab398efd8": "La unidad es segundos, solo incluye números.", - "78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Usa el archivo de youtube-dl", "fa9fe4255231dd1cc6b29d3d254a25cb7c764f0f": "Con la función de archivo de youtube-dl,", "09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones.", "29ed79a98fc01e7f9537777598e31dbde3aa7981": "Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos.", "bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avanzado", "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Usar agente de descarga predeterminado", - "cdf75b1bdda80487e2ce1ff264ae171cbc5dc3b1": "Agente personalizado", "dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Permitir descarga avanzada", "52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Salvar", "d7b35c384aecd25a516200d6921836374613dfe7": "Cancelar", + "cec82c0a545f37420d55a9b6c45c20546e82f94e": "Sobre YoutubeDL-Material", + "199c17e5d6a419313af3c325f06dcbb9645ca618": "es un descargador de código abierto de YouTube creado bajo las especificaciones de \"Material Design\" de Google. Puede descargar sin problemas sus videos favoritos como archivos de video o audio, e incluso suscribirse a sus canales favoritos y listas de reproducción para mantenerse actualizado con sus nuevos videos.", + "c072eebcb5b1f1eef6fb2ee1756e839dd302f3de": "tiene algunas características increíbles incluidas! Una amplia API, soporte de Docker y soporte de localización (traducción). Lea todas las funciones admitidas haciendo clic en el icono de GitHub a continuación.", + "b33536f59b94ec935a16bd6869d836895dc5300c": "¿Encontró un error o tiene una sugerencia?", + "9b3cedfa83c6d7acb3210953289d1be4aab115c7": "¡Haga clic aquí", + "e1f398f38ff1534303d4bb80bd6cece245f24016": "para crear una cuestión!", + "a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versión instalada:", + "effdc7dfbbc49c08d25ea1748fca00c38c918abd": "Ver la última actualización", + "004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Sobre", "92eee6be6de0b11c924e3ab27db30257159c0a7c": "Inicio", "5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Guardar cambios", "a9806cf78ce00eb2613eeca11354a97e033377b8": "Suscríbase a la lista de reproducción o al canal", @@ -100,7 +110,7 @@ "2e0a410652cb07d069f576b61eab32586a18320d": "Nombre no disponible. Recuperación de listas de reproducción en progreso.", "587b57ced54965d8874c3fd0e9dfedb987e5df04": "No tienes suscripciones a listas de reproducción.", "7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Buscar", - "2054791b822475aeaea95c0119113de3200f5e1c": "Duración:", + "2054791b822475aeaea95c0119113de3200f5e1c": "Longitud:", "94e01842dcee90531caa52e4147f70679bac87fe": "Eliminar y volver a descargar", "2031adb51e07a41844e8ba7704b054e98345c9c1": "Borrar para siempre" } \ No newline at end of file diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index 8ec6073..21f8758 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -1,6 +1,6 @@ - + Create a playlist @@ -160,7 +160,7 @@ app/settings/settings.component.html - 83 + 92 Custom args placeholder @@ -334,6 +334,22 @@ Playlist video count + + Delete + + app/file-card/file-card.component.html + 21 + + Delete video button + + + Delete and blacklist + + app/file-card/file-card.component.html + 22 + + Delete and blacklist video button + Settings @@ -350,7 +366,7 @@ Host app/settings/settings.component.html - 8 + 17 Host settings title @@ -358,7 +374,7 @@ URL app/settings/settings.component.html - 15 + 24 app/dialogs/subscribe-dialog/subscribe-dialog.component.html @@ -370,7 +386,7 @@ URL this app will be accessed from, without the port. app/settings/settings.component.html - 16 + 25 URL setting input hint @@ -378,7 +394,7 @@ Port app/settings/settings.component.html - 21 + 30 Port input placeholder @@ -386,7 +402,7 @@ The desired port. Default is 17442. app/settings/settings.component.html - 22 + 31 Port setting input hint @@ -394,7 +410,7 @@ Encryption app/settings/settings.component.html - 34 + 43 Encryption settings title @@ -402,7 +418,7 @@ Use encryption app/settings/settings.component.html - 40 + 49 Use encryption setting @@ -410,7 +426,7 @@ Cert file path app/settings/settings.component.html - 45 + 54 Cert file path input placeholder @@ -418,7 +434,7 @@ Key file path app/settings/settings.component.html - 51 + 60 Key file path input placeholder @@ -426,7 +442,7 @@ Downloader app/settings/settings.component.html - 62 + 71 Downloader settings title @@ -434,7 +450,7 @@ Audio folder path app/settings/settings.component.html - 69 + 78 Audio folder path input placeholder @@ -442,7 +458,7 @@ Path for audio only downloads. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 70 + 79 Aduio path setting input hint @@ -450,7 +466,7 @@ Video folder path app/settings/settings.component.html - 76 + 85 Video folder path input placeholder @@ -458,7 +474,7 @@ Path for video downloads. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 77 + 86 Video path setting input hint @@ -466,15 +482,27 @@ Global custom args for downloads on the home page. app/settings/settings.component.html - 84 + 93 Custom args setting input hint + + Use youtube-dl archive + + app/settings/settings.component.html + 98 + + + app/settings/settings.component.html + 206 + + Use youtubedl archive setting + Extra app/settings/settings.component.html - 95 + 109 Extra settings title @@ -482,7 +510,7 @@ Top title app/settings/settings.component.html - 102 + 116 Top title input placeholder @@ -490,7 +518,7 @@ File manager enabled app/settings/settings.component.html - 107 + 121 File manager enabled setting @@ -498,7 +526,7 @@ Allow quality select app/settings/settings.component.html - 110 + 124 Allow quality seelct setting @@ -506,7 +534,7 @@ Download only mode app/settings/settings.component.html - 113 + 127 Download only mode setting @@ -514,7 +542,7 @@ Allow multi-download mode app/settings/settings.component.html - 116 + 130 Allow multi-downloade mode setting @@ -522,7 +550,7 @@ API app/settings/settings.component.html - 126 + 140 API settings title @@ -530,7 +558,7 @@ Use YouTube API app/settings/settings.component.html - 132 + 146 Use YouTube API setting @@ -538,7 +566,7 @@ Youtube API Key app/settings/settings.component.html - 136 + 150 Youtube API Key setting placeholder @@ -546,7 +574,7 @@ Generating a key is easy! app/settings/settings.component.html - 137 + 151 Youtube API Key setting hint @@ -554,7 +582,7 @@ Themes app/settings/settings.component.html - 148 + 162 Themes settings title @@ -562,7 +590,7 @@ Default app/settings/settings.component.html - 155 + 169 Default theme label @@ -570,7 +598,7 @@ Dark app/settings/settings.component.html - 156 + 170 app/app.component.html @@ -582,7 +610,7 @@ Allow theme change app/settings/settings.component.html - 161 + 175 Allow theme change setting @@ -590,11 +618,11 @@ Subscriptions app/settings/settings.component.html - 171 + 185 app/app.component.html - 34 + 38 Subscriptions settings title @@ -602,7 +630,7 @@ Allow subscriptions app/settings/settings.component.html - 177 + 191 Allow subscriptions setting @@ -610,7 +638,7 @@ Subscriptions base path app/settings/settings.component.html - 181 + 195 Subscriptions base path input setting placeholder @@ -618,7 +646,7 @@ Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 182 + 196 Subscriptions base path setting input hint @@ -626,7 +654,7 @@ Check interval app/settings/settings.component.html - 187 + 201 Check interval input setting placeholder @@ -634,23 +662,15 @@ Unit is seconds, only include numbers. app/settings/settings.component.html - 188 + 202 Check interval setting input hint - - Use youtube-dl archive - - app/settings/settings.component.html - 192 - - Use youtube-dl archive setting - With youtube-dl's archive app/settings/settings.component.html - 193 + 207 youtube-dl archive explanation prefix link @@ -658,7 +678,7 @@ feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. app/settings/settings.component.html - 193 + 207 youtube-dl archive explanation middle @@ -666,7 +686,7 @@ This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. app/settings/settings.component.html - 194 + 208 youtube-dl archive explanation suffix @@ -674,7 +694,7 @@ Advanced app/settings/settings.component.html - 204 + 218 Advanced settings title @@ -682,23 +702,15 @@ Use default downloading agent app/settings/settings.component.html - 210 + 224 Use default downloading agent setting - - Custom agent - - app/settings/settings.component.html - 214 - - Custom agent setting placeholder - Allow advanced download app/settings/settings.component.html - 219 + 239 Allow advanced downloading setting @@ -706,7 +718,7 @@ Save app/settings/settings.component.html - 229 + 249 Settings save button @@ -714,7 +726,7 @@ Cancel app/settings/settings.component.html - 232 + 252 app/dialogs/subscribe-dialog/subscribe-dialog.component.html @@ -722,11 +734,83 @@ Settings cancel button + + About YoutubeDL-Material + + app/dialogs/about-dialog/about-dialog.component.html + 1 + + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + + app/dialogs/about-dialog/about-dialog.component.html + 6 + + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon below. + + app/dialogs/about-dialog/about-dialog.component.html + 9 + + About second paragraph + + + Found a bug or have a suggestion? + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About bug prefix + + + Click here + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About bug click here + + + to create an issue! + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About bug suffix + + + Installed version: + + app/dialogs/about-dialog/about-dialog.component.html + 17 + + Version label + + + View latest update + + app/dialogs/about-dialog/about-dialog.component.html + 17 + + View latest update + + + About + + app/app.component.html + 26 + + About menu label + Home app/app.component.html - 33 + 37 Navigation menu Home Page title From f08506d690d828689b39cbd0295c5d901139c381 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Mon, 16 Mar 2020 01:23:35 -0400 Subject: [PATCH 26/48] When locale 'en' is selected, no translation is retrieved --- src/main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8dfc77d..77ff462 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,7 +13,7 @@ if (environment.production) { } const locale = localStorage.getItem('locale'); -if (locale) { +if (locale && locale !== 'en') { getTranslations(`./assets/i18n/messages.${locale}.json`).then( (data: ParsedTranslationBundle) => { loadTranslations(data as any); @@ -31,7 +31,6 @@ if (locale) { }); }); } else { - console.log('no locale'); import('./app/app.module').then(module => { platformBrowserDynamic() .bootstrapModule(module.AppModule) From 3bdacd4b52885951cffbe7ab1b612e07cc65b134 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 05:29:25 -0400 Subject: [PATCH 27/48] Added additional volumes to docker compose --- docker-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f03bdea..5fa8c4e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,10 @@ services: ALLOW_CONFIG_MUTATIONS: 'true' restart: always volumes: - - ./appdata:/app/config + - ./appdata:/app/appdata + - ./audio:/app/audio + - ./video:/app/video + - ./subscriptions:/app/subscriptions ports: - "8998:17442" image: tzahi12345/youtubedl-material:experimental \ No newline at end of file From 1a79b489abca25fbb6811e30156c4f38dc87dd5d Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 06:58:05 -0400 Subject: [PATCH 28/48] Added video info dialog File cards and subscription file cards now use video info dialog so that users can see info on each individual video Ellipsis are now added client-side to video titles in file cards --- backend/app.js | 40 ++++++++++++------- package.json | 1 + src/app/app.module.ts | 4 +- .../video-info-dialog.component.html | 28 +++++++++++++ .../video-info-dialog.component.scss | 18 +++++++++ .../video-info-dialog.component.spec.ts | 25 ++++++++++++ .../video-info-dialog.component.ts | 22 ++++++++++ src/app/file-card/file-card.component.css | 8 ++++ src/app/file-card/file-card.component.html | 12 +++--- src/app/file-card/file-card.component.ts | 18 +++++++-- src/app/main/main.component.html | 4 +- .../subscription-file-card.component.html | 1 + .../subscription-file-card.component.ts | 15 +++++-- 13 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.html create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.scss create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.ts diff --git a/backend/app.js b/backend/app.js index d616e57..3fdd32c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -84,12 +84,16 @@ app.use(bodyParser.json()); // objects -function File(id, title, thumbnailURL, isAudio, duration) { +function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = null, size = null, path = null) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; this.isAudio = isAudio; this.duration = duration; + this.url = url; + this.uploader = uploader; + this.size = size; + this.path = path; } // actual functions @@ -951,20 +955,21 @@ app.post('/api/getMp3s', function(req, res) { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(audioFolderPath.length, file.length); + + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp3(id); if (!jsonobj) continue; var title = jsonobj.title; - - if (title.length > 14) // edits title if it's too long - { - title = title.substring(0,12) + "..."; - } + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = true; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); mp3s.push(file_obj); } @@ -984,20 +989,21 @@ app.post('/api/getMp4s', function(req, res) { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(videoFolderPath.length, file.length); + + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp4(id); if (!jsonobj) continue; var title = jsonobj.title; - - if (title.length > 14) // edits title if it's too long - { - title = title.substring(0,12) + "..."; - } + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); mp4s.push(file_obj); } @@ -1101,6 +1107,8 @@ app.post('/api/getSubscription', async (req, res) => { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(appended_base_path.length, file.length); + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp4(id, appended_base_path); if (!jsonobj) continue; @@ -1108,8 +1116,12 @@ app.post('/api/getSubscription', async (req, res) => { var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; + var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); parsed_files.push(file_obj); } diff --git a/package.json b/package.json index dcbef0f..64c1aec 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@locl/core": "0.0.1-beta.2", "core-js": "^2.4.1", "file-saver": "^2.0.2", + "filesize": "^6.1.0", "ng-lazyload-image": "^7.0.1", "ng4-configure": "^0.1.7", "ngx-content-loading": "^0.1.3", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a430fff..e88d6fb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,6 +49,7 @@ import { SettingsComponent } from './settings/settings.component'; import es from '@angular/common/locales/es'; import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; +import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component'; registerLocaleData(es, 'es'); export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps) { @@ -70,7 +71,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible SubscriptionFileCardComponent, SubscriptionInfoDialogComponent, SettingsComponent, - AboutDialogComponent + AboutDialogComponent, + VideoInfoDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html new file mode 100644 index 0000000..234b77d --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -0,0 +1,28 @@ +

{{file.title}}

+ + +
+
Name: 
+
{{file.title}}
+
+
+
URL: 
+ +
+
+
Uploader: 
+
{{file.uploader ? file.uploader : 'N/A'}}
+
+
+
File size: 
+
{{filesize(file.size)}}
+
+
+
Path: 
+
{{file.path}}
+
+
+ + + + \ No newline at end of file diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss b/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss new file mode 100644 index 0000000..6e7c4f9 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss @@ -0,0 +1,18 @@ +.info-item { + margin-bottom: 12px; + width: 100%; +} + +.info-item-value { + font-size: 13px; + display: inline-block; + width: 70%; +} + +.spacer {flex: 1 1 auto;} + +.info-item-label { + display: inline-block; + width: 30%; + vertical-align: top; +} diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts new file mode 100644 index 0000000..126ea43 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VideoInfoDialogComponent } from './video-info-dialog.component'; + +describe('VideoInfoDialogComponent', () => { + let component: VideoInfoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ VideoInfoDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(VideoInfoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts b/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts new file mode 100644 index 0000000..4bfc8e3 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import filesize from 'filesize'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'app-video-info-dialog', + templateUrl: './video-info-dialog.component.html', + styleUrls: ['./video-info-dialog.component.scss'] +}) +export class VideoInfoDialogComponent implements OnInit { + file: any; + filesize; + constructor(@Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit(): void { + this.filesize = filesize; + if (this.data) { + this.file = this.data.file; + } + } + +} diff --git a/src/app/file-card/file-card.component.css b/src/app/file-card/file-card.component.css index 342885f..a581bca 100644 --- a/src/app/file-card/file-card.component.css +++ b/src/app/file-card/file-card.component.css @@ -51,6 +51,14 @@ -webkit-line-clamp: 2; } +.file-link { + width: 80%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} + @media (max-width: 576px){ .example-card { diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index 1c796c5..fd4317d 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -1,8 +1,9 @@
- {{title}} -
+
+ {{title}} +
ID: {{name}}
Count: {{count}}
@@ -15,10 +16,11 @@
- - + + + - + diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 770976e..1c896c2 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -5,6 +5,8 @@ import {EventEmitter} from '@angular/core'; import { MainComponent } from 'app/main/main.component'; import { Subject, Observable } from 'rxjs'; import 'rxjs/add/observable/merge'; +import { MatDialog } from '@angular/material/dialog'; +import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component'; @Component({ selector: 'app-file-card', @@ -12,7 +14,7 @@ import 'rxjs/add/observable/merge'; styleUrls: ['./file-card.component.css'] }) export class FileCardComponent implements OnInit { - + @Input() file: any; @Input() title: string; @Input() length: string; @Input() name: string; @@ -29,8 +31,10 @@ export class FileCardComponent implements OnInit { scrollSubject; scrollAndLoad; - constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) { - this.scrollSubject = new Subject(); + constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent, + private dialog: MatDialog) { + + this.scrollSubject = new Subject(); this.scrollAndLoad = Observable.merge( Observable.fromEvent(window, 'scroll'), this.scrollSubject @@ -57,6 +61,14 @@ export class FileCardComponent implements OnInit { } + openSubscriptionInfoDialog() { + const dialogRef = this.dialog.open(VideoInfoDialogComponent, { + data: { + file: this.file, + } + }); + } + onImgError(event) { this.image_errored = true; } diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index dc11d79..982a917 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -203,7 +203,7 @@
- @@ -244,7 +244,7 @@
- diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.html b/src/app/subscription/subscription-file-card/subscription-file-card.component.html index a2972a9..81897db 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -4,6 +4,7 @@
+ diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index c2c23e2..6c9b3b7 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -3,6 +3,8 @@ import { Observable, Subject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; import { PostsService } from 'app/posts.services'; +import { MatDialog } from '@angular/material/dialog'; +import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component'; @Component({ selector: 'app-subscription-file-card', @@ -25,7 +27,7 @@ export class SubscriptionFileCardComponent implements OnInit { @Output() goToFileEmit = new EventEmitter(); @Output() reloadSubscription = new EventEmitter(); - constructor(private snackBar: MatSnackBar, private postsService: PostsService) { + constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) { this.scrollSubject = new Subject(); this.scrollAndLoad = Observable.merge( Observable.fromEvent(window, 'scroll'), @@ -55,6 +57,14 @@ export class SubscriptionFileCardComponent implements OnInit { this.goToFileEmit.emit(this.file.id); } + openSubscriptionInfoDialog() { + const dialogRef = this.dialog.open(VideoInfoDialogComponent, { + data: { + file: this.file, + } + }); + } + deleteAndRedownload() { this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => { this.reloadSubscription.emit(true); @@ -77,8 +87,7 @@ export class SubscriptionFileCardComponent implements OnInit { } -function fancyTimeFormat(time) -{ +function fancyTimeFormat(time) { // Hours, minutes and seconds const hrs = ~~(time / 3600); const mins = ~~((time % 3600) / 60); From 9dc607e7ee553448de4d345a5a68ba50275f441c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:07:35 -0400 Subject: [PATCH 29/48] fixed bug where no subscriptions resulted in a client error --- src/app/subscriptions/subscriptions.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/subscriptions/subscriptions.component.ts b/src/app/subscriptions/subscriptions.component.ts index 9614e87..0f89c61 100644 --- a/src/app/subscriptions/subscriptions.component.ts +++ b/src/app/subscriptions/subscriptions.component.ts @@ -33,6 +33,9 @@ export class SubscriptionsComponent implements OnInit { this.postsService.getAllSubscriptions().subscribe(res => { this.subscriptions_loading = false; this.subscriptions = res['subscriptions']; + if (!this.subscriptions) { + return; + } for (let i = 0; i < this.subscriptions.length; i++) { const sub = this.subscriptions[i]; From a5e1906196e3f4d338aca4a4827296c897564123 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:14:11 -0400 Subject: [PATCH 30/48] Fixed bug that prevented auto start from working --- src/app/main/main.component.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 9c356ce..73ca33e 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -214,14 +214,6 @@ export class MainComponent implements OnInit { constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, private router: Router, public dialog: MatDialog, private platform: Platform, private route: ActivatedRoute) { this.audioOnly = false; - - this.configLoad(); - - this.postsService.settings_changed.subscribe(changed => { - if (changed) { - this.loadConfig(); - } - }); } async configLoad() { @@ -299,6 +291,14 @@ export class MainComponent implements OnInit { // app initialization. ngOnInit() { + this.configLoad(); + + this.postsService.settings_changed.subscribe(changed => { + if (changed) { + this.loadConfig(); + } + }); + this.iOS = this.platform.IOS; // get checkboxes From aa80f52838acaad1241de8aacda66a336b361d22 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:15:07 -0400 Subject: [PATCH 31/48] Updated firefox extension to v0.3, focused on making the settings menu look nicer --- chrome-extension/css/bootstrap.min.css | 7 ++++ chrome-extension/js/bootstrap.min.js | 7 ++++ chrome-extension/js/jquery-3.4.1.min.js | 2 + chrome-extension/js/popper.min.js | 5 +++ chrome-extension/options.html | 49 ++++++++++++++++--------- chrome-extension/options.js | 7 ++-- 6 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 chrome-extension/css/bootstrap.min.css create mode 100644 chrome-extension/js/bootstrap.min.js create mode 100644 chrome-extension/js/jquery-3.4.1.min.js create mode 100644 chrome-extension/js/popper.min.js diff --git a/chrome-extension/css/bootstrap.min.css b/chrome-extension/css/bootstrap.min.css new file mode 100644 index 0000000..6561b6f --- /dev/null +++ b/chrome-extension/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropup .dropdown-menu{margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::before{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:first-child) .custom-file-label::before{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-control{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-control::before{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(calc(2.25rem + 2px) - 1px * 2);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .dropup .dropdown-menu{top:auto;bottom:100%}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .dropup .dropdown-menu{top:auto;bottom:100%}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;background-color:#007bff;transition:width .6s ease}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;-webkit-clip-path:inset(50%);clip-path:inset(50%);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;-webkit-clip-path:none;clip-path:none}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-muted{color:#6c757d!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/chrome-extension/js/bootstrap.min.js b/chrome-extension/js/bootstrap.min.js new file mode 100644 index 0000000..534d533 --- /dev/null +++ b/chrome-extension/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/chrome-extension/js/jquery-3.4.1.min.js b/chrome-extension/js/jquery-3.4.1.min.js new file mode 100644 index 0000000..a1c07fd --- /dev/null +++ b/chrome-extension/js/jquery-3.4.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0=o.clientWidth&&n>=o.clientHeight}),l=0window.devicePixelRatio||!re),h='bottom'===o?'top':'bottom',g='right'===n?'left':'right',u=K('transform');if(s='bottom'==h?'HTML'===a.nodeName?-a.clientHeight+m.bottom:-l.height+m.bottom:m.top,p='right'==g?'HTML'===a.nodeName?-a.clientWidth+m.right:-l.width+m.right:m.left,d&&u)f[u]='translate3d('+p+'px, '+s+'px, 0)',f[h]=0,f[g]=0,f.willChange='transform';else{var b='bottom'==h?-1:1,w='right'==g?-1:1;f[h]=s*b,f[g]=p*w,f.willChange=h+', '+g}var y={"x-placement":e.placement};return e.attributes=T({},y,e.attributes),e.styles=T({},f,e.styles),e.arrowStyles=T({},e.offsets.arrow,e.arrowStyles),e}function se(e,t,o){var n=U(e,function(e){var o=e.name;return o===t}),i=!!n&&e.some(function(e){return e.name===o&&e.enabled&&e.orderp[c]&&(e.offsets.popper[m]+=d[m]+g-p[c]),e.offsets.popper=C(e.offsets.popper);var u=d[m]+d[l]/2-g/2,b=s(e.instance.popper),w=parseFloat(b['margin'+f]),y=parseFloat(b['border'+f+'Width']),E=u-e.offsets.popper[m]-w-y;return E=Math.max(Math.min(p[l]-g,E),0),e.arrowElement=n,e.offsets.arrow=(o={},S(o,m,Math.round(E)),S(o,h,''),o),e}function ae(e){if('end'===e)return'start';return'start'===e?'end':e}var le=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],fe=le.slice(3);function me(e){var t=1f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),x=y||E;(m||b||x)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),x&&(r=ae(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=T({},e.offsets.popper,R(e.instance.popper,e.offsets.reference,e.placement)),e=V(e.instance.modifiers,e,'flip'))}),e}function ge(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Math.floor,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}function ue(e,t,o,n){var i=Math.max,r=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),p=+r[1],s=r[2];if(!p)return e;if(0===s.indexOf('%')){var d;switch(s){case'%p':d=o;break;case'%':case'%r':default:d=n;}var a=C(d);return a[t]/100*p}if('vh'===s||'vw'===s){var l;return l='vh'===s?i(document.documentElement.clientHeight,window.innerHeight||0):i(document.documentElement.clientWidth,window.innerWidth||0),l/100*p}return p}function be(e,t,o,n){var i=[0,0],r=-1!==['right','left'].indexOf(n),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(U(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,n){var i=(1===n?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return ue(e,i,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,n){$(o)&&(i[t]+=o*('-'===e[n-1]?-1:1))})}),i}function we(e,t){var o,n=t.offset,i=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=i.split('-')[0];return o=$(+n)?[+n,0]:be(n,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}function ye(e,t){var o=t.boundariesElement||c(e.instance.popper);e.instance.reference===o&&(o=c(o));var n=K('transform'),i=e.instance.popper.style,r=i.top,p=i.left,s=i[n];i.top='',i.left='',i[n]='';var d=B(e.instance.popper,e.instance.reference,t.padding,o,e.positionFixed);i.top=r,i.left=p,i[n]=s,t.boundaries=d;var a=t.priority,l=e.offsets.popper,f={primary:function(e){var o=l[e];return l[e]d[e]&&!t.escapeWithReference&&(n=Math.min(l[o],d[e]-('right'===e?l.width:l.height))),S({},o,n)}};return a.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';l=T({},l,f[t](e))}),e.offsets.popper=l,e}function Ee(e){var t=e.placement,o=t.split('-')[0],n=t.split('-')[1];if(n){var i=e.offsets,r=i.reference,p=i.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:S({},d,r[d]),end:S({},d,r[d]+r[a]-p[a])};e.offsets.popper=T({},p,l[n])}return e}function xe(e){if(!se(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=U(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right -YoutubeDL-Material Extension Options + + YoutubeDL-Material Extension Options + + + + + + + +

Settings

-
-

Frontend URL

- +
+
+ + +
+ +
+ +
+ +
+ + +
+
+ Settings successfully saved! +
+
-
- -
- -
- -
- -
- - \ No newline at end of file diff --git a/chrome-extension/options.js b/chrome-extension/options.js index 78597b0..c4da699 100644 --- a/chrome-extension/options.js +++ b/chrome-extension/options.js @@ -7,11 +7,10 @@ function save_options() { audio_only: audio_only }, function() { // Update status to let user know options were saved. - var status = document.getElementById('status'); - status.textContent = 'Options saved.'; + $('#collapseExample').collapse('show'); setTimeout(function() { - status.textContent = ''; - }, 750); + $('#collapseExample').collapse('hide'); + }, 2000); }); } From 18371010835fa681b6466293983a0cde4c54c58d Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:15:17 -0400 Subject: [PATCH 32/48] Updated manifest --- chrome-extension/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 02adf1e..1ecf992 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "YoutubeDL-Material", - "version": "0.2", + "version": "0.3", "description": "The Official Firefox & Chrome Extension of YoutubeDL-Material, an open-source and self-hosted YouTube downloader.", "background": { "scripts": ["background.js"] From 35f8454db35efd1f1f2d6d0e146a4848c8f948e7 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:20:25 -0400 Subject: [PATCH 33/48] Fixed bug where auto started downloads would begin if navigated back from the player --- src/app/main/main.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 73ca33e..9103fa1 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -477,7 +477,7 @@ export class MainComponent implements OnInit { this.downloadAudioFile(decodeURI(name)); } } else { - localStorage.setItem('player_navigator', this.router.url); + localStorage.setItem('player_navigator', this.router.url.split(';')[0]); if (is_playlist) { this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]); } else { @@ -515,7 +515,7 @@ export class MainComponent implements OnInit { this.downloadVideoFile(decodeURI(name)); } } else { - localStorage.setItem('player_navigator', this.router.url); + localStorage.setItem('player_navigator', this.router.url.split(';')[0]); if (is_playlist) { this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]); } else { From 393267b9195d665e10f45d31a8e26068514ab0c1 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:49:19 -0400 Subject: [PATCH 34/48] fixed bug where youtube search API failed to load when enabled through the settings --- src/app/settings/settings.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 8c0135d..b4f8b3b 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -143,11 +143,11 @@
- Use YouTube API + Use YouTube API
From b2730926c82c6294fed0172cf91bd9e225cfbd1c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 21:38:49 -0400 Subject: [PATCH 35/48] Updated translation details to improve clarity Added upload date property to files in UI Subscription videos can now be filtered by some of their properties (size, upload date, name, duration) Subscription videos are now centered --- backend/app.js | 21 ++++-- .../video-info-dialog.component.html | 14 ++-- src/app/file-card/file-card.component.html | 2 +- src/app/file-card/file-card.component.ts | 5 +- .../subscription-file-card.component.ts | 3 +- .../subscription/subscription.component.html | 66 +++++++++++-------- .../subscription/subscription.component.scss | 8 +++ .../subscription/subscription.component.ts | 50 ++++++++++++++ 8 files changed, 128 insertions(+), 41 deletions(-) diff --git a/backend/app.js b/backend/app.js index 3fdd32c..9fbecb9 100644 --- a/backend/app.js +++ b/backend/app.js @@ -84,7 +84,7 @@ app.use(bodyParser.json()); // objects -function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = null, size = null, path = null) { +function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; @@ -94,6 +94,7 @@ function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = this.uploader = uploader; this.size = size; this.path = path; + this.upload_date = upload_date; } // actual functions @@ -964,12 +965,15 @@ app.post('/api/getMp3s', function(req, res) { var title = jsonobj.title; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = true; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); mp3s.push(file_obj); } @@ -998,12 +1002,15 @@ app.post('/api/getMp4s', function(req, res) { var title = jsonobj.title; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; - var size = stats.size; - + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; + + var size = stats.size; + var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); mp4s.push(file_obj); } @@ -1118,10 +1125,12 @@ app.post('/api/getSubscription', async (req, res) => { var duration = jsonobj.duration; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; var size = stats.size; var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); parsed_files.push(file_obj); } diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html index 234b77d..0c0c105 100644 --- a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -2,25 +2,29 @@
-
Name: 
+
Name: 
{{file.title}}
-
URL: 
+
URL: 
-
Uploader: 
+
Uploader: 
{{file.uploader ? file.uploader : 'N/A'}}
-
File size: 
+
File size: 
{{filesize(file.size)}}
-
Path: 
+
Path: 
{{file.path}}
+
+
Upload Date: 
+
{{file.upload_date}}
+
diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index fd4317d..ef79cf6 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -19,7 +19,7 @@ - + diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 1c896c2..16f33ed 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -61,11 +61,12 @@ export class FileCardComponent implements OnInit { } - openSubscriptionInfoDialog() { + openVideoInfoDialog() { const dialogRef = this.dialog.open(VideoInfoDialogComponent, { data: { file: this.file, - } + }, + minWidth: '50vw' }); } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index 6c9b3b7..093eb94 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -61,7 +61,8 @@ export class SubscriptionFileCardComponent implements OnInit { const dialogRef = this.dialog.open(VideoInfoDialogComponent, { data: { file: this.file, - } + }, + minWidth: '50vw' }); } diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index d94a0a4..86ece5a 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -1,30 +1,44 @@ -
- -
-

- {{subscription.name}} -

-
- -
- -
-
-
-
-

Videos

-
-
- - - search - -
+
+ +
+

+ {{subscription.name}} +

-
-
-
- + +
+ +
+
+
+
+ + + {{filterOption['value']['label']}} + + +
+
+ +
+
+
+
+
+

Videos

+
+
+ + + search + +
+
+
+
+
+ +
diff --git a/src/app/subscription/subscription/subscription.component.scss b/src/app/subscription/subscription/subscription.component.scss index 10557a0..a9c26bb 100644 --- a/src/app/subscription/subscription/subscription.component.scss +++ b/src/app/subscription/subscription/subscription.component.scss @@ -8,6 +8,13 @@ left: 15px; } +.filter-select-parent { + position: absolute; + top: 0px; + left: 20px; + display: block; +} + .search-bar { transition: all .5s ease; position: relative; @@ -29,6 +36,7 @@ .flex-grid { width: 100%; display: block; + position: relative; } .col { width: 33%; diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 0bcf008..f9ff060 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -17,6 +17,30 @@ export class SubscriptionComponent implements OnInit { search_mode = false; search_text = ''; searchIsFocused = false; + descendingMode = true; + filterProperties = { + 'upload_date': { + 'key': 'upload_date', + 'label': 'Upload Date', + 'property': 'upload_date' + }, + 'name': { + 'key': 'name', + 'label': 'Name', + 'property': 'title' + }, + 'file_size': { + 'key': 'file_size', + 'label': 'File Size', + 'property': 'size' + }, + 'duration': { + 'key': 'duration', + 'label': 'Duration', + 'property': 'duration' + } + }; + filterProperty = this.filterProperties['upload_date']; constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { } @@ -27,6 +51,12 @@ export class SubscriptionComponent implements OnInit { this.getSubscription(); this.getConfig(); } + + // set filter property to cached + const cached_filter_property = localStorage.getItem('filter_property'); + if (cached_filter_property && this.filterProperties[cached_filter_property]) { + this.filterProperty = this.filterProperties[cached_filter_property]; + } } goBack() { @@ -42,6 +72,7 @@ export class SubscriptionComponent implements OnInit { } else { this.filtered_files = this.files; } + this.filterByProperty(this.filterProperty['property']); }); } @@ -72,4 +103,23 @@ export class SubscriptionComponent implements OnInit { this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue)); } + filterByProperty(prop) { + if (this.descendingMode) { + this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1)); + } else { + this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1)); + } + } + + filterOptionChanged(value) { + // this.filterProperty = value; + this.filterByProperty(value['property']); + localStorage.setItem('filter_property', value['key']); + } + + toggleModeChange() { + this.descendingMode = !this.descendingMode; + this.filterByProperty(this.filterProperty['property']); + } + } From bdb6a082745c999690338fca26b6b64289e69390 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 22:24:52 -0400 Subject: [PATCH 36/48] Added ability to download subscription videos as zip --- backend/app.js | 37 +++++++++++++------ src/app/posts.services.ts | 7 ++-- .../subscription/subscription.component.html | 1 + .../subscription/subscription.component.scss | 20 ++++++++++ .../subscription/subscription.component.ts | 18 +++++++++ 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/backend/app.js b/backend/app.js index 9fbecb9..a02a6cd 100644 --- a/backend/app.js +++ b/backend/app.js @@ -358,10 +358,16 @@ function getVideoFormatID(name) } } -async function createPlaylistZipFile(fileNames, type, outputName) { +async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null) { return new Promise(async resolve => { - let zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); - // let name = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0]; + let zipFolderPath = null; + + if (!fullPathProvided) { + zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); + } else { + zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path')); + } + let ext = (type === 'audio') ? '.mp3' : '.mp4'; let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip')); @@ -381,7 +387,8 @@ async function createPlaylistZipFile(fileNames, type, outputName) { for (let i = 0; i < fileNames.length; i++) { let fileName = fileNames[i]; - archive.file(zipFolderPath + fileName + ext, {name: fileName + ext}) + let file_path = !fullPathProvided ? zipFolderPath + fileName + ext : fileName; + archive.file(file_path, {name: fileName + ext}) } await archive.finalize(); @@ -1141,9 +1148,6 @@ app.post('/api/getSubscription', async (req, res) => { } else { res.sendStatus(500); } - - - }); app.post('/api/downloadVideosForSubscription', async (req, res) => { @@ -1278,11 +1282,12 @@ app.post('/api/deleteMp4', async (req, res) => { app.post('/api/downloadFile', async (req, res) => { let fileNames = req.body.fileNames; - let is_playlist = req.body.is_playlist; + let zip_mode = req.body.zip_mode; let type = req.body.type; let outputName = req.body.outputName; + let fullPathProvided = req.body.fullPathProvided; let file = null; - if (!is_playlist) { + if (!zip_mode) { fileNames = decodeURIComponent(fileNames); if (type === 'audio') { file = __dirname + '/' + audioFolderPath + fileNames + '.mp3'; @@ -1293,10 +1298,20 @@ app.post('/api/downloadFile', async (req, res) => { for (let i = 0; i < fileNames.length; i++) { fileNames[i] = decodeURIComponent(fileNames[i]); } - file = await createPlaylistZipFile(fileNames, type, outputName); + file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided); } - res.sendFile(file); + res.sendFile(file, function (err) { + if (err) { + next(err); + } else if (fullPathProvided) { + try { + fs.unlinkSync(file); + } catch(e) { + console.log("ERROR: Failed to remove file", file); + } + } + }); }); app.post('/api/deleteFile', async (req, res) => { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 6ea4133..97b63de 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -114,11 +114,12 @@ export class PostsService { return this.http.post(this.path + 'getMp4s', {}); } - downloadFileFromServer(fileName, type, outputName = null) { + downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null) { return this.http.post(this.path + 'downloadFile', {fileNames: fileName, type: type, - is_playlist: Array.isArray(fileName), - outputName: outputName}, + zip_mode: Array.isArray(fileName), + outputName: outputName, + fullPathProvided: fullPathProvided}, {responseType: 'blob'}); } diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index 86ece5a..44ccb41 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -42,4 +42,5 @@
+
\ No newline at end of file diff --git a/src/app/subscription/subscription/subscription.component.scss b/src/app/subscription/subscription/subscription.component.scss index a9c26bb..7734644 100644 --- a/src/app/subscription/subscription/subscription.component.scss +++ b/src/app/subscription/subscription/subscription.component.scss @@ -38,7 +38,27 @@ display: block; position: relative; } + .col { width: 33%; display: inline-block; +} + +.spinner { + width: 50px; + height: 50px; + bottom: 3px; + left: 3px; + position: absolute; +} + +.save-button { + right: 25px; + position: absolute; + bottom: 25px; +} + +.save-icon { + bottom: 1px; + position: relative; } \ No newline at end of file diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index f9ff060..496f2d1 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -41,6 +41,7 @@ export class SubscriptionComponent implements OnInit { } }; filterProperty = this.filterProperties['upload_date']; + downloading = false; constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { } @@ -122,4 +123,21 @@ export class SubscriptionComponent implements OnInit { this.filterByProperty(this.filterProperty['property']); } + downloadContent() { + const fileNames = []; + for (let i = 0; i < this.files.length; i++) { + fileNames.push(this.files[i].path); + } + + this.downloading = true; + this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { + this.downloading = false; + const blob: Blob = res; + saveAs(blob, this.subscription.name + '.zip'); + }, err => { + console.log(err); + this.downloading = false; + }); + } + } From b987e258b5071497fb7d1731069d4cd81e5ab1ed Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 22:25:21 -0400 Subject: [PATCH 37/48] Updated firefox extension zip --- .../youtubedl-material-firefox-extension.zip | Bin 4487 -> 75374 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chrome-extension/youtubedl-material-firefox-extension.zip b/chrome-extension/youtubedl-material-firefox-extension.zip index 54d612014896cbd2020c5353c08f04981dc8ca6e..ebf34b89dfea500c4c9ffeff57fe330540257c57 100644 GIT binary patch delta 72886 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 delta 1409 zcmaENhNWG3!+gfge*TS|TpSDnv$Fy|@4t9<`W_iZ&3ZPaH(nq34?aAb=i+wa(&TSD z)HE$$noZri`{b>y#Yvhbmd~y4`_FoRnp;dhZ;30f$PqT1g_~=REnB=UeQD#4=lSb6 z&vLzsKUOSxVDXwCf<`_cJ5p+32&H(X%5rSgg|J4!?p{FS>c4i#xGfL)LQ$ltKWqKzB$(1T=dPhwLF?he~DKi+bq2m(|ko)R++7wHhasNtmqHM^Q87T zdRg+XQdeEMCfVzRDqtE%>jJ&vEto3tcNj3ZueU z{x4Y~C34JOmBYy)`ohc*#;mt}TlaNUPO4Yay!z*4ebg_mhHu*pgjV}lUY;rWsWQZI z#)}tEm}2jz2-`jQR(P}S(QoDeZ)O$|1_lNWhKXxp1MCk48geo+Fx&-V9-wG`K}lwQ zUa?+ANp4Q*L|=bqMS-^WYW4rvpR7|^=05GnjjB~EH&(q^nK=ETs~`u@*?0S=_g^(J zEJ{dtHut$I^Zuy!@8bFVrHvj<7o0Bs$Gj<|X|WsgqX$CE(fHL zW1{Czs8D8?m>VcyRi(FtC7oZ^b;bmtwN}5jh4gDmM9CeTVWj!C@7%1J^Gxqd)zQ4!dP3_{{qs{@8iuY~vzBQHE!w=V2CxcFfec-g<4ke)L`%I z!v;M2_`k`Q6mzNGP34IyyZPAQg~ppgy?JZR7OsoUS<3JB|96i1+TRhzX&#GQE~&q~ zJUx#8-cKRf8LU&?tQD9SZAk0eshs>%Wc_-zsbU8?&$-w4%|5V(`Tq6r2j?e6&xlye zxm6}S?4YfO@+!-$Q_DThb2b0mP&rZ3x+i&V&B@T#W|qU+k+U{l%(~1rX^~BfM!=bX zYYNBL{(kP(eMd2M;niO^?$kIWj!V;M;_1o6XW5>(Pt4l(X*9h zsx!B7Zdi3!c!pt-X8q?0cX;kE*~%N|zTsTL^sxF9oY%idJXe^1&aHIrG8XTP9`a9R zd|hw8WXLTrbuj*U_wCtrf3}%zd-!aO$zdF z+L`XmUBc_aG9lpA-t zn{WqDi}KE+|2^+tmj3+N`S}C?75D2JY(t9Xz1MUs(wsm2p2W0s*Ij3CTz)Zo^OuDW zg;Zb9+ayyEwTr9h^WQnj+@W2Oi%gHGo{*pJq{NsxJwusMfoVG9^e$yaYh^^46A6sk z^-K&5ybLP9$OQofAeP`}n4Y7`DCN3umItr#_p6LBT0ZD;{Hu3Y83Vi-nd}*G Date: Tue, 17 Mar 2020 23:22:26 -0400 Subject: [PATCH 38/48] youtube-dl now auto updates on server restart --- backend/app.js | 50 ++++++++++++++++++++++++++++++++++++++++++++ backend/package.json | 1 + 2 files changed, 51 insertions(+) diff --git a/backend/app.js b/backend/app.js index a02a6cd..dfc0a90 100644 --- a/backend/app.js +++ b/backend/app.js @@ -10,6 +10,8 @@ var bodyParser = require("body-parser"); var archiver = require('archiver'); var mergeFiles = require('merge-files'); const low = require('lowdb') +const downloader = require('youtube-dl/lib/downloader') +const fetch = require('node-fetch'); var URL = require('url').URL; const shortid = require('shortid') const url_api = require('url'); @@ -56,6 +58,9 @@ let debugMode = process.env.YTDL_MODE === 'debug'; if (debugMode) console.log('YTDL-Material in debug mode!'); +// updates & starts youtubedl +startYoutubeDL(); + var validDownloadingAgents = [ 'aria2c', 'avconv', @@ -613,6 +618,51 @@ function writeToBlacklist(type, line) { fs.appendFileSync(blacklistBasePath + 'blacklist.txt', line); } +async function startYoutubeDL() { + // auto update youtube-dl + await autoUpdateYoutubeDL(); +} + +// auto updates the underlying youtube-dl binary, not YoutubeDL-Material +async function autoUpdateYoutubeDL() { + return new Promise(resolve => { + // get current version + let current_app_details_path = 'node_modules/youtube-dl/bin/details'; + let current_app_details_exists = fs.existsSync(current_app_details_path); + if (!current_app_details_exists) { + console.log(`Failed to get youtube-dl binary details at location: ${current_app_details_path}. Cancelling update check.`); + resolve(false); + return; + } + let current_app_details = JSON.parse(fs.readFileSync(current_app_details_path)); + let current_version = current_app_details['version']; + + // got version, now let's check the latest version from the youtube-dl API + let youtubedl_api_path = 'https://api.github.com/repos/ytdl-org/youtube-dl/tags'; + fetch(youtubedl_api_path, {method: 'Get'}) + .then(res => res.json()) + .then((json) => { + // check if the versions are different + const latest_update_version = json[0]['name']; + if (current_version !== latest_update_version) { + let binary_path = 'node_modules/youtube-dl/bin'; + // versions different, download new update + console.log('INFO: Found new update for youtube-dl. Updating binary...'); + downloader(binary_path, function error(err, done) { + 'use strict' + if (err) { + resolve(false); + throw err; + } + console.log(`INFO: Binary successfully updated: ${current_version} -> ${latest_update_version}`); + resolve(true); + }); + } + + }); + }); +} + app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", getOrigin()); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); diff --git a/backend/package.json b/backend/package.json index b67c591..8992bc6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,7 @@ "express": "^4.17.1", "lowdb": "^1.0.0", "merge-files": "^0.1.2", + "node-fetch": "^2.6.0", "shortid": "^2.2.15", "uuidv4": "^6.0.6", "youtube-dl": "^3.0.2" From 2e71a0bef112f3ccbd89872d891c0785acddbba9 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Wed, 18 Mar 2020 00:04:18 -0400 Subject: [PATCH 39/48] fixed bug that caused youtube downloader update to fail when the binary was being used to check for new subscription videos. now it waits for file access with a 10 second timeout --- backend/app.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index dfc0a90..76cf30a 100644 --- a/backend/app.js +++ b/backend/app.js @@ -636,18 +636,20 @@ async function autoUpdateYoutubeDL() { } let current_app_details = JSON.parse(fs.readFileSync(current_app_details_path)); let current_version = current_app_details['version']; + let stored_binary_path = current_app_details['path']; // got version, now let's check the latest version from the youtube-dl API let youtubedl_api_path = 'https://api.github.com/repos/ytdl-org/youtube-dl/tags'; fetch(youtubedl_api_path, {method: 'Get'}) - .then(res => res.json()) - .then((json) => { + .then(async res => res.json()) + .then(async (json) => { // check if the versions are different const latest_update_version = json[0]['name']; if (current_version !== latest_update_version) { let binary_path = 'node_modules/youtube-dl/bin'; // versions different, download new update console.log('INFO: Found new update for youtube-dl. Updating binary...'); + await checkExistsWithTimeout(stored_binary_path, 10000); downloader(binary_path, function error(err, done) { 'use strict' if (err) { @@ -663,6 +665,34 @@ async function autoUpdateYoutubeDL() { }); } +async function checkExistsWithTimeout(filePath, timeout) { + return new Promise(function (resolve, reject) { + + var timer = setTimeout(function () { + watcher.close(); + reject(new Error('File did not exists and was not created during the timeout.')); + }, timeout); + + fs.access(filePath, fs.constants.R_OK, function (err) { + if (!err) { + clearTimeout(timer); + watcher.close(); + resolve(); + } + }); + + var dir = path.dirname(filePath); + var basename = path.basename(filePath); + var watcher = fs.watch(dir, function (eventType, filename) { + if (eventType === 'rename' && filename === basename) { + clearTimeout(timer); + watcher.close(); + resolve(); + } + }); + }); +} + app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", getOrigin()); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); From 57e3f1b2ac49cd05fbee9739e6a965faa7715821 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Wed, 18 Mar 2020 00:32:43 -0400 Subject: [PATCH 40/48] removed frivolous logging --- backend/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/app.js b/backend/app.js index cd269b6..7c30a6b 100644 --- a/backend/app.js +++ b/backend/app.js @@ -834,7 +834,6 @@ app.post('/api/tomp3', async function(req, res) { } var full_file_path = output_json['_filename'].substring(0, output_json['_filename'].length-5) + '.mp3'; - console.log(full_file_path); if (fs.existsSync(full_file_path)) { let tags = { title: output_json['title'], @@ -842,8 +841,7 @@ app.post('/api/tomp3', async function(req, res) { } // NodeID3.create(tags, function(frame) { }) let success = NodeID3.write(tags, full_file_path); - if (success) console.log('successfully tagged file'); - else console.log('failed to tag file'); + if (!success) console.log('ERROR: Failed to apply ID3 tag to audio file ' + full_file_path); } else { console.log('Output mp3 does not exist'); } From f796a5863c3be759f79a0a4f61ffc03a65be4489 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Wed, 18 Mar 2020 00:33:12 -0400 Subject: [PATCH 41/48] Added package-lock to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 51cae26..8af628e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ backend/subscriptions/playlists/* backend/subscriptions/archives/* src/assets/default.json package-lock.json +backend/package-lock.json From 2b2a033b7ebff4f2d8f2942560e956277941ca7f Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:16:10 -0400 Subject: [PATCH 42/48] Added extensions settings where information on extensions can be found and bookmarklet is generated Created arg modifier dialog to assist in editing youtube-dl args - This arg dialog contains all the available args and their description - Includes a convenient search bar and categorized list of args to help you find the one you're looking for, or just explore what's available. Arg modifier is available for both global args (in settings) and local args (in the advanced mode) --- src/app/app.module.ts | 14 +- .../arg-modifier-dialog.component.html | 68 ++ .../arg-modifier-dialog.component.scss | 3 + .../arg-modifier-dialog.component.spec.ts | 25 + .../arg-modifier-dialog.component.ts | 117 ++ .../arg-modifier-dialog/youtubedl_args.ts | 230 ++++ src/app/main/main.component.css | 5 + src/app/main/main.component.html | 1 + src/app/main/main.component.ts | 17 +- src/app/settings/settings.component.html | 33 + src/app/settings/settings.component.scss | 4 + src/app/settings/settings.component.ts | 55 +- src/locale/messages.es.xlf | 987 ---------------- src/locale/messages.xlf | 1003 ----------------- 14 files changed, 568 insertions(+), 1994 deletions(-) create mode 100644 src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html create mode 100644 src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.scss create mode 100644 src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts create mode 100644 src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.ts create mode 100644 src/app/dialogs/arg-modifier-dialog/youtubedl_args.ts delete mode 100644 src/locale/messages.es.xlf delete mode 100644 src/locale/messages.xlf diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e88d6fb..00dc243 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,10 +1,12 @@ import { BrowserModule } from '@angular/platform-browser'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import { NgModule, LOCALE_ID } from '@angular/core'; import { registerLocaleData } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatNativeDateModule, MatRippleModule } from '@angular/material/core'; import { MatDialogModule } from '@angular/material/dialog'; import { MatExpansionModule } from '@angular/material/expansion'; @@ -13,6 +15,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatRadioModule } from '@angular/material/radio'; @@ -24,7 +27,6 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import {DragDropModule} from '@angular/cdk/drag-drop'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { AppComponent } from './app.component'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import { HttpModule } from '@angular/http'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { PostsService } from 'app/posts.services'; @@ -50,6 +52,7 @@ import { SettingsComponent } from './settings/settings.component'; import es from '@angular/common/locales/es'; import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component'; +import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; registerLocaleData(es, 'es'); export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps) { @@ -72,7 +75,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible SubscriptionInfoDialogComponent, SettingsComponent, AboutDialogComponent, - VideoInfoDialogComponent + VideoInfoDialogComponent, + ArgModifierDialogComponent, + HighlightPipe ], imports: [ BrowserModule, @@ -103,6 +108,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible MatDialogModule, MatSlideToggleModule, MatMenuModule, + MatAutocompleteModule, + MatTooltipModule, DragDropModule, VgCoreModule, VgControlsModule, @@ -116,6 +123,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible providers: [ PostsService ], + exports: [ + HighlightPipe + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html new file mode 100644 index 0000000..b86db12 --- /dev/null +++ b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html @@ -0,0 +1,68 @@ +

Modify youtube-dl args

+ + +
+
+
+ +
Simulated new args
+ + + +
+
+
+ +
Add an arg
+
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + +
+
+
+ Use arg value +
+
+ + + +
+
+
+ +
+
+
+
+
+ + +
+ + + + + \ No newline at end of file diff --git a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.scss b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.scss new file mode 100644 index 0000000..f2f0ad0 --- /dev/null +++ b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.scss @@ -0,0 +1,3 @@ +.info-menu-icon { + float: right; +} \ No newline at end of file diff --git a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts new file mode 100644 index 0000000..5b67052 --- /dev/null +++ b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ArgModifierDialogComponent } from './arg-modifier-dialog.component'; + +describe('ArgModifierDialogComponent', () => { + let component: ArgModifierDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ArgModifierDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ArgModifierDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.ts b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.ts new file mode 100644 index 0000000..087a641 --- /dev/null +++ b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit, Inject, Pipe, PipeTransform, NgModule } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog'; +import { FormControl } from '@angular/forms'; +import { args, args_info } from './youtubedl_args'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators/map'; +import { startWith } from 'rxjs/operators/startWith'; + +@Pipe({ name: 'highlight' }) +export class HighlightPipe implements PipeTransform { + transform(text: string, search): string { + const pattern = search ? search + .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') + .split(' ') + .filter(t => t.length > 0) + .join('|') : undefined; + const regex = new RegExp(pattern, 'gi'); + + return search ? text.replace(regex, match => `${match}`) : text; + } +}; + +@Component({ + selector: 'app-arg-modifier-dialog', + templateUrl: './arg-modifier-dialog.component.html', + providers: [HighlightPipe], + styleUrls: ['./arg-modifier-dialog.component.scss'], +}) +export class ArgModifierDialogComponent implements OnInit { + myGroup = new FormControl(); + firstArg = ''; + secondArg = ''; + secondArgEnabled = false; + modified_args = ''; + stateCtrl = new FormControl(); + availableArgs = null; + argsByCategory = null; + argsInfo = null; + filteredOptions: Observable; + + static forRoot() { + return { + ngModule: ArgModifierDialogComponent, + providers: [], + }; + } + + constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef, + private dialog: MatDialog) { } + + ngOnInit(): void { + if (this.data) { + this.modified_args = this.data.initial_args; + } + + this.getAllPossibleArgs(); + + // autocomplete setup + this.filteredOptions = this.stateCtrl.valueChanges + .pipe( + startWith(''), + map(val => this.filter(val)) + ); + } + + // autocomplete filter + filter(val) { + if (this.availableArgs) { + return this.availableArgs.filter(option => + option.key.toLowerCase().includes(val.toLowerCase())); + } + } + + addArg() { + // adds space + if (this.modified_args !== '') { + this.modified_args += ' '; + } + + this.modified_args += this.stateCtrl.value + ' ' + (this.secondArgEnabled ? this.secondArg : ''); + } + + canAddArg() { + return this.stateCtrl.value && this.stateCtrl.value !== '' && (!this.secondArgEnabled || (this.secondArg && this.secondArg !== '')); + } + + getFirstArg() { + return new Promise(resolve => { + resolve(this.stateCtrl.value); + }); + } + + getValueAsync(val) { + return new Promise(resolve => { + resolve(val); + }); + } + + getAllPossibleArgs() { + const all_args = args; + const arg_arrays = Object.keys(all_args).map(function(key) { + return all_args[key]; + }); + + // converts array of arrays to one array + const singular_arg_array = [].concat.apply([], arg_arrays); + + this.availableArgs = singular_arg_array; + this.argsByCategory = all_args; + this.argsInfo = args_info; + } + + setFirstArg(arg_key) { + this.stateCtrl.setValue(arg_key); + } + +} diff --git a/src/app/dialogs/arg-modifier-dialog/youtubedl_args.ts b/src/app/dialogs/arg-modifier-dialog/youtubedl_args.ts new file mode 100644 index 0000000..dab6731 --- /dev/null +++ b/src/app/dialogs/arg-modifier-dialog/youtubedl_args.ts @@ -0,0 +1,230 @@ +const uncategorized = [ + {'key': '-h', 'alt': '--help', 'description': 'Print this help text and exit'}, + {'key': '--version', 'description': 'Print program version and exit'}, + {'key': '-U', 'alt': '--update', 'description': 'Update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)'}, + {'key': '-i', 'alt': '--ignore-errors', 'description': 'Continue on download errors, for example to skip unavailable videos in a playlist'}, + {'key': '--abort-on-error', 'description': 'Abort downloading of further videos (in the playlist or the command line) if an error occurs'}, + {'key': '--dump-user-agent', 'description': 'Display the current browser identification'}, + {'key': '--list-extractors', 'description': 'List all supported extractors'}, + {'key': '--extractor-descriptions', 'description': 'Output descriptions of all supported extractors'}, + {'key': '--force-generic-extractor', 'description': 'Force extraction to use the generic extractor'}, + {'key': '--default-search', 'description': 'Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". Use the value "auto" to let youtube-dl guess ("auto_warning" to emit awarning when guessing). "error" just throws an error. The default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.'}, + {'key': '--ignore-config', 'description': 'Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the user configuration in ~/.config/youtube-dl/config (%APPDATA%/youtube-dl/config.txt on Windows)'}, + {'key': '--config-location', 'description': 'Location of the configuration file; either the path to the config or its containing directory.'}, + {'key': '--flat-playlist', 'description': 'Do not extract the videos of a playlist, only list them.'}, + {'key': '--mark-watched', 'description': 'Mark videos watched (YouTube only)'}, + {'key': '--no-mark-watched', 'description': 'Do not mark videos watched (YouTube only)'}, + {'key': '--no-color', 'description': 'Do not emit color codes in output'} +]; + +const network = [ + {'key': '--proxy', 'description': 'Use the specified HTTP/HTTPS/SOCKS proxy.To enable SOCKS proxy, specify a proper scheme. For example socks5://127.0.0.1:1080/. Pass in an empty string (--proxy "") for direct connection.'}, + {'key': '--socket-timeout', 'description': 'Time to wait before giving up, in seconds'}, + {'key': '--source-address', 'description': 'Client-side IP address to bind to'}, + {'key': '-4', 'alt': '--force-ipv4', 'description': 'Make all connections via IPv4'}, + {'key': '-6', 'alt': '--force-ipv6', 'description': 'Make all connections via IPv6'} +]; + +const geo_restriction = [ + {'key': '--geo-verification-proxy', 'description': 'Use this proxy to verify the IP address for some geo-restricted sites. The default proxy specified by --proxy\', if the option is not present) is used for the actual downloading.'}, + {'key': '--geo-bypass', 'description': 'Bypass geographic restriction via faking X-Forwarded-For HTTP header'}, + {'key': '--no-geo-bypass', 'description': 'Do not bypass geographic restriction via faking X-Forwarded-For HTTP header'}, + {'key': '--geo-bypass-country', 'description': 'Force bypass geographic restriction with explicitly provided two-letter ISO 3166-2 country code'}, + {'key': '--geo-bypass-ip-block', 'description': 'Force bypass geographic restriction with explicitly provided IP block in CIDR notation'} +]; + +const video_selection = [ + {'key': '--playlist-start', 'description': 'Playlist video to start at (default is 1)'}, + {'key': '--playlist-end', 'description': 'Playlist video to end at (default is last)'}, + {'key': '--playlist-items', 'description': 'Playlist video items to download. Specify indices of the videos in the playlist separated by commas like: "--playlist-items 1,2,5,8" if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: "--playlist-items 1-3,7,10-13", it will download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13.'}, + {'key': '--match-title', 'description': 'Download only matching titles (regex orcaseless sub-string)'}, + {'key': '--reject-title', 'description': 'Skip download for matching titles (regex orcaseless sub-string)'}, + {'key': '--max-downloads', 'description': 'Abort after downloading NUMBER files'}, + {'key': '--min-filesize', 'description': 'Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)'}, + {'key': '--max-filesize', 'description': 'Do not download any videos larger than SIZE (e.g. 50k or 44.6m)'}, + {'key': '--date', 'description': 'Download only videos uploaded in this date'}, + {'key': '--datebefore', 'description': 'Download only videos uploaded on or before this date (i.e. inclusive)'}, + {'key': '--dateafter', 'description': 'Download only videos uploaded on or after this date (i.e. inclusive)'}, + {'key': '--min-views', 'description': 'Do not download any videos with less than COUNT views'}, + {'key': '--max-views', 'description': 'Do not download any videos with more than COUNT views'}, + {'key': '--match-filter', 'description': 'Generic video filter. Specify any key (seethe "OUTPUT TEMPLATE" for a list of available keys) to match if the key is present, !key to check if the key is not present, key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against a number, key = \'LITERAL\' (like "uploader = \'Mike Smith\'", also works with !=) to match against a string literal and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the operator. For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike functionality is not available at the given service), but who also have a description, use --match-filter'}, + {'key': '--no-playlist', 'description': 'Download only the video, if the URL refers to a video and a playlist.'}, + {'key': '--yes-playlist', 'description': 'Download the playlist, if the URL refers to a video and a playlist.'}, + {'key': '--age-limit', 'description': 'Download only videos suitable for the given age'}, + {'key': '--download-archive', 'description': 'Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.'}, + {'key': '--include-ads', 'description': 'Download advertisements as well (experimental)'} +]; + +const download = [ + {'key': '-r', 'alt': '--limit-rate', 'description': 'Maximum download rate in bytes per second(e.g. 50K or 4.2M)'}, + {'key': '-R', 'alt': '--retries', 'description': 'Number of retries (default is 10), or "infinite".'}, + {'key': '--fragment-retries', 'description': 'Number of retries for a fragment (default is 10), or "infinite" (DASH, hlsnative and ISM)'}, + {'key': '--skip-unavailable-fragments', 'description': 'Skip unavailable fragments (DASH, hlsnative and ISM)'}, + {'key': '--abort-on-unavailable-fragment', 'description': 'Abort downloading when some fragment is not available'}, + {'key': '--keep-fragments', 'description': 'Keep downloaded fragments on disk after downloading is finished; fragments are erased by default'}, + {'key': '--buffer-size', 'description': 'Size of download buffer (e.g. 1024 or 16K) (default is 1024)'}, + {'key': '--no-resize-buffer', 'description': 'Do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.'}, + {'key': '--http-chunk-size', 'description': 'Size of a chunk for chunk-based HTTP downloading (e.g. 10485760 or 10M) (default is disabled). May be useful for bypassing bandwidth throttling imposed by a webserver (experimental)'}, + {'key': '--playlist-reverse', 'description': 'Download playlist videos in reverse order'}, + {'key': '--playlist-random', 'description': 'Download playlist videos in random order'}, + {'key': '--xattr-set-filesize', 'description': 'Set file xattribute ytdl.filesize with expected file size'}, + {'key': '--hls-prefer-native', 'description': 'Use the native HLS downloader instead of ffmpeg'}, + {'key': '--hls-prefer-ffmpeg', 'description': 'Use ffmpeg instead of the native HLS downloader'}, + {'key': '--hls-use-mpegts', 'description': 'Use the mpegts container for HLS videos, allowing to play the video while downloading (some players may not be able to play it)'}, + {'key': '--external-downloader', 'description': 'Use the specified external downloader. Currently supports aria2c,avconv,axel,curl,ffmpeg,httpie,wget'}, + {'key': '--external-downloader-args'} +]; + +const filesystem = [ + {'key': '-a', 'alt': '--batch-file', 'description': 'File containing URLs to download (\'-\' for stdin), one URL per line. Lines starting with \'#\', \';\' or \']\' are considered as comments and ignored.'}, + {'key': '--id', 'description': 'Use only video ID in file name'}, + {'key': '-o', 'alt': '--output', 'description': 'Output filename template, see the "OUTPUT TEMPLATE" for all the info'}, + {'key': '--autonumber-start', 'description': 'Specify the start value for %(autonumber)s (default is 1)'}, + {'key': '--restrict-filenames', 'description': 'Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames'}, + {'key': '-w', 'alt': '--no-overwrites', 'description': 'Do not overwrite files'}, + {'key': '-c', 'alt': '--continue', 'description': 'Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.'}, + {'key': '--no-continue', 'description': 'Do not resume partially downloaded files (restart from beginning)'}, + {'key': '--no-part', 'description': 'Do not use .part files - write directlyinto output file'}, + {'key': '--no-mtime', 'description': 'Do not use the Last-modified header to set the file modification time'}, + {'key': '--write-description', 'description': 'Write video description to a .description file'}, + {'key': '--write-info-json', 'description': 'Write video metadata to a .info.json file'}, + {'key': '--write-annotations', 'description': 'Write video annotations to a.annotations.xml file'}, + {'key': '--load-info-json', 'description': 'JSON file containing the video information (created with the "--write-info-json" option)'}, + {'key': '--cookies', 'description': 'File to read cookies from and dump cookie jar in'}, + {'key': '--cache-dir', 'description': 'Location in the file system where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may change.'}, + {'key': '--no-cache-dir', 'description': 'Disable filesystem caching'}, + {'key': '--rm-cache-dir', 'description': 'Delete all filesystem cache files'} +]; + +const thumbnail = [ + {'key': '--write-thumbnail', 'description': 'Write thumbnail image to disk'}, + {'key': '--write-all-thumbnails', 'description': 'Write all thumbnail image formats to disk'}, + {'key': '--list-thumbnails', 'description': 'Simulate and list all available thumbnail formats'} +]; + +const verbosity = [ + {'key': '-q', 'alt': '--quiet', 'description': 'Activate quiet mode'}, + {'key': '--no-warnings', 'description': 'Ignore warnings'}, + {'key': '-s', 'alt': '--simulate', 'description': 'Do not download the video and do not writeanything to disk'}, + {'key': '--skip-download', 'description': 'Do not download the video'}, + {'key': '-g', 'alt': '--get-url', 'description': 'Simulate, quiet but print URL'}, + {'key': '-e', 'alt': '--get-title', 'description': 'Simulate, quiet but print title'}, + {'key': '--get-id', 'description': 'Simulate, quiet but print id'}, + {'key': '--get-thumbnail', 'description': 'Simulate, quiet but print thumbnail URL'}, + {'key': '--get-description', 'description': 'Simulate, quiet but print video description'}, + {'key': '--get-duration', 'description': 'Simulate, quiet but print video length'}, + {'key': '--get-filename', 'description': 'Simulate, quiet but print output filename'}, + {'key': '--get-format', 'description': 'Simulate, quiet but print output format'}, + {'key': '-j', 'alt': '--dump-json', 'description': 'Simulate, quiet but print JSON information. See the "OUTPUT TEMPLATE" for a description of available keys.'}, + {'key': '-J', 'alt': '--dump-single-json', 'description': 'Simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist information in a single line.'}, + {'key': '--print-json', 'description': 'Be quiet and print the video information as JSON (video is still being downloaded).'}, + {'key': '--newline', 'description': 'Output progress bar as new lines'}, + {'key': '--no-progress', 'description': 'Do not print progress bar'}, + {'key': '--console-title', 'description': 'Display progress in console title bar'}, + {'key': '-v', 'alt': '--verbose', 'description': 'Print various debugging information'}, + {'key': '--dump-pages', 'description': 'Print downloaded pages encoded using base64 to debug problems (very verbose)'}, + {'key': '--write-pages', 'description': 'Write downloaded intermediary pages to files in the current directory to debug problems'}, + {'key': '--print-traffic', 'description': 'Display sent and read HTTP traffic'}, + {'key': '-C', 'alt': '--call-home', 'description': 'Contact the youtube-dl server for debugging'}, + {'key': '--no-call-home', 'description': 'Do NOT contact the youtube-dl server for debugging'} +]; + +const workarounds = [ + {'key': '--encoding', 'description': 'Force the specified encoding (experimental)'}, + {'key': '--no-check-certificate', 'description': 'Suppress HTTPS certificate validation'}, + {'key': '--prefer-insecure', 'description': 'Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)'}, + {'key': '--user-agent', 'description': 'Specify a custom user agent'}, + {'key': '--referer', 'description': 'Specify a custom referer, use if the video access is restricted to one domain'}, + {'key': '--add-header', 'description': 'Specify a custom HTTP header and its value, separated by a colon \':\'. You can use this option multiple times'}, + {'key': '--bidi-workaround', 'description': 'Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH'}, + {'key': '--sleep-interval', 'description': 'Number of seconds to sleep before each download when used alone or a lower boundof a range for randomized sleep before each download (minimum possible number of seconds to sleep) when used along with --max-sleep-interval'}, + {'key': '--max-sleep-interval', 'description': 'Upper bound of a range for randomized sleep before each download (maximum possible number of seconds to sleep). Must only beused along with --min-sleep-interval'} +] + +const video_format = [ + {'key': '-f', 'alt': '--format', 'description': 'Video format code, see the "FORMAT SELECTION" for all the info'}, + {'key': '--all-formats', 'description': 'Download all available video formats'}, + {'key': '--prefer-free-formats', 'description': 'Prefer free video formats unless a specific one is requested'}, + {'key': '-F', 'alt': '--list-formats', 'description': 'List all available formats of requested videos'}, + {'key': '--youtube-skip-dash-manifest', 'description': 'Do not download the DASH manifests and related data on YouTube videos'}, + {'key': '--merge-output-format', 'description': 'If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm, flv. Ignored if no merge is required'} +]; + +const subtitle = [ + {'key': '--write-sub', 'description': 'Write subtitle file'}, + {'key': '--write-auto-sub', 'description': 'Write automatically generated subtitle file (YouTube only)'}, + {'key': '--all-subs', 'description': 'Download all the available subtitles of the video'}, + {'key': '--list-subs', 'description': 'List all available subtitles for the video'}, + {'key': '--sub-format', 'description': 'Subtitle format, accepts formats preference, for example: "srt" or "ass/srt/best"'}, + {'key': '--sub-lang', 'description': 'Languages of the subtitles to download (optional) separated by commas, use --list-subs'} +]; + +const authentication = [ + {'key': '-u', 'alt': '--username', 'description': 'Login with this account ID'}, + {'key': '-p', 'alt': '--password', 'description': 'Account password. If this option is left out, youtube-dl will ask interactively.'}, + {'key': '-2', 'alt': '--twofactor', 'description': 'Two-factor authentication code'}, + {'key': '-n', 'alt': '--netrc', 'description': 'Use .netrc authentication data'}, + {'key': '--video-password', 'description': 'Video password (vimeo, smotri, youku)'} +]; + +const adobe_pass = [ + {'key': '--ap-mso', 'description': 'Adobe Pass multiple-system operator (TV provider) identifier, use --ap-list-mso'}, + {'key': '--ap-username', 'description': 'Multiple-system operator account login'}, + {'key': '--ap-password', 'description': 'Multiple-system operator account password. If this option is left out, youtube-dl will ask interactively.'}, + {'key': '--ap-list-mso', 'description': 'List all supported multiple-system operators'} +]; + +const post_processing = [ + {'key': '-x', 'alt': '--extract-audio', 'description': 'Convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)'}, + {'key': '--audio-format', 'description': 'Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "best" by default; No effect without -x'}, + {'key': '--audio-quality', 'description': 'Specify ffmpeg/avconv audio quality, insert a value between 0 (better) and 9 (worse)for VBR or a specific bitrate like 128K (default 5)'}, + {'key': '--recode-video', 'description': 'Encode the video to another format if necessary (currently supported:mp4|flv|ogg|webm|mkv|avi)'}, + {'key': '--postprocessor-args', 'description': 'Give these arguments to the postprocessor'}, + {'key': '-k', 'alt': '--keep-video', 'description': 'Keep the video file on disk after the post-processing; the video is erased by default'}, + {'key': '--no-post-overwrites', 'description': 'Do not overwrite post-processed files; the post-processed files are overwritten by default'}, + {'key': '--embed-subs', 'description': 'Embed subtitles in the video (only for mp4,webm and mkv videos)'}, + {'key': '--embed-thumbnail', 'description': 'Embed thumbnail in the audio as cover art'}, + {'key': '--add-metadata', 'description': 'Write metadata to the video file'}, + {'key': '--metadata-from-title', 'description': 'Parse additional metadata like song title/artist from the video title. The format syntax is the same as --output'}, + {'key': '--xattrs', 'description': 'Write metadata to the video file\'s xattrs (using dublin core and xdg standards)'}, + {'key': '--fixup', 'description': 'Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn (the default; fix file if we can, warn otherwise)'}, + {'key': '--prefer-avconv', 'description': 'Prefer avconv over ffmpeg for running the postprocessors'}, + {'key': '--prefer-ffmpeg', 'description': 'Prefer ffmpeg over avconv for running the postprocessors (default)'}, + {'key': '--ffmpeg-location', 'description': 'Location of the ffmpeg/avconv binary; either the path to the binary or its containing directory.'}, + {'key': '--exec', 'description': 'Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec'}, + {'key': '--convert-subs', 'description': 'Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)'} +]; + +export const args_info = { + 'uncategorized' : {'label': 'Main'}, + 'network' : {'label': 'Network'}, + 'geo_restriction': {'label': 'Geo Restriction'}, + 'video_selection': {'label': 'Video Selection'}, + 'download' : {'label': 'Download'}, + 'filesystem' : {'label': 'Filesystem'}, + 'thumbnail' : {'label': 'Thumbnail'}, + 'verbosity' : {'label': 'Verbosity'}, + 'workarounds' : {'label': 'Workarounds'}, + 'video_format' : {'label': 'Video Format'}, + 'subtitle' : {'label': 'Subtitle'}, + 'authentication' : {'label': 'Authentication'}, + 'adobe_pass' : {'label': 'Adobe Pass'}, + 'post_processing': {'label': 'Post Processing'}, +}; + +export const args = { + 'uncategorized' : uncategorized, + 'network' : network, + 'geo_restriction': geo_restriction, + 'video_selection': video_selection, + 'download' : download, + 'filesystem' : filesystem, + 'thumbnail' : thumbnail, + 'verbosity' : verbosity, + 'workarounds' : workarounds, + 'video_format' : video_format, + 'subtitle' : subtitle, + 'authentication' : authentication, + 'adobe_pass' : adobe_pass, + 'post_processing': post_processing +} diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css index 890d4a3..8414c07 100644 --- a/src/app/main/main.component.css +++ b/src/app/main/main.component.css @@ -119,4 +119,9 @@ mat-form-field.mat-form-field { .advanced-input { width: 100%; +} + +.edit-button { + margin-left: 10px; + top: -5px; } \ No newline at end of file diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 982a917..aa5bb21 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -111,6 +111,7 @@ Use custom args + diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 9103fa1..a45717a 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -20,6 +20,7 @@ import { Router, ActivatedRoute } from '@angular/router'; import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component'; import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; +import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; export let audioFilesMouseHovering = false; export let videoFilesMouseHovering = false; @@ -42,7 +43,7 @@ export interface Download { styleUrls: ['./main.component.css'] }) export class MainComponent implements OnInit { - youtubeAuthDisabledOverride = true; + youtubeAuthDisabledOverride = false; iOS = false; @@ -1088,4 +1089,18 @@ export class MainComponent implements OnInit { } }); } + + // modify custom args + openArgsModifierDialog() { + const dialogRef = this.dialog.open(ArgModifierDialogComponent, { + data: { + initial_args: this.customArgs + } + }); + dialogRef.afterClosed().subscribe(new_args => { + if (new_args) { + this.customArgs = new_args; + } + }); + } } diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index b4f8b3b..b596f08 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -92,6 +92,7 @@ Global custom args for downloads on the home page. +
@@ -211,6 +212,38 @@
+ + + + + Extensions + + +
+
+
+
Chrome
+

Click here to download the official YoutubeDL-Material Chrome extension manually.

+

You must manually load the extension and modify the extension's settings to set the frontend URL.

+ +
+
+
Firefox
+

Click here to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page.

+

Detailed setup instructions. Not much is required other than changing the extension's settings to set the frontend URL.

+ +
+
+
Bookmarklet
+

Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark.

+ +

YTDL-Bookmarklet

+
+
+
+
+ + diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index ee91f38..2c6e6cf 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -5,4 +5,8 @@ .locale-select { margin-bottom: 10px; width: 175px; +} + +.ext-divider { + margin-bottom: 14px; } \ No newline at end of file diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index e582e77..7b9fe2f 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -2,6 +2,9 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { isoLangs } from './locales_list'; import { MatSnackBar } from '@angular/material/snack-bar'; +import {DomSanitizer} from '@angular/platform-browser'; +import { MatDialog } from '@angular/material/dialog'; +import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; @Component({ selector: 'app-settings', @@ -16,11 +19,15 @@ export class SettingsComponent implements OnInit { initial_config = null; new_config = null loading_config = false; + generated_bookmarklet_code = null; - constructor(private postsService: PostsService, private snackBar: MatSnackBar) { } + constructor(private postsService: PostsService, private snackBar: MatSnackBar, private sanitizer: DomSanitizer, + private dialog: MatDialog) { } ngOnInit() { this.getConfig(); + + this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode()); } getConfig() { @@ -56,6 +63,52 @@ export class SettingsComponent implements OnInit { this.openSnackBar('Language successfully changed! Reload to update the page.') } + generateBookmarklet() { + this.bookmarksite('YTDL-Material', this.generated_bookmarklet_code); + } + + generateBookmarkletCode() { + const currentURL = window.location.href.split('#')[0]; + const homePageWithArgsURL = currentURL + '#/home;url='; + const bookmarkletCodeInside = `'${homePageWithArgsURL}' + window.location` + const bookmarkletCode = `javascript:(function()%7Bwindow.open('${homePageWithArgsURL}' + encodeURIComponent(window.location))%7D)()`; + return bookmarkletCode; + } + + // not currently functioning on most platforms. hence not in use + bookmarksite(title, url) { + // Internet Explorer + if (document.all) { + window['external']['AddFavorite'](url, title); + } else if (window['chrome']) { + // Google Chrome + this.openSnackBar('Chrome users must drag the \'Alternate URL\' link to your bookmarks.'); + } else if (window['sidebar']) { + // Firefox + window['sidebar'].addPanel(title, url, ''); + } else if (window['opera'] && window.print) { + // Opera + const elem = document.createElement('a'); + elem.setAttribute('href', url); + elem.setAttribute('title', title); + elem.setAttribute('rel', 'sidebar'); + elem.click(); + } + } + + openArgsModifierDialog() { + const dialogRef = this.dialog.open(ArgModifierDialogComponent, { + data: { + initial_args: this.new_config['Downloader']['custom_args'] + } + }); + dialogRef.afterClosed().subscribe(new_args => { + if (new_args) { + this.new_config['Downloader']['custom_args'] = new_args; + } + }); + } + // snackbar helper public openSnackBar(message: string, action: string = '') { this.snackBar.open(message, action, { diff --git a/src/locale/messages.es.xlf b/src/locale/messages.es.xlf deleted file mode 100644 index 0fbee7f..0000000 --- a/src/locale/messages.es.xlf +++ /dev/null @@ -1,987 +0,0 @@ - - - - - - Create a playlist - - app/create-playlist/create-playlist.component.html - 1 - - Create a playlist dialog title - Crea una lista de reproducción - - - Name - - app/create-playlist/create-playlist.component.html - 5 - - Playlist name placeholder - Nombre - - - Audio files - - app/create-playlist/create-playlist.component.html - 10 - - Audio files title - Archivos de sonido - - - Videos - - app/create-playlist/create-playlist.component.html - 11 - - - app/subscription/subscription/subscription.component.html - 15 - - Videos title - Archivos de video - - - Youtube Downloader - - app/main/main.component.html - 5 - - Youtube downloader home page label - Descargador de Youtube - - - Please enter a valid URL! - - app/main/main.component.html - 16 - - Enter valid URL error - Por favor entre una URL válida - - - Quality - - app/main/main.component.html - 24 - - Quality select label - Calidad - - - Use URL - - app/main/main.component.html - 52 - - YT search Use URL button for searched video - Usa URL - - - View - - app/main/main.component.html - 55 - - YT search View button for searched video - Ver - - - Only Audio - - app/main/main.component.html - 65 - - Only Audio checkbox - Solo audio - - - Multi-download Mode - - app/main/main.component.html - 70 - - Multi-download Mode checkbox - Descarga múltiple - - - Download - - app/main/main.component.html - 79 - - Main download button - Descarga - - - Cancel - - app/main/main.component.html - 84 - - Cancel download button - Cancela - - - Advanced - - app/main/main.component.html - 96 - - Advanced download mode panel - Avanzado - - - Simulated command: - - app/main/main.component.html - 102 - - Simulated command label - Commando simulado: - - - Use custom args - - app/main/main.component.html - 110 - - Use custom args checkbox - Usar argumentos personalizados - - - Custom args - - app/main/main.component.html - 115 - - - app/settings/settings.component.html - 83 - - Custom args placeholder - Argumentos personalizados - - - No need to include URL, just everything after. - - app/main/main.component.html - 117 - - Custom Args input hint - No es necesario incluir URL, solo todo después - - - Use custom output - - app/main/main.component.html - 125 - - Use custom output checkbox - Usar salida personalizada - - - Custom output - - app/main/main.component.html - 130 - - Custom output placeholder - Salida personalizada - - - Documentation - - app/main/main.component.html - 132 - - Youtube-dl output template documentation link - Documentación - - - Path is relative to the config download path. Don't include extension. - - app/main/main.component.html - 133 - - Custom Output input hint - La ruta es relativa a la ruta de descarga de la config. No incluya el extensión. - - - Use authentication - - app/main/main.component.html - 139 - - Use authentication checkbox - Usa autenticación - - - Username - - app/main/main.component.html - 144 - - YT Username placeholder - Nombre de usuario - - - Password - - app/main/main.component.html - 149 - - YT Password placeholder - Contraseña - - - Audio - - app/main/main.component.html - 193 - - Audio files title - Audio - - - Your audio files are here - - app/main/main.component.html - 198 - - Audio files description - Tus archivos de audio están aquí - - - Playlists - - app/main/main.component.html - 213 - - - app/main/main.component.html - 255 - - - app/subscriptions/subscriptions.component.html - 27 - - Playlists title - Listas de reproducción - - - No playlists available. Create one from your downloading audio files by clicking the blue plus button. - - app/main/main.component.html - 224 - - No video playlists available text - No hay listas de reproducción disponibles. Cree uno de tus archivos de audio haciendo clic en el botón azul más. - - - Video - - app/main/main.component.html - 234 - - Video files title - Vídeo - - - Your video files are here - - app/main/main.component.html - 239 - - Video files description - Tus archivos de video son aquí - - - No playlists available. Create one from your downloading video files by clicking the blue plus button. - - app/main/main.component.html - 268 - - No video playlists available text - No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más. - - - ID: - - app/file-card/file-card.component.html - 6 - - - app/download-item/download-item.component.html - 7 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 13 - - File or playlist ID - ID: - - - Count: - - app/file-card/file-card.component.html - 7 - - Playlist video count - Cuenta: - - - Settings - - app/settings/settings.component.html - 1 - - - app/app.component.html - 22 - - Settings title - Configuraciones - - - Host - - app/settings/settings.component.html - 8 - - Host settings title - Host - - - URL - - app/settings/settings.component.html - 15 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 8 - - URL input placeholder - URL - - - URL this app will be accessed from, without the port. - - app/settings/settings.component.html - 16 - - URL setting input hint - URL desde la que se accederá a esta aplicación, sin el puerto. - - - Port - - app/settings/settings.component.html - 21 - - Port input placeholder - Puerto - - - The desired port. Default is 17442. - - app/settings/settings.component.html - 22 - - Port setting input hint - Puerto deseado. El valor predeterminado es 17442. - - - Encryption - - app/settings/settings.component.html - 34 - - Encryption settings title - Cifrado - - - Use encryption - - app/settings/settings.component.html - 40 - - Use encryption setting - Usa cifrado - - - Cert file path - - app/settings/settings.component.html - 45 - - Cert file path input placeholder - Ruta del archivo de certificado - - - Key file path - - app/settings/settings.component.html - 51 - - Key file path input placeholder - Ruta de archivo de clave - - - Downloader - - app/settings/settings.component.html - 62 - - Downloader settings title - Descargador - - - Audio folder path - - app/settings/settings.component.html - 69 - - Audio folder path input placeholder - Ruta de la carpeta de audio - - - Path for audio only downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 70 - - Aduio path setting input hint - Ruta para descargas de solo audio. Es relativo a la carpeta raíz de YTDL-Material. - - - Video folder path - - app/settings/settings.component.html - 76 - - Video folder path input placeholder - Ruta de la carpeta de video - - - Path for video downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 77 - - Video path setting input hint - Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material. - - - Global custom args for downloads on the home page. - - app/settings/settings.component.html - 84 - - Custom args setting input hint - Argumentos personalizados globales para descargas en la página de inicio. - - - Extra - - app/settings/settings.component.html - 95 - - Extra settings title - Extra - - - Top title - - app/settings/settings.component.html - 102 - - Top title input placeholder - Título superior - - - File manager enabled - - app/settings/settings.component.html - 107 - - File manager enabled setting - Administrador de archivos habilitado - - - Allow quality select - - app/settings/settings.component.html - 110 - - Allow quality seelct setting - Permitir selección de calidad - - - Download only mode - - app/settings/settings.component.html - 113 - - Download only mode setting - Modo de solo descarga - - - Allow multi-download mode - - app/settings/settings.component.html - 116 - - Allow multi-downloade mode setting - Permitir el modo de descarga múltiple - - - API - - app/settings/settings.component.html - 126 - - API settings title - API - - - Use YouTube API - - app/settings/settings.component.html - 132 - - Use YouTube API setting - Utilizar la API de YouTube - - - Youtube API Key - - app/settings/settings.component.html - 136 - - Youtube API Key setting placeholder - Clave API de YouTube - - - Generating a key is easy! - - app/settings/settings.component.html - 137 - - Youtube API Key setting hint - ¡Generar una clave es fácil! - - - Themes - - app/settings/settings.component.html - 148 - - Themes settings title - Temas - - - Default - - app/settings/settings.component.html - 155 - - Default theme label - Defecto - - - Dark - - app/settings/settings.component.html - 156 - - - app/app.component.html - 17 - - Dark theme label - Oscura - - - Allow theme change - - app/settings/settings.component.html - 161 - - Allow theme change setting - Permitir cambio de tema - - - Subscriptions - - app/settings/settings.component.html - 171 - - - app/app.component.html - 34 - - Subscriptions settings title - Suscripciones - - - Allow subscriptions - - app/settings/settings.component.html - 177 - - Allow subscriptions setting - Permitir suscripciones - - - Subscriptions base path - - app/settings/settings.component.html - 181 - - Subscriptions base path input setting placeholder - Ruta base de suscripciones - - - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 182 - - Subscriptions base path setting input hint - Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material. - - - Check interval - - app/settings/settings.component.html - 187 - - Check interval input setting placeholder - Intervalo de comprobación - - - Unit is seconds, only include numbers. - - app/settings/settings.component.html - 188 - - Check interval setting input hint - La unidad es segundos, solo incluye números. - - - Use youtube-dl archive - - app/settings/settings.component.html - 192 - - Use youtube-dl archive setting - Usa el archivo de youtube-dl - - - With youtube-dl's archive - - app/settings/settings.component.html - 193 - - youtube-dl archive explanation prefix link - Con la función de archivo de youtube-dl, - - - feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. - - app/settings/settings.component.html - 193 - - youtube-dl archive explanation middle - los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones. - - - This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. - - app/settings/settings.component.html - 194 - - youtube-dl archive explanation suffix - Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos. - - - Advanced - - app/settings/settings.component.html - 204 - - Advanced settings title - Avanzado - - - Use default downloading agent - - app/settings/settings.component.html - 210 - - Use default downloading agent setting - Usar agente de descarga predeterminado - - - Custom agent - - app/settings/settings.component.html - 214 - - Custom agent setting placeholder - Agente personalizado - - - Allow advanced download - - app/settings/settings.component.html - 219 - - Allow advanced downloading setting - Permitir descarga avanzada - - - Save - - app/settings/settings.component.html - 229 - - Settings save button - Salvar - - - Cancel - - app/settings/settings.component.html - 232 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 37 - - Settings cancel button - Cancelar - - - Home - - app/app.component.html - 33 - - Navigation menu Home Page title - Inicio - - - Save changes - - app/player/player.component.html - 22 - - Playlist save changes button - Guardar cambios - - - Subscribe to playlist or channel - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 1 - - Subscribe dialog title - Suscríbase a la lista de reproducción o al canal - - - The playlist or channel URL - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 9 - - Subscription URL input hint - La lista de reproducción o la URL del canal - - - Custom name - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 14 - - Subscription custom name placeholder - Nombre personalizado - - - This is optional - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 15 - - Custom name input hint - Esto es opcional - - - Download all uploads - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 19 - - Download all uploads subscription setting - Descargar todas las cargas - - - Download videos uploaded in the last - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 22 - - Download time range prefix - Descargar videos subidos en el último - - - Type: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 5 - - Subscription type property - Tipo: - - - URL: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 9 - - Subscription URL property - URL: - - - Archive: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 17 - - Subscription ID property - Archivo: - - - Close - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 23 - - Close subscription info button - Cerca - - - Export Archive - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 24 - - Export Archive button - Exportar el archivo - - - Unsubscribe - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 26 - - Unsubscribe button - Darse de baja - - - Your subscriptions - - app/subscriptions/subscriptions.component.html - 3 - - Subscriptions title - Sus suscripciones - - - Channels - - app/subscriptions/subscriptions.component.html - 8 - - Subscriptions channels title - Canales - - - Name not available. Channel retrieval in progress. - - app/subscriptions/subscriptions.component.html - 14 - - Subscription playlist not available text - Nombre no disponible. Recuperación de canales en progreso. - - - You have no channel subscriptions. - - app/subscriptions/subscriptions.component.html - 24 - - No channel subscriptions text - No tienes suscripciones de canal. - - - Name not available. Playlist retrieval in progress. - - app/subscriptions/subscriptions.component.html - 33 - - Subscription playlist not available text - Nombre no disponible. Recuperación de listas de reproducción en progreso. - - - You have no playlist subscriptions. - - app/subscriptions/subscriptions.component.html - 43 - - No playlist subscriptions text - No tienes suscripciones a listas de reproducción. - - - Search - - app/subscription/subscription/subscription.component.html - 19 - - Subscription videos search placeholder - Buscar - - - Length: - - app/subscription/subscription-file-card/subscription-file-card.component.html - 3 - - Video duration label - Duración: - - - Delete and redownload - - app/subscription/subscription-file-card/subscription-file-card.component.html - 7 - - Delete and redownload subscription video button - Eliminar y volver a descargar - - - Delete forever - - app/subscription/subscription-file-card/subscription-file-card.component.html - 8 - - Delete forever subscription video button - Borrar para siempre - - - - \ No newline at end of file diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf deleted file mode 100644 index 21f8758..0000000 --- a/src/locale/messages.xlf +++ /dev/null @@ -1,1003 +0,0 @@ - - - - - - Create a playlist - - app/create-playlist/create-playlist.component.html - 1 - - Create a playlist dialog title - - - Name - - app/create-playlist/create-playlist.component.html - 5 - - Playlist name placeholder - - - Audio files - - app/create-playlist/create-playlist.component.html - 10 - - Audio files title - - - Videos - - app/create-playlist/create-playlist.component.html - 11 - - - app/subscription/subscription/subscription.component.html - 15 - - Videos title - - - Youtube Downloader - - app/main/main.component.html - 5 - - Youtube downloader home page label - - - Please enter a valid URL! - - app/main/main.component.html - 16 - - Enter valid URL error - - - - Quality - - - app/main/main.component.html - 24 - - Quality select label - - - Use URL - - app/main/main.component.html - 52 - - YT search Use URL button for searched video - - - - View - - - app/main/main.component.html - 55 - - YT search View button for searched video - - - - Only Audio - - - app/main/main.component.html - 65 - - Only Audio checkbox - - - - Multi-download Mode - - - app/main/main.component.html - 70 - - Multi-download Mode checkbox - - - - Download - - - app/main/main.component.html - 79 - - Main download button - - - - Cancel - - - app/main/main.component.html - 84 - - Cancel download button - - - - Advanced - - - app/main/main.component.html - 96 - - Advanced download mode panel - - - - Simulated command: - - - app/main/main.component.html - 102 - - Simulated command label - - - - Use custom args - - - app/main/main.component.html - 110 - - Use custom args checkbox - - - Custom args - - app/main/main.component.html - 115 - - - app/settings/settings.component.html - 92 - - Custom args placeholder - - - - No need to include URL, just everything after. - - - app/main/main.component.html - 117 - - Custom Args input hint - - - - Use custom output - - - app/main/main.component.html - 125 - - Use custom output checkbox - - - Custom output - - app/main/main.component.html - 130 - - Custom output placeholder - - - Documentation - - app/main/main.component.html - 132 - - Youtube-dl output template documentation link - - - Path is relative to the config download path. Don't include extension. - - app/main/main.component.html - 133 - - Custom Output input hint - - - - Use authentication - - - app/main/main.component.html - 139 - - Use authentication checkbox - - - Username - - app/main/main.component.html - 144 - - YT Username placeholder - - - Password - - app/main/main.component.html - 149 - - YT Password placeholder - - - - Audio - - - app/main/main.component.html - 193 - - Audio files title - - - - Your audio files are here - - - app/main/main.component.html - 198 - - Audio files description - - - Playlists - - app/main/main.component.html - 213 - - - app/main/main.component.html - 255 - - - app/subscriptions/subscriptions.component.html - 27 - - Playlists title - - - - No playlists available. Create one from your downloading audio files by clicking the blue plus button. - - - app/main/main.component.html - 224 - - No video playlists available text - - - - Video - - - app/main/main.component.html - 234 - - Video files title - - - - Your video files are here - - - app/main/main.component.html - 239 - - Video files description - - - - No playlists available. Create one from your downloading video files by clicking the blue plus button. - - - app/main/main.component.html - 268 - - No video playlists available text - - - ID: - - app/file-card/file-card.component.html - 6 - - - app/download-item/download-item.component.html - 7 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 13 - - File or playlist ID - - - Count: - - app/file-card/file-card.component.html - 7 - - Playlist video count - - - Delete - - app/file-card/file-card.component.html - 21 - - Delete video button - - - Delete and blacklist - - app/file-card/file-card.component.html - 22 - - Delete and blacklist video button - - - Settings - - app/settings/settings.component.html - 1 - - - app/app.component.html - 22 - - Settings title - - - Host - - app/settings/settings.component.html - 17 - - Host settings title - - - URL - - app/settings/settings.component.html - 24 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 8 - - URL input placeholder - - - URL this app will be accessed from, without the port. - - app/settings/settings.component.html - 25 - - URL setting input hint - - - Port - - app/settings/settings.component.html - 30 - - Port input placeholder - - - The desired port. Default is 17442. - - app/settings/settings.component.html - 31 - - Port setting input hint - - - Encryption - - app/settings/settings.component.html - 43 - - Encryption settings title - - - Use encryption - - app/settings/settings.component.html - 49 - - Use encryption setting - - - Cert file path - - app/settings/settings.component.html - 54 - - Cert file path input placeholder - - - Key file path - - app/settings/settings.component.html - 60 - - Key file path input placeholder - - - Downloader - - app/settings/settings.component.html - 71 - - Downloader settings title - - - Audio folder path - - app/settings/settings.component.html - 78 - - Audio folder path input placeholder - - - Path for audio only downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 79 - - Aduio path setting input hint - - - Video folder path - - app/settings/settings.component.html - 85 - - Video folder path input placeholder - - - Path for video downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 86 - - Video path setting input hint - - - Global custom args for downloads on the home page. - - app/settings/settings.component.html - 93 - - Custom args setting input hint - - - Use youtube-dl archive - - app/settings/settings.component.html - 98 - - - app/settings/settings.component.html - 206 - - Use youtubedl archive setting - - - Extra - - app/settings/settings.component.html - 109 - - Extra settings title - - - Top title - - app/settings/settings.component.html - 116 - - Top title input placeholder - - - File manager enabled - - app/settings/settings.component.html - 121 - - File manager enabled setting - - - Allow quality select - - app/settings/settings.component.html - 124 - - Allow quality seelct setting - - - Download only mode - - app/settings/settings.component.html - 127 - - Download only mode setting - - - Allow multi-download mode - - app/settings/settings.component.html - 130 - - Allow multi-downloade mode setting - - - API - - app/settings/settings.component.html - 140 - - API settings title - - - Use YouTube API - - app/settings/settings.component.html - 146 - - Use YouTube API setting - - - Youtube API Key - - app/settings/settings.component.html - 150 - - Youtube API Key setting placeholder - - - Generating a key is easy! - - app/settings/settings.component.html - 151 - - Youtube API Key setting hint - - - Themes - - app/settings/settings.component.html - 162 - - Themes settings title - - - Default - - app/settings/settings.component.html - 169 - - Default theme label - - - Dark - - app/settings/settings.component.html - 170 - - - app/app.component.html - 17 - - Dark theme label - - - Allow theme change - - app/settings/settings.component.html - 175 - - Allow theme change setting - - - Subscriptions - - app/settings/settings.component.html - 185 - - - app/app.component.html - 38 - - Subscriptions settings title - - - Allow subscriptions - - app/settings/settings.component.html - 191 - - Allow subscriptions setting - - - Subscriptions base path - - app/settings/settings.component.html - 195 - - Subscriptions base path input setting placeholder - - - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 196 - - Subscriptions base path setting input hint - - - Check interval - - app/settings/settings.component.html - 201 - - Check interval input setting placeholder - - - Unit is seconds, only include numbers. - - app/settings/settings.component.html - 202 - - Check interval setting input hint - - - With youtube-dl's archive - - app/settings/settings.component.html - 207 - - youtube-dl archive explanation prefix link - - - feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. - - app/settings/settings.component.html - 207 - - youtube-dl archive explanation middle - - - This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. - - app/settings/settings.component.html - 208 - - youtube-dl archive explanation suffix - - - Advanced - - app/settings/settings.component.html - 218 - - Advanced settings title - - - Use default downloading agent - - app/settings/settings.component.html - 224 - - Use default downloading agent setting - - - Allow advanced download - - app/settings/settings.component.html - 239 - - Allow advanced downloading setting - - - Save - - app/settings/settings.component.html - 249 - - Settings save button - - - Cancel - - app/settings/settings.component.html - 252 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 37 - - Settings cancel button - - - About YoutubeDL-Material - - app/dialogs/about-dialog/about-dialog.component.html - 1 - - About dialog title - - - is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. - - app/dialogs/about-dialog/about-dialog.component.html - 6 - - About first paragraph - - - has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon below. - - app/dialogs/about-dialog/about-dialog.component.html - 9 - - About second paragraph - - - Found a bug or have a suggestion? - - app/dialogs/about-dialog/about-dialog.component.html - 12 - - About bug prefix - - - Click here - - app/dialogs/about-dialog/about-dialog.component.html - 12 - - About bug click here - - - to create an issue! - - app/dialogs/about-dialog/about-dialog.component.html - 12 - - About bug suffix - - - Installed version: - - app/dialogs/about-dialog/about-dialog.component.html - 17 - - Version label - - - View latest update - - app/dialogs/about-dialog/about-dialog.component.html - 17 - - View latest update - - - About - - app/app.component.html - 26 - - About menu label - - - Home - - app/app.component.html - 37 - - Navigation menu Home Page title - - - Save changes - - app/player/player.component.html - 22 - - Playlist save changes button - - - Subscribe to playlist or channel - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 1 - - Subscribe dialog title - - - The playlist or channel URL - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 9 - - Subscription URL input hint - - - Custom name - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 14 - - Subscription custom name placeholder - - - This is optional - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 15 - - Custom name input hint - - - Download all uploads - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 19 - - Download all uploads subscription setting - - - Download videos uploaded in the last - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 22 - - Download time range prefix - - - Type: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 5 - - Subscription type property - - - URL: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 9 - - Subscription URL property - - - Archive: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 17 - - Subscription ID property - - - Close - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 23 - - Close subscription info button - - - Export Archive - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 24 - - Export Archive button - - - Unsubscribe - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 26 - - Unsubscribe button - - - Your subscriptions - - app/subscriptions/subscriptions.component.html - 3 - - Subscriptions title - - - Channels - - app/subscriptions/subscriptions.component.html - 8 - - Subscriptions channels title - - - Name not available. Channel retrieval in progress. - - app/subscriptions/subscriptions.component.html - 14 - - Subscription playlist not available text - - - You have no channel subscriptions. - - app/subscriptions/subscriptions.component.html - 24 - - No channel subscriptions text - - - Name not available. Playlist retrieval in progress. - - app/subscriptions/subscriptions.component.html - 33 - - Subscription playlist not available text - - - You have no playlist subscriptions. - - app/subscriptions/subscriptions.component.html - 43 - - No playlist subscriptions text - - - Search - - app/subscription/subscription/subscription.component.html - 19 - - Subscription videos search placeholder - - - Length: - - app/subscription/subscription-file-card/subscription-file-card.component.html - 3 - - Video duration label - - - Delete and redownload - - app/subscription/subscription-file-card/subscription-file-card.component.html - 7 - - Delete and redownload subscription video button - - - Delete forever - - app/subscription/subscription-file-card/subscription-file-card.component.html - 8 - - Delete forever subscription video button - - - - From ce2f294a3dc1bc99eb95918bf38bbcb78c22126c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:17:15 -0400 Subject: [PATCH 43/48] Removed potential race condition with youtube-dl archives in main downloader Fixed bug where downloaded subscriptions' zip files would include erroneous folders for their videos --- backend/app.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/app.js b/backend/app.js index 7c30a6b..5752fa9 100644 --- a/backend/app.js +++ b/backend/app.js @@ -393,8 +393,9 @@ async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvid for (let i = 0; i < fileNames.length; i++) { let fileName = fileNames[i]; + let fileNamePathRemoved = path.parse(fileName).base; let file_path = !fullPathProvided ? zipFolderPath + fileName + ext : fileName; - archive.file(file_path, {name: fileName + ext}) + archive.file(file_path, {name: fileNamePathRemoved + ext}) } await archive.finalize(); @@ -744,6 +745,7 @@ app.post('/api/tomp3', async function(req, res) { let downloadConfig = null; let qualityPath = '-f bestaudio'; + let merged_path = null; let merged_string = null; if (customArgs) { @@ -788,7 +790,8 @@ app.post('/api/tomp3', async function(req, res) { fs.closeSync(fs.openSync(blacklist_path, 'w')); } - let merged_path = audioFolderPath + 'merged.txt'; + // creates merged folder + merged_path = audioFolderPath + `merged_${uuid()}.txt`; // merges blacklist and regular archive let inputPathList = [archive_path, blacklist_path]; let status = await mergeFiles(inputPathList, merged_path); @@ -853,9 +856,10 @@ app.post('/api/tomp3', async function(req, res) { let is_playlist = file_names.length > 1; if (merged_string !== null) { - let current_merged_archive = fs.readFileSync(audioFolderPath + 'merged.txt', 'utf8'); + let current_merged_archive = fs.readFileSync(merged_path, 'utf8'); let diff = current_merged_archive.replace(merged_string, ''); fs.appendFileSync(audioFolderPath + 'archive.txt', diff); + fs.unlinkSync(merged_path) } var audiopathEncoded = encodeURIComponent(file_names[0]); From 0ac87fe86d5251e6d9a378c66439eda1b9ddeeb0 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:18:51 -0400 Subject: [PATCH 44/48] Updated spanish language string label to avoid confusion Fixed bug where if no locale was set, it would *not* default to English --- src/app/settings/locales_list.ts | 2 +- src/main.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/settings/locales_list.ts b/src/app/settings/locales_list.ts index 62ba078..cd69919 100644 --- a/src/app/settings/locales_list.ts +++ b/src/app/settings/locales_list.ts @@ -594,7 +594,7 @@ export const isoLangs = { }, 'es': { 'name': 'Spanish; Castilian', - 'nativeName': 'español, castellano' + 'nativeName': 'español' }, 'su': { 'name': 'Sundanese', diff --git a/src/main.ts b/src/main.ts index 77ff462..00f55c9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,9 @@ if (environment.production) { } const locale = localStorage.getItem('locale'); +if (!locale) { + localStorage.setItem('locale', 'en'); +} if (locale && locale !== 'en') { getTranslations(`./assets/i18n/messages.${locale}.json`).then( (data: ParsedTranslationBundle) => { From 87e7f00aefe25ead1b09cdf74e7ab94725a2dff4 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:19:23 -0400 Subject: [PATCH 45/48] Updated chrome extension --- .../youtubedl-material-chrome-extension.zip | Bin 4405 -> 75374 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chrome-extension/youtubedl-material-chrome-extension.zip b/chrome-extension/youtubedl-material-chrome-extension.zip index e9663045ab21f85a399bd355a4744b252baa73cc..87bb073ab0d26c6fa5f1b1ffb849cb9cab7d73bd 100644 GIT binary patch delta 73061 zcmagFV~}KR)OK06ZQFL2ZJS-TjV#-?ZQIpl+vu`wbWJ@o?>FE4nVHNuCs$s3?K^kg zKTbrRh-@Lbo&tkclmP`p0|Ekq0!paXYk*G#1^@w(H90E&ME~~#?1zk%bO-qB!K>gRd+Pt(xzCTCUO*Yt7b^q`PS zW38lch>81n^@`4 z$Az0PveBya-!I<0qy=1G$7qV+^GQ1CST&S5hEXnR2?O7|oI)`-eqFcrua}v=o5~kJ zu=LH9VuLp$B5nNBPMU$w{n_fSpR}rvy$;BSizhRGT`~y1rR(ra% zfWAE8fivY?MG)|hH#QgtA-7M{5Anua@(U>WNwS?@WQnF>A7FDaYyy@1ozW_?bddo< zXyM@-#ril9064g12HEHp?xJfu3El~SOw{3AA`8AY_8(LMhi1s)HjX?%z~ltX`3Bnh z6_N_22BRuHC-$Ho%+~Bdi|eJq$uu?Wh%iz}4Fz2yUzxi~28~>8xP{2<2DW9rp3iJl zRf%`REhVx9${h=-`X$R3>Liq;iv=3MjlO=z*Q>5mR6NlZwS4cgEc4*erCke{R!+9# z%&O4x65BL9|0wn_5FFs2VtpKKhlxW^*ERdN;^Gq;jj0mIzj3}X2HSpC!}67p7XZb$ zd9qJxmK5Iw+;zUlHPFt4yu6=4zteM@D{;N^bdc3~6Q*c|^HDE(f05Fmkzk-7oi292 za(nAdG{E+Lk?x|Ox{8co3gFWcEgMRh|O)xf_f@s5~y8&?+O^kO$@n{geRUG+d1FQ%r70d+N~38A>y1Ra2|4&pwU*yuJxg}a|{pjuA64`EFCBr_v zM5Yk|OD$b_eF8Iy8?1kTW1&RDeVuGIN-!yiBJsz`Nj>Cm#~>H!CQ{M*{n~F;n*jZ# zx6OQ6C$D$jtKNxwj$nOsw zbyMD-O#|C8I>$HLF}lXb*fARS;pfMs%r~dp`{SHoPYKI(e^e1e){pkrPU9a^*a=eT zAsmee8^5`E^5R4{^XdL92-^{(RRbZ%w zmYinO9xQtPshS)fi1K$@9;AO~Pn4?C;)a=(5{C)eGeInLzY=p=hp)}C94piQKKm~U zakOe)n@tmgKaTEyt{{f7tbadpaAQ=wEpDp9AjaUNbb-xvKWkf-4tdfr#H2 zxHY)bpkko`9+EhIMNrGjyPdZ~;zQWzaT7`8-a0X8mzAZNtkjEePcCL5D|=#$QW<5? zqhN%s*;6-U0!_Es7v6y~k&UM9!6t8E>3~g#meH)=2c_A(cjg#|`xhi*v1=v#F&3L* z)0g%NHs-U|HQyicNZe8$Njn*mON>AU59tECi$IbFNbx6J*5XIFV{&mj@lG(!FaW)1 zHq1{BoJ~NA={Nh6Y~hmTG6Wmz{{9FTlTA#+S4eBp$ewXI4rL1kyRZKjZ)bExH!YW# zFeq~#s$5f$#VysKy~6N>JO2DDdb+s}!1e7Yy%YAAkNf*@9u>><9&QGURa}o$TSUgM zlevi*kSi%151AgX-m`Stssj`QU^y`vB^AVK;qKDb=&v=WSx4vFYxR?pMBP;!pC^v+ z3-J7+RG|K|ZSbiY$87*LP>GdZU?j}}GozIotpvx{Q6Gi%jTs zo(3HOkvkrL1syq%(1tgd^;_29d~#pk1B^}(AeD({jL*b_EpOIxkAgOl?2SZCZxUkok0&#@%O>xGOWH12U7eq$NMwK7;5uyysYgPu= zR{)|_Ku~$*3<5a6w)q2OW<%%X0?guKP<-jDCqpVQ+>3oJWQ5FAcwl86E>v2wQ)dLr zvT$~3#rYxST4271jL$1+h1t@=)tt@IR8 z`rVa~$G<$)D5tkP5KBOU!;7ry zz19GWHFgD^2c+5v!&>;V(dXkmu6~J!@ay!0@U1RL%#&xQNCsm!`b-AQ=8)##j389s> zn3uhhrRG0L+o7~X%a@EO=sjW9a9cH#qIAFFu3}&msWJyqHsBrRp5mcZfHqg9=lATf z$C;+?*OyS;r$2a^c)Agu%@Akm_W+1yNFrg%%H%m!t^JwU^f7&19#a0nS(N8Cr~e zjRmDLR(6B`3-55XHIJX(>Rn;{IcoZPJaxtBhg(>elnNEqwY6pj=X&yKmN>&hP? z?v5`>j9T;#<3XU;eQ<$27R5ChlJqQ=z;#%^e$!WgEeM>SV&s?8b$*DqRm%+S$<`vq zb;bbv{u<*+Zi|CXs`F6D_5;Rs$p01TH#;!G+aZnmalbT%c$+!|aXn9VXwYw22jTu{ zFqrAF`@4g+MHKt9o}5l|Ix+11S{+o{iUsQsyv!k_u<#tk00K9%b3i? zQ4mC{G_+i!zB}7h>`S#K&AE&$-4bjuq&mZzy(xp-y8w9#ROVTkRP(jbtYme~tByUe zq~=AOR8y*_z}z)&EfTVG^>A&&r(+W6N+9~a@>5H&ZXz%Asq!BdO`1J?!f0Brg z%1@!W|KQ(G0#yG~0~p8(}2kpBr38-9#o8bTPXs!!nbh6zF-+^SFHv4rB(UL?9I9!Jv@cM+NVhWV1} zONex}ofzhTki)&gxf~kvV|?tlRP3qTvsq z{y@NZg+|I@r(k7{AFMoruGqqq;r=QkLYRbPhYcy7V-{(Og=#sy%vV@H8C zYQm0zIL^A{EMRpLSVAK||i$p2b^epW@U zM=egTS5bHUG2Q<6m{eDxIfBMR@>=Wgyh7W9`N@COeE+^wMq3x>)&B$LbU(?|Pl=MgZq|40{(b9&zAn!9@II0tkhkuX zxcwm#Vt>iHp*roZG2XF70oK+G4DXJwB;n!Nkqz&;xTIQ-npRobdD+qo+Q}j-tO;skwf^t!=+(O!Wo@TcExZ~)kzTXA(FhE80zOZc z5pRaqJGH%FD%)5l`Q%u=#));a8|I&n9MJz+1#G8aStT~Y&~t-`L<%FKqQu0inPF>y zgDpy&-%=ZMxIl$#(`sc=p~ObZsT^N6$pmvFQkvhgXu1<2$WFaF5_GUgRh~StNlu9hm#MsN0Z!C+ad^=98|yi#@MB0&4Cl(cLy}?j zOFe2lln;ICaA?Joa;=*310M8lBkGk5wAr%HWNtw_!DGp~Ur&ReTx9V|w@;8DOBUD{g<9V|<(NK4}}kNF}p zbGmW-o7Rf+f^VR!G3G8;tbePzwi|)c8nFoajhREIjYGqp@cgO)cR+v6iFem}THaC# zW%i)nuZ*qRXVA?+3GxovX2T#Rv3^hy4`9D}mH@AXXr%jTw|w?*4F!{ORRyXmtJTh4 z??$2Xul-4M+iuq4_Qvk8BGq${AF*2K5#Uo`yC+)i?|_SV*vr|z?IH+)rb*r!^P^od z-dO8Cr*G6DNUP4|48R6-6MQ(&{Wjnvw3*w#LjX4TmwepXVkCeH)!`NS>u#|I8Bih_ z;X-R72ef^JTL!s%$J>|h>?Z6m;~o|A)OK1!9kVyzMfw>|hG{B+;yPtd>{B#MUhYCW zj!~DZFUmjr+oZ=6Hj63VwtjiLtzw@Ek7*cGaceEIEA`Ks;qFfK2A-%a`7NIL#e7AG zu%SKu`np-lYBTqhjf?i~xk*o@HDLLa;WY6w+EFC{{LYBrRld+o7{U9~IP&IPWO z_7qJ%)Ut0JswMrh{#Z239f$M)sE0XFC+U9mH{db@3_s}lygsg_HhuEWBR3z!z7N+YG}ozUWn}FVB7v+lLd$y0@*}*+JY*#LPf#8WV?gmRb#{Co}o?AilrWYhc*EXr@W0P)y<<@&f~D^e*WIcbT>ecq%)n zQP6;O-|DqXF?Ac)Qsrv?(W%^raFows_?>leYHs4qe;UbZ`#764l&ne^aImc@*%a!9S&DGYWBgO!prRg>)3}Wh!!--}%U`2uuGh{c^q^ro(Y)j81`VDi0`KXOOUJH&yVB8` z;nnqDiAx<(aN?`_EbG}mlAY@9+p0_DwJ)YM(Y8TAn`Q22{eZrC7EMfsax-#w^gnkz zm#VX;=$uX~xIIhmGRh-CNoYsZ?7dak< z&$=h@-9YwXJ=NB;yyHE#ziV>-dU4EN;panO7E}zazOcLMHo-0Kr2S5R$o(Aj`AUj% zTrc3s&%*`ZdIt!mUA}5Q$_n_z9sQfVeQ-JVj?xnNgcIL@%2aPRJiR>iFT_Tk#h#vS zpX)0pPT#>X^wH;Voc;TbUs(FVUtVC6zZKiMURI3E7!0lCE+X}@TIoZmt zG>==!T=P1(y7b`Ow=?NEW43eTam^vSS@wl+&Lg-qy8=M>Hy#+G<>KQza6LWbc-eRt z&`>J5DyUr!S++YWGV^xsJD$YPfj=U^az1|u55dd6D38mT=wVK${bRXSUk6rxQCSl* zB#f3nDRhfQfCN?aa&!4}#>VVjRqrZszAgN2_;gM8=kcYC=(v~jo9V`P-29vmtH*p4 z_*D;4fd|0d*z&|{e{ErpYyKM42KoAh3aCM`Y;jblKpwfhoGiFk2Yxf*+nU5q$P#j! zzwLt0EI8MtHK?(DON<INT8UocT-jD&-#9;lhP5;M^7eq- zKsR0G%Qx}2mbmY3(`{F=&)Q;=6M5uH;BU4%I_@=g-ya2qrIGYEXs>%j~IFrF!6g8V-8bX4zg?3N!!Uf!WDYD{hMEw!rRE_;+IWe!)hdX?O4cxxGFZ; zpAe2JQVRDqARD`gk6+93-L3_gR>~HAuHyi0yZsn)Ywj2lxztl*tv~M@4PjD{GG=F_ zEvjy>#&~;MCw6!`GfMb-8ARLJja&h+w{y9rRF*3S@2d*{U zHc%mJ>iQ)80(zrj$^q7NlmoY3Exe_JXQz%lyCqAmX@QO?!w09Sn~Y4~?T*C@;e^~;(tcMe@=*U8xmVGTk8;>FqvCp$F* zGC%yvMF#d?p+;#lXSvjHd#yvmqV_v8Eh-cbIA5TK_@dtBh7~wnDa}0rHe(& zKF2Ss|Ib->&Ps-0s{jt22L)xA(#Qj z<7?<*u>lumjWTuKv4>t`Fr~S7(ms}zS9PDuy=5Cq7r#=f*%>nI8i5%b^Q_H>nx!hFWg2aJ{EEB5thrMb{=sFhzj5!2wwrZH<>`Gs|N43-UcV z+j}VRxx8W6qlvT8LGq~OY4E6pCDvLqk~wTEg`~a2kdVR)DAD{RQDX|a!QX>Q#s)| zZNs^pInE~N#;Ff1+1HokHWPUxK{zV3RLpwQC}hZ@UPGyL@vawkYLc$3{Mvl!FW0NK z(-ugPd(x2{xx8i(3!bbrf}IRSnrD5IyWJqrU&?gUu{wgD%UhfR9|zndI`>^WHb%KE zeXS_{0}yZ^M8S2C)zH@91b@RjQ$0Mc2!f ze7WT)Tj-(P`s$h-=h?6ou_;FJ(&FJ#KZI%5UB=HF%X z?kx{Y^>`X_)njG&yXw5gmG`Thb-xjO7NwLz>oHu1`B$%k23Lm2$B}#yRa2EEsEB{v zjkV135NpuHeCD|)WWF=`Hj+*f$YPKBiU@SI{;8Da-ldNH^~MP=Ge6 zy!Abwr57v5$&}s;xz}@4l2UWjHAhTi7cLha$sb?X4m1HijdHx20)2N5o*PsPVe3TG zwHyRFN#8~+cYZG}Me9M+r@S(0gWh9VK*s}wWS_nxOXTxhPpOifIcQiYXrB;?JqX+^ z+J9T|#vpyN4XtJ?_fb_}L8a|Je+8 za3IN#BeUM}g%=JGsK1U3dEffiW{gv9bb{i_zd?1~xa}=tCk!#|E=A@@QoH(A|1biIJvE5a7=x5_3=-Q$ z{Q3#RavUm-n(Y-4$M!LtMy=i?8R_;??oJaKTpyGSc$O zQnz4-4OP;ljLa+G z9VnUD%9dm^wu}bOJpIUo0aA&yB>LA9b}yTuU){rt&m^Uc%}TqMLq=`&{DG4)Z?JB4 zeYJil?kqA+?=ueFta+c&%)iP@FrYej|u)=NE|Ae3m{Z1`n8Kj**@O!6B1gkrFn!TasvBB z{O9hcfy0|E_76QK#Oy5H>JKdCulHj4&i|?kPIi$U6UVq8`=@q@CE1+rXw6XcQ>wpf zjlI1VNE&-8(>VZJ8J!X?LtKcryl;e)82Ch*-{9Xb7PxW9^oKq#34WcwCAerW2-}?lgvi=mQUv7W}Hfe}|slNp1@!M`!G-@8Kt)4QItnl(Z z*?{VqV#Y~0HDK(!Ax5^9tQbm81Oa@pQOC@1){;=P5GFPv_th3|k57n&P$G`1x$ltwN_AmE?$3TKWN{oYV!YT9`4$r?*p|)oUgVWDm8S zzOi(+@k(*OF4=S3FV^3bXS?ihX4Fdq3l$Bj&h1c3Vr^I0$90;`Q#0YKEpi^7)fyqp zI%NEOlyt1@JB!N-)XT4E$eeGPd105~)Deo(8+X)rx0_=t3Av*GucJApl52P@-|8Ch zY%+PqdI(kOJwjj_>^SWMX=~(jR$^Co2St-9A!h_gbM7!+&onC8EL(>tsR=YtExnbHD4S)CQZtW5DCTPAU88<3gR}?6TJ_-oSGi z&XVzxiB4FarQxL>vo)t(wJ30K5|{%3t)}HYT+`w`fa$f=4`r*8A-2p~NG=p}1zn!W z#Px35R0@=(nG-HQn>Py3=L3b3kpKx6OBxXt%kWA*VhOxfQc*`ezK2z4fVkIaje6;xQC5B4>B(*~za`DN`i z)2OTG=~>P~vfNRlD>;SDhNn}&3Psa=b+=8UI9pd#3UJG8Nc^fyZGp>4w{28aUv^hm z4Ldb{AJo-5_i5OaFhgE{@4?4v3kQ`7nr#W{ybBC-^67{qI|_K5^9v`-2Fy11_PIBw zyI%_y!Ad-6Oh@AIq-;gpw|G6rB_z8XhtDr9`JIEk5D${d-1R$eQUpf8-s&Xa@kHn` zCgoz&gj49B*^PyFR#^amoDT&jsD;e3%Z&($5hMW22Z)%MIDUa*8pchB93PG1AFp4G z!Hj%%vtAh3!MgT`k2`8wGlFhpi$ZgJ<5ETxA@4>QwQCmVAX_!?J zL0VFS+>D-iCSw=m+od+ZW}f4n(M5+lURHlDDrw6V1NQo)snK;Q`E& zn>XT>5I%6$$Lwws`E6BIWQL-!mH@1bdYkKA)BoBIfP%ile|vma2GjFEF^jMcdHM12 zM0n&a5zVjr%S=rVkKc=@8q?igfuHgWS!#EMaGQ?+;RA68@I4C<_{RI@#=fb%|6+?Z z*1o5BH&*$4Tfcsthi{>YvT%HOK7>{F%EKopdV(m3v)hrU{6n%O`X<4-yOU&_O`TCy zIB&w|9ZqrZL57cfAfPq3+ho6Q!6ScD}=Ju@VRJ>Y$ zM-M$(C+5y`R7K-@FiQ>xDE8_pjsLB>>}~#Xn%yL?B(VWpqw7_^wUcLX)AI=o4arI2 z4DT{I-*d*2S}jr@l3Fa3V=t0&;A5iP#_Yqg0Oa<+8g@H{Syd*UgOyWkH9|6}0z@ym zmfvT}3^DImC>QlkP${zL6I#=~Q1O`x`wreNQ{{%uSxBWY-N*NZRDMZ*f*zlICKQzI zug}4`J9Gj<4$MDA2}4AN8Qs+(Qf?Wiv`h#Eypp=#j?_nV8@H*rzW&YlJh#e0rp%gQ8cZorBozn8 zHrNCEo}pgGUiJ5{er%{$dLt8W)M-O4ICdjY(Ki8_RGz%x%Msaa*FshG$`xDbcd1+3 zAUK@iY`fED#|DZzJsuFjT)U)2ExHwk_iknK{*qa|I$%faz_w`j2=fW4{VE8o3`AXI zW~;m?cV_#Y`nXMnuj$(r)45W+zMxvByC3dM{f)G|YX*SZ!aR-kWy(u#@^14ri)a{> zS8oN3@+KL;sDeSW?dlLbDu;~NvCaY`h>}jt5zAHnP7x9ke?*O&$H1&R?WpWG&JUu%q_9+~ zD}R$ALPK21AVgYk#Y5^AbG+e%5)xzDR1^XXdhJ+xej<5*BDeC(6rAB{F0KO?@EaDz zkRB(*W|6Gc%c#190Q6MVh)YMAIngGocN9BXm15FNm|J{jnma|eQKfUExF;kyWAc0_LiAlA31)IL%GfC)>EB2c+?r6#77O^-YS{qJ+olFK z!IMY?GU8OMP+h1bk4pYbRQUNlx4<+@(@4Fb10CG%U9~V9Z=j)K-n7Hhae|AP!*Z<` z^$r?d;~8(__LB_+7Ba`c-(Wh#v4Le*4`}_@wH){z46SsLF!M z&NCr#@Pqhy)07#$^UgIUx(E1N{>fihkhuhwMfv#1>`((HQYnOvxMlwgiJX)DPxHi2 zvudshiK6FE^ThvZ&cFZZRefebF6UboB^w_K+7!o2*rE9OU=9+ggEgr)y03FD@%`{x zJF?hQGEzrTb0!oC7j@Ek9b!oA2vV=7pBljHhtAE(qW8b=sCzVw-@Amw@2YrV${w#5 zyPml}T=OV!7K#kK-LDtZyj@MS&>9T6o%@0r&(H5n5!>>CwubA8_HxAu|CC<$2R7vg z1~wr{5{5kx30t+{|4wf)Qlf%Nz83iazVbcimm^a{!BBFaAM6Gz1O&m} zmVqPV5|^UBf6ED)RFW3z-Qq`lOZC`^666*6xg>Ewi+T;yY$gdOUr=D%BRonw-=7p^ ziHbJM7rhy0CJKy4f6GxG*5NO}92Gy;yt_cb5SsSjq^1D*LIwuASbIJ7m>Q_kiNYp} z5r8=C=ZtlS28(};14O-mOTcA-0scJJ@oRkHnYPMAxl4nn7MfHR>3Rk6#AYFb;F{=j zl5V>%OK3P~PzmXU9#85aEcoLF8qM^=nw1(a*CbnLE}RW^svAUxZThBaeytvPX)ykZ zu-JED0F>>cq&ZVHlfJmqmwwp2jt$7I?_4hi|C9ZX*9PS@)&7qvV_`0O_hiW#LbB?{=Kk+a}%lWW00UC{Ep zR1|SB`dG>tSWXY*73RlE-h$2`YuxgeC5bP2lqQOMGh-z=rHJqWLHPm>+))*ZHZ$vH zss^F$71ok%zS=C-A(0My0I38@!!pF+3tI}z^11ln#rE}Hiex<))X4R@vvT9S(bX9i z8X0iWGo9B;XSJEnjny1-UA7q+TKRHvslVqdZSmh>3Tb->?!rzPI9HETfi^ui1y!uP zmIn&GjUwrJ3=r}H-ykag8rZ@S>mtE4W|FF+`MVK%rz+qrixEZLiSrjW zXzfCgii2Dc0|)nB0M%ugFmOG;p?|}n<4trPtz_Y(Xmha>H~X<~+0Gl3PUV9F$pnuF z3qL1FA-Y4&K_}scz$I-$?<9&N=|dj(UdtO_uH=s7LRL{{I2RUA1I&^u)n;N-Ezptb z_j%tQL`UkOyFdoyn|ig$Cv6zZ0GEYqvSCuS?=IWLLcxNV z`uA#2!D*mnI5`seHf=~eYHZYd_<{#Z^*{Fjex*;o=jP1)W71qBsD49A;2FF9mj(20 z11{|J7x4GVM0PU=G(xk#RRR8nbpfV&t$t)x3=EOcQ#%rgeho+F#5OeMxW7wd*|gyp z8&CL;&ok#M8-QOxX>uJF0L>+-=1Ed)s0W%?`61_}H!-(&;n^RS3VOktAZ$MKdr01+ z4K67XZ$fto?VrD6dw@S$a_7a`vMVAmWh^)|tI`l-Nach5GyTbOU?K1+jb+@>ju$6> zfifwy#+GnwMT|Meh@ISR%NJNP+*DfyyhgB87=fS@J75I~(Z_H@*dwrcp*9v|T|h|N zGOHP_!!$LmS4n9+D$?3&chgwc6qpVDN^DmrqzJzh^ZFhm5vOD&JaarCWZTdX?d`3BW~DEjUO)qb&W!PXr^?OO*LYg3!p$9CqUdk!yxeaPxm zmBUjTHKqQv6U5&!kuS-k>LjB?C)PV?qm&&-Boni|DcZxgvBbdzd#J49vJkUh z04B)}{7IrfEi^E5J|c3ZyRe!ihPZM^qB4r6BW!a6BCeeh%r9C(XuPVM$78^JHS3(9 zDy5_x<7dn(fPzK-nlO)-MaWPu{#Ks4%y{+f9ZRENVT`I8w(8K%>L;LBr$gt^d9L_kJ(8^=Vth!qF*?l|bzyk(kyr{9+C0`P3K0BR z%&-FUZrqfbls71NcwDWC?Ay)>q!+!-cp3-93ejbffmX4sq?YGX?>+GA%r_%;bP=2& zb<(t8h@0IOXLf&GAa6{pVK=I8MJ+UnXG5c_k)+2=6!h_2q-7oR>7jGuulefq8+2d8 zZN2mH2=cEJccCrg2*)*=ynjAGIRKXWU(2KLXO%kJ-np~y(f7Ykt?A7j6&lw2T)HZtw;Mn>NF@ zlDy00wEEqht~$F+WM;Ph7yg( z@EQA$40p5X(_#v%GXXaou4f(J-}SFO-@>1gqqY`aV(FUV*t9jn!JfOyNe?Q7 z2!GK%ol@+J1OG`d6WqWuM@`cLPGvpzA7Q`dH` zul)V-p`)@T$lDpp;lauX)&OHm7{L9Qermjyh~a4$aT{zrMgk%tJ>m*a#$e4cX$SkZ z!Pkzt#J$7?_Y8bN!n^p#q-#<~|H|8Z6p~1!!836B6R%&M%_FR3PG~KnDbjK?piZA0 ziaTa6hvp2TbB@x9c-QxLJOQnNqSWOyN|mC5%xl{xxjk(@2F&-$LI7bfI&4~~9xWJ@ zg8T~6aund6dUELA_dSL!6^~}g_(LGpV-yKSv%o(TP&5q{qJq_A7 z)F>3ratDn~15F^;m;tMe?+vJAJVHtl^5h^^Mp4E-_)~iAWqTG0L><6pQ^T$LQ(zHt zNUFb8BEG}v5&Zhtv-&DqXzmc~BwmGCkt5fm4ML2ScBx{i6sAS^4l;4^sh;x5W4t8ine>#_yIamek2MVg%{HeW4tqk z?AI!M*7`I@%Wd^lL|i#x;Q%{LYco(HxllK9mo(2uT2OR(W=pH(c{;!17>F?i|Ls@P3 z;B+^BT^tacw=-&fE#N5GjRuRlbdoa3t&eMh8>=2yr!r3&jz96wUQoKWK=d~YE+hK z7B%EpWkB!Wz~m56@fdbxRO!kiGjk<~vrBr9j60*YBE&FZV#{X-yI&6s*60u8mpntb zqRA;!$BN9A-<1)$+rHv$&3)7A0`+wUDD7RG0%%9I;eMa*)7vk+3omZ&_PsQ7gy8sT zg*TC9`_tQH=*Sa#au82Sda7AZ+gS?K3#ZOjL4YF0+v~Pz+z?2L&s$9W>p9$sG%hp} zaSr2+Uok>ytDKJIB#}20={)B1?|6FTS|=Lr@N197-w&*Z-E zH9+1Z{Fv;BgUzO$02+ssCO9u*Tv${*z5n|i`D~|e>x_s@C>A|?hzdcFxV?>ueF3>h z0#Q~O0YjXKf!sfQ z(@rRAywjzUzs%b4RNO54Fmp^mfb?_kcLl8QXEc}NP zco_nUNj#0)#5^&oc(;-`G-_?E)q7RGN2Z&%Sv>#7g8?|)NuP)up2U*EGM|d&SYF7_ zuXigQ8w+hfKnN2A-#COxe6Op_B6+%b1Ca&GoX!CW_ej+8ZtDAo;O0pe86b4)7bUO; zAbg!GowpzT<|+H1i)uUxJ1?5tyawkG93TDBxqQD`XR|NmqgSST93(_lKGViJaqF+^ z`OWu+Lw#=|q9Hn7QUYy*)M+WsFWLUno~d)RSzC>*vGGKLcAyypVFlq+?@5HY#DC2O zyFAdnJ!(PC)+g=d1;9qUJb>$S+=9DH?W%1RJ)-unqWgAGMB-*WCEgi9^k>Kw+TPjv z>7s@Oj9+tj*YgGM$2%?gDO#$ID=y!+adJIcc1T*Dsm$pAs!Jbx%g=oq3Ij=8e@~J# zjVIn&$b8=M(%KrJ1IpP*N4Bw`gdN{TFf}}BBV0f!rCB4~16$|9^8h;u3CK)ec?#!c zzkGozVs|_t_mB`6h_x}735P9&Ya2Gz=6P*_+GZIwnxtl?C@b`Jjs?EMF=GPEm29f~v3brQ1^C1Sfq*cFe<7nCgyT7|VI z0FB$a34<496Wp-cWCOTHxmB1~rgrQMYSn?63DzSV#N8j;!q|61}E z*+mcLyr>3EPkydnBI6-e(bZiMOsj^_-p*PHm)04IZ*G^@J0`B2%L7>zs~&N&;qw)*e_vzRhDM^gn6|n$5j{S}rPS1S7TtO(KH|sv`Xx1m^%?*U1hY8I zH{d>7FkHrydERv}x2Kw-*j*{wPY(vk%`tjAB9e4)3|4;ADiUM>zUxqx2&WHBCCm95 z{6diFb8np5%ZunrL0(U^=>vI5O+-6y2)E;H!Lov9@~G!waa+2F`>dKcKnxUe29bBW zBLYZL@YJ4jKPi|g1xeGbL<^F_MIyVP8|*@)#~6;_+jYg@34?FWuR?+QN5{mwyod0! znA0d?&)YTMQbtmLpv41zeW`!7Xb`i4ibj%r90Oq`#7sbxOF079!UW?;rkMD%d~dp% zNDBpoK)EwaJC2V^2_3#cpM`7-!ThpxJvPS-6^w{Q8hK{CVYo5E?<++lc#tTY(_cLAQoT_VmSp4VD=8F499& z4}478X}g+%Q;0YGCwhMlJ6l21HjaE2U0lU=30y@_DO-kW!7~=v2fW%S(0vL%X zer$Oi51vxn{~YZU)Wq15mmW+#}%n$}hF$FI~2B&tg1`4$G~2nIc|pmpG!EmuzpbEBz$M zl17eD?nv?ETnhls%RwiJ5xX~?1|BY!e`|-swY>L%jK%%`Rj&uLin{;@lzrt=O!8$_UH-#n_c6#T0A!nP^Vq}Lct zH@ro`F6#4JOQl}hf0^X-W^Cj}HOP*JSWKu37C)5bs)z!hhEUeKNZW$}i}=G?PFyAx zYKy^G{p(iJqSWdYp{ePA&-QSsHJ zeIHguOjtC49*sZi^#R@&-cSmnR34?fTV5#Y)Y$@M@~I+`chlW7IgDzKIQ2w{BxPN^ z_I3ryDo*fBjnJMyI{0x;bpR*6aw^b8hlp*92)ETj4NpA|VSxlv`5DwA+>{~TTB`(t z(saSrGy1pf`)_Uh@N}||x%VfKHT3YblUKzL9KuWhe7M%560U2T7E`}yStwY(sUjQ5 zRiMAJ7no%@9U*XEDbloFld&wkdZ~`^mqlo(3c95eQLFeF5l`yDAM*;lKQxc<`($VI zSYix+%2eSC54G&7X%P2qqN7o(rd;;for%qsACVfyocB{QguF4j;cu;{Jj3+5euUoOuz47+S2Tfg+ z?7iV($_Fw$)UVlo(lT7|L(vbb=jD5!DZDg4|4*Iers4Xo%c?L?h7vp ze6apW`a*wz_6Gpyu`n}?mp?r`KVY!(BdYy~2FpKyj{gG-KVbS_GV=qlTt6Mq65uCk z&Z-W6W<^^5{)p&5qQ-w9_5*1ys{d-PeNu5L1DX8( z54PSpIFO)w7mjT^+1Pfnv2EM7HE}k!lZ|cL8*^iBoNR1kzr64LRo#2Px_?a7sp>ON zPoML2_0-HkS(K;LKv>D%PM;SShB`lpUiqGwa{`07vwb`RIfIzt;x=_Y#v`!CsYy>ptBIt>?a4HTw6r)TppyoeC|l#4lk*?@NgN z5;VVrXF#!9jb6|D>mGLvS}X&PFG2l3!hcXOHEFSGJ5(B+a=*0CYSiev`(=g)W&d-7 zCf_=s}(&M>W!2$>{ zH01rS@`96k-%q&TNDMG~DKa=NFDTP`gw}n)XHVut%QWv=>$U|Nft8-8lwjkC8C44j ziQfX+rCcCS?3s`1<$4|$$l2i-v|2OSXNIXw3#dO2T5dLCLM#QVP0KU24x>v^| z52^H3wl1}_ z^o(0+db!B;?qr{okCu-_!zTyF$I8~9wREa80r#(ZWLvKR+=OFI_SNYez_y`?(t;&Jl%{uRdNMCU1Mp+x)aY zCN%pJhU=K^hNBI~)-`dOPZt^8`L323^x?kYgin;%wf15NxcyndX}wGlaNAqSXt~4` zbUNxW?RyUbRIlW_z;U>r@C8Ft3HqV-IsHKBv_GMeIdgAI9FDg-hh@J(3@LRcbm-4A z8LH(%XSF@ zaYkX@bOK;BbjlVXQ-GED$DpIx1dWhZE3vln2d|o-8{~3E+vT#L8|*Sp+hx4b4j#^3L`sKe>f&m#Ohi_}ryr;=eTm~=3Ow9z%}@6)nF~FaGdZj0 zrF=lGYe>TSye-2@D_P=ct{akTMM?3~x+o53?OJ5si*CO!B8HlYDr7ojURN0DZB#ZhPn22)o_m;bVmi7zZk zR;mYM&TN8W%k;+Jq+^@N4uGTAyJUCwDT$70q;ic#FJ~q?oSP6+!ob9+23095v?5Gn z1t@YFVGs*7EV5LTg)phLmLjS3#jB_EM{DSXgzHi>A++qup1DVO5yfqd(!8q@($;{{ zfmT-3R&mH-TbPv!YW>lUDMRTldXb^-)XZN7c4c6D*Jtc0HNr+mHDJaT#G|{kGQW!R zU$lpY3nHq7B}ca~D9AiR3XAz~vKiUfPN->3@ii>wrPUt)W3KY4??vVGcB4vb#S7&z zU^43Yjp)j?aH*MHNU7R^V5#!Fk-)^N!i}?<2VT^2KI@%E0c%ryQKNL367HA9q9*=p zF^d^IHH#x=u+M>Wl%<^+Fj2aR1qgqjOtb8H!WQ77&PE{X_)ZEZu%<`!mj`FFg zqH?==Wwl3Dqg|IWVKZF zQYdIbLF|N9&Ek?*%>oqeYff@nQ@-I*%c3ctbQ%qrFJ)x-iSeY^|WJ;xwIaQ(F7m_C-Z4(G2t*Aic z|HEXg2dd>U()kZv`zPz}g)cl3p_T_GWg4A#E|1XP$`vGXF z4Y_bX{-fRce`z;r&b?O)AI=Re<%=FVM4_d23Fm#C_ivC>Q-joWrubHgzF?fNu>yZ>N_SN$It7<8s=C6d}}($rc56I0q>IQ$b6SQ6T6ia}wM zxfbSiZi&ho7PZbASR+N%cHaj7%Q3qDa7-)ng--j6VqXu!PFEC2t5pBB2|A;`HbLCi zCIEs{8RQySTUGsXQgg!#IjX0+)vKq@_x}gO3-Jr1WePBiH~4U;9(~S}yl3O*M|A1o0S9m2X^`Ai#|#SFb)UE z50lfUjyLorQ{BuiKIYnbLgF1p;E=W%pBfWrH}c|WWM4h{w(L|BF+#u55nx$o0}mEY z7b>Oxt5;2xx0Qz*W?g@N&@&YkUk*6TkZxF1wT$r)5G!Dhb827yi9%U?4s|NF+;$7- zO4ekeiJx9^ZVz}H_M=1VjuGjE?&3K))#uEM&SB>{l4~}!KUll1OhAis7xg%CY50mS z%DfTve!IT>hKgH5Efy-eWlX-t4{RVH6!~3IR6WpY&x(WCbOYS;U3k+VX}2cX$nI$# z+r1(Se({#i4c&PFTc32V?GkqjhV6#;0FGu=QNQJj{^yN@@c&-=mm|_ z2!QNVp7+iP-u+`p`4oon;K}Rm<@@q+o#WM2a)bq`!{?UyG(<-38^>!cpp}5h;1-+> z{-EjE_4-`OA(b6(m$}mmJH695e-U}dvUV|HcqWRt=7{0@_(tIGe7A(nC9R)sX#AF) z&Gg6brh&Q%Ntr`%)<>=-Wa0DUidezj<80Aw0xPViLcvMOvDfeEQom_e?B}$Hd0Mxl zK!Cby>SeN^Ld3o4M+U_S!1G-Vl3el0eLkl%lP@Ke%~Lrl9y)n@`Ov%i-~R2Rv%7AN zqWR6LUhA8B6OWjyOBS|0`qhu2k^sT>{o~VXP1EMrcD@qDVv39=lS0~q|0waxa57LV2HDoecax~Z(k{tbyj9`4eGl}Tt2G1TPE!t zojHC&{qI6?Uq$7bib4Hg{~uN5z`tt7+5G>D%DukyeXg>AfN=JKfS~^`h5x&zoQ>-i zp|>WP;tjBU#}}%+hHDLV&AFy+28wA8%G3S)%~2|=c*~4(Ms8IcO|VPdUqSJS+*m&z zDC^x$I(aLvt*WZ@?Cra`aCGP0zUrR6tE`T4Tlaod?#93J7*tl5|Eiublc0f2%XWF3 zETzUhRW=KWA6Gep?D6#2x|^5hUMtzWJU38{cPOrH8>wEj2Hx7B>hDAC>DI(2`fRCo zd4E?;yE;@IY}1&nx?H9$4V|manJ<0}42?hVUVM+ai^!GM(P&=f`Od>H(8Y^&apCu- ziyz-qZP>sMPbX58P69mBU1$m?sm-cp33~iJwl+{ZJhBMX7if$@3Pw!j8Ha9S*(xib z7l(Uo!=JTkz()dp!pm*F{@_^2yY7l>;yhOOun-FEYsmj<>~hJ;!GJcXg=9n4_b0fYoS;tLm8A0@h^U+@%1KAb0mI{b0tJi2`C>hKc4bb=V!)$m4dKpm@A@RB8StN zj;aQqga(f<6E`?)uV#i!x?!glqg8EyQO~WHGK;X*ZZzi1%Z6mVD;qb6@0E}KCP!Z- z(}JW{BbxTNTi0hrbx8}lG()@)oUPg`CG9wmm7}daFK*NrN;PD-++hvSPZ|*XEuv7V zBj(JT8<#iyc@kF$DQ`45y*vn_!I&FcMY3Mj@V*tME@MQ|ND;=S&waMLJjf}1Fi znJCFh@?<7eY9{h?jHQEe_8=iA0sK}dZVdB9Y-Wt-MT#-$^)xbdW0lP)HB5~GCr>W2 z!{(=7fO3u{hS1*Y?an!HdhmMie*XDAvvil_>JBlg;PED7Z}w@H3cQ@)q=K>jgpL=0svnCs#Cj3KQq#|L zCw2N&UlJm-BLb(Jrg7o{=_F*l;o}q>d<7ySv+76G872wok5KsX^?3l|5RDr9Nz&R` zDXa^|QF|~yn*PS9n6}hhFjs|R=EDN4+FEah2*f+oSD7mso|mx9AMY}B>xJv)J$vJY z%hQ!r@u=^IM>sj~e3X5430V)Le)C?4B~qt>A;`K)fi>Rr;NY3(-n8b`{kKdVfT$`v zz5WFmC6iulU6Sk=PWmXHv1XFR!nzGJ61QnuC(v&K*_BK@~G z(7j(6D6Ovab*l2JtGE4e<2zQ3`xeZl`xRG~56e^Wx|^oyAY+5 zkkCIK)5DDB!cMe6^$V0DCbeXLgHohR)_^Dw>@r!kZ>yB?h2@ zfr!_mwVe=VQZPl79czwmRuZ{bZ3M>J3wx?DfTP5LN@dLzsRObh!+U5cMmF)0O`aU{v|fQ}h^1!j}j z3XCdJ4`U+VIJ(MK-@?hRCsFCBs_D^@32Woy*(z0? zb>sY?Ovd0|HhaDejo69kB^g67z!j$>e>vuAr3yf=sKpsc|@KdM; z4vz-}0X`a>^+1$#rC=grh~*>t6b5^5Y(`a^;z*x@q+Z@KutrdLrSgOAd76(~(LksE z^Jbaj9%%XC#Mf>5|a#i)N~ih`DV|y) z5t-))=vT$RUP}gHD%ZhYK>u(JEuV^j(J2=akzD05jECG9>y}klE6LTVq@~8LPs&pN zDJrwfvZK$Y$qb*$@61Prc9a6oke(UaKFDe|I^iTq0nm7m8k;5i zkuLXSuM^DI$L^YQ`2}Cb?lk)sO!5=^hZL3L(aqr{Hz=gSGqPqS3QiG}Ir2{c+yVvr zP@g|y<;cHGY6u|ewCKj`9!tN~Tuezrs20JJXC-?=CBOWoFF+B`&(E}OP7r0yD6!Y$ zhB=o&ELz4F)S0VAM1nRu2AIHbw6QJ#PstxFsoV>5ESYfp+3jSh+ML?Y)T){ZkV~gc zmXIJc5(jlem(+jD#F1#borPExNT#^<-N?XC>ITzj-~yQtbRiyepGNN^AdL3DDMkxp z&l&STIak4-c3Q(Nbq&Du_k!plzlx0+BskYNpah3s1!3Jme|wh*0<38htb?pjCniF@ z$A@L8>3m_tgB`@&Ebz4Qxs!g+3OT<@2i9W--lN8xkQE1>2fkV`@^$Ow{v-$z8stN0 z#TjtCi8So{{<-k`g?OeDGzOKt7hyplXIqbuI8UG0&?5x47JInkBxhkiWSe-B0A~{2 z@!|FT=z2v@S?!RmA8^NY{J4MLDVJCn@s1jI7cP^>GihaaGu)mmDA-vLQ;3n1i?rAH zJ>IGpRA$ksRk&}_s5SYO-mbNFd~mpP{R&ZP|D#K)U9dyoGhfxT%0)=bg6-k_Iez5{ zJAJe-g5DFQ82v5W$;HFX#mmV*z}tLj0;)Q1k3vZ38E;|>3y6U;%;KA`@CUtfp^7#z zkshDNNKn_^>*9iU`D@NDL`0ft!r|maF?xZvRVAvVIF$OEG)BF>TS$u-M0|lplUV}9 zH}_YNbUABHr--$~!Mx}vp@n>>D(sDXY1R_2#*4x7ArEJje$`JTC_N8R8$~vql!Cb` z0?Lj_Q^JwbQ=*|*f$DP+C1QgQ0h0O!1TJlL+ z4N+(gUQBus;5C1vz&J8r%TS-yB9OgC4fUgk@vueiW2)>ASSQZ~!))@*HZ*N!=n}wf zKh4gT4PCt+tQvP{rsJ&rw+X$(c8`zxoQOq*CL@Y(1&|S=3^jXwgctr|tLd@u$2q9# zil%>#lUK#}@SO7~;&ck^BrCGu;`37va_Wsw%~D!kid25@Kfr56Am4k_JG9}z^Tg_T zO*C{Lv4Kfx!qdxOmVAXsn1n5-7L52Q5hlPm z93z$74)9Atp^IOr{W3>plp;aKU3yjnz3oR7IJBE%nWJ3mVlB8@{VdC62g2dO{rm(OsDFwp8wM#x_62P_={v<05gGpGvBYILY?XBxVTdLf=t|QbogN%jCOVDE)EOB$A#TPX(fbM8Anu^9ASPSB$u5a?XyOq z0nXR6*Yvr8%_;#V@2~XXljEP%J||^PTmOdgJP3{D321-9QN4lUeXOE~0hdg6)HE@R z5|#mpD+FtBU*E{*LkgvKO~5Alv#`sVm%Nh6TxUp^=VOFZT0440p)F48dT}@PJnG!C<5-fjEvf7#C`b{|R zytfVCtsd&vpBq63g{@EdTD^vn>AeG+?q?LYe7!J9--Y;>)J9Yb-84UW-d0THc}Fzm zs9z|oc_`~=%khcz$Uq7Q288V8xX9w2pnpSVC3voh7(%o2vdI*HtsmcsrXowW15|Kb zv&&OH*VYvTPrIc62#N2R{zey3KU|+0R0@OcL7A9}*Y1dh@pC*1y@smyKpBeSg#RH)SVQOcD z$qZiPz9c@ujleI3Jof$$*y#^Ec5JY$QJCo`kZD=&=Sp=tp4dmx^DQB%$a_N^jvb<% zmX2|=j5$Zj;;Pzal+QO#D>kJTXkBwcc5l^Ka1xmtprC5=Pbby}zJIkVFq|Z7Q=#L+#Tto1-+c{q%p$5l6XQkzNi4g ze%;d#OCY!Q&1N`&AqI7QWD)Q8B1vcAnRgb+VL-i zGwl2mtL#K?>z-;t}Bzdijgt2LrocapnT@8odq7M z7WQl1dF-=}IyWOV(7+8{b_#A~cO~L!q zJd0bbrUw@)Zz!W;>{$q6t@U&X$wj+*ZW&Z>#XDL`szGqu!0bi~#1}>Bn?RFbNXrOt zXPm<{ct?m-&-P{3gpz#I$bQaDpf+POi2Vdn(={cTe|)$#kUFWGWXUc2>sQ;d0PS?; zn}CRPXd>MT^sh_Img7|lG#msoB@nC3=2A^9xgHbg274^m5=Un4f`;(yIwGxnQ{nrZ z{JoK&5|yuacnqn5!kg|s&NW)yhN2^C171dBVisjhub8*)0y*HaAK2{6DF#*qhV(aA zX6sUdT8n^j+0uI{5Lf=e=+`AVnRJfpbX>g2FN2(JppDg==RV6964JpFqY-QVH>Vh> z6Io8#3&BMBH&ka5AVOniq>lJke(FHSO3~NSX@cO{MSgH0ux-uQMq;!E+(W z_MU8k4uSfKSZGG{@Mf9WQs)x_LEddxgbs-0_%=hHzggoY1&}=z{1eu{qr-todAy1V zj9-e(08*FF+Rgpn9g}OwbMjlP`ask`j+AMf{1B`5FmV40K<>L)+Uk&(Id}QZcbaP4 zUVLn6Fnt4K zwsy~2;=-BtqlipUYEtjZJ7YA~fZNy7QuxTOw5_ zl+|&hxu{ACw#g^}hMxf4%#lH}RsiP$@BLiso!AOr<59Wu5%O%7XUNO0{rBLvGr|hD z@x;^8+dyvg%A0xnOzUKR z;Wb;hh0WLjP5C}jzMU_qK zYZwtM}Nsg&$HDrp@96tdOSPpi>sT|aSI{o9{wl7rK0=*`1AFulx6FTt2 z)~8hk_FrI-Y>h2`m8vRjPpsm&#P9`=6r1Vv#TCibcS#j`hF9vwXi%il^AAgwYUQ&o#vL?rTx)&0cSGwOf<>iAqi7t`{`eu_fP)9# zlO^QOg0ol1;+6A0OB!YA4w)9{iAK(g1nUr5kTp%LLVZfmVlL|?!qnDztHz)6R<}AO zi5rZ6>DuT2A`6uK2I+ojFCaD6vo`B>tz|bMZ;AOgsuRX8WAo`bfDCZ{`+fNiEqExP z$K>9k_($o@NJif}g)2p(LkZ4WA5i*JNsALD#iVvIFm6-kYY!aN$Sx9He9qC87rCdff+N1 z-YEk9VGH!g1&lM9bi5g{^=Cp#i6XLf10v90+Mr!*DsaQt77b=qO-bl13B=Oo?Ip~y zTnhUs-%AL98I27PnijIwsRfVyBbPT(f5dG|P0iaB7JVM}@PrLK$7qQ`9;+)GcGa9v z4n_pI_#>&ly80?3UlhDTy;FmeTkJ4ZmCFB6d932B>By!h6GSFLP;HDRjj0=PX6qu@ zpFT)Ff_4$(7)K9Ts~PAf2B4*AJAm^jsAzJrKmH9*xYbgff9q0;ItTyoZ$D_gJv;Q105ea@EdBQK>Fo#{7`j#iW?NpSEe6 zADfaT0Y~Q$Bj(1HEOIT_Gsx&CR!zk?hRjHpoQD|lxNTl51y%V>1BSB$SC^AG#9J^u z!zmtv)~r^=DvAOLaBE-<6_vqTX{NqU+3v`)KSyQ4p^R?CYG&{i+}2fgsT0u36C%MV zoP$&wx1frS(eyHVU`zbl8v}KpEJJLG@R-*=$u_n9c?xgiS{|?GWM7Oc)Fga^@O=aO zT97rM=o?lZMYR8uxz9hSa9UxA+=_?a^=!}?(SPh3p+(fDkjEb zL|@EFft(MO_kbhpsHWvKlqRQ0+s$Hl`D**e5BKtROWM5wRp61A8oq*=u{0EK6cTDO z!)y3b;%!6-$?S$~f6wX~7rPf(itf*z$^$se+~Pcl*?5`H1d8fR=cH~2nyhumDQ_io zSPVu8Mn)Hf)$D+G?J;HgnAL5e2)~y}nwFN96Z0EJhH2i8v^l7Ce1V|!lGV3GZ6Q1Z ztiSDl)TJZKB!OGjR9!mjsx|wDNEEK9@2uQu`oreR19HMBa5GeHH5x3o!UwSY&ghGxG@ED6-i|yd99K??MyK^^8Z`OG$u;~v2V)YA2UE(1g`iY^X!k{rUbM%-9ydQC zi_1w^&EGwQi;bVWFN4H1Wj|UcCgjJI4wzMF-HzM)=G49^A798>dyZj{61k}o4(VJR zo{w9AH3hJcc0()D87?jBP-6asuGn7Yj$toHEiGEQ@1Z39ZHA;3#jT9NHGq!XF;5#) zhpP@A@eO|$88GqykYpgw#@?;kK3|6NGMNIxPPzXG&@CckSd+gl9>Ztdg+}EQd4wd} zYPL3t({|L$!gipPuu2BQ;m)vE$k+73anByxQRZ**PW?U+9cG#x`*K+1n@XF<^u~dvpb+J4EvhcQRp3ER_jx zxZgNP_)MjqI+d7%=t?=rVoD%~C=nuWm&sa}-KWW1)(c-T@K@c+Jo$QBhe66BIv?-d zzF(Z2oHdu3&Jqaa{%R>B_FGpqs#Uo_j3y{Gt?TTprhpL894&Ris%agN@tR@8a zP7tJ}vwk^gf{sPo^f-wG|?^~yjYEl8=oZpzC-uP;??{fA0 zxjQiCu$0ZZCWK+a(pqvyvu$gOPuUERH$-i0asT+imHRr z=KiqCns>py=`~KeCdrxLVaD3LBfvU#{&Ig-7pvmX2=QDc``V$-dc~}&e`QN^E!R&D zqrMZOr^W2sJjTh^`54Qg({Da6q!FmK4R$Q1TdA5hh*dnJ6!1CfI`$!2W>*AaP&THs z_f z<=lh6ooajty}iq^A%V4D6pFiIn1vv)HND`nMeZzf34cXp>4 z2~ViF6^&1Cqb`7iy)=MBnYQeJDEv^~Sl{UeJt81CG|yWO73SuxK8!1fG%vmx#U`pT z=s0ldAp*1IKiVgo(nC9?bOJcZ@Lo9<<^S4)71JMxkUM?YW?6Y7_kSN8fiAXB*gR@` z7}6(vo5MI(>Az2XeVKZWMQI7(l($jB$E4ZesQqU$>@|VcJW1ZMIBDh+UpC=Ufib%) z89xcbHB%FaEG+U;haq8PV;r1Tq_~PrOiou-njC1uz{5+xI#&3O-wpf|1bK1=JObSK z^K?T&P1gq1pT1T4oGW|PxbncnskWCyV1opXH&0M&*_{TFKW*75o$ql7O)!E!OrF_m zU)f7x&bJ$+Av8z%zsky~I&R%?`xGd6frUD*qd#m$nDxs)VS*0a{>`q=X%v@P)z#YqPrzvRMH5`JzBAkQL zwAp`Ta1o96<|(txXG~^0xyzThJETg2lkjNb*tZQ#zeq)I5G%|lG(S*v)H$`~Kw*o1 zpz_oI8kLsSR6h!tZ+X!|(6+`MnK#V?t%s_rs~k?`Vnpj@YXpLS+c?$BV>bM7jnQ}Q zjys%CD&@p6!{^smwQry=^yWxM^|5!1aI*6&AJdX!KGM*R+kFg{L?s; zA93H9+pTW@c068{_Ad1(ayQ{5R9e(k>9rZyQRWh3%`l z6Y)sMC}GY8SuMKdQ2J=fz6C>~UZ7(_3iw`=18^ob3*$#O1Ig_jks9!ZSW}?+{?oYF zHEc(2Dl^yU8bB27^Sqys!y;oX1Mh4A3 z(*%NbOf*m)6e)XYf%TO)*Iwo;4Zs?!-yFF|w+e}iIh~6DucrfwAi0fGgk4ge5w$LO zWLy=O{cY*Uo68rUOo-j?!{$H8-%y_OgD6gudLhZxJmB@r=yrp}b!Ab)f8l%=A@B&xob$;{oQKLmFv$R)$tJov&0m#(agUcSDT_Y(M)KU#8j<@cN@_R8ncz z?7Vgkn~pTl!REAvbtqxZJoGm3et}JdU0}pATxmcY7%rWNkoCvIX1r+TXw7LI?3?O) zDUBhTu-87!cB}=tgB~IgNe*H+MR)K~ZlVX_Dx{gf(Dx?z7N%}``nt8{Q+k5@7eyVP{?0f%)E*#EIRvgA>wD3rFE&!*A?LbU}3Nf54Q zETsdcE7fn=Z9-DKIX^ODuP~TB?p|sQpu%&2;9^24-{Xqz>0uCUq0cvoi%h@lf}z>y zKejC)T_&TPkM|k_j!hgc%M<8w0Fyx4Ed%R15p+#}Nm{%?oFn+wS73cO)iv+cA%_4J zbXrL>h}oXApqrhc+xp9Ni(Kfgkj+j(AXRD;fX?0ec$7dTwTB-W7joJY zWBV~P?@Agz=?_L_%_%Bcob18X&g7Ytv`=ezNL~`!Z))u9aCt+AJER5p-$mZcor-vH z8rRoB@&^kCWRDsQl{kj|BbplMuw@15> zhUG|}F(I$JAu#}x)GW|)(oWhAGdJzJTd}KC;}!UNXFm;<k@M1*Je1ZZ7djjQ_BK0U^JJ9javO#HG3H`I#GD;d1GKSxO;S{M}^jW_Y zXu(O++qZnO?i4u+%tY~th97kL#yG@f)1a-S>Vl` z&-QYqPGX77Fq~ta`%I$OL>#h#Yv^)hW@*1Y8rcF!v=J{m94;wCvOF4ij|J;oZTFAPf!JFK2W6L zPk5h%Bu~&M;j5&Qi^Aq}%+Gqqes(FI`7ZOsxs%T#b%<2h>Wqc&T;V>VN8xB%&D>3WDsd&p9Q zo=fzQke>i_&PnLbUEy|)QjUZ0HsgSGE)Nq0cOSx4Eo?a4SunJD@7So(dAqrK2MQKm z(jUB!vR26szW->~6Aw`O>v9n^?VVrNUio#thd$l3}K+z%X2DylQadiBJjP2l6`zj!IV4+^AnyG{twd?HTg6KcvB$Mhyb&dpZW34HHkKou*TWL| zj?4|nIa|b9tQ$tMs0t${y?F?SFaKTFLnr!`i25efz8|zbMJG@lYA@u@N-zCDKuZKR z5xC9In;ouY@E%2Sk#+&SYB`6<%+Ma9*F%+hg?jwD`y~EX6T>Upb`-3rUy{8(O83D1 zF&ATq0`GB-=2wlKB3@3xtVtW7yS8g{<<$cu)grAcU#ZG$lk770D-sGR7@2w784|mG zgg8Ual15jGz#%9~JADpyvnPToaI4LQxxIL8uPDk39*-|$sR5H#}Vc`YwJJXyWkbT9-$1a=vMZrDS(nD&^*A5)V5`BWha+Xg;BVh;1T{U{Py6iY%X! zY0;KXq~&AOhC&3Jw=ZD+@-Yr)Vri*Z6&eU>VmQK^{s9;_jMduZZ9gnJ0kOd2Ya|J z`YT2&Yg*j(q(VRZvvo8M%bDH^IefCUGOj;!STh<)~Q5dejqB<>B(K!>poRD9p?0)Sm>{VNE>am5$MLK#od@dw8xCZ-ERe=49DR; z3DONAG===>`I!XFgAs#XZ#79jNn?BWs5xS~mtKRpLZe*(Bk52(Akn!l4TbT137zqOY=JFK7Zw^-0o0!*A#AR3NOSc_l zpVms-vm2cYLSn|nGue)Rpi-Ua6G2%1tl48W{yl^41(R5`|yE9vu{OeuN3dT13ow>JV?mi}D}wHfkEIyux;M7NmpV>(w5Sipk~I z7HVADxkA#cU9bvuuMpq{Cpc8oVgw z{>@Z8(O9v>y%Z`Ce(^CTuVl@NMT~+rKIaE4Jc=x=*j%o}NE{iDq+g|->rlaX5WLi# zGS!1L63^2U|JA1e?(JEqG^=~8C?y?cETVjtkFA|nS4^nY1z;$CObse}NIvES`A{prpbg(s5l3j@lTRiVWQ8%4Vaof5Qd@ zNN7W!U!Z}~PRyjg%}iLTxzkX5R5qf?nV+O-svokIK(7elcZ`6{F_tYyq#u`+O)z7r zRzo{>fwog4`rS-dTaqMHp-kUOsu_AcR7DF`SeY`rb^x^ei#Wt^2;>^-{ZxNH?+;F2 zKQ+^jxv)vOaUihqhtvlj>&Glr)ep|wG)X;uryAq=Qw6D*R1b~nX>#rG5<4W*kFh1k ziZaR=JS4<^>fwGbaL|6}3mw$D*GqqsvR$~5v91ReS+t2B7}Uze zm2GycUziDY=C`%nCs%#m_0#pB-uOP8^Q1hzyo0)(BmZ9aED*dixLIPNOaHKA&I6dT zB|?1XL@IUo)6kN&;D})7_*96!O@Oz+$CpdY~5?f$()q4R)$%w_5nC&G=8Wnhi6!FO=YowonGDa`_!&E))?YOl&Rd9zkm>Yn?NM_1@KGnUTjAG{g;^`qk5qS(A8HWZYyp=6 z>Cf(|`2RS!Mu64vt*Aqb$fjSeLkjrF1^gp2KA1AhVHK>u)Q&K9{b1U->~4TN3$dO* z;Vhwx2Et`!Mjn(c=31+M6lOH zuAe{UVxf%n07=(kM#k6EJrfPz6tpG8+D;+8uSz-X%%8Pv7P+2;x$JfVsgdw5lDr;! zDY2stv>qWkG1hz3pzTErZl-|z^!@9BIz<06x1$te@m2wyLnG#l8UC&&l1V4Mn--kS z@#AJgKyRqg`HyAn79!*Nu-2TxzwEH&4R!hhhT6zVs*<^sRek}mdgw}m~(A|?d zR?(RWYqo1*Ma*o#KnON%PFgx5Rp=6hJX2-DR~5XQ-pb;@*07chUAF6E`5R-#F-AUA z&WG_WbB)g2>@+#J0GEkQ(nh#4luRE@W^Mw(Ds#pBvWPJN=g1UAnk4+Y;ob?zDdJm0 z6{_BZuM=Nme&?kz(ux^ytwV^=B&4}d)SPrB%EvH+dct}PlP}#4<8XKBl8?zw&+@+C z(Jj+bWT~~y+#!Je`Kl1v#Bi~W5(%BiV7N9CrjZz}MVmQ$sN$saEjca6lBFbDoS?WscY%MM><%sY;KwiWGv3myhTqhAa`_lHa|gE|ZWp(SX%|=7bd~6; z+-Z-n#wYo&_3)c$@HgR$hP`~Wwy2q=jBcvj?ySjuM7CKgtOM&H-8SR)?q$LXjbFQ7 zsvZH!0wwt;Mhh~)@E$c^;i%T^Ko>zR+EU#W}fhQ10PM7fxDj-Jv7y0^3WL1ScZ}%y+lTa=I-f*RUrEj!vu%5c( z+kC&+IkkWxBsfS~IK&+;V4yI3&U*Ib z!htY@9KSJ8Pe@@y^a=aFGgHW$oR#1pq#3>4KtNQrKtZtnSGI|bv%9&AHv=0ZJ0lCD zy|n|Qjcd2Qx97$P>c76L2fiQWRbWR^Xt|}(1gT(5Q)2L1rZ+YU*40auSjzw0FTdwgc|pndcD{=Bhon?<+F z+}SbBVEKBxr(*ct@mEFNo2VX&M^+2IONmQ*JA1dw97}6Z`YIv=pG^ZU$Xcb^4(@=jj@;_W zaMK8J!E>#6{Kc|>Q4_?HdSc(kWRj<^rFbHCZyA(6xuC;5Db3SfkEeC(*!FH&=U_JD z3FN4_ihYz_ok+_@!oOFdQcCSo?Z#`U%C5?HvCq&Ybse~c0@cLk(p^r*$4V<+bj#lr zC#BSdb>3(a>CzAKiJCl19}j>^iZTbSb@(vF&w7}9NQc`$=qFBJ>AxB@k=G_Qsf*m5`W|*mtu$u@M*FEz zc&dx#cR)~M_*%DeA^T13HD@ewD1JuE1oQB&`8`FGtZvUrmSl&whL}#dpfO3@_~e>J zFI2g;T;CZr$HBYp(8&Q~{NsCZ zQN;Hc9WCS$eyX}wAZCYL?e8C*;7@fhrq#oiq@G9DqqW{N(SyWI;Zk6`VApNtz**t7 zkX;CmM3ujwmMqpVGf~RPq%Dj&!0OvP-Ia1Phsnv&gr26!kbcy7XQ51+Z3XSu*Ht{f zHdUG#L+@{r;xh5Mq|<3X5PylqLKtFlE4OOINT%9~B%WXO_3?T{U${b4flVE+ZMMv0 z;|5L7{Ob|&$wK{r!Xu_6sx&~ojGAoCyDBbebFA)V)p2pjIIpA%mT6H|yDCV2^pB8d z6+fEgV2nJ;k3I8BIV|K2lcCSc6dnb~{rx;TiL(9u?4+7ZdEv6zEPtHNsQPTNs|R*f z>bkpta^b^`C~l*^lhf=M)P*$SO^i?cOV~M>;1GzZ#ym=aSDG2JU^1PBXRlM23~ix^0TA)$3||ae9&;r>K5V`Dg)o0_=ep2#1dYPBS~aXda*) z;uF=!Xn)7R69a@DNq$ls=V(eH#5AHNb6_b{LR&<(2tv|8Q_j%z{sO9hm+E-MP-CK7 zHt8Z9i*mDG)1h+@RNX^7zP`V?$XASBm)TUljnXP9V(eVZcPA+jimLEFkw?kiS4^Dk zNp@oIQ%wspob0^>6~6afa(Q;QR%k{dFx9ZfhkqXk^gS)QxS_ZaVttO7xy59y)ymB zKURr196tqW<<5fh$t*+mIyz#ATt^$UC+_6WSMukD{CU|=PbY)`_;3YpDuA|rz;W5; z#DACt{E-;Bd@a7v4Bcn?;cLV9eG~22I@Cc6WEp@glQ=5@-9lXmRDU$?KNxStcab;0 zO6J$eqArsarWg-#d7sVe7_9vgqc8ah2U@n41x~X(Tiw@^$$zG{=J>Gi#FvpbJwLyDc65GTou3!y=h^xB za^{Iwk>^jsbNn-^k>TCJte(C-KRQ6(_-GakJ@J>wJ3pU#!!L*4Q{NlD8hQarsQ2m9 zw@>Q9KWCFDkSX}|E8o`mmVSRV3!VnQs?WXlhjWjg!D=jZ6!lgJCz2Y)W- z`~FX_I;h)U{9uaeXSFx{%g{rfdW3xE)U^0C0!XT@tmc=%Wc^dCd-Zc9{?$!<5{N%H>*M)* zf_#679$vi+8{dARW*1*izj*Qbb$|0K#$vy}dGUwlSNW-)KAq9$7jNEt*Zhvz4a7IE z&;IoC>-L8yXzsIDU$i{O0s3E>80W zgh4|t5QM!SpFbZT)gOQS?DxMBBD3iu;Gpv_K*HS`wQ#Bi@))q6|dy zt(rRTMl@q(l@O`Uuc+srMGstUsAQ(sT0w--ON8s$WH#d++D_5@lum2Ykv|On&E`M2OTP=kAh}HE`aicMmOu`!?l~4Eg-(b!}5`mvQ6=wlU z-epB7xrzP`o;aD)6cJ65Mw{t)#(91pAqzhZAUa4$$f~5ZpWMG%fUFG~9f?Qj0wq$D zD5>8V+gGw*$bYkBDa5j;s{MUht{A`XtA{$YnLeB8?;6B1;dZh73Nq|cu6Wl`{ z+($FCv1sNz1%54&m)2~@m-n`2a$+>pRpK#5KbA4cH>MTs&KdFRw^-I&h_}m%`h^r; z2rCjE?A~*7<>paE&?UwZFW{d{T@OA~E=@_emo7YTk$+`W8rHN(fQ*o+EscNNwo2s z(|=wA(k|aK4lt@WtpAxL4FDO88%5A$0#5(DwB~C8$~P;FKPPz z`sEibvKEKUp%rt83g^XQwH}u^41Ub!bsY3S(H z%f9_!h=B@JfYsafbg++{l1N5S8`+}Sf~c56>)X^-i{#zkKQqT)n;N!V$|lo{6r0;^ z5I#Dp?1M%n$KTVv)<7lt@1)5IFg3IO1{$6XpEv)_DUr(4o{5phAzUN&cYnU4gS6F6 z$28#%LiHH(`7RKlR)_h6LQ?X5|6UV{~cqoL6!uYnhqB}ThBvY!l0 z&EjWjb#`W9MQup~zokEtYkxDytuuwpw6*YA7m;N+KInvuypwnjXzf?t@OtR|>iNlG z{QN`;-fZ##3D|$NcT<8&&)?1CHTs)Gze4VO&18et0C3u&OvHmse(s5+yU^aWS>0Qd zof)*gzke%N#v_UREV9CQ5{{pf47a~h-4k0?U8|}{>~HGfy-|}ObbohU>EfS}9DiSJ z_|{)8RCJkO!wNl{+-l2J}@yD?6w^%KmcKGSK&E(S;3JQm&}g+n|7H+N6C*(t)ZeusC*9xR^;;%i!;L;Ia>t znwh32_R%jO|Ju=Y5<>iDf9F>eB}sDUNUD^qp^Ds22K2=t5C%ubMkWKDbT#SJlep^O z743qfV>wBKoqv5g7*xAH89EYnSmS-apY=nNe$`H{g2~PXG1_n(V0ss9CHFER9*8st zJNqHo_-sPMJ4Kt#){uBZ;@=xsu#j0wC397~a-nbIa5MSF-=M$&{h&if;ji~IDm}*f zeE7T?V-~w=O#9eZvKUEbL?Bc%mba%Ed9Lf?5t&Y-UwnFB2ivlizh24 zTd@y%uzzV?Ta=?zEH=TQ6TNCZ0%`T-=!A7br*b|3H2Qp^iP6}ne=^@QDfgV0s$rQz ztBS`9EBw(A$hM4hW>Wh4gPbfkr)cF2CQ<=AIMaNDhAnG(IVnO%^g)YtHfb@^*bcC} z>N1#eJqt*elX<#KA#*Gwmle{K^7xYy1Z~pevwt8>@LGBfF2bgojS?MSlh^lHoVz{F zDfTv5k<9bUEd58a*n^=cyiZD$459N-Rc^8r6vgYj=oNQ?jZk=nH$CtTpf0HZaW6My zNE~BYU<0i1CoGyT$h*cUenGGB2mfUtR+`0v+(NQgKs5A7I{~YvEzAN(_D<4yt|_Cf zK7TgwxrrDsNj)5uJo#^uyZ!-qo*IBXml=IY-647aB=~#zGp1Mhp=+baejIH3kZJVW zOgv_pCYoV82c`p$lVc<_ME2P*%F#WeM*K)N{cBfED0}*6f)<(2%o{WGvJRjDvyFv+ z!J4VFn?kS{~+x4AZujeVd=L>R%980H1D|=cC_Ql zfS!zFy}h3ANFM5BvQ$s=O{s?8DErS{nIM2c(x)sU3Ln_Qrca^;@y_G~Ri%FAW@n`eFBi27maI zsp+F&qI#!yG+vKj>qwoAhCcmFsLS;D8)SpH_8AP&$f|}alVv^F*XXx^%6=@%{&t4$ z|E`lE#1F>UZgHzEmHM{M0L`Ne&5nk(Ka6+|_<)jiOlT*DJfI2XjG9v(0vrETX3yw= zt0Kry=%|q|nf4{~!CVWieLH~FlYf3jK5i(&A3KLo>~Uk6lj5${N1$y1&u8n4Fioe!p-P1^R^iUch^eOyQe*m!+YPs0$TMfPVpi{A<~G z6Yob1Qb^np6xM{-J()DA2ziFcpzRxAIhAiTlIqfu!sOAydYj9(d8)pZZYTswqFSjcS$+gu4ESKwkP zr|Ujqp2AGO)g~_-R<5iVhz)I^4Jp#@+%^<5k8^sH8}ygA~tMC3}>7bi@fh9 z@$T)n3~A(Xn<7U2n`nip%2w(1nwb!eS5>%bN*Ar~4gCRk>L+buDKi-*CZj!qh5=+$ zYLP%8ro6;Sb2KqV1IPhf%p{2t^Kj@VC0mJIPD=&TG6JGIC2_!!C^(T{2dRW<(z;;< z+%Z04`eIr3M1OPYRDojDchv$BUz;HAR!m1+gU_jJVqom)R&iffadz2g#*X;Uiu_}$ zU>C$vd4g96Tp<*Clrj@t7rsUQzyeBQ&$*^HVRCn~3h4uNp#4pzt0MrFv;_r@ceojJ z#;5dFf&y`Ut{AIr{S_ElDa{lt)X{zq-3N*{pT|;iYk!|shOm!6`NQDZAy=_8Aj_({SKRyDXYT(jC?HKamh$u;CgA zKBcq>VWFvOr2_tTu6}=PzA}RXD_PN|~W=<(L zPhel>Z+kLJQ@sChnH+;Y4Kz*?A_=e?Np~}UxJ{x*HQ4$Aypmn;siT)&O(Xdh>oTov zZUY0r)~>_s0X;L0)1SLy&ncoqP@d#RouiBUr+*o|IIudu=kG@AHS#giifh>hgJ8rIwAj)>=?w$!9j7XD=Qk6U4CF?+3|2#oK(HF z&@p159IQ>`!2FNmp@30pb^Icvn=sCTKFDY>wxP(-+V1o~B zcYpX$ZZ2-pit-l;h@Ni#E6#7s9d8oyc{i_j_-Y4OLBQB>!0^0d%-eA_971Wq5eKC3 z*QhUSP4HwXg`uA|Y^7R=l|mk1l~$m0#gaFa#Zd&nbFfB^N<^WbNqZHPD+qeAVaBL= zHr4eq&@_=OVNa_^89t(Y*Q`eBX``o+{eOL3zfmDdR^t8rLn4#ti6qezL!u`w65YOY z1_4*&)guTbkd5LjJJ#M9U}n|M-n-{DlrM33jAS549XtG0i|^ZXlBw0z?1k^ z1~NWLH6`Bf6_$4G(OGc^ak$FZbB=^5`fgi%llUf(hlN=zp(K-!1c1)Q?vM`Pm4At< z!Sh}+jZ17eOpdu8VwL0`&Uv0I`BDZ>kP6#{)5KqhD-vG76WSgLc4I1)CpA9^FL+J|B~klv+IhWwmuYV z5CjTq560Gv8FL29XaF*V0uWtI=6@k3drs^!_YfQOa&k`~J6VL-$G0301ZN&W4&*ME z{!*-E&Ra0s5el!x1}}b!s^tfm@pau-{1{-G^P;S$8$1#3u~bhn&N3o4q?iQkr+mO) zOc3f@_tv+T^`&whmv}=($M<}qiZ!!U0ba(f#Bb_J9gYnM$DzB!735%I?XX zzvcncQ9M~|MGxB2qPB1iXqc!Tz~v0hhuX;ouMKPTvj#p+C4^Y8&T*J%t;UoD$xJVh z=p?Ue=M%(sv?WOCP1^#vc?a2?ZUJ>f(XRS4VIIE0A|`>(H;<88&O7L*P`+p_Xc>0Xl%5z5{p$IBaGH<8c8gD0i}} zY32qfRjaZC67ZZY_45zF6HZqNkI4bhlV4EU`y0=N0@AS9 z(I^|eWGar1nPDvq3*(7Zj|JhKkGT%a+;4&COKC`yu6oy#54Rg}hjmq$c1jrLD`#B? zSR<*_({S{lUx+pmX_ z{5O8iV0xwv27k)vb{Z>#_tMX)@!Zn#UPxqI1Mi4%V*DQ9!+9-V9grJlfw&RJ}|WUDIY?8&ENb@osj99GOX{v5tBdqsV1;b#?SRfOXX z4%)VD3x6r>Ig)FXB>6i%xNRE!VdO^3P%@SP=sfR8#i^e(M2ZRJ45WH;1${#z|L|7^yyMw6DNbu>Lqz%z^9WSs4wPr{GmA?Y4 z1A^f5>=$fd1yG^@0J=9jdch&TTtlMhnmfcUUVqw*NLlZ)^U}5G8Y3YTC}J<+h-g@J z?sD52KVl#Ih^I0gYW)@V+t>d>8q-5#{R@C|Lr~ZfQ&E08GckioXjQYA^(sj(ud4c8 zx`57Cv?)n>to3_(Rf4POk$3@dKSNUu@4o&04;dGW<_<9)cO)%aeclfq#~SpygYITk zqkq<~I|jMG|9-iknF!-FNfpW^+a3F^)h8{vHr@J8^wRH&q_6=_?owTbj2|r*F|(?ExlCI6cq6+lFx=Hy>@N;^a~2xRAk}k#8=9K4hhot7MlTy z{D>rD?|Ee&kt)p>jdkpmN?;?+H?5PTp?{n+_LbSO)}QdC1u`~>mHl+3K@0^r;;z{< zsq{0gbX7+@_$?bh@Aa-IX6Qzbe7^0mpUA+VnX1_}xBE&>wZyy)L~kXAFR0a@Y!}o_ z;b6)y5u515pT-COm?1FF;rY??ki(f(@U_Yi%Is+{ZN8r$PM*VY=>&Pl&ufZ;WPf}0 zgRcU;Xx7HhC^jPnG{l!a$@XB-h`~#CvCwW>QHU+QgICT}kxpEg%ogqCpi>nN(zbf7 zmNm|!rQK6#O)Vk+dnPHnKf)? zLE~ybRo`4R0gPiAJF$2D2R6U#b$>Z;L={f?jqpxqLX%)ekY&`SXU;SrH$(+`wgL}d zNgm#Ac0Z-4wp5WL&jh(DS^ri8BnNYAlJkQ3;7Y70ZG2KpT(sn8fFy0F*BE>MKz>l+ zrJ0-@jA;t7o(!{@>D?ep2w`WH?>v18waK#z7v+WB~CEUO`?f9&a_qIe6O(MV;JNt~F^<+Ba9 zg~X3eBKY1pIP_?GvYE3@1U4c=1N0AMRsd4*7<}ZDFkYvgRm2ax-#FW%WT#o zmGp2IMXorTQpOo-gzl7bZ9c4u~de8MwcRiP|Xm3dSv>@vD! zgYZ|u4YkGYBNO%DSNJ1`A}tXMmIXILcWWXyjn3Lm-J}zs6P@cNt|k@E#*IKzI~D9X zeqyFT(kvjiUwA_xXHU&hL6Ms$>X#wWzb4#hg7o^b9*{1M+=^fg(SJX${WA%spGhE< zG?wahtZB#e`R!X3XOFH;X#V3U6PHHYD&FReys;q!ebDY(9wKIBvN_(Qda^1LHBt@W|{mmZm6#K--?`z2_Dm znZ)PiS{bakwtjp$KGAGy6JO^LLSd4)?Ov;jNl$cVb&k`72nFduC=(T0);J|0T%eE% z<1Y2!%n0vQ<|T;hCh#L4SdD{23OI}4C#~h{A7o=>F8-m74SyE$nhWC}uJRKU*?H37 zyRU<2w?g!Xt#=Kq3F$#hfa3=_!d{b$uCJ(e?s9RWax7B&xv!^hFdkQj9GKj^>s#91 zoyG8J%lp6EhTQ!Pds*bAvG2;;F%DLBV^MpDu%r52*&k`){bP39!8h5``-|!J8bwR_ z(z`CN{YGFpx_=HKaJXgGB56F_U*2V|k=Y>XZsvZgepNspsFO}g=)a($Rj@vSbPAxu z)U?S)l0JQc%Scn!8kxY{lEW;0CX`Q3ntd+R&V?YUy^6%+Y4?Z?&{pjqOAit>jNQ<| zIu)xc48K5&L<%PdcO%2sd4%R~5q-7Fll*-YrvV>`^I9<_cVDjP$txRXs zI>buJ?IbZlj-6T?6gbiLvQ91x^?G!QiY_p6$L~}1GNHR3qRtu=6A~fvEm0cHDX&`p zI~KLJgMYBoEf;hOQ&;W%7F$flu~O@HwEYKTh@PA0J}J5Cx){-Y45U6smB$-RHBbpm zd$du4Tdh184p%1{GJ?UFU5-~;4N4@cs3c7Z_(SZiud`2MC{Q`#x-JF|Xh1HC*akOw zL27?aP?vllD=yX|{lDk2GU=glOj%!KWF$kzw0}`IhPlxyN^1bS#~WKuZEH4fkabc> z*rwPsWXXvn7~txP_}&WDfZal*hsH+$KmLLu^6lk0LUnAex5HM*Xp24LkiD{nMbBne za}RQc_o}+?0h^W8wn1sUKgF2TwGr9#92{(Q+Za$k4T1_yZLGx$kIdpK`^_CQPoSl% zSAQbFo}6*S(1-PerB^d|&yo)C71q4hyDK7%7{Eo=$IYuEz10n3HdRdig3IIj=^TK+ zH0;M;zMDg-F(=x17)w9104314SyHaMZ_vw;ZnS-;k}cQY-+a3@F}&Q1YhF@_|Dwkn z7*zGt17n{q?>#zC^Gotu_Jo6jd{6E&FMrsP3dfkKhhq8amc-D6CR-H{e%RCbANzD9 zbVUnK)=G37DkYrz?^Ze%svfG8#G)RBk70TNR~>;ruKE^%>`qqi;`_1>e&tzU)`A^?!6+ zP6^4*{Ps0ed_z|MeO||W-Z>|uc?3_OA;&L95}M?3iC49ra*Ft!uOoy31L69iM_}&F zWMdg$Ph*?h;$N(nl+oMPwwPX*K66fS=-4eeu=15Ywd>8H=Z2dt>^zI4=VmVEE&cRl zBb9e_>6O*WvDZRwJLlX{$65jP^^6Rz1GLV-P~EJ`|5lTP#gfP8yKRWR>p_Jr;(6pXLcW6?~TGh|0X{3qkX!BwFxI5zj$~ZhubTy$KGT zJNT-ngSmn;>YMJ`aXP6;L1bGY?vqJO*}U&I679%F*!z?;aBtF^B;P>PMRo7qo-|@o z3d3ugdDj(;uM#X?0g@s7QY4Kj7f`7;bIN6|!Y}#bv-U@MzuBuGXzma781BBMbD|Fi zG9Q#4UkGMkPLD?sN=3eq1%G}%o*l;$kwhE_RlqQTA6&eWLZ5)He{_({P)V`?DFL~s^57||{&mWDbcz?;i(bM>(-RPv# z2Et+;9AvG{%dFyd%ku7%h_)5+M!7oV2*7o2v#-Pe?3+1983Xb`N=8Y1-aw*6e;HL- zYlfWP4J%N=BI7So{Xxx`+KtT8;#I=}J|ST$T@qKn%D``Cc=!%(Wl8-V&0K<$zm|bn zYBp*|^x}YVFq8nh(0@ydJ3eLMdQRb0%;V@?T;OO#(i7vvLk>LcEcymw)6$-4iG4&z zH#j8YOE`i`RKpQ^uBCkWOy_JwS83oH^TG17!Ir1x(+2F=Gw>5+JyVg|TXBkCe*L9# zH2eYqh$;TvCqQlec~{6m-*sPoc{Qti>AMX0Q(9?Cm?|P@)_=f=?vPcQVKyw;YOYQk z3(o$R7>*?Bxv}SbX%8?tI6%cF=k-WcKt_$`1xzX_Ioal`k-x4#ZS0COF})^!+zgtB z6v>eZkE~fJY(GK&nauE-wm@C;Y%zn^`~sHltR1Hg@2}^$hHEuxEVOD7r_~vuz7JbD zow2)Ya_f9=7k{d8->Ae{z5WSgI*=|cbwV6#CdKO|>2`?a2M75*Dr8=AT9VR_D?Qfk z>y7(0sQ{gR@}g0;>t;c_Z%C9v-z0#+!^Z$GJ4AphN7$Wl6h@_WG9DG!rsxm`8sn@j zI6J4Hr}|UiSb2kPSs8jNKrl8Lc!gvnh&=lbAfAJp@_*nEcVrzNNGpNBl|KEo!5NZ$ z!JpP^V_#sh{n^wPe^Z;r<3Z@>mDTvIoh zM%Vgj__q-*790+@{0oKH->gDQ1v%=Im`~l>nsHP}7WD^t`Lm;dYn4D;ekH4yvQp~= zFWh`W*?%8bBye4(mzx39`kVFu~+&p3c)fcD0vpXy5y zG_R;ko7YD-)?Gq7yIM3F4qGGN-)|+z9)Rnd&v8byN7Jpou6IadO_pq;RV>`Y(C1f7 zm8w%%O(%%RD$p{fevMpbu^U@EcyM1y#0cNe=6@}|Ovtj#`Onmsh{hYF6k!kqKv*^8 zl|k27TV0X@qK9s7-umuLT#p)8&tk#e19plURCTt#23wYR;o8Xf!(rZz9caGYAIz;h zroUyMg};f<)$bpb{lbGZUU+)Gt);T=5L_u5YXZJ46Vb32#iOTG?4-(ZBmM<6hJ#uo~cw3bf3l(<^L*=X{z}>*;4MrYUUB=mg;y2IO|yE|O3V@rYSsEZwbe(Y z)%CZ|8zhQu8P%9ACpV-_HhYB1w)V;Aihp!caTmXTtk!SawSK>|+HV`xZiA*NQ?rpw zXfRdb@TFFl-LGJI@0g!9A%=HvQ)81xpEKsjrR`SksJ#c@$}hrGlSq(JdJGhi_VI8? z{FvDRY6xIRFHAezxE2aBrJ41geXV!;I3TlX=>^GmEFXV6Dtp>>!)d1Kp(9R_xPOqj zT9=BJ;6_&nj1IO)7J6vlgh0)QbzoST8T!?}|erwJg0c6BsmOIzA zBbU1eT$>t9RA?>%K*$z45U@#;Zhxl;;8gp^TB*so=%ahf6iE@49!m1r-*YzN{z}e! z3~Og2{*EN}a?6lEu%JY0E0XrACWfwCHW2Di$yt3Rj9So{{p*n&j7AkXMoCT`yhmAS zLIMbuSHGJX=X)2^W28@lVqaTRUtGYXIgYl*aPUgb+@$iA8J5=N)LoxTz5$a z+P=@Mn<~INR3&}z)@xRxw)q@aZWrG^R*!~j3GHxNHe$YA^x9aY4WWQK4n*9rt14X? z+Q9)fTiW1)b1@}~=(x}DihnShNYr^8;7Rq)4cwF|Y(U|gMB+lLyb(phJE4Basnr_; zVd=z>B1c3c3aX4OsN!p44`vf0p%zbZ{c?ayckwb!&Sv}puXS)J?|dUio#w^G`p(`U z!WFonjL24*8M|N3p6aW3=>pfO_aXhNgi{CBbl`EwXK2}z^I$S zqp*)rg+DecaJRnCRev5JBpq;aQ$?1Yb96>YUnia&VV@L}uCy6Kg-zkR(c|4EQD>XS z!;A=uSFB*;u(Cd9khkoUBa27z8}I05hK6%QGaJfLra?p+`r(E)P$I2}O>@%ub`IMC zG(#DKhs|O13{AsZjMC7+aD!O4&Mv*CiBUtJ4_8p|j1ThQDS!U@IraMx`A49~PNE}r zZ6-JS!ze$VKSjlz!paMA>-Ynp$$4EzXMte5J*0RYKMV$~~)SZ<3N2DqS!94neDHZB|Td zRS~pybQz__uSKB1oWq7tTozE@7y078aYIVn@~$hjjH0QTs{_9S-ouoGo{N)7wUkewQk&)rkMRIN;|<*zQN zURBwxIoj5Vlnm$?Of^Sk;(OK?_|)`LYB?z(&lMGaQop^)7n@b`q&`1HT|dOPah=R> z;-H)t>3_OHzDw+*T&gFgu#?c25Z%C+FNJhF`jvkA75P-!>xV#qQBX8lh+RnKclC(v zhAZx;&&X5ZGzwG%$TBxSHnO7Ok6KyrQ^@T2?J4uUa#0MYN7#q> zZ|2~STla2RBUp$&{HD#xu4|+@oJ|n+)zrOUXn)d8GBp5&wPb(N?QLR=K@*l#XYR$J zok(X+s1_4YgtsT(LMpt=Relk#NWJEH1v0wM0@HA1r(tn||E13YM_^hIDm~&mSK68n@M(Is*CKa96y>upw+fF7?F$Zp z2wO-lo0o015Hir>t-MCbU#P=8Cn2bW(x4mbeiL01+O2EMng(kTJAT6(I#;hpSJlnx zn<9~hxu|ovp%ZqF&qq3J@f?Rgx@bcwAAj2c#*KUS=?Zq&_Sp?oiqlQS8Dg&8j1Z2l zlH$w>-M31Jg>!-8db&{YBtQT(j0$ry-bhWjBxjmeQBF1Eki_=YQ*2KxiQ_z0Fx^wk z7Gg%{PMh&`PHkT4)zXm@J)){~Yg=PZcLO4SCuB8RKijdu{++I(dtqD9e_tD8eSeJM zE@^$&m(~gWWHNPcaidANX<^^4KPS#mJ}O zaoeZk<=XI)6P-k;c;? zjb0~a4-d>$ER9I{Idd7QKs9~LF7{i#T$0{5?z2^7cf3ndW5)bLs%H6q%|F8-NtSyA zGYM(WN%)`zMJXmLYlJ_0iCL7(lTUm;Ir*Ke&ztFM z7d>W37?tUZ)W+w!XF~OZ1fnF_&YB&k*di0RxV!)xtQu!Gfiq-}Ug1{hMT%}Vx+ci1 zl!x_FVgn?pYjfopQ&=5^+mu!j>p7|9)cIPb&0)B(MrZWZ>b5Zu6MyDeyK;)h`bQMH zl|33!cZx%!4FK1W=%}>lK~hiQL(6Fn@)=Fdv?&>iSg2w->S<=sxFx?41y0Pc`Z=an zr+~fAP1Fjo%R9XkQ|h$H^bW~dJA@d^TiN*%>*~B=Ihh=|RZY@Y`xbqeClt&rTPwvk z_$M;(ochG9A9Y*ra(}zU^%79*Q4&h|^5k}v$e!0$Bd?^5ZMry>t=r;U_L@sp`udf7G=xLNrNOwL9ZRehwW0URo>rMs6p=8 z{e5>o1rGq)3V;87AkdrQRFVbj~qN1^GK&4k&P$hJBDQXp9$@($&htdq`j9I3H;U zrd|T93eKnZ1d%FFpLzp%-K$L}s5U^7lgpD^rUf+!Re!fs;;{XF{iKz8b1*hB`lCn1 zfLlSAvw+A>?FyTqOI-;EcV4>|m-x6aAB`%JcjDGqCM`(7y$RHUs^U~~NKnCm3NSuy z8=-6nCT(s{HnJx{(j&0f=(dkYu@Y4SNz3$(Xs`_{RV268*4#06c4!1ox5k9Zv}ANI zfotA|P=EVX=>UjtjX;(m;(FE5hjTQP4cS4PFY5Bu(gHSL2oXt<;GF1;mukdy%}dot zbKK@>$HuyU8t;^D2C?F}EIQ6oN_Gy!T{5hur9(oz25GS-eH?^>q&*;!tLj)fLPu{z zX6wn3rkk5)PKAaz>8-gO6=q{7D9lH0ijzUJzkiLb>hz)qASVkwfO(@!Optb$^!0u`A_rAw<6I-3jvpqiiwG}jR1HnOHndmtzXS9Trp zty$8Q+^n0E>b<43$K{47_>#$Nf|CZR9e+IZTbs7o0y^&>k_>1>8=AHaPsspOhKn)v z8zU;-0BJ_DQuhkezHi&`LyLF60KK&gUw4j=3cr6y?~1h2&ytyvcSVrH>0SS2oY%$< z62{YX45*o8vpR(l!Ur-#r;DUM*<2{IUBMt6J}|x5J#Ki_>9zoK>$SS5S%=oFMt@uZ z3BfC&WN1i!nc!7z4`ROrU7D`=i~mh4XyRyVlndQ941V~jjmSgOwo}*qJ?4Y@BEkMn zHkq7Q7t1wzU=VepupNW}O&G#XZ>5Z6KUoH6%ArlP>QX9Qt9-39Y!Ujs$!r-!6?Usq zfwgF9+~`#*tMMdqV2w(#cdB9@dw({O3irg{CDK$0X{pYZZ{$gnEbw{JN~&vvGrJ$v z(UrLKHn~p1*Rln<`W6fj4-C2Wn^hs06ng@MI*lr7lM0D>w^=x#ExM$$G~a{JnF5M0 zpjl31{UpD`0|nkHH|vOI)V|jBOVou9zw>2IyZ4D}GK<-88?ZsjLiJg6XMdcgs!JiM zbW7;$%J4e9+3+w)%GM+p!j2(W7c`D{!NGMR=nR*iZ7cixT>;frw5XDw(r|lRk2l?F zNVjcf>EG;xDgV=Z(^pi1NKbFKCJa-&s{WYV(^+%D$3n?R5^eXDIiPU3Ujlyf&HPGz zuZoo_g?Pm9D5oc0jBmhtbAO>5q^m4wH~Fi6_!~X2y{7tln9XFunm3eo1r<6TcLP4Kkoe3@uKCSG*M2%BRvrl^(e8DnSTpY-gSOBZPhj2 zuPFX8{(cnx8h@XKN1-aX-MT_^0u&piOWvjD@eh<7;$pQa)N76ta@+DuZgC-WuT-13 zFl5V1ZF(ubYLW<#<&_70Lqt-!QN>s>MVJG<7(f9= zPBVS|iW;HHkZNJuF(=NmjtI^y%nP+JI(;jkQ~coUVoU{=%kYtCph*|iBgIaQCPf7_DlC6Ju`@J;m~QBN3Z2O-3{xh|EHYm0R|%?Hd4Ez+O?akk+%AQ3!lgjH zp*06%xn*sj32T;J*UBtg?ro*MlVI>#g%{kVLTfq~V7@EIvgrplUz!X^LtI~w4tG^2 zHy0qYu8KOnxvZH4t`WoSx+Yv+gCdG=0{;<QDsdDv-24} zKSz!yu3OgEP5rQp<3?wh7Z7#djU!k&1{KI!AfF7ltz*#n9$V9RbW=iIJHgAoNgOsz z?IB-c?Ohi#4!uKfsAylOrth4Z*qQ54n)zC52ygOv)BUB!LqcQKd$-$JAf2(?-IIy8HHO$tRU7iBzAafpF$X$418P z?1o)kuUyPb#SfT@lP6;sqCR={oK&j}dVi1H_G!0$%S;MLfW0YXFsim)70*e-Z(uAF z>?GL>Cs2#Tl59CQ4^*^eKgTP5>s-|s<#bOY0GEE0X}Y}~1p8pK>w=K|eJ$@Q6+5ue zCv8;fr%=|Vyc#@hKF054dC;&6LoonoK$pMgRnAAn!JBzVIo&hJ!w_tG=?*Y&4-6*~ zkBooDC2eB(kfKd)?s`{~I>941PeStmylP-R(=d)?GD*Fk3=illM6L6iT z#yx4jZzUOO!}jZ#uKzEgx%hqs^!Tj#a&Jr+5_$gU zT%$IC&AqYDPad<+2S!e!w)H&$b#Z^sD`$8NR(Y$zWKvoF$bwaJ!JK|aRB3eNniqqa zcMzN2;Q00!29m^zigIgL%-rDgL6B}2#*LzH?D(~u=;Ww7GTvhx^4QRjywUacqs})X z_{&kf$wN;*d4#rf5*}rmyR|}3;tQSnf_5{C(~qYofI{1Q&SW%2Zn2D}PI7;?{v(~{ z3XsV0s9^qV7y~}lb40A{DS>jEt?x-&ld zla_ z&hZ1HSy`w$eyV^raK-ENU%g#>bKFP~|DP(pgDsAflpWja98e%fY#kwi0Czbq<{s=V zDa9+>x~wH5X=5Ajes_M|)AN$FPI#s&UTHM%>FMd|=WVh2O?}H9ZexGc`H5`!9M8Xz z=MK-TC(R)N*>U4lS$)GVHNE7w`o*Ptsis_(EA&+AFlminrc_?3I`OPhI2apV<^yNBVlCQjUdh)hzme`cjNUe8P zZ$fsSh*4#wo%c znqENXfW9Z&zJk=a*^^rD_;9keHrHk#jjv{1o#LhJM zcFYSA)`LkIZ_a;tzqwQQ$oZP#>8oN@?(j`tkQKyMg;-=qzFP8`n5R3kRXB{L!Oty# z?8uvMm4<8G*Ihj<$zEX_YdHN2`Lg(?c>As-Hx{4Q|G?j?Hd?4}i}6*GX;xo{_V&Lk z+=;=Kb3n?4-V%Q&g)_>ivcD8VZ*c5~8(%;; zO}d0zNGz*{F@B*T^gA{JBSZ8J^Wt$79-PgKe38Hy>`48YJ|DoT;~y|Wi)Ory^uBD% zx8X*(Z_Z3{Ov(V9lTY>?EILPm)NZE#nDcJ+U4=s!3DKb9Vk&5=(6s zo7!yV1DiP#lpBSGOzr!F0;Y?cpZqlQn4}#0IDdb*ZkNi=4%d^9syS^xE}vPm{f2zX zX0@IfZ~6G~tSHI*Xr$3G2(uy7cNXdw2lc`Fpgka`8?@?b?P2FGgdHY`h<^u?W`MV1 zqJ+o=i#rzsM=Z8LL>&2soCK9Pr-UHGdz*b+j|re0`P7S)oSy&?>D-NkAH1WlF!SAP zbjE)yBlg|CJ9AJgyJcFH$Vf9oLS*?rUHj!ca^J z4z%fv%uTOqB$@41KjIxai;knTVm&|WZzg}e&#Ur^Rd-$jxA6n(J!@r|)%wc4;-0&D zrEBKbRdM?}c&IkduP+;jVDaW-MZ8@!MM`z=h&Vg1Q__Tk= zk^g1yT`o64>9{>Z9R7pZX03)0YajZ zA04B)%yMn3BwJ`!rYka@a7+;h2du4cLfgomSXIJdMUI&zoQWHtlJH1WF%SXznNh@^ zk2GdCU(hzV^)#kxl9i}t=oqPWj*)+oA!!F>!5DqBVdW#|QPPoi5ncXhG#Q3{Su`bW zN1BMXHxU_g+;eTx%j?KykFyn}I8AEuvBA}0;CinN0HD93?ofBj>aSz`@IofRs`-mG zRh7Lb8lQy@b+gx=<_h9N{jDQp_Q!%V{joqh?2w3O5BA%&kJicrmFrJs`+|Qk5P4dd z)tKj$JhXbun0cfl=0{2>jUd(-?uy=6){BfW)JVX28~FA+nW6!Dr{#D6v-79l(24_M zT^8ivZq4}AT+`~Y+r>W=YDh2JJlo_!m(p_N))`JQp9bd>%+iiL?e=n}a_pgha|B)_)CpdNe-kH>6x_8)Q4)(j8C9q0OEqo zCq$7+&=l$;y&*fSHR=GlJ0%D4T0rC=!n+1%n7zBh;q52X^YcN)zmMvZ&DF-xvKu6b zIdQ;naPP{b)9naMJY2~((TBR*4^m-@o2by7^eF5GAsfARfF*)0QcizrY!WS38y6jd ze%BWD*+>A`!dQRq;ch?|u@0nrM^bat+|Z_{`n4lD{5^O=zkclY?beau8l&_&N7yj? zYmxK?eSJjOXX5V^dDB>OeIe<)3)iEz6t0U%oI`+{V^sCL12w*|utsluhPenLTY2z& zojXP#V{=;=o1T(}zIK0^`An8>r#D3bCS;fF(sHjlMc6VO3$$Qd@8f=L>=97QbygW& zx)zI>mB9Y;Y@PdX263^}X7b9e@r)$xh99&uLaDBTk`O;?Z+dm_V6EK2x>YDQ)E3uN z=(S#Y+g%GPh2Y#7{WVfmzl`m^`wpmGS7S=3A8O2>fWzfllcIk!ov+4}WMcS(GHv8 z@57VozH?DPDBl!3zzpo!V!x8?-qgZ3hT(#kX=fc0?x-igWok~6=Y?J;aT_D4dViU3 zvoLEK+p|GWflhx{5&MJHmp>xrW_C=ktbzReP;=oZ@={ZvNZWm8E~zgX za>chJJ%$-u8U)!{NqzFx(C({)jn`C*+&7AXTMBhE&VvssI6=Yz}7Ca=*<=ku%ZZ z>H2Zqjp^UTOIRe*@m4f^2jL(bINBgw4nDyd;etqCa!y*B^SFMRiWR)Inkg(Z zv2k5klAC;QGIE&?G<0o5=FUTP;Klk-N9FYA-gtjW;9aq$>vJd!%gx>5m?v4EZVhbh=VV#oJVUH_K#g` zuSiLLfmWU#;~EUq7lRHMW;#|=WNP~kx>rb~fXLt#w8l-g7)aNOa6}sfdB$jPBzbAL zNZx;o3fgl#oy1N$=EUvLqGzJ19c{~h8z_JMaK?Y#w-mHp=FrX@JLlz zsH?bQqeU8c7$RWB*u^x*9}>=*=@RTNhvQXPBRj1Dex|MgqKUeSr7Uj>G~mDLF4dH8 zWrOKT0sF3F>Pjjno z(DrD}Z0TjE?#(HY-s6}^=Odsf_Y{A(&H0;|F%s?xO{E;iLGd14Y?V~s!wl?&K|&Ge z<0&mN7yh1s-am7TKANsFhC2V01E__sI1l?dZ1@klI644J=O3ZntuEf?cTtSg5h@Jvd-)aC?ddW5$@$HC>8G)V%6}7iBHX&Qzib!7qf_nP~3%i0(CilW)29h4evb%0PhK-kzA*& z;G^_{EZ>)ei{RjkK5aJkZa{xFiKqz%C&O@H#GM+XE)%D{4W;sd0%vt-6k4+RrF~ZJ z@muftyM>ZfUvb}z7jQs)THsb_BGA18~Y(2aOSG9;z}1v zh}-SS@Xj_Hc{^ZHI;TB)B%a_$BS&CxIXlQ;S;#e`K_7}Poxk{PB6EMR;s^xx3%YIV zJtHp28M@`lg+0P2H}R;)O>t)Q0Y)L*%je4D@buTq|8l(WSI__QWI;djAT-|#b4DFt^a8NF2S&7MFQ{*?B;zG7?ShgPp zY8IUF7Bhnr;C~R3uMlNY&* zMq<}!B#a(&j7NK($Wz9sS;1s^wZvC58OlBSr^Hpv88LAFb&Rk?NqzkQOD3JzhTD+a ze4Ya>*1OHUskkvGYm0=DhZTIQsR_S8poYI%%3Z`(j^1H;#j0Gsg&td-kS`nJb{sSU zTOvoqO?kjvU5bB0JRhQ{XmUa~Ki^~CIT$PLCGe1OxIx-)7IgAM{XNSS$}xwe*0bgX zyvvi+3n3-nRi5%Q@nV0+4+xUZV3h$(>~PcLeXc_9>E8NZYeL_o7>ACm>L zrMEFkmQd=IcgvR>@aNghS!*TNv-;4Ks?MJ(%ps&GCeoXZ^Pl^(jI`2Cb4{^h-Nl$E z#egK1DhPkPmh;+@mrfqC`E{6pkDtP^l?s~Ea$e{;6}urUI#L7!>0u(lpGvBwG}Bnn zG;+TR2$_nf^-00vumUSgf2Fg*i|diojh0W_#Fu>=YEo%>Q86VMNWgtWab8gjVfh`L zJE3Qm&1Nj&|`Pn)rjM$bB=_ZMWe>Is1z7sR>5ec*8T$2m=hq!p%nD zeClBchKYbxOUxVJnJGp+bza#h*{ zu_Oi~6;A@hx4U@$j;)U*l{N@7hZT_op<`P@mI3Pv|GjE zAed^%gYwW>jXp&#MI1YR`Y>tT&3Y?|Md*#GVs54Nb2o$>2}UY?K0<)2ql;t&Gv?Vv z$AUO-*-khq+r4voplj877{*b!UMT*{(OERWSA~VXoRkl?itNbl1Y46Zv=^?!c*-Ogq z8M@C{kML#H(Uofo4w_u{3hUGa!@nCQvNJfamS$7fITF5-*@5umye9R3vH*X9CnfNi z>R&1xVd+Am0W<8)rllXU9m6VoD{J2{*CmHR9E!z1MGf8q^7(^RZhVn8KtUy70giqtpwR8e}plBabGMbT?KoRfmbZ$x+1 zm|Z+yvODYGK9<}&Af`>#JL&m?+1G4D7oO3&c`P7vqwvfhjZqcx_LALO8k1~7ewupY zF7T>lJd`BjNej1-w@81oerN1)#z37#Nj@gm^C zl!=NrPH!H0YtF}Z`cOgPH}r${Vw<6!Mn~;#NANG{_j)5Y2=9L~dAjP4nZNx$CiWX( zykPquMgON6*RSxWKQ{GAlQqTTgGP5FPEd)lUw^%AKR$27sjXen7yor?Y zrs=PxW{r+7Zy|q--jZZjVBL+^tI45+IfZ6eJEG4)Z6zZ>D+tg|0%&gp+WRmL*o_6w zc^hb0H_+_1F;rp<{cOm|8&HYgH%AxYRTZ?q>zyTB3I#Cbo_I>?{S8gQ2cfv(^EihZ zt;gBGH@LP$D1N&Jwp$~t3#dSI}#XX^Kz|d#%(fVP| zBa)Hv{S56{4QLzvrk_0SYHg7?f~)ZeuExP#ry{;#5oB_LfPb~sIqZj0ET#T)S3MN1oBzze^HSM<{Tx)$s8QZ$PQ3W z+3iI5m7P6eS|%e$Zs>!F;T`d=Fq_0vAQhaG!?1rpW)0HZ8(J2`19reD6*G8|;WFze zRN#aL*WTV@pfY6LZhgwsu-q)4FQgcI`)`qrvPOo+EB5Zn)egoH7iM-zTikIL(dT}P z?7d|d8W}kJHGn;f>Iz#%VrM3N;ShJ~89vD)?bv5J*sk2(5} zA_VH}Jj=5xqv{o1fh*rWxA(q(DtEr+WDu&7Kn=^ebmSWvLE18lFSDiBaB`?M5x`#3 z(fC*$!Yh|*TAS8tvQ$8X4+Pa&s-aE-ZlQlk;zGq{4IJ!$s=QgqywHwFZ`=nj)9l|2 z>8Dvn5+=roJd_Ds0#G)O;Oco!;=sX317x6Br{ru`kU|dMXJ%hwwb^&)CQDEk`C&kI z6K@DFw{c}12cHFT4k;|BS?6}Lq?57EY7aBQ{pLw8MdC-+9MJG2XT#ypZ5F@_&>(;L za^H#8W|K=*-;`Kkyg!4Qm>=-%dR|JLw_;b|xbv(wj`NS7ma{fJW9?Hjp4i6RtNQ`> zxD<&#?RV|*lhq4WGLtoCk}6q4^bBiG_vO)}-)Ws-yWl|)CQ8kxu{bFfF-zx{w6MiU z@D-A{omq7hQ5SFt(kSMaWo;dD@56sPDt^#qP=WxPL{=T0B^P0O7%}}a5GHD{rO}&R z6AtyLv2dl&FlK5iP97>Lw}@fkkv^CWl$*ZhNn&>wQ6dt2Wt`3Dba5ThAT+BP`n^~= zQ&8i*_Tz@xLYU9K8`n_;{xIjVUpERkXPP=uL{_8ReM|GRXJ2V(tj>zv$NGPV+h+5x z#}M%pdR%ARAk4hSjm2#$jDm?{uaCPL6M|$s5KkXtXcFCO?};cB`(p@tl`2`l@8?x< z$F4}o48+76{r4L_K#0wmpF%RL*ho%x^hUmN1W5ETU?JMRZRCNjn~o1mkp!kQ zch65ZzkmMOZ;`4#M;Zm{IXY4=J2GSWhW~y7F52Ux$e5KzkI4(|h2Zl$;SU>|CaSSfdi~6pEuzP@r7*4zyv&~K z+OE$bg6euE$&to5 z&&9$XGS5WmfG{bBfiw_i9EONq^Y*SfqX&_Eo^ns*)62=H})AQ+aiOF6(n` zd9p`A{m7roUrt{f{4!ER?p=3cz4u9{qb+E1>6#9#h&{DS3Oz;&WK`G^64aihu+$H2 zU4?%cC`EGokiX9*YR1;|9j#NHN8i82&-ZVS(4>B)-y^P%-sl%!NygaXXaBN4KyBMXKY}7>K9*-FlEw5Bdlu~I;W=irc zWpX6#RymN?AY+yandu^Cfogrd zuQ2MSqr`6{vDd7n>;`z%>hO0NZVuw(fq8bSQn=2mOLf6m?LD@&9hDe2+<|2$!-hqj zx^l0m{?2pMaVNwQ4#@Z}NevtEHgE)>-s1HcIDq8q4aYo%CEB~f4WuD^vl~?2c6ood zZl9rA>Z*=}E@(F5K%+zxsMGA8)DiApBR6e)?7y!BE`E=6%--mgFo+Gn;nlNv*Qe0!LI=~YWJ}Rrijl0_fWqiBh0zJor%0EIh611V;b_J@ z6kpD+Q>zg2m3E$?a}VLsHL2rrwf%q2Ek=?+|EiqMtxn9=6Jc z*&%0Sr;YJcnX&RM;nwP|$w4(J?{PYPsm%aK=_ zQxXBu;^wO)QEv!cs`si>SS)|5FLHWfA`8YBhEYvT>th+$hG~XmO+ZX0j~?~8RT1Mu zQs8&XsDDCFefS1>y$Q*(|CL-!bk;=4G`nbPBqa?C%|_>iJ4ldVsRRZFPip^Rk|9dm zBpXd6qJU3(@`-Jf6!udI< z(9^>{(gX|W{J`WncwJJwJT)kpAc=^|hbsA^h8^te~llRuQ^Iwzk zmZX9M$`Yxdf<%oK_>6xEMPv=ge(H{pkn`}%Vz%?kgzg9Ec9=Y)6KwJc7(XX_f6u0e z)8mLZN~x`eZx;;p`cz9sx>>r<8~~RQhK>XC8pOWwDCMVVk>Z^?RqufdFB7}>1+{Y} zARqV*3}ucF`rg{RVz$_L8cFS%d8bIxJ~`fN_8*I1%1`S z8XVAOj$SPSg!KN9Nq=%X4)ma{V^|8g%T1LTM$*+~yJen0~ zscC5p%Ugdi9O_@=r`F4Z8DGuUydX8Xsscje$HYO!is}ROmY!4B1XHE;^SufPW4b!{ z4Y0!=obwrFD;Fu5@beR!WK$k>W>>VO1@HijGw0NNjv1LpyysgW+%C)GPRM7%75r{6 zgkxT|e3uFYC5nHb%7k>F3B80Z`^5ucjBe9Vx*C5Eq@5GiBe*nDH1#mfC&+67du5`^ zS=>9Kfu2vtcHp0I{Mu6)6FGL8XX`}mDo>}cDMrsad!n9bOat6R+)pgN5Op*5GGami zq#7YfYIkM&k;de+jmN35ESjt-GXjrRP7IJ#wAPAD!6_apYA{pETH3&Z$fL~dCM)bC z2Ty+@x!{T}7MQEsz`@v^n%8QjP8Ugf>pYBc`YF=$1uFF}(eVwg)q;(~Gpd~FQ%aU8 z<+tbDM?SRBa-C9jZ`p5;*9g*3Q`|f5p2=})kGz8eLs)M%JILyqHP^Q0XynT;ac26W zawq6O20>R5F>>U>{F?buL8(C*nS;*8d<}mM9grzf6tk)eO~FeDl(7M`on9-hOt3MW zbd?Ii?+fS0IqPCRQlXiSr7}1vZDfxq*)wUiRYQRjDGhO-UG(Q{e8k+UY0PmrU4v!{ z6jR+EH}cr>E006Y8uQ4cl+TO+AIxFep3_$s3!A!!uJ>hHH%)#GUdL`%wxUl!z=nTs zylo1n^Gk<&LQc!YW$++gc|xJkr-=*JXS4aHFuk~?k_CCkC1Uj5=%90eQ;{}HoYNv3 zNp%6KjUBTVI~leJO~lJ8@RC+}09JiS_1>0`1C{9HVO@KxS>Jio6j{_8747a&RwyQu zNz?NRwdyXr-Ui~@laDe6u~nlPzt4YZ$Mvr(WY}qQCku#F^2!)Pi+>3QVy`jRmKPtA zQ@n8X$<{e#6tmCkwpcIg);L=lc45Dlk;6?%_{8x~W0ma3g&*c^voWta=E(zr6VOag z{&IWS_@?(b<;`qq-3oBDf8TZ2a@F#KkLnO(kn~O2m%#CpC!gZsXIu4cmkoa#0d(8} zRGK#1-~Sf3mgojk`V6dH^bsQFHlmpfiB}xbJR-aZUzdJj zLqaMq0k0=6qrFIwdlbhd-JO3HyCR7ShKgmMN8cXEN`VzYKubL4cr-nW!lfm3(WDt% zt3}rUdXZx)$sXj?OljsBx?HX5e8~??x$qNTMEOaspZv^sJ0^!9o2o8!kYOeFZ6=@^ za}`g84C2(Gvn-)pW*u0uk`o9aHtDMIh$Q;(mu_y7Fh!%;w0uX0zA%5c2)Xp>FUg3S zl9v-%X7|w4%$vK^C2!MvmIQ?vw`h5ke;+4olwfDICjERjxBiUcn~dC<=DN9`xmC#1 z(Yf~)lZe;G-Mj&byI2f0xkzHXTT}bofMpXiQ=tr)R$6q-o|1aJhT8BNwvGkwH&e$Z zo=70=3XX`v-w*&ucRGI^Y7!*Vvg|-wW#yncOB(g0m1@spl4f!;A@Jt96i-}e;p;-uqPLXFNTuyN(#*+{?`QEWdj#G4&G$3bob=(At})lJG8ds}q&UIINQc^>zw5LH z^=4yD4!{W4d~kn{@-iAH#VGtx8++VPq#oe2W&1iN1Lp+Hd}j^nG4)`?<8L(LlHqXa zBp1bhD!H(28u>y*zuM@R_Qo^vs*68C&2?tYGq5PAdLN*6wyjzJWv8d(n!Xb zppLExi5Gu|KYB{Q_Uord{1d!Hio!5|-FeppD-RPD5w2pKOEFY%o6Q$#JZgU(GrxfG zGa25DjdDiDb@4}!GwF+a-+}{+WYL|nO5LLVG=k&6x;bACH7SMmjk5P5$-^9Sow75q&1xzeGgrEr1 zku<-2iL_Q8oUXXk8&`3lRke?FmhKlvrX07B~Va1q{KlK!xaD!W-i5& zUlMKoUr*NV{MBKb0Qxz(>^ZZYz-5Ia-t zfwNrN<4Vj8s0oty)31IzXzA}Hw^_J#%EJ%xj$HIJDHHM~14PUI_rXNs7t`)}lAeF@ z^qeQ#9@nw%t);dE-sNz_o_yL-Mmo;aGQe^uu;Q%uS^3}kPU(9^XlBhWCeMt_@OQ;A zR8`i5yiVUDCzSD;r-ujL#zj zv2Hv3(E&EX%7|98z$kFr_!$r z(UWymK~&Rfp~JggW8bd6lm&m<9oZG9=$nEoQJ)mn(@@=CM zGk~YjO+WBzjuYhG6ta<8nWm2>x#7yBts=iKU~zG>SH4lTkQw!2)Y_~+Z?taCtFv#$ z2poqm5GrXoGXHin6ZS2Y-3nUtV*p3;Fz`@CwLPO{6bY`McAS6^i>!b8SZf}gG3umj zJs6x32=?N2TZ9Afnc3*H?b0BWD-u8#sA^V7 zesT}(14l##zZkQJ|MD$F zKjEQ~C)C9c=)Fn9iX(rc=Bn;#v&}ti?zsn}O|BQe*XTk^52#)g<1w%6$W#9qY%#!J zwHTM(P6s|HXNLBzw4pcUW-7!A)2pHB_htS5LBnr1R@$F#xG{;HhSiayg7hH=-<-}Z4i{!v<>?mc+&^bJuPSID5Y(TB?L|dYu@_GX%jNxMzm_Z5`gl%v# z&y?n!wa&~w=rL)$h0Euuitg|v5ej{5^RF!`b7k(IZ5|FFN)a(|K{Zr%2*1w1O)zUo2kv4Lnh!<+a z5LamZKa=_V4}Yed>JRk%i9zu+LP$~0fuKgc10gHXMSy=Idiq1hfj3ND^2NUX8K2*6 z32I#$6?=zd^yJ4s`X^9J0|XQR000O8B(rf)#F-5s-W&h`*-`)i5C8xGYI84eZ*XvB zaxQIYZZ2wbom_c$+e8rmpZE9<4o4(m*>Sj$EBs1Gp^!GEDW~xR#ai0}St}$t2eH39 zzu7~(vXp-mcpTni_L`l0M>%@$%{O`{JijfH<*MqrCtmLfi&>G(dGD9xil@By{^ZO-_jcb#N0zdk(d>Wle&vu0V< z-J3;M$ug>V;aA~elg+DyXRdd*;Dt-eRpHb*jhD>vomWK?V;Yp6Bn4aW8)xS8a5UzR zs$t5qWwjdfet7I93zr=pjvj^e)Wc25ipNU~9wykq&H2KurhMjk1*u_49WYw!=&G@Er?QT7(U(vKQQ4#_J&0YCLm`r$(nd9ZOtXd7=&XiyN z!RA%(i2|58VHn=+h84d6S!K)NJW5lHF&lp`U4}L9fbipkFl57w$LtlZRpE6y8_QFH zpsq7ke5nXw2qK6!Y4t=I6^oP;LQLf+Ke1{* zYkVBJ(Wc_ta$fK>-BNcN-FipEij|ei`$<3ZfP%;+ELqo;HZPnAb-{q7IU_7h_UZu> zTU-z+9UQodnx0)e&2UlASheEu3D18jb_2$)v8M~qFoEZlG{!7$_)0ENA*hrI=dpz^m zmYT;LOIL)ejNRzzE|SX>{4n--=(4b@%WF&}xBzs1c*tvl(pQ-s9!gRI1EGH`C`^da~pW+wA6u&kmgnG+wBF87_BmkD9X@g{SZdjSvFL{sTK zSPJe#pF#Q3NJO@O6m<-NF1Kb%;1TyUTqH%Qr#o3CY3!lT79|_&;tBE8ZCIE%8W5(z z9(q|zp#!k`K`n-;Qqh09;q`xXqQ%vmuW2cbsTT^FZsi@);=?b`PriS4`ux>zFU~KX zzIuB8{I@6PXJ_X>K7IZb2{i1s@)P`40mbx1 z{WOc+1U0dhJS=D{Yo0v@QLLkPxkjuM}c&a_Pzfuow_BH#nzgu#kLhZvOaDR4m1TN~L!X59^lc@eJF!;z`^a zP3+ddsd(;y&FEK9#>9~V>=o*PDxa|h6nth;7adfP7LCca+nTa6xt}C_4O#m|3qq_# ze5ydU+eD+JfL*ZDG7^7fgMnK~W|BnS@4$gPG)`}h{r>Fm(5~TR*KXwt6nK6jD=D4X z!!|j1J~$rNdIC`WFnh1?No7_Z-L#wA%tTmvKnGp z=Orego-f2Qj@|-zpkz-{9#t-zI+whFP3iP`-}y0#tCchJ`rUsJnSn4{x}h!k+|O!0 ze->4%;X1m3G3H2l>GavmM@?RksPU;YPZQuz)*6t!0Vzeh*wNFha&1uTv~JI)&Kh@; zWKJJqiiwj8{UY!ZNUV7OZiK>DAP^`<1taUoSB7b%)+a5~qR&h!rNhIP8N@ZG-mS!{ z!A-rsLZyFAct(G_M7U~JR^WMUP#X#9vR;xwS7dWCzF{y{tAbzmGIre~33*x+P?gR} zlx4i?k-^uCdh;|Y;Vj_a+A4KmZJycodVOXl*U}mnyMc+G852G0_n|kdX@=_?vJ99g zqRW(pR)qxjF?u1-7RhoWn#lZhuA+3qoFwZ(l|ubp7fFAm`aJ(!7_UR=U%V*b%oG(^ z7SsI6fOl`VZ&%y2C}w1CHOvcMaq>(s!p}q-VY%ADG4L;Jd}4S#xauW1_@r|S0`yMo zJm9`LV}Jvx1J-pqkSg!IfWhVsF=Xez3f0uOhhzyMX!c=wN+7P|F>eW`F$cyqP)0kg zED6PDxGjG@X6HAT1a_@FN^Y02lx=L!X zp`ch_3ba57IW$W8-dVi!rQw~s-MA`liF^g4kQ>FwXZV7{pM(@Y5%S?Bj^KJqg+pCw zDVtG6OWAQH76e)=B}Froj#*_fy@XfCn+@;Ap!|<%KMS(=v_*;igP`SY%B z(iF0P3{g>lIbyoTkjr1P>0)u+XpsdpNeW$tC+;25NKTN0Sp0wO6AYT@2W{eKE(r*& z;#X5nUb75c&T=Rf)GMq$@^y{&hw4Fs%}IYGZyLuigFOGT*<&W~#!Ioea6Q;yW=XHA zCU+Rl2bU-leb*JUolSE)mx>TvZ0aDJRK{pcy`aKp`aJF zn5gN8O4xn!Tbh)*KAJJR8aN5zU&GVJ(z1DX#KK|bD zF-0CTo-wgWVSX1;Rswzvh2T|TwFiH%R>Hck_lUnRR;|H5xt9%XNV`Sh+x|K7&c0f>unYj4{IHvTmNwI%HMf=Mi{0aY5B5)nHwVi*z3fEd) z%~2FoJuajsUkK^6u=t@+LMmMK(Mb(T)i1;^!5R)Jq}&SUec3D3ei1HqJmg?YC9NIU zZI^IuLLIi|no9KKO9WTBC}Wf_Hm4e?DB6^Wl&!LY_IkgnL)Ao&p|h_o7L^DfUqWKV zQ0#l$jCiam(@(@m9O|P7J@1Z~jZO5wMFEDMHr%B~H1IL2`)U&63 zcQHX*e-Sv2C8eKf$45pqKX{OyK4{RBX_fKTB+MY*M8v7`Q|N-)pLh-$O8m@P_DPMA z2cHt)R(3okzyq*t8*YCj1!>)WN7omc0b$HGhZ@0g-`JP6Ixj*+bPfBupJ=jc8rl7< z*3A~zKdle0ybDJ#g`-$T8V8CaRDg|m&XFxnJtc+q^E8?>_ee-6DSMQl!HA=!-$CUS z$k}$uUh3P2Xte2OG*c1oCxq=ppF3-^W>#}>5z5%MK8!eq9oT>N$hnz_^XM~{Xplv9 z&X^j+jMQ{3qaP6(RXNa|8VAWVhvSO>^{Z(-J9>YRi61|imT{`&?SKQ4;s*7BGa*!)EP5K%MD(-on|O!BivAtY+o6i*2SwNN!pT|!922N1s7 zc?$is!-wH?OO1bOo6CM^Ft_4d8YrIAN_r1=M?Rgv$)L6!-{RS92y^N*ny(rgMMzLr z9oRcC_3nInEc^Yy(GGrNYPh%P z9W#WGD0~th9_B75m!VQTRt|HXBbE`Kw3ZZYp)1lNCV1!%}2t>{q=WrMjtTxq*j ztE3##LktjHe#!6}&u>{Q5z4|l9e4`}&M!$kmE_4s1wwEardr8Yd|6wh3APyrh=|Dz zQR@z8ZEyOSYc}AcsE;Vh22NIaPFn|vGT5Jx8Sf#WEaU{q*6^`wLz)i1z4*UhPhV#_ zq>~LVDYAbO6$vYq-S{AM9>p;|uh9;Q1mZSIU)0-W_70op3hzHMK=)|mrE8s~^Q_iN zUTb_k{R4|ml-CqE(Ir>{ckTEr{6Q>i&g_HY8YFdvU3FAc?bjWUk`_UwQ(@>vIu(YH zMnXEIhgR|eLkdWPl+q1DNT;NNG>pIy(m5a?NC_XW{`9Bsx7PQ~KYM1@y?Z}rpYzP~ zths01E3+$qXZF>rr}Jw(sUNhRn%co`jY8<+W^#-K3-A#CYrO{ob`{lr)Qmf!8f8-h zUj0nMz%KNtXvN+VnDWbnc3$!&24gi_=*n6w>6n92ql9t|je|WXCL)S7j zD@#$|q%<`&>-!0k`nq(Ty1E)TEwekDdXbKzGtd$Ibk+*eIi^2nmoN3(pUV-f!5BjVVJ5hDOchp}8`%@Mv% zCkj5H+|Ngwo9WVh&0F>Q1<+aY7MFdS;K2al{RE@gOeU{a!%9PBhZ%A0F_C7T#q<7^ z`0@hMv8NV|9dBl^wIc4aX*;)??cKAKkq`uUOYfobLWu@;b@lX#TN&b6z;EP-49{jKVYspO4;Y)%SK45nU52DwsmP9 z`u!8aihHHEjJJD@;`xxx@|J}vO6D~R2PAcfS%*kvkvA1mWGd7&)AH)Rw_R$d`+Kto z)ctM_4?cLWD@t;RzldS((+UMvD@1?>%lBj?+lN++Z9_Vx!9G!l+VVr!?N@VNyI(!f zFZ3fE)~00GO^*z897r`pVtTv+v#%aQDWOX$6S7(r$S!Hr{t30e=&`)txam&d%&5g{ z2uYOZSZ~O8mHk8jbEgj|6C{w|5fcyAjwgkF!~xpQB+L-It&(7LF>yM4gDgOcHK@a$GjamWH^7N?QkShoFGY{%Rte2(YPi~Kmpat+-i3YTI2c$GW@2FrI;Z+n zd0{rc@O^Kue@%fH&#P@W>ZFAk5YUa5lb$JgzWc2q(lmU<=hpK}*TZJAYPLJuT{i(Jrw+Ti>!l{3||nTf3(xRw`i<48As9CCRkeE(mpBNB_QrVYhh)3GWoE{j)312 zO|K>StgGmLz#MJv3Xo6Q;r!?&J>*?3tG5f{HM$60$s@AK?MQ> zTQRSwO%=I(h^SVJR8EHTI=owRmAp-dhlnImoo`iK9=7PgNS3^C^Rsp8V9%bL1SLD| z%Ge=3Vym@Ec8l)g#$`E1m$_4m^(BiA>a z`^-|;1Dt4g7O*nPq|aF~?T|B+o6M=~RRmBtVi_{G(9GB#NfKho^6b#!*jn8_lT~)nk7u`N~Xm#V(0xR&tZi8d0MSoIu@+h3&^B+%e5 zHs#%i&N1;csb8J7SG6Oy0gDKsRPdep!Z+Y$#yJ_T?z3K+523>EvmL1x(6@;~+OsQy z!lDC}Gu?u5&f}{KxX?pquJuX^i%Omx^%mSNKqHAf0nUf)e3*g}2fJ_DM0{2Tt|fWvndC1T8*+wVJN6K4&-d1R_m8<;YPcd|h`EJ-?vO?=)ybxcCPF4@~y z(j#>G3BZp)9hTTlVIL4Zq|gYS9yU#)u^V_*_uYB&orX;Ss2?TxxGP*K?5r1S11=v? zpInGC$Tj^m_O8R@D5{zx7}S{#F+ejcPilN6sB z3bKd~$np>l-KIA*9~!W{j$2R(E`KpBj9_{~3YcUF-CjGKNsclvKN{qRQkgWgA)|mY z{!&tx#!uFhk^J57r0>4k#8zwgVy?)-?-OH-VNaj*Fh1#hui^vUdQN zWA`_x-bCf750IzbtC)sRd_L899a@JRtBg2@^`nlkoLM+HEoQXb-mhy(U=|H>W7IGN z0X^X6)oVtGXLzwa>U2>#Wi5HAS%#V9q@a^JxCxE8vAdGgs!~8iyNRqUC&yiNRaIds zTa|^Hv27rJkEJ_naQZCfLpKX}QK ziQuBk1P7p#k9Lr~r|b(`1m%T;TDA(6n{^}hHGI!cIqkKX^7Z4$+XK!yhVwAE13&FA zA#f0;PfC_?^l;>Rb4I*R9_8|s%aPF4f8W$kSkAV|2q3kMfh^7?hnzr1ZDF4PLV(3v zDAAz&Fzj%`bjvEA5#aZBa8U|jD;g_*Ib(c=G{&AAX-ToN$9ZfQ1t-9mKvergmktTO z)lKQ>RhPC{fC3!R>jnqTgdHMcq+lPd(t>wK&EL5v8km%yPgk_&D2fz+D#;CGONCuZ z5q%B4rXNnB9xb6A=~ccPt{MsGk`if;g9)SkIOY%bXg$UyNhIRy^(~e##+qwC_f40k zjS7;*nJqe33>zW$?DMk@@$d5236+33BabRU?z@RX&Xh5x54IwG#E@&=hQ3qJlzo+HN$3G}K%^;DEg@Gy z&e}qLoWv&nDRilHmecx}I)x`*nT>sD9%i5Tw7-J(hTQ&gvrf#yG$C(Hy^c&7xMbHg zr$2;}IuxQ=nD%)8r{o8Y4M=Fj3~9S<(%rigFAhaD;hKac5Me#uT2j^jv58&5*I^p0;*ne^6s3&`4(+CVHOaiI3g2cPVj@V3-2L;9ldpLnB@*%7)C zBQ?*o=7G)bX|v4QMg^R-b~4nsA5Wle>Cg*T7AnytE&ZsXHve2=ayjY{%jx4=Q+K=w z8rnO3MLF$isqg^Tyrii^R=Jjt(XbG^l|Ho@tv)7fxiCLn2 zd54__*=s3JF_oL7UtW4PIoCNy!Cj@M+=CDueNpXwu-GYW5=I8J@d%+a*TctBN9NVq z1#doFcJ?FY(4*EkI-}igiTc6ViEw?eQHnPG=9IHkw-jZAYB%b zML-#iquD&N2%W`0HZ!SRh~Xv)Fy}$;U8|V*qrK@)E4IlZW}=8+~i@WWe8g%Iz7$7ljJ)M?*7oaHs{KQ$rJyLc_R#&$^J zvs^cBJ$#N~yM}~PdL0rOlyIf=bx+XI)<~Jvw|GTu3`j{0QKjUS$otMvVB!wbgUNh- zoW8A6aGU%`1rdD%A)2#9bR67F|;VzMZA-i zQ1i`to}I5{g3TVSe!@Zrz6kvkm{AoUO}8}SwH{X3p6?)eSarT-b&{pw`_5t}=IRb3 zMf!K^?7BJc#c}LoRgku2-A|ne@eM1yFZKftIl&su@xUehL037(@#6OxGEHtcTk=3D zJQ%2duG6(}MAE>UsJ)8n;x7(SgK1M5$@GO`!DGYp+6Mf<;@;^6u^1kY3tAO(OM=Yx zY%Iz#$EF;)!08u?Y69o7VnmBgc)CZ48S<ZWS`RPPh{^28@5a?&xEI@GQn|6Js1ue)HNLl{7$eajGnC zNyK`BoYpU>nLI8>=fjZ*t}cuyFWY>J#g9A~VS!+ISAQ6u0OAV+U|72^l~TU5JsTdR zY~`xtVXx7U>^v!0v=f&v-#p%rc7Ch3PP7+#)q5i~`Cht`Y41$_`>a5*UDG$!JwRox z19D=#dYs3DOnS zRzeJn78?-nC-DjCZfSjUvlggn;Y!lf^n;9D=7Db~=^g(k+0PFP(>c06RNHXgV3B(LaMd|z#nGIN+4YMa)B-$F{H46o z^XrJ^oRB_1TCyvjxeYtcet`1A-4(4o%E6RLC&eY7k8k;)Fpzs>WCXLZ z^}{`%cGP0k_pWc)f2TqCA(a3*aFv617$6Y-RT`j9ZZ=RyS59j;2m2_ken%uXar5nU zNqgO|ENl>@EcKLBY<&SKc=ISmQ!3mpjqpL$GHcC6YsuqfZ2T_&2euwo!c9zxhBM+b z7k=jr{sf6u7Z-*{BH3DE=$E{s7ETxIti>+PO{#th9hrFn-7kk9qSyhk=Zo}KHvQB= zw;|~L4A!bQ;w+@hBc$-VmF>+NzZ;kq#;e8;#g{;eP!^QkVoIV2@~CHX=FizgpS@(j z+!(rkv_03Sd)50!k)%Z#w1TzQ-ZFATE{OJ+PrYktCK3n7+KNGNb21Dwq42FmAx@YM z|E$W{kyV4HK(tCHd`$^pT1;dNg^Q|f+_vT$=#nh6<_m@!OUy}uJN1#^CxNB!XT#GA*eV-zIrA$**o7?Q71%OGm?G)%u?_x; z=!Hky5l+2#ZQT>xh6}dC*9x#A2yP8EAPGK>EYfi6TR*1Mo_zsSX-ta?+u1rZog(Bi z!y}&?Pw72%=u?Pem7q7QCt4+ot?qo}HmmB=Ya$$6qSV%OLsm~%?=!Fnvohk4g2Y{K zIBbl)HF#%(k&yeTOlmmMwbscGqm+b7t;W}Pn&0AHZ9{K*yRA#Y-AgxdW4KsWmHe!1 z*3RC3KLu-e1}ByW7k%=Ba$XqbIUAb;CToAl07F>iiIPekJ#J#)?8lX@cTPY8F~ z1Q>FS z5hnpVOa` zdm{FsvGV;)NSaJn-)@(+L=a!zoBW6K8qS7&{a;vfDtouUEoqMmI=dIFsYavQq20fxp2D~r5Tc- z&sA&h!xMX8#?n;)HI{U_u`#F=r5_2`esup?v0`=K;S2-Y9lP;+{i%z*%f@O-Xy}q4 z(yIjGfMQDxOA&ySj4Wg6MvHiw3#_I)10%^znQG?H5)n|NQ)4R-7NKhd>n0n7#@KbRd8ZBKuAI z4^hK^Xw9HdH&-_oQ>PydI9Eddm(G{e|E+TssT!D9=Ro&5=;|gpAQ0Z4G63kIDhOm{ z>S1FJb>wt%eEKK*Uj9#Se&J{OrT^EPYron5@upW71hW0xnv5~X4{LrR@L!Mq?ak>) z+r=ag2qf4B0^R)0`j0j1KUx2F#`Z5`E{T4s{2Tu*0^njmI6d7!AWdB~(5=5z_FnuL zyREakrHdyAA16O2&wqNL{`82-=%>)X@!vv!_f`F>A3ESFoY$8>g#K!?`cq>1^ryrx z{N>=kCH~xW5d0NNF%{b1JCNV>z*VuPLlu8y)luh{EbezO0<3+SKh%s*%2KQrv_AK5?Yo$1ixzYyQ%oWN81G0$PD0RQ8Huk4Hr z49wFH$}o!5rv~iJJFFmZPx+~=KylJmS>y7Y?K+{N3yo#WblJL_mjqoc*S~SZFZc+< zo$sf=mmU7}h}mNC#6=d#3Xf&&H!P2xYbjxH|L1If!>vF5Y^rbVIN0zs{KTn8{AVA3 zS~DTVAbHK=)!0p*{3rN$7{m=94_0=^c$Y z(9U|{OW}rkmeb!kAG?&DvZ&{swqe=ryifmF5kYr(SxkWap+G}UU?|-MVjf_C<`ZXI2zwd$0D7{mD9&W$x3C+^AZ$a%0ttm5I|Yx(ageoPD={djC}u z!=i+QXLFycGVhOi|1O@-U)t!=biwK3f6SXgnijh;KYAdvtX}Tg?bq&|0$WT{j1_OJ zn5$~tZ|QPw*0jx+B02U37x%T6YJY23@wR%}Q*U3b+EeV4#O1>uDO$|#?Al|Ozd0?` zJ0^PmgbHPLiMfFSR#kdSSkn1rU1v-XT5I)dTS&jAM3mgYS-zEi3H3d_Zw^lJjP(2sx!Re9A*M%NU`s2N( zVcw)5|3C7MXvj=2fG!7O1Qsi z;!%sSvO&XQ7~FSK6>K Date: Fri, 20 Mar 2020 16:21:15 -0400 Subject: [PATCH 46/48] Updated source translation file --- src/assets/i18n/messages.en.xlf | 492 +++++++++++++++++++++++++------- 1 file changed, 384 insertions(+), 108 deletions(-) diff --git a/src/assets/i18n/messages.en.xlf b/src/assets/i18n/messages.en.xlf index 8ec6073..96b978c 100644 --- a/src/assets/i18n/messages.en.xlf +++ b/src/assets/i18n/messages.en.xlf @@ -1,6 +1,6 @@ - + Create a playlist @@ -34,10 +34,90 @@ app/subscription/subscription/subscription.component.html - 15 + 28 Videos title + + Modify youtube-dl args + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 1 + + Modify args title + + + Simulated new args + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 8 + + Simulated args title + + + Add an arg + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 16 + + Add arg card title + + + Search by category + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 42 + + Search args by category button + + + Use arg value + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 46 + + Use arg value checkbox + + + Arg value + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 50 + + Arg value placeholder + + + Add arg + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 55 + + Search args by category button + + + Cancel + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 66 + + + app/settings/settings.component.html + 285 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 37 + + Arg modifier cancel button + + + Modify + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 67 + + Arg modifier modify button + Youtube Downloader @@ -156,11 +236,11 @@ Custom args app/main/main.component.html - 115 + 116 app/settings/settings.component.html - 83 + 92 Custom args placeholder @@ -170,7 +250,7 @@ app/main/main.component.html - 117 + 118 Custom Args input hint @@ -180,7 +260,7 @@ app/main/main.component.html - 125 + 126 Use custom output checkbox @@ -188,7 +268,7 @@ Custom output app/main/main.component.html - 130 + 131 Custom output placeholder @@ -196,7 +276,7 @@ Documentation app/main/main.component.html - 132 + 133 Youtube-dl output template documentation link @@ -204,7 +284,7 @@ Path is relative to the config download path. Don't include extension. app/main/main.component.html - 133 + 134 Custom Output input hint @@ -214,7 +294,7 @@ app/main/main.component.html - 139 + 140 Use authentication checkbox @@ -222,7 +302,7 @@ Username app/main/main.component.html - 144 + 145 YT Username placeholder @@ -230,7 +310,7 @@ Password app/main/main.component.html - 149 + 150 YT Password placeholder @@ -240,7 +320,7 @@ app/main/main.component.html - 193 + 194 Audio files title @@ -250,7 +330,7 @@ app/main/main.component.html - 198 + 199 Audio files description @@ -258,11 +338,11 @@ Playlists app/main/main.component.html - 213 + 214 app/main/main.component.html - 255 + 256 app/subscriptions/subscriptions.component.html @@ -276,7 +356,7 @@ app/main/main.component.html - 224 + 225 No video playlists available text @@ -286,7 +366,7 @@ app/main/main.component.html - 234 + 235 Video files title @@ -296,7 +376,7 @@ app/main/main.component.html - 239 + 240 Video files description @@ -306,15 +386,79 @@ app/main/main.component.html - 268 + 269 No video playlists available text + + Name: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 5 + + Video name property + + + URL: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 9 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 9 + + Video URL property + + + Uploader: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 13 + + Video ID property + + + File size: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 17 + + Video file size property + + + Path: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 21 + + Video path property + + + Upload Date: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 25 + + Video upload date property + + + Close + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 31 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 23 + + Close subscription info button + ID: app/file-card/file-card.component.html - 6 + 7 app/download-item/download-item.component.html @@ -330,10 +474,38 @@ Count: app/file-card/file-card.component.html - 7 + 8 Playlist video count + + Info + + app/file-card/file-card.component.html + 22 + + + app/subscription/subscription-file-card/subscription-file-card.component.html + 7 + + Video info button + + + Delete + + app/file-card/file-card.component.html + 23 + + Delete video button + + + Delete and blacklist + + app/file-card/file-card.component.html + 24 + + Delete and blacklist video button + Settings @@ -350,7 +522,7 @@ Host app/settings/settings.component.html - 8 + 17 Host settings title @@ -358,7 +530,7 @@ URL app/settings/settings.component.html - 15 + 24 app/dialogs/subscribe-dialog/subscribe-dialog.component.html @@ -370,7 +542,7 @@ URL this app will be accessed from, without the port. app/settings/settings.component.html - 16 + 25 URL setting input hint @@ -378,7 +550,7 @@ Port app/settings/settings.component.html - 21 + 30 Port input placeholder @@ -386,7 +558,7 @@ The desired port. Default is 17442. app/settings/settings.component.html - 22 + 31 Port setting input hint @@ -394,7 +566,7 @@ Encryption app/settings/settings.component.html - 34 + 43 Encryption settings title @@ -402,7 +574,7 @@ Use encryption app/settings/settings.component.html - 40 + 49 Use encryption setting @@ -410,7 +582,7 @@ Cert file path app/settings/settings.component.html - 45 + 54 Cert file path input placeholder @@ -418,7 +590,7 @@ Key file path app/settings/settings.component.html - 51 + 60 Key file path input placeholder @@ -426,7 +598,7 @@ Downloader app/settings/settings.component.html - 62 + 71 Downloader settings title @@ -434,7 +606,7 @@ Audio folder path app/settings/settings.component.html - 69 + 78 Audio folder path input placeholder @@ -442,7 +614,7 @@ Path for audio only downloads. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 70 + 79 Aduio path setting input hint @@ -450,7 +622,7 @@ Video folder path app/settings/settings.component.html - 76 + 85 Video folder path input placeholder @@ -458,7 +630,7 @@ Path for video downloads. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 77 + 86 Video path setting input hint @@ -466,15 +638,27 @@ Global custom args for downloads on the home page. app/settings/settings.component.html - 84 + 93 Custom args setting input hint + + Use youtube-dl archive + + app/settings/settings.component.html + 99 + + + app/settings/settings.component.html + 207 + + Use youtubedl archive setting + Extra app/settings/settings.component.html - 95 + 110 Extra settings title @@ -482,7 +666,7 @@ Top title app/settings/settings.component.html - 102 + 117 Top title input placeholder @@ -490,7 +674,7 @@ File manager enabled app/settings/settings.component.html - 107 + 122 File manager enabled setting @@ -498,7 +682,7 @@ Allow quality select app/settings/settings.component.html - 110 + 125 Allow quality seelct setting @@ -506,7 +690,7 @@ Download only mode app/settings/settings.component.html - 113 + 128 Download only mode setting @@ -514,7 +698,7 @@ Allow multi-download mode app/settings/settings.component.html - 116 + 131 Allow multi-downloade mode setting @@ -522,7 +706,7 @@ API app/settings/settings.component.html - 126 + 141 API settings title @@ -530,7 +714,7 @@ Use YouTube API app/settings/settings.component.html - 132 + 147 Use YouTube API setting @@ -538,7 +722,7 @@ Youtube API Key app/settings/settings.component.html - 136 + 151 Youtube API Key setting placeholder @@ -546,7 +730,7 @@ Generating a key is easy! app/settings/settings.component.html - 137 + 152 Youtube API Key setting hint @@ -554,7 +738,7 @@ Themes app/settings/settings.component.html - 148 + 163 Themes settings title @@ -562,7 +746,7 @@ Default app/settings/settings.component.html - 155 + 170 Default theme label @@ -570,7 +754,7 @@ Dark app/settings/settings.component.html - 156 + 171 app/app.component.html @@ -582,7 +766,7 @@ Allow theme change app/settings/settings.component.html - 161 + 176 Allow theme change setting @@ -590,11 +774,11 @@ Subscriptions app/settings/settings.component.html - 171 + 186 app/app.component.html - 34 + 38 Subscriptions settings title @@ -602,7 +786,7 @@ Allow subscriptions app/settings/settings.component.html - 177 + 192 Allow subscriptions setting @@ -610,7 +794,7 @@ Subscriptions base path app/settings/settings.component.html - 181 + 196 Subscriptions base path input setting placeholder @@ -618,7 +802,7 @@ Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. app/settings/settings.component.html - 182 + 197 Subscriptions base path setting input hint @@ -626,7 +810,7 @@ Check interval app/settings/settings.component.html - 187 + 202 Check interval input setting placeholder @@ -634,23 +818,15 @@ Unit is seconds, only include numbers. app/settings/settings.component.html - 188 + 203 Check interval setting input hint - - Use youtube-dl archive - - app/settings/settings.component.html - 192 - - Use youtube-dl archive setting - With youtube-dl's archive app/settings/settings.component.html - 193 + 208 youtube-dl archive explanation prefix link @@ -658,7 +834,7 @@ feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. app/settings/settings.component.html - 193 + 208 youtube-dl archive explanation middle @@ -666,15 +842,87 @@ This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. app/settings/settings.component.html - 194 + 209 youtube-dl archive explanation suffix + + Extensions + + app/settings/settings.component.html + 219 + + Extensions settings title + + + Click here + + app/settings/settings.component.html + 226 + + + app/settings/settings.component.html + 232 + + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + Chrome ext click here + + + to download the official YoutubeDL-Material Chrome extension manually. + + app/settings/settings.component.html + 226 + + Chrome click here suffix + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + + app/settings/settings.component.html + 227 + + Chrome setup suffix + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + + app/settings/settings.component.html + 232 + + Firefox click here suffix + + + Detailed setup instructions. + + app/settings/settings.component.html + 233 + + Firefox setup prefix link + + + Not much is required other than changing the extension's settings to set the frontend URL. + + app/settings/settings.component.html + 233 + + Firefox setup suffix + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + + app/settings/settings.component.html + 238 + + Bookmarklet instructions + Advanced app/settings/settings.component.html - 204 + 251 Advanced settings title @@ -682,23 +930,15 @@ Use default downloading agent app/settings/settings.component.html - 210 + 257 Use default downloading agent setting - - Custom agent - - app/settings/settings.component.html - 214 - - Custom agent setting placeholder - Allow advanced download app/settings/settings.component.html - 219 + 272 Allow advanced downloading setting @@ -706,27 +946,79 @@ Save app/settings/settings.component.html - 229 + 282 Settings save button - - Cancel + + About YoutubeDL-Material - app/settings/settings.component.html - 232 + app/dialogs/about-dialog/about-dialog.component.html + 1 + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 37 + app/dialogs/about-dialog/about-dialog.component.html + 6 - Settings cancel button + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon below. + + app/dialogs/about-dialog/about-dialog.component.html + 9 + + About second paragraph + + + Found a bug or have a suggestion? + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About bug prefix + + + to create an issue! + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About bug suffix + + + Installed version: + + app/dialogs/about-dialog/about-dialog.component.html + 17 + + Version label + + + View latest update + + app/dialogs/about-dialog/about-dialog.component.html + 17 + + View latest update + + + About + + app/app.component.html + 26 + + About menu label Home app/app.component.html - 33 + 37 Navigation menu Home Page title @@ -794,14 +1086,6 @@ Subscription type property - - URL: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 9 - - Subscription URL property - Archive: @@ -810,14 +1094,6 @@ Subscription ID property - - Close - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 23 - - Close subscription info button - Export Archive @@ -886,7 +1162,7 @@ Search app/subscription/subscription/subscription.component.html - 19 + 32 Subscription videos search placeholder @@ -902,7 +1178,7 @@ Delete and redownload app/subscription/subscription-file-card/subscription-file-card.component.html - 7 + 8 Delete and redownload subscription video button @@ -910,7 +1186,7 @@ Delete forever app/subscription/subscription-file-card/subscription-file-card.component.html - 8 + 9 Delete forever subscription video button From 996d5989e76ee337259f5aaca0c407a4bbc62d7c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:34:57 -0400 Subject: [PATCH 47/48] Updated spanish translation --- src/assets/i18n/messages.es.json | 35 +- src/assets/i18n/messages.es.xlf | 1835 ++++++++++++++---------------- 2 files changed, 876 insertions(+), 994 deletions(-) diff --git a/src/assets/i18n/messages.es.json b/src/assets/i18n/messages.es.json index e72f728..738643c 100644 --- a/src/assets/i18n/messages.es.json +++ b/src/assets/i18n/messages.es.json @@ -3,6 +3,15 @@ "cff1428d10d59d14e45edec3c735a27b5482db59": "Nombre", "f47e2d56dd8a145b2e9599da9730c049d52962a2": "Archivos de sonido", "a52dae09be10ca3a65da918533ced3d3f4992238": "Archivos de video", + "d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modificar args de youtube-dl", + "7fc1946abe2b40f60059c6cd19975d677095fd19": "Args nuevos simulados", + "0b71824ae71972f236039bed43f8d2323e8fd570": "Añadir un arg", + "c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Busqueda por categoria", + "9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Usar valor de arg", + "25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Valor de arg", + "7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Añadir arg", + "d7b35c384aecd25a516200d6921836374613dfe7": "Cancelar", + "b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modificar", "038ebcb2a89155d90c24fa1c17bfe83dbadc3c20": "Descargador de Youtube", "6d2ec8898344c8955542b0542c942038ef28bb80": "Por favor entre una URL válida", "a38ae1082fec79ba1f379978337385a539a28e73": "Calidad:", @@ -31,10 +40,18 @@ "9d2b62bb0b91e2e17fb4177a7e3d6756a2e6ee33": "Vídeo", "960582a8b9d7942716866ecfb7718309728f2916": "Tus archivos de video son aquí", "0f59c46ca29e9725898093c9ea6b586730d0624e": "No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más.", + "616e206cb4f25bd5885fc35925365e43cf5fb929": "Nombre:", + "c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:", + "c6eb45d085384903e53ab001a3513d1de6a1dbac": "Cargador:", + "109c6f4a5e46efb933612ededfaf52a13178b7e0": "Tamaño del archivo:", + "bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Ruta:", + "a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Subido:", + "f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Cerca", "ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:", - "e684046d73bcee88e82f7ff01e2852789a05fc32": "Count:", + "e684046d73bcee88e82f7ff01e2852789a05fc32": "Cuenta:", + "321e4419a943044e674beb55b8039f42a9761ca5": "Información", "826b25211922a1b46436589233cb6f1a163d89b7": "Eliminar", - "34504b488c24c27e68089be549f0eeae6ebaf30b": "Eliminar y ", + "34504b488c24c27e68089be549f0eeae6ebaf30b": "Eliminar y pones en la lista negra", "121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Configuraciones", "fe22ca53e651df951dac25b67c17894b0980f767": "Host", "801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL", @@ -75,16 +92,22 @@ "fa9fe4255231dd1cc6b29d3d254a25cb7c764f0f": "Con la función de archivo de youtube-dl,", "09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones.", "29ed79a98fc01e7f9537777598e31dbde3aa7981": "Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos.", + "0feab442129ba239106e55cf029069d3d4adeadc": "Extensiones", + "9b3cedfa83c6d7acb3210953289d1be4aab115c7": "¡Haga clic aquí", + "7f09776373995003161235c0c8d02b7f91dbc4df": "para descargar la extensión Chrome oficial de YoutubeDL-Material manualmente.", + "5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Debe cargar manualmente la extensión y modificar la configuración de la extensión para establecer la URL de la interfaz.", + "9a2ec6da48771128384887525bdcac992632c863": "para instalar la extensión Firefox oficial de YoutubeDL-Material directamente desde la página de extensiones de Firefox.", + "eb81be6b49e195e5307811d1d08a19259d411f37": "Instrucciones detalladas de configuración.", + "cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "No se requiere mucho más que cambiar la configuración de la extensión para establecer la URL de la interfaz.", + "61b81b11aad0b9d970ece2fce18405f07eac69c2": "Arrastra el enlace de abajo a tus marcadores, ¡y listo! Simplemente navegue hasta el video de YouTube que desea descargar y haga clic en el marcador.", "bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avanzado", "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Usar agente de descarga predeterminado", "dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Permitir descarga avanzada", "52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Salvar", - "d7b35c384aecd25a516200d6921836374613dfe7": "Cancelar", "cec82c0a545f37420d55a9b6c45c20546e82f94e": "Sobre YoutubeDL-Material", "199c17e5d6a419313af3c325f06dcbb9645ca618": "es un descargador de código abierto de YouTube creado bajo las especificaciones de \"Material Design\" de Google. Puede descargar sin problemas sus videos favoritos como archivos de video o audio, e incluso suscribirse a sus canales favoritos y listas de reproducción para mantenerse actualizado con sus nuevos videos.", "c072eebcb5b1f1eef6fb2ee1756e839dd302f3de": "tiene algunas características increíbles incluidas! Una amplia API, soporte de Docker y soporte de localización (traducción). Lea todas las funciones admitidas haciendo clic en el icono de GitHub a continuación.", "b33536f59b94ec935a16bd6869d836895dc5300c": "¿Encontró un error o tiene una sugerencia?", - "9b3cedfa83c6d7acb3210953289d1be4aab115c7": "¡Haga clic aquí", "e1f398f38ff1534303d4bb80bd6cece245f24016": "para crear una cuestión!", "a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versión instalada:", "effdc7dfbbc49c08d25ea1748fca00c38c918abd": "Ver la última actualización", @@ -98,9 +121,7 @@ "ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Descargar todas las cargas", "28a678e9cabf86e44c32594c43fa0e890135c20f": "Descargar videos subidos en el último", "e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Tipo:", - "c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:", "a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archivo:", - "f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Cerca", "8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Exportar el archivo", "3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Darse de baja", "e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Sus suscripciones", @@ -110,7 +131,7 @@ "2e0a410652cb07d069f576b61eab32586a18320d": "Nombre no disponible. Recuperación de listas de reproducción en progreso.", "587b57ced54965d8874c3fd0e9dfedb987e5df04": "No tienes suscripciones a listas de reproducción.", "7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Buscar", - "2054791b822475aeaea95c0119113de3200f5e1c": "Longitud:", + "2054791b822475aeaea95c0119113de3200f5e1c": "Duración:", "94e01842dcee90531caa52e4147f70679bac87fe": "Eliminar y volver a descargar", "2031adb51e07a41844e8ba7704b054e98345c9c1": "Borrar para siempre" } \ No newline at end of file diff --git a/src/assets/i18n/messages.es.xlf b/src/assets/i18n/messages.es.xlf index 0fbee7f..ac71d6b 100644 --- a/src/assets/i18n/messages.es.xlf +++ b/src/assets/i18n/messages.es.xlf @@ -1,987 +1,848 @@ - - - - - - Create a playlist - - app/create-playlist/create-playlist.component.html - 1 - - Create a playlist dialog title - Crea una lista de reproducción - - - Name - - app/create-playlist/create-playlist.component.html - 5 - - Playlist name placeholder - Nombre - - - Audio files - - app/create-playlist/create-playlist.component.html - 10 - - Audio files title - Archivos de sonido - - - Videos - - app/create-playlist/create-playlist.component.html - 11 - - - app/subscription/subscription/subscription.component.html - 15 - - Videos title - Archivos de video - - - Youtube Downloader - - app/main/main.component.html - 5 - - Youtube downloader home page label - Descargador de Youtube - - - Please enter a valid URL! - - app/main/main.component.html - 16 - - Enter valid URL error - Por favor entre una URL válida - - - Quality - - app/main/main.component.html - 24 - - Quality select label - Calidad - - - Use URL - - app/main/main.component.html - 52 - - YT search Use URL button for searched video - Usa URL - - - View - - app/main/main.component.html - 55 - - YT search View button for searched video - Ver - - - Only Audio - - app/main/main.component.html - 65 - - Only Audio checkbox - Solo audio - - - Multi-download Mode - - app/main/main.component.html - 70 - - Multi-download Mode checkbox - Descarga múltiple - - - Download - - app/main/main.component.html - 79 - - Main download button - Descarga - - - Cancel - - app/main/main.component.html - 84 - - Cancel download button - Cancela - - - Advanced - - app/main/main.component.html - 96 - - Advanced download mode panel - Avanzado - - - Simulated command: - - app/main/main.component.html - 102 - - Simulated command label - Commando simulado: - - - Use custom args - - app/main/main.component.html - 110 - - Use custom args checkbox - Usar argumentos personalizados - - - Custom args - - app/main/main.component.html - 115 - - - app/settings/settings.component.html - 83 - - Custom args placeholder - Argumentos personalizados - - - No need to include URL, just everything after. - - app/main/main.component.html - 117 - - Custom Args input hint - No es necesario incluir URL, solo todo después - - - Use custom output - - app/main/main.component.html - 125 - - Use custom output checkbox - Usar salida personalizada - - - Custom output - - app/main/main.component.html - 130 - - Custom output placeholder - Salida personalizada - - - Documentation - - app/main/main.component.html - 132 - - Youtube-dl output template documentation link - Documentación - - - Path is relative to the config download path. Don't include extension. - - app/main/main.component.html - 133 - - Custom Output input hint - La ruta es relativa a la ruta de descarga de la config. No incluya el extensión. - - - Use authentication - - app/main/main.component.html - 139 - - Use authentication checkbox - Usa autenticación - - - Username - - app/main/main.component.html - 144 - - YT Username placeholder - Nombre de usuario - - - Password - - app/main/main.component.html - 149 - - YT Password placeholder - Contraseña - - - Audio - - app/main/main.component.html - 193 - - Audio files title - Audio - - - Your audio files are here - - app/main/main.component.html - 198 - - Audio files description - Tus archivos de audio están aquí - - - Playlists - - app/main/main.component.html - 213 - - - app/main/main.component.html - 255 - - - app/subscriptions/subscriptions.component.html - 27 - - Playlists title - Listas de reproducción - - - No playlists available. Create one from your downloading audio files by clicking the blue plus button. - - app/main/main.component.html - 224 - - No video playlists available text - No hay listas de reproducción disponibles. Cree uno de tus archivos de audio haciendo clic en el botón azul más. - - - Video - - app/main/main.component.html - 234 - - Video files title - Vídeo - - - Your video files are here - - app/main/main.component.html - 239 - - Video files description - Tus archivos de video son aquí - - - No playlists available. Create one from your downloading video files by clicking the blue plus button. - - app/main/main.component.html - 268 - - No video playlists available text - No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más. - - - ID: - - app/file-card/file-card.component.html - 6 - - - app/download-item/download-item.component.html - 7 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 13 - - File or playlist ID - ID: - - - Count: - - app/file-card/file-card.component.html - 7 - - Playlist video count - Cuenta: - - - Settings - - app/settings/settings.component.html - 1 - - - app/app.component.html - 22 - - Settings title - Configuraciones - - - Host - - app/settings/settings.component.html - 8 - - Host settings title - Host - - - URL - - app/settings/settings.component.html - 15 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 8 - - URL input placeholder - URL - - - URL this app will be accessed from, without the port. - - app/settings/settings.component.html - 16 - - URL setting input hint - URL desde la que se accederá a esta aplicación, sin el puerto. - - - Port - - app/settings/settings.component.html - 21 - - Port input placeholder - Puerto - - - The desired port. Default is 17442. - - app/settings/settings.component.html - 22 - - Port setting input hint - Puerto deseado. El valor predeterminado es 17442. - - - Encryption - - app/settings/settings.component.html - 34 - - Encryption settings title - Cifrado - - - Use encryption - - app/settings/settings.component.html - 40 - - Use encryption setting - Usa cifrado - - - Cert file path - - app/settings/settings.component.html - 45 - - Cert file path input placeholder - Ruta del archivo de certificado - - - Key file path - - app/settings/settings.component.html - 51 - - Key file path input placeholder - Ruta de archivo de clave - - - Downloader - - app/settings/settings.component.html - 62 - - Downloader settings title - Descargador - - - Audio folder path - - app/settings/settings.component.html - 69 - - Audio folder path input placeholder - Ruta de la carpeta de audio - - - Path for audio only downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 70 - - Aduio path setting input hint - Ruta para descargas de solo audio. Es relativo a la carpeta raíz de YTDL-Material. - - - Video folder path - - app/settings/settings.component.html - 76 - - Video folder path input placeholder - Ruta de la carpeta de video - - - Path for video downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 77 - - Video path setting input hint - Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material. - - - Global custom args for downloads on the home page. - - app/settings/settings.component.html - 84 - - Custom args setting input hint - Argumentos personalizados globales para descargas en la página de inicio. - - - Extra - - app/settings/settings.component.html - 95 - - Extra settings title - Extra - - - Top title - - app/settings/settings.component.html - 102 - - Top title input placeholder - Título superior - - - File manager enabled - - app/settings/settings.component.html - 107 - - File manager enabled setting - Administrador de archivos habilitado - - - Allow quality select - - app/settings/settings.component.html - 110 - - Allow quality seelct setting - Permitir selección de calidad - - - Download only mode - - app/settings/settings.component.html - 113 - - Download only mode setting - Modo de solo descarga - - - Allow multi-download mode - - app/settings/settings.component.html - 116 - - Allow multi-downloade mode setting - Permitir el modo de descarga múltiple - - - API - - app/settings/settings.component.html - 126 - - API settings title - API - - - Use YouTube API - - app/settings/settings.component.html - 132 - - Use YouTube API setting - Utilizar la API de YouTube - - - Youtube API Key - - app/settings/settings.component.html - 136 - - Youtube API Key setting placeholder - Clave API de YouTube - - - Generating a key is easy! - - app/settings/settings.component.html - 137 - - Youtube API Key setting hint - ¡Generar una clave es fácil! - - - Themes - - app/settings/settings.component.html - 148 - - Themes settings title - Temas - - - Default - - app/settings/settings.component.html - 155 - - Default theme label - Defecto - - - Dark - - app/settings/settings.component.html - 156 - - - app/app.component.html - 17 - - Dark theme label - Oscura - - - Allow theme change - - app/settings/settings.component.html - 161 - - Allow theme change setting - Permitir cambio de tema - - - Subscriptions - - app/settings/settings.component.html - 171 - - - app/app.component.html - 34 - - Subscriptions settings title - Suscripciones - - - Allow subscriptions - - app/settings/settings.component.html - 177 - - Allow subscriptions setting - Permitir suscripciones - - - Subscriptions base path - - app/settings/settings.component.html - 181 - - Subscriptions base path input setting placeholder - Ruta base de suscripciones - - - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 182 - - Subscriptions base path setting input hint - Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material. - - - Check interval - - app/settings/settings.component.html - 187 - - Check interval input setting placeholder - Intervalo de comprobación - - - Unit is seconds, only include numbers. - - app/settings/settings.component.html - 188 - - Check interval setting input hint - La unidad es segundos, solo incluye números. - - - Use youtube-dl archive - - app/settings/settings.component.html - 192 - - Use youtube-dl archive setting - Usa el archivo de youtube-dl - - - With youtube-dl's archive - - app/settings/settings.component.html - 193 - - youtube-dl archive explanation prefix link - Con la función de archivo de youtube-dl, - - - feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. - - app/settings/settings.component.html - 193 - - youtube-dl archive explanation middle - los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones. - - - This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. - - app/settings/settings.component.html - 194 - - youtube-dl archive explanation suffix - Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos. - - - Advanced - - app/settings/settings.component.html - 204 - - Advanced settings title - Avanzado - - - Use default downloading agent - - app/settings/settings.component.html - 210 - - Use default downloading agent setting - Usar agente de descarga predeterminado - - - Custom agent - - app/settings/settings.component.html - 214 - - Custom agent setting placeholder - Agente personalizado - - - Allow advanced download - - app/settings/settings.component.html - 219 - - Allow advanced downloading setting - Permitir descarga avanzada - - - Save - - app/settings/settings.component.html - 229 - - Settings save button - Salvar - - - Cancel - - app/settings/settings.component.html - 232 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 37 - - Settings cancel button - Cancelar - - - Home - - app/app.component.html - 33 - - Navigation menu Home Page title - Inicio - - - Save changes - - app/player/player.component.html - 22 - - Playlist save changes button - Guardar cambios - - - Subscribe to playlist or channel - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 1 - - Subscribe dialog title - Suscríbase a la lista de reproducción o al canal - - - The playlist or channel URL - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 9 - - Subscription URL input hint - La lista de reproducción o la URL del canal - - - Custom name - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 14 - - Subscription custom name placeholder - Nombre personalizado - - - This is optional - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 15 - - Custom name input hint - Esto es opcional - - - Download all uploads - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 19 - - Download all uploads subscription setting - Descargar todas las cargas - - - Download videos uploaded in the last - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 22 - - Download time range prefix - Descargar videos subidos en el último - - - Type: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 5 - - Subscription type property - Tipo: - - - URL: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 9 - - Subscription URL property - URL: - - - Archive: - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 17 - - Subscription ID property - Archivo: - - - Close - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 23 - - Close subscription info button - Cerca - - - Export Archive - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 24 - - Export Archive button - Exportar el archivo - - - Unsubscribe - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 26 - - Unsubscribe button - Darse de baja - - - Your subscriptions - - app/subscriptions/subscriptions.component.html - 3 - - Subscriptions title - Sus suscripciones - - - Channels - - app/subscriptions/subscriptions.component.html - 8 - - Subscriptions channels title - Canales - - - Name not available. Channel retrieval in progress. - - app/subscriptions/subscriptions.component.html - 14 - - Subscription playlist not available text - Nombre no disponible. Recuperación de canales en progreso. - - - You have no channel subscriptions. - - app/subscriptions/subscriptions.component.html - 24 - - No channel subscriptions text - No tienes suscripciones de canal. - - - Name not available. Playlist retrieval in progress. - - app/subscriptions/subscriptions.component.html - 33 - - Subscription playlist not available text - Nombre no disponible. Recuperación de listas de reproducción en progreso. - - - You have no playlist subscriptions. - - app/subscriptions/subscriptions.component.html - 43 - - No playlist subscriptions text - No tienes suscripciones a listas de reproducción. - - - Search - - app/subscription/subscription/subscription.component.html - 19 - - Subscription videos search placeholder - Buscar - - - Length: - - app/subscription/subscription-file-card/subscription-file-card.component.html - 3 - - Video duration label - Duración: - - - Delete and redownload - - app/subscription/subscription-file-card/subscription-file-card.component.html - 7 - - Delete and redownload subscription video button - Eliminar y volver a descargar - - - Delete forever - - app/subscription/subscription-file-card/subscription-file-card.component.html - 8 - - Delete forever subscription video button - Borrar para siempre - - - - \ No newline at end of file + + + + + + Create a playlist + Crea una lista de reproducción + Create a playlist dialog title + string + rsNew + + + Name + Nombre + Playlist name placeholder + string + rsNew + + + Audio files + Archivos de sonido + Audio files title + string + rsNew + + + Videos + Archivos de video + Videos title + string + rsNew + + + Youtube Downloader + Descargador de Youtube + Youtube downloader home page label + string + rsNew + + + Please enter a valid URL! + Por favor entre una URL válida + Enter valid URL error + string + rsNew + + + Quality + Calidad: + Quality select label + string + rsNew + + + Use URL + Usa URL + YT search Use URL button for searched video + string + rsNew + + + View + Ver: + YT search View button for searched video + string + rsNew + + + Only Audio + Solo audio + Only Audio checkbox + string + rsNew + + + Multi-download Mode + Descarga múltiple + Multi-download Mode checkbox + string + rsNew + + + Download + Descarga + Main download button + string + rsNew + + + Cancel + Cancelar + Cancel download button + string + rsNew + + + Advanced + Avanzado + Advanced download mode panel + string + rsNew + + + Simulated command: + Commando simulado: + Simulated command label + string + rsNew + + + Use custom args + Usar argumentos personalizados + Use custom args checkbox + string + rsNew + + + Custom args + Argumentos personalizados + Custom args placeholder + string + rsNew + + + No need to include URL, just everything after. + No es necesario incluir URL, solo todo después + Custom Args input hint + string + rsNew + + + Use custom output + Usar salida personalizada + Use custom output checkbox + string + rsNew + + + Custom output + Salida personalizada + Custom output placeholder + string + rsNew + + + Documentation + Documentación + Youtube-dl output template documentation link + string + rsNew + + + Path is relative to the config download path. Don't include extension. + La ruta es relativa a la ruta de descarga de la config. No incluya el extensión. + Custom Output input hint + string + rsNew + + + Use authentication + Usa autenticación + Use authentication checkbox + string + rsNew + + + Username + Nombre de usuario + YT Username placeholder + string + rsNew + + + Password + Contraseña + YT Password placeholder + string + rsNew + + + Audio + Audio + Audio files title + string + rsNew + + + Your audio files are here + Tus archivos de audio están aquí + Audio files description + string + rsNew + + + Playlists + Listas de reproducción + Playlists title + string + rsNew + + + No playlists available. Create one from your downloading audio files by clicking the blue plus button. + No hay listas de reproducción disponibles. Cree uno de tus archivos de audio haciendo clic en el botón azul más. + No video playlists available text + string + rsNew + + + Video + Vídeo + Video files title + string + rsNew + + + Your video files are here + Tus archivos de video son aquí + Video files description + string + rsNew + + + No playlists available. Create one from your downloading video files by clicking the blue plus button. + No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más. + No video playlists available text + string + rsNew + + + Name: + Nombre: + Video name property + string + rsNew + + + URL: + URL: + Video URL property + string + rsNew + + + Uploader: + Cargador: + Video ID property + string + rsNew + + + File size: + Tamaño del archivo: + Video file size property + string + rsNew + + + Path: + Ruta: + Video path property + string + rsNew + + + Upload Date: + Subido: + Video upload date property + string + rsNew + + + Close + Cerca + Close subscription info button + string + rsNew + + + ID: + ID: + File or playlist ID + string + rsNew + + + Count: + Cuenta: + Playlist video count + string + rsNew + + + Info + Información + Video info button + string + rsNew + + + Delete + Eliminar + Delete video button + string + rsNew + + + Delete and blacklist + Eliminar y pones en la lista negra + Delete and blacklist video button + string + rsNew + + + Settings + Configuraciones + Settings title + string + rsNew + + + Host + Host + Host settings title + string + rsNew + + + URL + URL + URL input placeholder + string + rsNew + + + URL this app will be accessed from, without the port. + URL desde la que se accederá a esta aplicación, sin el puerto. + URL setting input hint + string + rsNew + + + Port + Puerto + Port input placeholder + string + rsNew + + + The desired port. Default is 17442. + Puerto deseado. El valor predeterminado es 17442. + Port setting input hint + string + rsNew + + + Encryption + Cifrado + Encryption settings title + string + rsNew + + + Use encryption + Usa cifrado + Use encryption setting + string + rsNew + + + Cert file path + Ruta del archivo de certificado + Cert file path input placeholder + string + rsNew + + + Key file path + Ruta de archivo de clave + Key file path input placeholder + string + rsNew + + + Downloader + Descargador + Downloader settings title + string + rsNew + + + Audio folder path + Ruta de la carpeta de audio + Audio folder path input placeholder + string + rsNew + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + Ruta para descargas de solo audio. Es relativo a la carpeta raíz de YTDL-Material. + Aduio path setting input hint + string + rsNew + + + Video folder path + Ruta de la carpeta de video + Video folder path input placeholder + string + rsNew + + + Path for video downloads. It is relative to YTDL-Material's root folder. + Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material. + Video path setting input hint + string + rsNew + + + Global custom args for downloads on the home page. + Argumentos personalizados globales para descargas en la página de inicio. + Custom args setting input hint + string + rsNew + + + Use youtube-dl archive + Usa el archivo de youtube-dl + Use youtubedl archive setting + string + rsNew + + + Extra + Extra + Extra settings title + string + rsNew + + + Top title + Título superior + Top title input placeholder + string + rsNew + + + File manager enabled + Administrador de archivos habilitado + File manager enabled setting + string + rsNew + + + Allow quality select + Permitir selección de calidad + Allow quality seelct setting + string + rsNew + + + Download only mode + Modo de solo descarga + Download only mode setting + string + rsNew + + + Allow multi-download mode + Permitir el modo de descarga múltiple + Allow multi-downloade mode setting + string + rsNew + + + API + API + API settings title + string + rsNew + + + Use YouTube API + Utilizar la API de YouTube + Use YouTube API setting + string + rsNew + + + Youtube API Key + Clave API de YouTube + Youtube API Key setting placeholder + string + rsNew + + + Generating a key is easy! + ¡Generar una clave es fácil! + Youtube API Key setting hint + string + rsNew + + + Themes + Temas + Themes settings title + string + rsNew + + + Default + Defecto + Default theme label + string + rsNew + + + Dark + Oscura + Dark theme label + string + rsNew + + + Allow theme change + Permitir cambio de tema + Allow theme change setting + string + rsNew + + + Subscriptions + Suscripciones + Subscriptions settings title + string + rsNew + + + Allow subscriptions + Permitir suscripciones + Allow subscriptions setting + string + rsNew + + + Subscriptions base path + Ruta base de suscripciones + Subscriptions base path input setting placeholder + string + rsNew + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material. + Subscriptions base path setting input hint + string + rsNew + + + Check interval + Intervalo de comprobación + Check interval input setting placeholder + string + rsNew + + + Unit is seconds, only include numbers. + La unidad es segundos, solo incluye números. + Check interval setting input hint + string + rsNew + + + With youtube-dl's archive + Con la función de archivo de youtube-dl, + youtube-dl archive explanation prefix link + string + rsNew + + + feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. + los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones. + youtube-dl archive explanation middle + string + rsNew + + + This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. + Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos. + youtube-dl archive explanation suffix + string + rsNew + + + Advanced + Avanzado + Advanced settings title + string + rsNew + 1 + + + Use default downloading agent + Usar agente de descarga predeterminado + Use default downloading agent setting + string + rsNew + + + Allow advanced download + Permitir descarga avanzada + Allow advanced downloading setting + string + rsNew + + + Save + Salvar + Settings save button + string + rsNew + + + Cancel + Cancelar + Settings cancel button + string + rsNew + + + About YoutubeDL-Material + Sobre YoutubeDL-Material + About dialog title + string + rsNew + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + es un descargador de código abierto de YouTube creado bajo las especificaciones de "Material Design" de Google. Puede descargar sin problemas sus videos favoritos como archivos de video o audio, e incluso suscribirse a sus canales favoritos y listas de reproducción para mantenerse actualizado con sus nuevos videos. + About first paragraph + string + rsNew + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon below. + tiene algunas características increíbles incluidas! Una amplia API, soporte de Docker y soporte de localización (traducción). Lea todas las funciones admitidas haciendo clic en el icono de GitHub a continuación. + About second paragraph + string + rsNew + + + Found a bug or have a suggestion? + ¿Encontró un error o tiene una sugerencia? + About bug prefix + string + rsNew + + + Click here + ¡Haga clic aquí + About bug click here + string + rsNew + + + to create an issue! + para crear una cuestión! + About bug suffix + string + rsNew + + + Installed version: + Versión instalada: + Version label + string + rsNew + + + View latest update + Ver la última actualización + View latest update + string + rsNew + + + About + Sobre + About menu label + string + rsNew + + + Home + Inicio + Navigation menu Home Page title + string + rsNew + + + Save changes + Guardar cambios + Playlist save changes button + string + rsNew + + + Subscribe to playlist or channel + Suscríbase a la lista de reproducción o al canal + Subscribe dialog title + string + rsNew + + + The playlist or channel URL + La lista de reproducción o la URL del canal + Subscription URL input hint + string + rsNew + + + Custom name + Nombre personalizado + Subscription custom name placeholder + string + rsNew + + + This is optional + Esto es opcional + Custom name input hint + string + rsNew + + + Download all uploads + Descargar todas las cargas + Download all uploads subscription setting + string + rsNew + + + Download videos uploaded in the last + Descargar videos subidos en el último + Download time range prefix + string + rsNew + + + Type: + Tipo: + Subscription type property + string + rsNew + + + Archive: + Archivo: + Subscription ID property + string + rsNew + + + Export Archive + Exportar el archivo + Export Archive button + string + rsNew + + + Unsubscribe + Darse de baja + Unsubscribe button + string + rsNew + + + Your subscriptions + Sus suscripciones + Subscriptions title + string + rsNew + + + Channels + Canales + Subscriptions channels title + string + rsNew + + + Name not available. Channel retrieval in progress. + Nombre no disponible. Recuperación de canales en progreso. + Subscription playlist not available text + string + rsNew + + + You have no channel subscriptions. + No tienes suscripciones de canal. + No channel subscriptions text + string + rsNew + + + Name not available. Playlist retrieval in progress. + Nombre no disponible. Recuperación de listas de reproducción en progreso. + Subscription playlist not available text + string + rsNew + + + You have no playlist subscriptions. + No tienes suscripciones a listas de reproducción. + No playlist subscriptions text + string + rsNew + + + Search + Buscar + Subscription videos search placeholder + string + rsNew + + + Length: + Duración: + Video duration label + string + rsNew + + + Delete and redownload + Eliminar y volver a descargar + Delete and redownload subscription video button + string + rsNew + + + Delete forever + Borrar para siempre + Delete forever subscription video button + string + rsNew + + + + From c3220dcb60171d6c59e409a6801d27f5c41a0480 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 20 Mar 2020 16:35:28 -0400 Subject: [PATCH 48/48] Updated language select size --- src/app/settings/settings.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index 2c6e6cf..9547506 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -4,7 +4,7 @@ .locale-select { margin-bottom: 10px; - width: 175px; + width: 130px; } .ext-divider {