first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{compose.yaml,compose.*.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,38 @@
|
||||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/routing ###
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
DEFAULT_URI=http://localhost
|
||||
###< symfony/routing ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
DATABASE_URL=mysql://root:root@172.28.2.1:3306/anyvideo?mariadb-10.4.34
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_SECRET=1f540f22d77c65ca6c8fc867a45a7eb3
|
||||
###< symfony/framework-bundle ###
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
@@ -0,0 +1,77 @@
|
||||
sudo apt update && sudo apt install jq
|
||||
|
||||
|
||||
|
||||
//pour audio la meilleur qualité
|
||||
yt-dlp -J "https://www.youtube.com/watch?v=udjFBEM85to" | jq '
|
||||
.formats
|
||||
| map(select(.vcodec=="none" and .acodec!="none"))
|
||||
| max_by(.abr // 0)
|
||||
| {id: .format_id, ext, abr, codec: .acodec,
|
||||
size_mb: ((.filesize // .filesize_approx // 0)/1024/1024|floor)}'
|
||||
|
||||
|
||||
|
||||
///pour le vidéo pour chaque format la meilleur qualité
|
||||
yt-dlp -J "https://www.youtube.com/watch?v=udjFBEM85to" | jq '
|
||||
.formats
|
||||
| map(select(.acodec=="none" and .vcodec!="none"))
|
||||
| group_by(.height)
|
||||
| map(max_by((.tbr // 0), (.filesize // .filesize_approx // 0)))
|
||||
| sort_by(.height)
|
||||
| map({
|
||||
id: .format_id,
|
||||
ext,
|
||||
resolution,
|
||||
fps,
|
||||
vcodec,
|
||||
size_mb: ((.filesize // .filesize_approx // 0)/1024/1024|floor)
|
||||
})
|
||||
'
|
||||
|
||||
|
||||
yt-dlp -f 137+140 URL
|
||||
|
||||
//la taille en Mo
|
||||
echo $(($(yt-dlp --print "%(filesize,filesize_approx)s" "https://www.youtube.com/watch?v=r8gPG3JrNhw") / 1024 / 1024))
|
||||
|
||||
|
||||
yt-dlp --newline "https://www.4s.io/video/k2gW_G5Yku/amira_w_dofda3.html"
|
||||
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
$url = $_POST['url'];
|
||||
|
||||
$cmd = "yt-dlp --newline " . escapeshellarg($url) . " 2>&1";
|
||||
$handle = popen($cmd, "r");
|
||||
|
||||
$socket = fsockopen("127.0.0.1", 8080);
|
||||
|
||||
function ws_send($socket, $msg) {
|
||||
fwrite($socket, $msg . "\n");
|
||||
}
|
||||
|
||||
while (!feof($handle)) {
|
||||
$line = fgets($handle);
|
||||
|
||||
if (preg_match('/(\d+\.?\d*)%/', $line, $m)) {
|
||||
$data = json_encode([
|
||||
"progress" => (float)$m[1],
|
||||
"line" => trim($line)
|
||||
]);
|
||||
|
||||
ws_send($socket, $data);
|
||||
}
|
||||
}
|
||||
|
||||
pclose($handle);
|
||||
|
||||
ws_send($socket, json_encode([
|
||||
"progress" => 100,
|
||||
"status" => "done"
|
||||
]));
|
||||
|
||||
fclose($socket);
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
|
||||
return new Application($kernel);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"doctrine/doctrine-bundle": "^3.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||
"doctrine/orm": "^3.6",
|
||||
"symfony/asset": "7.4.*",
|
||||
"symfony/console": "7.4.*",
|
||||
"symfony/doctrine-messenger": "7.4.*",
|
||||
"symfony/dotenv": "7.4.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.4.*",
|
||||
"symfony/framework-bundle": "7.4.*",
|
||||
"symfony/messenger": "7.4.*",
|
||||
"symfony/mime": "7.4.*",
|
||||
"symfony/process": "7.4.*",
|
||||
"symfony/runtime": "7.4.*",
|
||||
"symfony/security-bundle": "7.4.*",
|
||||
"symfony/translation": "7.4.*",
|
||||
"symfony/twig-bundle": "7.4.*",
|
||||
"symfony/validator": "7.4.*",
|
||||
"symfony/yaml": "7.4.*"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"bump-after-update": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.4.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/maker-bundle": "^1.67",
|
||||
"symfony/stopwatch": "7.4.*",
|
||||
"symfony/web-profiler-bundle": "7.4.*"
|
||||
}
|
||||
}
|
||||
Generated
+6401
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
||||
@@ -0,0 +1,11 @@
|
||||
# Enable stateless CSRF protection for forms and logins/logouts
|
||||
framework:
|
||||
form:
|
||||
csrf_protection:
|
||||
token_id: submit
|
||||
|
||||
csrf_protection:
|
||||
stateless_token_ids:
|
||||
- submit
|
||||
- authenticate
|
||||
- logout
|
||||
@@ -0,0 +1,46 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
orm:
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
@@ -0,0 +1,6 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
||||
@@ -0,0 +1,15 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
@@ -0,0 +1,15 @@
|
||||
framework:
|
||||
messenger:
|
||||
transports:
|
||||
# 1. On définit le nom du transport
|
||||
async:
|
||||
# 2. On définit son DSN (en retrait)
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
|
||||
# 3. On définit sa stratégie de retry (au même niveau que dsn)
|
||||
retry_strategy:
|
||||
max_retries: 0
|
||||
|
||||
routing:
|
||||
# Tout message de ce type sera envoyé vers le transport 'async'
|
||||
'App\Entity\UserDownloader': async
|
||||
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
@@ -0,0 +1,10 @@
|
||||
framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
||||
@@ -0,0 +1,41 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: email
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
custom_authenticator: App\Security\LoginAuthenticator
|
||||
logout:
|
||||
path: security_logout
|
||||
remember_me:
|
||||
secret: '%kernel.secret%' # required
|
||||
always_remember_me: true
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/admin, roles: ROLE_ADMIN }
|
||||
- { path: ^/partner, roles: ROLE_PARTNER }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# By default, password hashers are resource intensive and take time. This is
|
||||
# important to generate secure password hashes. In tests however, secure hashes
|
||||
# are not important, waste resources and increase test times. The following
|
||||
# reduces the work factor to the lowest possible values.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
@@ -0,0 +1,5 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
providers:
|
||||
@@ -0,0 +1,9 @@
|
||||
twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
form_themes:
|
||||
- 'form_div_layout.html.twig' # Default
|
||||
- 'bootstrap_5_layout.html.twig' # Activates Bootstrap 5 formatting
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
@@ -0,0 +1,11 @@
|
||||
framework:
|
||||
validation:
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
||||
@@ -0,0 +1,13 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
profiler:
|
||||
collect: false
|
||||
collect_serializer_data: true
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||
|
||||
# This file is the entry point to configure the routes of your app.
|
||||
# Methods with the #[Route] attribute are automatically imported.
|
||||
# See also https://symfony.com/doc/current/routing.html
|
||||
|
||||
# To list all registered routes, run the following command:
|
||||
# bin/console debug:router
|
||||
|
||||
controllers:
|
||||
resource: routing.controllers
|
||||
@@ -0,0 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
@@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
@@ -0,0 +1,23 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
|
||||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
# See also https://symfony.com/doc/current/service_container/import.html
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
@@ -0,0 +1,371 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(145deg, #f5f7fa 0%, #e9edf5 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
/* ← CORRECTION ICI */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ---------- BARRE HORIZONTALE TOUT EN HAUT ---------- */
|
||||
.top-header-bar {
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
border-bottom: 1px solid #eef2ff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
/* ← une seule déclaration */
|
||||
}
|
||||
|
||||
.top-bar-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0.9rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Section langue statique (sans JS) */
|
||||
.static-lang {
|
||||
background: #f1f4f9;
|
||||
padding: 0.5rem 1.3rem;
|
||||
border-radius: 3rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: #1e293b;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.static-lang a {
|
||||
text-decoration: none;
|
||||
font-size: 23px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.main-alert {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
padding: 2rem 0rem 0rem;
|
||||
align-self: center;
|
||||
|
||||
/* Hauteur fixe pour réserver l’espace, même vide */
|
||||
min-height: 90px;
|
||||
/* Ajustez selon la hauteur réelle de votre alerte */
|
||||
}
|
||||
|
||||
/* Quand l’alerte est absente ou vide, on cache son contenu mais on garde l’espace */
|
||||
.main-alert:empty {
|
||||
visibility: hidden;
|
||||
/* optionnel : pour masquer complètement tout contenu fantôme */
|
||||
}
|
||||
|
||||
/* Conteneur principal (centré, avec marge supérieure) */
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 25px 50px -10px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 2.5rem;
|
||||
padding: 2rem 2rem 2.5rem;
|
||||
margin: 1rem 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
transition: all 0.2s ease;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 2.8rem;
|
||||
color: #ef4444;
|
||||
filter: drop-shadow(0 4px 6px rgba(239, 68, 68, 0.3));
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #1e293b, #4f46e5);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.url-section {
|
||||
background: #f8fafc;
|
||||
border-radius: 2rem;
|
||||
padding: 1.5rem;
|
||||
margin: 1.8rem 0 1.5rem;
|
||||
}
|
||||
|
||||
.url-input-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 1.2rem;
|
||||
padding: 1rem;
|
||||
font-size: 0.95rem;
|
||||
resize: vertical;
|
||||
min-height: 90px;
|
||||
background: white;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
.format-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.7rem 1.2rem;
|
||||
border-radius: 2rem;
|
||||
border: 1px solid #d1d5db;
|
||||
background: white;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.process-btn {
|
||||
background: #0f172a;
|
||||
border: none;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
padding: 0.8rem 2.2rem;
|
||||
border-radius: 3rem;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: 0.25s;
|
||||
box-shadow: 0 10px 20px -8px rgba(15, 23, 42, 0.4);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.process-btn:hover {
|
||||
background: #1e293b;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.process-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
|
||||
.video-item {
|
||||
background: white;
|
||||
border-radius: 1.2rem;
|
||||
padding: 1rem 1.2rem;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid #edf2f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.video-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-name {
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #e9eef3;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, #4f46e5, #818cf8);
|
||||
border-radius: 20px;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.progress-fill.error {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
/* Styles pour la barre de progression en mode indéterminé */
|
||||
.progress-container .progress-fill.indeterminate {
|
||||
width: 100%;
|
||||
background-image: linear-gradient(90deg,
|
||||
#4f46e5 0%,
|
||||
#818cf8 25%,
|
||||
#4f46e5 50%,
|
||||
#818cf8 75%,
|
||||
#4f46e5 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pending-text {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.4rem 1.2rem;
|
||||
border-radius: 2rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background: #334155;
|
||||
}
|
||||
|
||||
/* RESPONSIVE : barre horizontale adaptative */
|
||||
@media (max-width: 768px) {
|
||||
.top-bar-container {
|
||||
padding: 0.8rem 1rem;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.static-lang,
|
||||
.demo-status {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.format-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.process-btn {
|
||||
margin-left: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding: 1.5rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.top-bar-container {
|
||||
padding: 0.7rem 0.9rem;
|
||||
}
|
||||
|
||||
.static-lang,
|
||||
.demo-status {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.4rem 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/* ── Page header ── */
|
||||
.history-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.8rem;
|
||||
padding-bottom: 1.2rem;
|
||||
border-bottom: 1px solid #eef2ff;
|
||||
}
|
||||
|
||||
.history-page-title {
|
||||
font-size: 1.55rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.history-page-title i {
|
||||
color: #4f46e5;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.history-badge {
|
||||
background: #eef2ff;
|
||||
color: #4f46e5;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
/* ── Search / filter bar ── */
|
||||
.history-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.history-search {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.history-search input {
|
||||
width: 100%;
|
||||
padding: 0.6rem 1rem 0.6rem 2.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.9rem;
|
||||
background: #f8fafc;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
min-height: unset;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.history-search input:focus {
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.history-search .search-icon {
|
||||
position: absolute;
|
||||
left: 0.9rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .history-search input {
|
||||
padding: 0.6rem 2.5rem 0.6rem 1rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .history-search .search-icon {
|
||||
left: unset;
|
||||
right: 0.9rem;
|
||||
}
|
||||
|
||||
.history-sort-select {
|
||||
padding: 0.6rem 1.1rem;
|
||||
border-radius: 2rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.history-sort-select:focus {
|
||||
border-color: #4f46e5;
|
||||
}
|
||||
|
||||
/* ── Table ── */
|
||||
.history-table-wrapper {
|
||||
border-radius: 1.4rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid #edf2f7;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.history-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.history-table thead th {
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
padding: 0.85rem 1.2rem;
|
||||
border-bottom: 1px solid #edf2f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-table tbody tr {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.history-table tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.history-table tbody tr:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.history-table td {
|
||||
padding: 0.9rem 1.2rem;
|
||||
vertical-align: middle;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
/* Name cell */
|
||||
.video-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.video-thumb-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 0.6rem;
|
||||
background: linear-gradient(135deg, #eef2ff, #e0e7ff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.video-thumb-icon i {
|
||||
color: #4f46e5;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.video-title-text {
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
word-break: break-word;
|
||||
max-width: 550px;
|
||||
}
|
||||
|
||||
.video-ext-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
color: #6366f1;
|
||||
background: #eef2ff;
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.1rem 0.45rem;
|
||||
margin-top: 0.18rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Date cell */
|
||||
.date-cell {
|
||||
color: #475569;
|
||||
font-size: 0.88rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-cell .time-sub {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
color: #94a3b8;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
/* Size cell */
|
||||
.size-cell {
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.size-cell .size-unit {
|
||||
font-weight: 400;
|
||||
color: #94a3b8;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.actions-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-action-dl {
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.38rem 1rem;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, transform 0.15s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-action-dl:hover {
|
||||
background: #1e293b;
|
||||
transform: scale(1.03);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-action-del {
|
||||
background: transparent;
|
||||
color: #ef4444;
|
||||
border: 1px solid #fecaca;
|
||||
padding: 0.38rem 0.85rem;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-action-del:hover {
|
||||
background: #fef2f2;
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
/* ── Empty state ── */
|
||||
.history-empty {
|
||||
text-align: center;
|
||||
padding: 3.5rem 1rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.history-empty i {
|
||||
font-size: 3rem;
|
||||
color: #c7d2fe;
|
||||
margin-bottom: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.history-empty p {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.history-empty span {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
/* ── Pagination ── */
|
||||
.history-pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.8rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 0.85rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* ── Delete confirm modal ── */
|
||||
.modal-confirm .modal-header {
|
||||
background: #fff5f5;
|
||||
border-bottom: 1px solid #fee2e2;
|
||||
}
|
||||
|
||||
.modal-confirm .modal-title {
|
||||
color: #dc2626;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.history-table thead th:nth-child(3),
|
||||
.history-table tbody td:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-action-dl span,
|
||||
.btn-action-del span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-title-text {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
.history-table thead th:nth-child(2),
|
||||
.history-table tbody td:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
$(function () {
|
||||
// ------------------------- CONFIGURATION DES ENDPOINTS (à adapter avec vos vraies routes Symfony) -------------------------
|
||||
|
||||
|
||||
// ------------------------- VARIABLES GLOBALES -------------------------
|
||||
let videoEntries = []; // liste des vidéos
|
||||
let entryIdCounter = 0;
|
||||
let isProcessing = false; // file d'attente active ?
|
||||
let isLoggedIn = false; // état connexion simulé (pour démo)
|
||||
|
||||
// Mise à jour affichage connexion
|
||||
function updateAuthUI() {
|
||||
const authDiv = $('#authToggle');
|
||||
if (isLoggedIn) {
|
||||
authDiv.addClass('connected');
|
||||
$('#authStatusText').text('Connecté');
|
||||
} else {
|
||||
authDiv.removeClass('connected');
|
||||
$('#authStatusText').text('Non connecté');
|
||||
}
|
||||
}
|
||||
|
||||
// Récupération des infos vidéo (nom + taille Mo)
|
||||
function fetchVideoInfo(url, format) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Simulation d'appel AJAX vers app_frontend_size_movie
|
||||
|
||||
$.post(ENDPOINTS.getInfo, { url: url }, function (data) {
|
||||
console.log(data);
|
||||
resolve({ name: data.filename, sizeMB: data.size });
|
||||
}).fail(function (response) {
|
||||
reject(new Error("Erreur serveur lors de la récupération des infos"));
|
||||
return;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Téléchargement direct avec progression (streamed)
|
||||
// Téléchargement direct via SSE avec progression réelle
|
||||
function startStreamedDownload(entry, onProgress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const encodedVideoUrl = encodeURIComponent(entry.url);
|
||||
const finalUrl = `${ENDPOINTS.streamDownload}?url=${encodedVideoUrl}&format=${entry.format}`;
|
||||
const eventSource = new EventSource(finalUrl);
|
||||
let isResolved = false;
|
||||
|
||||
// ✅ Fonction de nettoyage centralisée
|
||||
function cleanup(timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
eventSource.close();
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup(null); // timeout déjà déclenché
|
||||
reject(new Error('Délai d\'attente dépassé'));
|
||||
}
|
||||
}, 300000);
|
||||
|
||||
eventSource.onmessage = function (event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.progress !== undefined) {
|
||||
onProgress(data.progress);
|
||||
}
|
||||
|
||||
// ✅ Gérer l'erreur serveur
|
||||
if (data.error === true) {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup(timeout);
|
||||
reject(new Error(data.message || 'Erreur serveur pendant le téléchargement'));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.done === true) {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup(timeout); // ✅ nettoyer le timeout avant resolve
|
||||
updateEntry(entry.id, { pathDownload: data.pathDownload });
|
||||
resolve({ success: true });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[SSE] Erreur parsing JSON:', err);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function () {
|
||||
// ✅ Ne rejeter QUE si pas encore résolu (évite faux reject après done)
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup(timeout);
|
||||
reject(new Error('Erreur de connexion SSE'));
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Téléchargement en tâche de fond (barre infinie)
|
||||
function startBackgroundDownload(entry) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`[BACKGROUND] Lancement tâche de fond pour : ${entry.name}`);
|
||||
// Appel POST vers app_background_download
|
||||
$.post(ENDPOINTS.backgroundDownload, { url: entry.url, format: entry.format }, function (data) {
|
||||
resolve({ success: true, jobId: "bg_" + entry.id });
|
||||
}).fail(function (response) {
|
||||
reject(new Error("Échec du téléchargement en arrière-plan"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Rendu de la liste avec les différents statuts
|
||||
function renderVideoList() {
|
||||
const container = $('#videoListContainer');
|
||||
container.empty();
|
||||
|
||||
videoEntries.forEach(entry => {
|
||||
let progressHtml = '';
|
||||
let statusHtml = '';
|
||||
let actionHtml = '';
|
||||
const status = entry.status;
|
||||
|
||||
// Gestion de la barre de progression
|
||||
if (status === 'downloading') {
|
||||
const pct = entry.progress || 0;
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:${pct}%;"></div></div>`;
|
||||
statusHtml = `<span class="status-text"><i class="fas fa-download"></i> Téléchargement : ${pct}%</span>`;
|
||||
}
|
||||
else if (status === 'background_downloading') {
|
||||
// 🔄 Affiche la barre indéterminée avec la classe 'indeterminate'
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill indeterminate"></div></div>`;
|
||||
statusHtml = `<span class="status-text"><i class="fas fa-spinner spinner-icon"></i> Tâche de fond a été créer. une fois le traitement terminé, vous recevrez un e-mail.</span>`;
|
||||
}
|
||||
else if (status === 'initializing') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:0%;"></div></div>`;
|
||||
statusHtml = `<span class="status-text pending-text"><i class="fas fa-spinner spinner-icon"></i> Initialisation...</span>`;
|
||||
}
|
||||
else if (status === 'pending') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:0%;"></div></div>`;
|
||||
statusHtml = `<span class="status-text pending-text"><i class="fas fa-clock"></i> En attente</span>`;
|
||||
}
|
||||
else if (status === 'completed') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:100%; background:#22c55e;"></div></div>`;
|
||||
statusHtml = `<span class="status-text success-text"><i class="fas fa-check-circle"></i> Terminé</span>`;
|
||||
actionHtml = `<a class="action-btn download-btn" data-id="${entry.id}" href="${entry.pathDownload}"><i class="fas fa-save"></i> Télécharger</a>`;
|
||||
}
|
||||
else if (status === 'error') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill error" style="width:100%;"></div></div>`;
|
||||
const errMsg = entry.errorMessage || "Erreur inconnue";
|
||||
statusHtml = `<span class="status-text error-text"><i class="fas fa-exclamation-triangle"></i> ${escapeHtml(errMsg)}</span>`;
|
||||
}
|
||||
else if (status === 'background_requires_auth') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:0%;"></div></div>`;
|
||||
statusHtml = `<span class="status-text pending-text"><i class="fas fa-lock"></i> Connexion requise pour tâche de fond</span>`;
|
||||
actionHtml = `<button class="action-btn outline" data-id="${entry.id}" data-action="login"><i class="fas fa-sign-in-alt"></i> Connexion</button>`;
|
||||
}
|
||||
else if (status === 'background_ready') {
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:0%;"></div></div>`;
|
||||
statusHtml = `<span class="status-text pending-text"><i class="fas fa-cloud-upload-alt"></i> Prêt pour tâche de fond</span>`;
|
||||
actionHtml = `<button class="btn btn-primary" data-id="${entry.id}" data-action="startBg"><i class="fas fa-play"></i> Lancer</button>`;
|
||||
}
|
||||
else if (status === 'ready_stream') {
|
||||
// Transition rapide, normalement ne devrait pas rester visible
|
||||
statusHtml = `<span class="status-text">Prêt pour téléchargement...</span>`;
|
||||
progressHtml = `<div class="progress-container"><div class="progress-fill" style="width:0%;"></div></div>`;
|
||||
}
|
||||
|
||||
const itemHtml = `
|
||||
<div class="video-item" data-entry-id="${entry.id}">
|
||||
<div class="video-header">
|
||||
<span class="video-name"><i class="fab fa-youtube" style="color:#ef4444; margin-right:6px;"></i>${escapeHtml(entry.name)}</span>
|
||||
${entry.sizeMB ? `<span style="font-size:0.7rem; background:#eef2ff; padding:0.2rem 0.6rem; border-radius:1rem;">${entry.sizeMB} Mo</span>` : ''}
|
||||
</div>
|
||||
${progressHtml}
|
||||
<div class="status-area">
|
||||
${statusHtml}
|
||||
${actionHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.append(itemHtml);
|
||||
});
|
||||
}
|
||||
|
||||
// Mise à jour d'une entrée spécifique et re-rendu
|
||||
function updateEntry(entryId, changes) {
|
||||
const index = videoEntries.findIndex(e => e.id === entryId);
|
||||
if (index !== -1) {
|
||||
Object.assign(videoEntries[index], changes);
|
||||
renderVideoList();
|
||||
}
|
||||
}
|
||||
|
||||
// Ajout des URLs en statut "pending"
|
||||
function addUrls(urlsArray, format) {
|
||||
const newEntries = [];
|
||||
urlsArray.forEach(url => {
|
||||
const trimmed = url.trim();
|
||||
if (trimmed === '') return;
|
||||
const displayName = `Nouvelle vidéo (${trimmed.substring(0, 30)}...)`;
|
||||
newEntries.push({
|
||||
id: ++entryIdCounter,
|
||||
url: trimmed,
|
||||
name: displayName,
|
||||
format: format,
|
||||
status: 'pending',
|
||||
progress: 0,
|
||||
sizeMB: null,
|
||||
errorMessage: ''
|
||||
});
|
||||
});
|
||||
videoEntries.push(...newEntries);
|
||||
renderVideoList();
|
||||
return newEntries;
|
||||
}
|
||||
|
||||
// Traitement d'une vidéo (séquentiel)
|
||||
async function processEntry(entry) {
|
||||
// Évite de traiter une vidéo déjà en cours ou terminée
|
||||
if (entry.status !== 'pending' && entry.status !== 'background_ready' && entry.status !== 'background_requires_auth') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Initialisation : appel à getInfo
|
||||
updateEntry(entry.id, { status: 'initializing', progress: 0, errorMessage: '' });
|
||||
|
||||
try {
|
||||
const info = await fetchVideoInfo(entry.url, entry.format);
|
||||
const sizeMo = info.sizeMB;
|
||||
const videoName = info.name;
|
||||
updateEntry(entry.id, { name: videoName, sizeMB: sizeMo });
|
||||
|
||||
// 2. Décision selon taille
|
||||
if (sizeMo !== null && sizeMo <= 250) {
|
||||
// Taille <= 250 Mo => téléchargement direct (en cours)
|
||||
updateEntry(entry.id, { status: 'downloading', progress: 0 });
|
||||
try {
|
||||
await startStreamedDownload(entry, (prog) => {
|
||||
updateEntry(entry.id, { progress: prog });
|
||||
});
|
||||
updateEntry(entry.id, { status: 'completed', progress: 100 });
|
||||
} catch (err) {
|
||||
updateEntry(entry.id, { status: 'error', errorMessage: `Échec direct : ${err.message}` });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Taille > 250 Mo ou inconnue => tâche de fond
|
||||
const auth = await checkAuth();
|
||||
if (auth) {
|
||||
// Connecté => bouton "Lancer" (en tache de fond prêt)
|
||||
updateEntry(entry.id, { status: 'background_ready' });
|
||||
} else {
|
||||
// Non connecté => bouton "Connexion"
|
||||
updateEntry(entry.id, { status: 'background_requires_auth' });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
updateEntry(entry.id, { status: 'error', errorMessage: `Erreur initialisation : ${err.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Lancement du background après clic sur "Lancer"
|
||||
async function launchBackground(entry) {
|
||||
if (entry.status !== 'background_ready') return;
|
||||
|
||||
try {
|
||||
// 🟢 Passe la vidéo en mode 'background_downloading' pour déclencher l'affichage de la barre indéterminée
|
||||
updateEntry(entry.id, { status: 'background_downloading', progress: 0 });
|
||||
await startBackgroundDownload(entry);
|
||||
} catch (err) {
|
||||
// 🔴 En cas d'erreur, affiche le message d'erreur
|
||||
updateEntry(entry.id, { status: 'error', errorMessage: `Tâche de fond échouée : ${err.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de la file d'attente séquentielle
|
||||
async function processQueue() {
|
||||
if (isProcessing) return;
|
||||
isProcessing = true;
|
||||
|
||||
// On traite uniquement les vidéos en attente ou celles en background_ready/requires_auth ?
|
||||
// Pour éviter de bloquer la queue, on ne traite que les "pending"
|
||||
const pendingEntries = videoEntries.filter(e => e.status === 'pending');
|
||||
for (const entry of pendingEntries) {
|
||||
await processEntry(entry);
|
||||
// Une fois cette vidéo traitée (même si elle est en background_ready ou error), on passe à la suivante
|
||||
// Mais attention : si la vidéo est en background_ready, elle attend une action utilisateur, donc on ne bloque pas la file.
|
||||
}
|
||||
isProcessing = false;
|
||||
}
|
||||
|
||||
// Déclenche l'ajout des URLs et démarre la queue
|
||||
function startNewDownloads(urls, format) {
|
||||
if (urls.length === 0) {
|
||||
alert("Ajoutez au moins une URL YouTube.");
|
||||
return false;
|
||||
}
|
||||
const newEntries = addUrls(urls, format);
|
||||
processQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gestion des clics sur les boutons (connexion, lancer background, téléchargement final)
|
||||
$(document).on('click', '.action-btn', async function (e) {
|
||||
const entryId = $(this).data('id');
|
||||
const action = $(this).data('action');
|
||||
const entry = videoEntries.find(e => e.id === entryId);
|
||||
if (!entry) return;
|
||||
|
||||
if (action === 'login') {
|
||||
window.open(ENDPOINTS.urlAuth, "_self");
|
||||
}
|
||||
else if (action === 'startBg') {
|
||||
if (entry.status === 'background_ready') {
|
||||
await launchBackground(entry);
|
||||
// Après fin du background, la queue pourrait reprendre s'il y a d'autres pending,
|
||||
// mais on ne relance pas automatiquement processQueue pour ne pas interférer.
|
||||
}
|
||||
}
|
||||
/*else if ($(this).hasClass('download-btn') && entry.status === 'completed') {
|
||||
// Téléchargement final du fichier (simulé)
|
||||
//window.open(ENDPOINTS.downloadFileAnonyme, "_self");
|
||||
console.log();
|
||||
//alert(`Téléchargement du fichier : ${entry.name}\nFormat : ${entry.format}\n(Simulation - fichier prêt)`);
|
||||
}*/
|
||||
});
|
||||
|
||||
// Bascule manuelle de connexion (pour démo)
|
||||
$('#authToggle').on('click', function () {
|
||||
isLoggedIn = !isLoggedIn;
|
||||
updateAuthUI();
|
||||
// Pour chaque vidéo en background_requires_auth, on la repasse en background_ready
|
||||
videoEntries.forEach(entry => {
|
||||
if (entry.status === 'background_requires_auth' && isLoggedIn) {
|
||||
updateEntry(entry.id, { status: 'background_ready' });
|
||||
} else if (entry.status === 'background_ready' && !isLoggedIn) {
|
||||
updateEntry(entry.id, { status: 'background_requires_auth' });
|
||||
}
|
||||
});
|
||||
renderVideoList();
|
||||
});
|
||||
|
||||
// Bouton principal "Démarrer le traitement"
|
||||
$('#processBtn').on('click', function () {
|
||||
const urlText = $('#youtubeUrls').val();
|
||||
const urls = urlText.split('\n').filter(line => line.trim() !== '');
|
||||
const format = $('#formatSelect').val();
|
||||
if (urls.length === 0) {
|
||||
alert("Collez au moins une URL YouTube.");
|
||||
return;
|
||||
}
|
||||
startNewDownloads(urls, format);
|
||||
$('#youtubeUrls').val(''); // nettoyer
|
||||
});
|
||||
|
||||
// Utilitaires
|
||||
function escapeHtml(text) {
|
||||
return String(text).replace(/[&<>]/g, function (m) {
|
||||
if (m === '&') return '&';
|
||||
if (m === '<') return '<';
|
||||
if (m === '>') return '>';
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
// Initialisation
|
||||
updateAuthUI();
|
||||
renderVideoList();
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
$(function () {
|
||||
|
||||
/* ── Delete modal: inject video id & name ── */
|
||||
$('#deleteModal').on('show.bs.modal', function (e) {
|
||||
const btn = $(e.relatedTarget);
|
||||
const videoId = btn.data('video-id');
|
||||
const name = btn.data('video-name');
|
||||
const url = '/delete-video/' + videoId; // Adapt to your route
|
||||
$('#deleteModalFileName').text(name);
|
||||
$('#deleteForm').attr('action', url);
|
||||
});
|
||||
|
||||
/* ── Live search ── */
|
||||
$('#searchInput').on('input', function () {
|
||||
const query = $(this).val().toLowerCase().trim();
|
||||
let visible = 0;
|
||||
$('#historyTbody tr').each(function () {
|
||||
const name = $(this).data('name') || '';
|
||||
const match = name.includes(query);
|
||||
$(this).toggle(match);
|
||||
if (match) visible++;
|
||||
});
|
||||
updatePaginationInfo(visible);
|
||||
});
|
||||
|
||||
/* ── Sort ── */
|
||||
$('#sortSelect').on('change', function () {
|
||||
const val = $(this).val();
|
||||
const tbody = $('#historyTbody');
|
||||
const rows = tbody.find('tr').get();
|
||||
|
||||
rows.sort(function (a, b) {
|
||||
switch (val) {
|
||||
case 'date_desc': return $(b).data('date') - $(a).data('date');
|
||||
case 'date_asc': return $(a).data('date') - $(b).data('date');
|
||||
case 'name_asc': return ($(a).data('name') > $(b).data('name')) ? 1 : -1;
|
||||
case 'size_desc': return $(b).data('size') - $(a).data('size');
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
|
||||
tbody.empty();
|
||||
$.each(rows, function (i, row) { tbody.append(row); });
|
||||
});
|
||||
|
||||
function updatePaginationInfo(count) {
|
||||
const total = $('#historyTbody tr').length;
|
||||
// Simple update — replace the number shown; adapt translation if needed
|
||||
$('#paginationInfo').text(count + ' / ' + total);
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,601 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[type=search]::-webkit-search-cancel-button {
|
||||
cursor: pointer;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,598 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[type=search]::-webkit-search-cancel-button {
|
||||
cursor: pointer;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12048
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12021
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4494
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Mark French
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,205 @@
|
||||
# Country Select JS
|
||||
A jQuery plugin for selecting a country, based on the excellent [International Telephone Input](https://github.com/Bluefieldscom/intl-tel-input.git) plugin. It adds a flag dropdown to any input, which lists all the countries in English and the predominant national language next to their flags.
|
||||
|
||||

|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Demo](#demo)
|
||||
- [Features](#features)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Options](#options)
|
||||
- [Public Methods](#public-methods)
|
||||
- [Static Methods](#static-methods)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Contributing](#contributing)
|
||||
- [Attributions](#attributions)
|
||||
|
||||
|
||||
## Demo
|
||||
Try it for yourself using the included demo.html.
|
||||
|
||||
|
||||
## Features
|
||||
* Automatically select the country as the user types
|
||||
* Navigate the country dropdown by typing a country's name, or using up/down keys
|
||||
* Selecting a country from the dropdown will update the country name in the input
|
||||
* Dropdown appears above or below the input depending on available space/scroll position
|
||||
* Lots of initialisation options for customisation, as well as public methods for interaction
|
||||
* Can optionally update a related field with the two-letter ISO country code on selection
|
||||
|
||||
## Getting Started
|
||||
1. Download the [latest version](https://github.com/mrmarkfrench/country-select-js/archive/master.zip)
|
||||
|
||||
2. Link the stylesheet (note that this references the image flags.png)
|
||||
```html
|
||||
<link rel="stylesheet" href="build/css/countrySelect.css">
|
||||
```
|
||||
|
||||
3. Add the plugin script and initialise it on your input element
|
||||
```html
|
||||
<input type="text" id="country">
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
|
||||
<script src="build/js/countrySelect.min.js"></script>
|
||||
<script>
|
||||
$("#country").countrySelect();
|
||||
</script>
|
||||
```
|
||||
|
||||
4. Optional: add an extra input field (with type hidden or text) named the same as your selector input appended with "_code". This will automatically be updated with the [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code for the selected country.
|
||||
```html
|
||||
<input type="text" id="country" />
|
||||
<input type="hidden" id="country_code" />
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
|
||||
<script src="build/js/countrySelect.min.js"></script>
|
||||
<script>
|
||||
$("#country").countrySelect();
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## Options
|
||||
Pass an (optional) object as a parameter to the `countrySelect` method when initializing the selector.
|
||||
```
|
||||
$("#country").countrySelect({
|
||||
defaultCountry: "jp",
|
||||
onlyCountries: ['us', 'gb', 'ch', 'ca', 'do', 'jp'],
|
||||
preferredCountries: ['ca', 'gb', 'us'],
|
||||
responsiveDropdown: true
|
||||
});
|
||||
```
|
||||
|
||||
Note: any options that take country codes should be lower case [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes
|
||||
|
||||
**defaultCountry**
|
||||
Type: `String` Default: `""`
|
||||
Set the default country by it's country code. Otherwise it will just be the first country in the list.
|
||||
|
||||
**onlyCountries**
|
||||
Type: `Array` Default: `undefined`
|
||||
Display only the countries you specify. Takes an array of country codes.
|
||||
|
||||
**excludeCountries**
|
||||
Type: `Array` Default: `undefined`
|
||||
Display only the countries not specified. Takes an array of country codes.
|
||||
|
||||
**preferredCountries**
|
||||
Type: `Array` Default: `["us", "gb"]`
|
||||
Specify the countries to appear at the top of the list.
|
||||
|
||||
**responsiveDropdown**
|
||||
Type: `Boolean` Default: `false`
|
||||
Set the dropdown's width to be the same as the input. This is automatically enabled for small screens.
|
||||
|
||||
**localizedCountries**
|
||||
Type: `Object` Default: `{}`
|
||||
Allows to translate the countries by its given iso code e.g.:
|
||||
```js
|
||||
{ 'de': 'Deutschland' }
|
||||
```
|
||||
|
||||
## Public Methods
|
||||
**destroy**
|
||||
Remove the plugin from the input, and unbind any event listeners.
|
||||
```js
|
||||
$("#country").countrySelect("destroy");
|
||||
```
|
||||
|
||||
**getSelectedCountryData**
|
||||
Get the country data for the currently selected flag.
|
||||
```js
|
||||
var countryData = $("#country").countrySelect("getSelectedCountryData");
|
||||
```
|
||||
Returns something like this:
|
||||
```js
|
||||
{
|
||||
name: "Afghanistan (افغانستان)",
|
||||
iso2: "af",
|
||||
}
|
||||
```
|
||||
|
||||
**selectCountry**
|
||||
Change the country selection (e.g. when the user is entering their address).
|
||||
```js
|
||||
$("#country").countrySelect("selectCountry", "gb");
|
||||
```
|
||||
|
||||
**setCountry**
|
||||
Insert a country name, and update the selected flag accordingly.
|
||||
```js
|
||||
$("#country").countrySelect("setCountry", "United States");
|
||||
```
|
||||
|
||||
|
||||
## Static Methods
|
||||
**getCountryData**
|
||||
Get all of the plugin's country data.
|
||||
```js
|
||||
var countryData = $.fn.countrySelect.getCountryData();
|
||||
```
|
||||
Returns an array of country objects:
|
||||
```js
|
||||
[{
|
||||
name: "Afghanistan (افغانستان)",
|
||||
iso2: "af",
|
||||
}, ...]
|
||||
```
|
||||
|
||||
**setCountryData**
|
||||
Set all of the plugin's country data.
|
||||
```js
|
||||
$.fn.countrySelect.setCountryData(countryData);
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
**Image path**
|
||||
Depending on your project setup, you may need to override the path to flags.png in your CSS.
|
||||
```css
|
||||
.country-select .flag {background-image: url("path/to/flags.png");}
|
||||
```
|
||||
|
||||
**Full width input**
|
||||
If you want your input to be full-width, you need to set the container to be the same i.e.
|
||||
```css
|
||||
.country-select {width: 100%;}
|
||||
```
|
||||
|
||||
**Input margin**
|
||||
For the sake of alignment, the default CSS forces the input's vertical margin to `0px`. If you want vertical margin, you should add it to the container (with class `country-select`).
|
||||
|
||||
**Displaying error messages**
|
||||
If your error handling code inserts an error message before the `<input>` it will break the layout. Instead you must insert it before the container (with class `country-select`).
|
||||
|
||||
**Dropdown position**
|
||||
The dropdown should automatically appear above/below the input depending on the available space. For this to work properly, you must only initialise the plugin after the `<input>` has been added to the DOM.
|
||||
|
||||
## Contributing
|
||||
Contributions and improvements to the library are welcome! For instructions on contributing to a project on Github, see this guide: [Fork A Repo](https://help.github.com/articles/fork-a-repo).
|
||||
|
||||
If you are treating the library as a Node package, the following will be relevant to you.
|
||||
> To start a local devserver with source code live reload install the dependencies with:
|
||||
>
|
||||
> ```
|
||||
> $ npm install
|
||||
> ```
|
||||
>
|
||||
> And run:
|
||||
>
|
||||
> ```
|
||||
> $ gulp
|
||||
> ```
|
||||
>
|
||||
> To transpile the scss source, minify and prepare your changes at src to build run:
|
||||
>
|
||||
>```
|
||||
> $ gulp build
|
||||
> ```
|
||||
|
||||
## Attributions
|
||||
* This library is built based on the excellent [International Telephone Input](https://github.com/Bluefieldscom/intl-tel-input.git), the authors of which deserve any credit you might like to give (though none of the blame)
|
||||
* Flag images and CSS from [Region-Flags](https://github.com/behdad/region-flags)
|
||||
* Original country data from mledoze's [World countries in JSON, CSV and XML](https://github.com/mledoze/countries)
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "country-select-js",
|
||||
"version": "2.1.1",
|
||||
"description": "A jQuery plugin for selecting a country",
|
||||
"main": [
|
||||
"build/js/countrySelect.js",
|
||||
"build/css/countrySelect.css"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.9.1"
|
||||
},
|
||||
"moduleType": [
|
||||
"amd",
|
||||
"globals",
|
||||
"node"
|
||||
],
|
||||
"keywords": [
|
||||
"country",
|
||||
"international",
|
||||
"jQuery"
|
||||
],
|
||||
"authors": [
|
||||
"Mark French"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"demo.html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/mrmarkfrench/country-select-js.git"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 20px;
|
||||
font-size: 14px;
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
color: #555; }
|
||||
|
||||
.hide {
|
||||
display: none; }
|
||||
|
||||
pre {
|
||||
margin: 0 !important;
|
||||
display: inline-block; }
|
||||
|
||||
.form-item {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
background: none; }
|
||||
|
||||
input, button {
|
||||
height: 35px;
|
||||
margin: 0;
|
||||
padding: 6px 12px;
|
||||
border-radius: 2px;
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
color: inherit; }
|
||||
input[disabled], button[disabled] {
|
||||
background-color: #eee; }
|
||||
|
||||
input, select {
|
||||
border: 1px solid #CCC;
|
||||
width: 250px; }
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #BBB; }
|
||||
|
||||
::-moz-placeholder {
|
||||
/* Firefox 19+ */
|
||||
color: #BBB;
|
||||
opacity: 1; }
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: #BBB; }
|
||||
|
||||
button {
|
||||
color: #FFF;
|
||||
background-color: #428BCA;
|
||||
border: 1px solid #357EBD; }
|
||||
button:hover {
|
||||
background-color: #3276B1;
|
||||
border-color: #285E8E;
|
||||
cursor: pointer; }
|
||||
|
||||
#result {
|
||||
margin-bottom: 100px; }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Country Select JS</title>
|
||||
<link rel="stylesheet" href="build/css/countrySelect.css">
|
||||
<link rel="stylesheet" href="build/css/demo.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Country Select JS</h1>
|
||||
<form>
|
||||
<div class="form-item">
|
||||
<input id="country_selector" type="text">
|
||||
<label for="country_selector" style="display:none;">Select a country here...</label>
|
||||
</div>
|
||||
<div class="form-item" style="display:none;">
|
||||
<input type="text" id="country_selector_code" name="country_selector_code" data-countrycodeinput="1" readonly="readonly" placeholder="Selected country code will appear here" />
|
||||
<label for="country_selector_code">...and the selected country code will be updated here</label>
|
||||
</div>
|
||||
<button type="submit" style="display:none;">Submit</button>
|
||||
</form>
|
||||
|
||||
<!-- Load jQuery from CDN so can run demo immediately -->
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
||||
<script src="build/js/countrySelect.js"></script>
|
||||
<script>
|
||||
$("#country_selector").countrySelect({
|
||||
// defaultCountry: "jp",
|
||||
// onlyCountries: ['us', 'gb', 'ch', 'ca', 'do'],
|
||||
// responsiveDropdown: true,
|
||||
preferredCountries: ['ca', 'gb', 'us']
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
var gulp = require('gulp');
|
||||
var sass = require('gulp-sass');
|
||||
var prefix = require('gulp-autoprefixer');
|
||||
var notify = require('gulp-notify');
|
||||
var cleanCSS = require('gulp-clean-css');
|
||||
var minifyJS = require('gulp-minify');
|
||||
var rename = require('gulp-rename');
|
||||
var webserver = require('gulp-webserver');
|
||||
var runSequence = require('run-sequence');
|
||||
|
||||
gulp.task('scss', function () {
|
||||
return gulp.src('./src/scss/countrySelect.scss')
|
||||
.pipe(sass({ errLogToConsole: true }))
|
||||
.pipe(prefix())
|
||||
.pipe(cleanCSS({compatibility: 'ie8', format: {
|
||||
breaks: {
|
||||
afterAtRule: true,
|
||||
afterBlockBegins: true,
|
||||
afterBlockEnds: true,
|
||||
afterComment: true,
|
||||
afterProperty: true,
|
||||
afterRuleBegins: true,
|
||||
afterRuleEnds: true,
|
||||
beforeBlockEnds: true,
|
||||
betweenSelectors: true
|
||||
},
|
||||
indentBy: 1,
|
||||
indentWith: 'tab' }, level: 0}))
|
||||
.pipe(gulp.dest('./build/css'))
|
||||
.pipe(notify("styles compiled"));
|
||||
});
|
||||
|
||||
gulp.task('js', function () {
|
||||
return gulp.src('./src/js/countrySelect.js')
|
||||
.pipe(gulp.dest('./build/js'))
|
||||
.pipe(notify("javascript updated"));
|
||||
});
|
||||
|
||||
gulp.task('handle-sources', ['scss', 'js']);
|
||||
|
||||
gulp.task('minify-scss', function () {
|
||||
return gulp.src('./build/css/countrySelect.css')
|
||||
.pipe(cleanCSS({level: 2, inline: ['all']}))
|
||||
.pipe(rename({extname: '.min.css'}))
|
||||
.pipe(gulp.dest('./build/css'))
|
||||
.pipe(notify("styles minified"));
|
||||
});
|
||||
|
||||
gulp.task('minify-js', function () {
|
||||
return gulp.src('./build/js/countrySelect.js')
|
||||
.pipe(minifyJS({ext:{min:'.min.js'}}))
|
||||
.pipe(gulp.dest('./build/js'))
|
||||
.pipe(notify("javascript minified"));
|
||||
});
|
||||
|
||||
gulp.task('minify-sources', ['minify-scss', 'minify-js']);
|
||||
|
||||
gulp.task('webserver', function() {
|
||||
gulp.src('.')
|
||||
.pipe(webserver({
|
||||
livereload: true,
|
||||
directoryListing: true,
|
||||
open: './demo.html'
|
||||
}));
|
||||
gulp.watch('./src/scss/**/*.scss', ['scss']);
|
||||
gulp.watch('./src/js/**/*.js', ['js']);
|
||||
});
|
||||
|
||||
gulp.task('default', ['handle-sources', 'webserver']);
|
||||
gulp.task('build', function() {
|
||||
runSequence('handle-sources', 'minify-sources');
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./build/js/countrySelect.js');
|
||||
+5147
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "country-select-js",
|
||||
"version": "2.1.1",
|
||||
"description": "A jQuery plugin for selecting a country",
|
||||
"main": "build/js/countrySelect.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://mrmarkfrench@github.com/mrmarkfrench/country-select-js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"country",
|
||||
"international",
|
||||
"jQuery"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "gulp build"
|
||||
},
|
||||
"author": "Mark French",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mrmarkfrench/country-select-js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mrmarkfrench/country-select-js",
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-clean-css": "^3.3.1",
|
||||
"gulp-minify": "^1.0.0",
|
||||
"gulp-notify": "^3.0.0",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-webserver": "^0.9.1",
|
||||
"run-sequence": "^2.2.1"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
.country-select {
|
||||
// need position on the container so the selected flag can be
|
||||
// absolutely positioned over the input
|
||||
position: relative;
|
||||
// keep the input's default inline properties
|
||||
display: inline-block;
|
||||
|
||||
// paul irish says this is ok
|
||||
// http://www.paulirish.com/2012/box-sizing-border-box-ftw/
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// need this during init, to get the height of the dropdown
|
||||
.v-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// specify types to increase specificity e.g. to override bootstrap v2.3
|
||||
input, input[type=text] {
|
||||
position: relative;
|
||||
|
||||
// input is bottom level, below selected flag and dropdown
|
||||
z-index: 0;
|
||||
|
||||
// any vertical margin the user has on their inputs would no longer work as expected
|
||||
// because we wrap everything in a container div. i justify the use of !important
|
||||
// here because i don't think the user should ever have vertical margin here - when
|
||||
// the input is wrapped in a container, vertical margin messes up alignment with other
|
||||
// inline elements (e.g. an adjacent button) in firefox, and probably other browsers.
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
// make space for the selected flag
|
||||
// Note: no !important here, as the user may want to tweak this so that the
|
||||
// perceived input padding matches their existing styles
|
||||
padding-right: $selected-flag-width;
|
||||
|
||||
// any margin-right here will push the selected-flag away
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.flag-dropdown {
|
||||
// positioned over the top of the input
|
||||
position: absolute;
|
||||
|
||||
// full height
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
// prevent the highlighted child from overlapping the input border
|
||||
padding: $border-width;
|
||||
}
|
||||
|
||||
.selected-flag {
|
||||
// render above the input
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
width: $selected-flag-width;
|
||||
|
||||
// this must be full-height both for the hover highlight, and to push down the
|
||||
// dropdown so it appears below the input
|
||||
height: 100%;
|
||||
padding: 0 0 0 $flag-padding;
|
||||
|
||||
// vertically center the flag
|
||||
.flag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
// split the difference between the flag and the arrow height to verically center
|
||||
top: 50%;
|
||||
margin-top: -1 * ($arrow-height / 2);
|
||||
right: $arrow-padding;
|
||||
|
||||
// css triangle
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: $triangle-border solid transparent;
|
||||
border-right: $triangle-border solid transparent;
|
||||
border-top: $arrow-height solid $arrow-color;
|
||||
|
||||
&.up {
|
||||
border-top: none;
|
||||
border-bottom: $arrow-height solid $arrow-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the dropdown
|
||||
.country-list {
|
||||
position: absolute;
|
||||
// popup so render above everything else
|
||||
z-index: 2;
|
||||
|
||||
// override default list styles
|
||||
list-style: none;
|
||||
// in case any container has text-align:center
|
||||
text-align: left;
|
||||
|
||||
// dropdown flags need consistent width, so wrap in a container
|
||||
.flag {
|
||||
display: inline-block;
|
||||
width: $flag-width;
|
||||
}
|
||||
|
||||
padding: 0;
|
||||
// margin-left to compensate for the padding on the parent
|
||||
margin: 0 0 0 (-$border-width);
|
||||
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
|
||||
background-color: white;
|
||||
border: $border-width solid $grey-border;
|
||||
|
||||
// don't let the contents wrap AKA the container will be as wide as the contents
|
||||
white-space: nowrap;
|
||||
// except on small screens, where we force the dropdown width to match the input
|
||||
@media (max-width: 500px) {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
|
||||
// the divider below the preferred countries
|
||||
.divider {
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: $border-width solid $grey-border;
|
||||
}
|
||||
|
||||
// each country item in dropdown (we must have separate class to differentiate from dividers)
|
||||
.country {
|
||||
// Note: decided not to use line-height here for alignment because it causes issues e.g. large font-sizes will overlap, and also looks bad if one country overflows onto 2 lines
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.country.highlight {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
|
||||
.flag, .country-name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// spacing between country flag and name
|
||||
.flag {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.inside {
|
||||
input, input[type=text] {
|
||||
padding-right: $input-padding;
|
||||
padding-left: $selected-flag-arrow-width + $input-padding;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.flag-dropdown {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.selected-flag {
|
||||
width: $selected-flag-arrow-width;
|
||||
}
|
||||
}
|
||||
|
||||
&.inside {
|
||||
// hover state - show flag is clickable
|
||||
.flag-dropdown:hover {
|
||||
cursor: pointer;
|
||||
|
||||
.selected-flag {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
}
|
||||
// disable hover state when input is disabled
|
||||
input[disabled] + .flag-dropdown:hover, input[readonly] + .flag-dropdown:hover {
|
||||
cursor: default;
|
||||
|
||||
.selected-flag {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flag {
|
||||
width: $flag-width;
|
||||
height: $flag-height;
|
||||
box-shadow: 0px 0px 1px 0px #888;
|
||||
background-image: url("#{$flags-image-path}#{$flags-image-name}.#{$flags-image-extension}");
|
||||
background-repeat: no-repeat;
|
||||
// empty state
|
||||
background-color: #dbdbdb;
|
||||
background-position: $flag-width 0;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2 / 1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||
background-image: url("#{$flags-image-path}#{$flags-image-name}@2x.#{$flags-image-extension}");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
@function retina-size($value) {
|
||||
@return floor($value / 2);
|
||||
}
|
||||
|
||||
@mixin retina-bg-size($spriteWidth, $spriteHeight) {
|
||||
background-size: floor($spriteWidth / 2) floor($spriteHeight / 2);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// rgba is needed for the selected flag hover state to blend in with
|
||||
// the border-highlighting some browsers give the input on focus
|
||||
$hover-color: rgba(0, 0, 0, 0.05) !default;
|
||||
$grey-text: #999 !default;
|
||||
$grey-border: #ccc !default;
|
||||
|
||||
$flag-height: 15px !default;
|
||||
$flag-width: 20px !default;
|
||||
$flag-padding: 8px !default;
|
||||
// this border width is used for the popup and divider, but it is also
|
||||
// assumed to be the border width of the input, which we do not control
|
||||
$border-width: 1px !default;
|
||||
|
||||
$arrow-height: 4px !default;
|
||||
$arrow-width: 6px !default;
|
||||
$triangle-border: 3px !default;
|
||||
$arrow-padding: 6px !default;
|
||||
$arrow-color: #555 !default;
|
||||
|
||||
$input-padding: 6px !default;
|
||||
$selected-flag-width: $flag-width + (2 * $flag-padding) !default;
|
||||
$selected-flag-arrow-width: $flag-width + $flag-padding + $arrow-width + (2 * $arrow-padding) !default;
|
||||
|
||||
// image related variables
|
||||
$flags-image-path: "../../build/img/" !default;
|
||||
$flags-image-name: "flags" !default;
|
||||
$flags-image-extension: "png" !default;
|
||||
|
||||
// enough space for them to click off to close
|
||||
$mobile-popup-margin: 30px;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user