diff --git a/.env.build b/.env.build index 2b01a90..d34401c 100644 --- a/.env.build +++ b/.env.build @@ -1,7 +1,7 @@ -VITE_APP_NAME="Eurofurence Stream" +VITE_APP_NAME="Streaming" # Reverb WebSocket configuration for production build VITE_REVERB_APP_KEY=stream -VITE_REVERB_HOST=stream.eurofurence.org +VITE_REVERB_HOST= VITE_REVERB_PORT=443 VITE_REVERB_SCHEME=https diff --git a/.env.dvr-process.example b/.env.dvr-process.example index 41dfa55..c2ab512 100644 --- a/.env.dvr-process.example +++ b/.env.dvr-process.example @@ -18,5 +18,5 @@ TEMP_DIR=/tmp/dvr-processing # Temporary directory for processing DVR_SOURCE_DIR=/var/dvr # Path to DVR storage directory # Optional: Override for specific environments -# API_BASE_URL=https://streaming.eurofurence.org/api +# API_BASE_URL=https://streaming.example.org/api # DVR_SOURCE_DIR=/mnt/dvr-storage \ No newline at end of file diff --git a/.env.example b/.env.example index e12cf71..05227ec 100644 --- a/.env.example +++ b/.env.example @@ -2,35 +2,37 @@ APP_NAME=Laravel APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://localhost +APP_URL=http://streaming.test LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug -DB_CONNECTION=mysql +DB_CONNECTION=pgsql DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_DATABASE=laravel -DB_USERNAME=root +DB_PORT=5432 +DB_DATABASE=ef_streaming +DB_USERNAME=postgres DB_PASSWORD= BROADCAST_DRIVER=reverb CACHE_DRIVER=file FILESYSTEM_DISK=local -QUEUE_CONNECTION=sync +QUEUE_CONNECTION=database SESSION_DRIVER=file -SESSION_LIFETIME=120 - -MEMCACHED_HOST=127.0.0.1 +# 4 weeks, in minutes. Matches AUTH_REMEMBER_LIFETIME so attendees stay signed in. +SESSION_LIFETIME=40320 +AUTH_REMEMBER_LIFETIME=40320 +# Valkey/Redis (production uses this for cache, queue via Horizon, and Reverb scaling) +REDIS_CLIENT=phpredis REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null REDIS_PORT=6379 +REDIS_PASSWORD=null MAIL_MAILER=smtp -MAIL_HOST=mailpit -MAIL_PORT=1025 +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null @@ -57,6 +59,19 @@ DVR_AWS_URL=https://s3.your-server.com/recording # Generate a secure random string for production STREAM_SYSTEM_STREAMKEY= +# Playback token secrets, shared with every edge server so it can verify tokens +# locally without calling back into Laravel. Two separate secrets so a leak of +# the viewer secret cannot mint long-lived embed keys. +# Generate with: openssl rand -hex 32 +HLS_VIEWER_SECRET= +HLS_EMBED_SECRET= + +# Viewer token lifetime in seconds, the grace window edges allow past expiry, +# and how long before expiry a fresh token is pushed to the player. +HLS_TOKEN_TTL=900 +HLS_TOKEN_LEEWAY=60 +HLS_TOKEN_REFRESH_MARGIN=180 + # Local streaming server override for specific IP subnets # When client IPs match these subnets, force use of LOCAL_STREAMING_HOSTNAME server # Use CIDR notation, e.g., 192.168.1.0/24 for IPv4, 2001:db8::/64 for IPv6 @@ -68,11 +83,11 @@ REVERB_APP_ID=my-app-id REVERB_APP_KEY=my-app-key REVERB_APP_SECRET=my-app-secret REVERB_HOST=localhost -REVERB_PORT=8080 +REVERB_PORT=8081 REVERB_SCHEME=http REVERB_SERVER_HOST=0.0.0.0 -REVERB_SERVER_PORT=8080 +REVERB_SERVER_PORT=8081 REVERB_MAX_REQUEST_SIZE=250000 REVERB_APP_MAX_MESSAGE_SIZE=100000 REVERB_SCALING_ENABLED=false @@ -98,8 +113,8 @@ VITE_PUSHER_SCHEME="${REVERB_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # DNS Configuration for dynamic updates -DNS_SERVER=85.199.154.53 -DNS_ZONE=stream.eurofurence.org +DNS_SERVER= +DNS_ZONE=stream.example.org DNS_KEY_NAME=stream-ddns DNS_KEY_ALGORITHM=hmac-sha256 DNS_KEY_SECRET= @@ -107,3 +122,27 @@ DNS_TTL=60 # Recording API Authentication RECORDING_API_KEY=your-secure-api-key-here + +# Branding (name, copy, links, logo, accent colour) is not configured here. +# It lives in /manage > Settings, stored in the branding_settings table, and +# applies without a deploy or a rebuild. config/branding.php holds the neutral +# defaults a fresh install boots with. For scripted setup: +# php artisan branding:set primary_color=#0e7490 site_name="My Con" + +# Is signing in mandatory? true puts every page behind the identity provider. +# false opens browse, schedule, archive and the player to guests, and leaves +# login mandatory only for chat, which needs an identity to attribute and +# moderate. Role-restricted shows and recordings stay hidden from guests either +# way. +AUTH_REQUIRED=true + +# Chat on or off for the whole installation. false 404s every chat endpoint and +# hides the panel, pop-out and emote pages; streams keep playing. +CHAT_ENABLED=true + +# Chat: comma separated domains whose links stay clickable. Empty strips them all. +CHAT_ALLOWED_DOMAINS= + +# Container images for the generated provisioning scripts, built from docker/. +#STREAM_IMAGE_FFMPEG_HLS= +#STREAM_IMAGE_DVR_UPLOADER= diff --git a/.github/screenshots/archive.jpg b/.github/screenshots/archive.jpg new file mode 100644 index 0000000..9455db5 Binary files /dev/null and b/.github/screenshots/archive.jpg differ diff --git a/.github/screenshots/browse.jpg b/.github/screenshots/browse.jpg new file mode 100644 index 0000000..aee6c5b Binary files /dev/null and b/.github/screenshots/browse.jpg differ diff --git a/.github/screenshots/login.png b/.github/screenshots/login.png new file mode 100644 index 0000000..071e08d Binary files /dev/null and b/.github/screenshots/login.png differ diff --git a/.github/screenshots/manage-cut-editor.jpg b/.github/screenshots/manage-cut-editor.jpg new file mode 100644 index 0000000..b81e9b6 Binary files /dev/null and b/.github/screenshots/manage-cut-editor.jpg differ diff --git a/.github/screenshots/manage-dashboard.png b/.github/screenshots/manage-dashboard.png new file mode 100644 index 0000000..0195dcb Binary files /dev/null and b/.github/screenshots/manage-dashboard.png differ diff --git a/.github/screenshots/manage-planner.png b/.github/screenshots/manage-planner.png new file mode 100644 index 0000000..1d10978 Binary files /dev/null and b/.github/screenshots/manage-planner.png differ diff --git a/.github/screenshots/manage-settings.png b/.github/screenshots/manage-settings.png new file mode 100644 index 0000000..6fda9bf Binary files /dev/null and b/.github/screenshots/manage-settings.png differ diff --git a/.github/screenshots/manage-shows.png b/.github/screenshots/manage-shows.png new file mode 100644 index 0000000..83c182d Binary files /dev/null and b/.github/screenshots/manage-shows.png differ diff --git a/.github/screenshots/manage-sources.png b/.github/screenshots/manage-sources.png new file mode 100644 index 0000000..122a4d8 Binary files /dev/null and b/.github/screenshots/manage-sources.png differ diff --git a/.github/screenshots/mobile-player.jpg b/.github/screenshots/mobile-player.jpg new file mode 100644 index 0000000..00d4fa9 Binary files /dev/null and b/.github/screenshots/mobile-player.jpg differ diff --git a/.github/screenshots/player-chat.jpg b/.github/screenshots/player-chat.jpg new file mode 100644 index 0000000..555ace4 Binary files /dev/null and b/.github/screenshots/player-chat.jpg differ diff --git a/.github/screenshots/schedule.png b/.github/screenshots/schedule.png new file mode 100644 index 0000000..fe45867 Binary files /dev/null and b/.github/screenshots/schedule.png differ diff --git a/.gitignore b/.gitignore index 37b6091..59ddfb8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,20 @@ frankenphp frankenphp-worker.php # Logs -logs \ No newline at end of file +logs +# Local dev stream loops (scripts/dev-streams.sh) +/public/dev-streams + +# Generated per-server install script (contains a live shared secret) +/install.sh + +# Python bytecode from the DVR uploader image build +__pycache__/ + +# Playwright MCP session artefacts (console logs, snapshots, screenshots) +.playwright-mcp/ + +# Throwaway screenshots dropped in the working directory. The ones the README +# uses live in .github/screenshots. +/*.png +/*.jpg diff --git a/CLAUDE.md b/CLAUDE.md index 9af4863..839f307 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,18 +4,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is a Laravel-based streaming system for Eurofurence (and other conventions) that manages live video streaming infrastructure. It includes server provisioning, client management, real-time chat, and auto-scaling capabilities. +This is a Laravel-based streaming system for conventions that manages live video streaming infrastructure. It includes server provisioning, client management, real-time chat, and auto-scaling capabilities. + +Nothing convention-specific is hardcoded. Names, copy, links, logo, login background and accent colour resolve through `App\Services\BrandingService`, backed by the `branding_settings` table with neutral fallbacks in `config/branding.php`. Never reintroduce a convention name, domain, or logo as a literal or a config default. + +Branding has exactly one source: the `branding_settings` table, edited at `/manage` > Settings or via `php artisan branding:set key=value`. Do not add `env()` to `config/branding.php` or `BRANDING_*` vars to `.env` - a saved row always wins, so a second source could only disagree. The accent colour is applied as runtime CSS custom properties (`app.blade.php`, after `@vite`), so changing it needs no rebuild; never move it into a `VITE_` var. ## Core Architecture ### Tech Stack - **Backend**: Laravel 12 with PHP 8.2+ - **Frontend**: Vue 3 with Inertia.js 2 -- **Admin Panel**: Filament 3 +- **Admin Panel**: Inertia + Vue at `/manage` (no Filament) - **Real-time**: Pusher/Soketi for WebSockets - **Streaming**: SRS (Simple Realtime Server) for RTMP/FLV streaming -- **Queue**: Laravel Horizon with Redis -- **Database**: MySQL 8.0 +- **Queue**: Laravel Horizon with Redis (production); database queue driver locally +- **Database**: MySQL 8.0 (production), PostgreSQL locally - **Infrastructure**: Hetzner Cloud API for server provisioning ### Key Components @@ -43,6 +47,9 @@ This is a Laravel-based streaming system for Eurofurence (and other conventions) ## Development Commands ### Local Development + +Not Sail/Docker. PHP, Postgres, and Valkey run natively. Yerd serves the site. + ```bash # Install dependencies composer install @@ -51,16 +58,25 @@ npm install # Run migrations and seeders php artisan migrate --seed -# Start development servers -php artisan serve # Laravel development server +# Start dev servers npm run dev # Vite dev server for assets -php artisan horizon # Queue worker -php artisan octane:start # High-performance server (optional) - -# Run with Docker Compose (includes all services) -docker-compose up +php artisan queue:work # Process queued jobs (database driver locally, no Horizon needed) +php artisan reverb:start # WebSocket server for chat/broadcasting ``` +Site is served by Yerd at `http://streaming.test` (`APP_URL`); no `artisan serve` needed. + +### Local Ports + +| Port | Service | Notes | +|------|---------|-------| +| 80 | Yerd | serves `streaming.test`; its daemon (`yerdd`) also holds 8080 | +| 5173 | Vite | `npm run dev`; `detectTls: false` in `vite.config.js` so the plugin does not probe Yerd's valet config for certs | +| 8081 | Reverb | WebSockets; `REVERB_PORT`/`REVERB_SERVER_PORT`, 8080 is unavailable | +| 6379 | Valkey | Redis-compatible, reached via the `phpredis` extension and the `REDIS_*` env vars | + +Local `CACHE_DRIVER=file` and `QUEUE_CONNECTION=database` by design, so Valkey is optional locally; production uses it for cache, Horizon queues, and Reverb scaling. + ### Testing ```bash # Run all tests @@ -88,10 +104,10 @@ php artisan view:clear ### Queue Management ```bash -# Process jobs +# Process jobs (local: database driver) php artisan queue:work -# Monitor with Horizon dashboard (visit /horizon) +# Production uses Horizon with Redis (visit /horizon) php artisan horizon ``` @@ -104,15 +120,16 @@ Key environment variables to configure: - `STREAM_*`: Streaming server configuration - `CHAT_*`: Chat moderation settings -## Docker Services +## Docker Images (production/Kubernetes) -The `docker-compose.yml` includes: -- `laravel.test`: Main application container -- `mysql`: Database -- `redis`: Cache and queues -- `soketi`: WebSocket server -- `stream`: SRS edge server -- `origin`: SRS origin server +`docker/` contains Dockerfiles for services deployed to Kubernetes in production. Not used for local development: +- `docker/origin-srs`, `docker/origin-nginx`, `docker/origin-caddy`: Origin streaming stack +- `docker/edge-nginx`, `docker/edge-caddy`: Edge streaming stack +- `docker/dvr-uploader`: DVR recording uploader +- `docker/ffmpeg-hls`: HLS transcoder +- `docker/mysql`: Production MySQL init scripts + +Root `Dockerfile` builds the main Laravel app image (built via `.github/workflows/docker.yml`). ## Job Queue Architecture @@ -125,15 +142,25 @@ Critical background jobs for server management: ## Admin Interface -Filament admin panel at `/admin` provides: -- Server management and monitoring -- Client connection tracking -- User management with role-based permissions -- Real-time capacity and performance widgets +The admin panel is the Inertia panel at `/manage`. Filament is gone; `/admin` is a 301 into `/manage`. + +`/manage` covers: +- Dashboard: capacity, server health, alerts, live viewers, the next few hours of programme +- Sources, Shows, the Show planner and Stream Control +- Import: pulls sessions from pretalx into shows; see docs/admin/pretalx-import.md +- Servers, including the generated install script +- Users, Roles, Emotes and Recordings +- Settings: branding, login copy, accent colour and footer links + +Tables, filters, row/bulk actions and toasts are declared server-side with the +`App\Support\Manage` toolkit (`Table`, `Column`, `Filter`, `Action`, `Status`, `Toast`) and +rendered by the shared components in `resources/js/Components/Manage`. Access runs through +the `access-manage` gate plus a policy per model. ## Important Development Rules - **NEVER use fetch() or make API calls** unless absolutely necessary. Always use Inertia.js 2 props for passing data from backend to frontend. Data should be passed through page controllers or HandleInertiaRequests middleware for global data. - Never use -gray- for tailwind colors always use -primary- as main color - no need t orun build i got a npm run dev running -- Always use sail instead of docker-compose \ No newline at end of file +- Local dev runs natively, not Sail/Docker +- The local dev server is **Yerd** (daemon `yerdd`), not Laravel Herd. They are different tools. Never call it Herd. \ No newline at end of file diff --git a/README.md b/README.md index 0cfb4f3..9c145b0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,132 @@ -# This is a Streaming System for Eurofurence. -Contributions are very welcome. Use this for whatever you like! +# Streaming -## Contributing? -Contact @Thiritin on Telegram! +A self-hosted livestream platform for conventions. It takes RTMP from the encoders in your rooms, transcodes an adaptive ladder, delivers HLS through edge servers you can add and remove during the event, and gives the video team one panel for the whole thing: programme, chat, moderation, recordings and infrastructure. -## Found a Bug? -Please create an issue in this repository +Nothing about any one convention is baked in. Names, copy, links, logo, login background and accent colour live in the database and are edited in the admin panel. -## Want to use this at your convention? -You may do so at your own risk, feel free to contact me on Telegram if you require help. -@Thiritin on Telegram. +![Browse](.github/screenshots/browse.jpg) -If you adopt this system, please make sure to remove the Eurofurence logo. As this is not part of the License. +## What it does + +**Watch.** A browse grid with a live hero, per-channel filters, hover previews and a programme guide. The player is HLS with a quality ladder, a seekable live window, theatre mode, a pop-out chat and an external-player link for VLC and friends. It works on a phone. + +The player on a phone + + +**Chat.** One channel per source, so chat survives the handover from one show to the next. Timeouts, bans, purge, clear, announcements, slow mode and per-message actions, plus chat commands for moderators. Custom emotes are uploaded by users and approved by staff. + +**Programme.** Shows are planned on a drag-and-drop timeline, imported from [pretalx](docs/admin/pretalx-import.md), or created by hand. [Auto mode](docs/admin/auto-mode.md) starts a show when its source comes online and stops it at a hard stop, so nobody has to sit on the button at 2am. + +**Archive.** Every source is recorded continuously to object storage. A recording is a time range over that archive, cut in a timeline editor with in and out markers, and rebuilt from the current markers whenever you move them. Published recordings are grouped by year and can require a role to watch. + +**Infrastructure.** Edge servers are provisioned on Hetzner Cloud from the panel, get their DNS record, run a generated install script, and are handed viewers by the assignment job. Health checks, viewer counts, capacity and alerts sit on one dashboard. + +**Access.** Sign-in goes through OpenID Connect. Roles carry permissions, and shows and recordings can require one. With `AUTH_REQUIRED=false` the public pages are open to guests and only chat stays behind sign-in. + +## Screenshots + +| | | +|---|---| +| ![Player and chat](.github/screenshots/player-chat.jpg) | ![Programme guide](.github/screenshots/schedule.png) | +| Player, live chat and moderator badges | Programme guide across channels and days | +| ![Archive](.github/screenshots/archive.jpg) | ![Cut editor](.github/screenshots/manage-cut-editor.jpg) | +| Archive, one collection per year | Cutting a recording out of the continuous archive | +| ![Dashboard](.github/screenshots/manage-dashboard.png) | ![Planner](.github/screenshots/manage-planner.png) | +| Capacity, server health and what is on air | Planning the programme on a timeline | +| ![Shows](.github/screenshots/manage-shows.png) | ![Settings](.github/screenshots/manage-settings.png) | +| Shows, with stream control per row | Branding, colours and links, applied without a rebuild | +| ![Sources](.github/screenshots/manage-sources.png) | ![Sign-in](.github/screenshots/login.png) | +| Sources, one per room, each with its own stream key | Sign-in, with what is on air next to it | + +The demo content in these screenshots is [Big Buck Bunny](https://peach.blender.org/) (CC BY 3.0, Blender Foundation). + +## How it works + +``` +OBS / encoder + | RTMP, one stream key per source +SRS ingress ──DVR──> uploader ──> S3 ──> archive playlists ──> recordings + | +ffmpeg ABR ladder (480p / 720p / 1080p, aligned GOPs) + | +origin (nginx + caddy) + | +edge servers (njs verifies playback tokens) + | +viewers +``` + +The Laravel app never carries video. It hands out signed playback tokens, assigns viewers to an edge, and proxies playlists so a viewer only ever talks to its own domain. Segments come from the edges. Archive segments come from the bucket as presigned URLs, which is why a recording playlist is rendered per request rather than stored. + +## Stack + +Laravel 12 on PHP 8.2, Inertia 2 with Vue 3 and Tailwind 4, Vidstack and hls.js in the player, Reverb or Pusher for websockets, Horizon on Redis for queues, MySQL or PostgreSQL, S3-compatible object storage, SRS and ffmpeg for the video path, Hetzner Cloud for servers. + +## Running it locally + +You need PHP 8.2+, Composer, Node 20+, a database, Redis or Valkey, ffmpeg, and Docker if you want the full video path. + +```bash +composer install +npm install +cp .env.example .env +php artisan key:generate +php artisan migrate --seed +npm run dev +php artisan queue:work +php artisan reverb:start +``` + +Video is optional for UI work, and comes in two flavours: + +```bash +# fake channels written straight to disk, no Docker +php artisan db:seed --class=DevStreamChannelsSeeder +./scripts/dev-streams.sh # then set DEV_STREAMS=true in .env + +# the real path: SRS, ABR ladder, origin, edge, S3, DVR +./scripts/dev-stack.sh up +``` + +[docs/dev-stack.md](docs/dev-stack.md) covers both, including the switches that keep five channels running on a laptop without melting it. + +## Configuration + +The settings that matter live in `.env`: + +| Variable | What it controls | +|---|---| +| `OIDC_URL`, `OIDC_CLIENT_ID`, `OIDC_SECRET` | Identity provider | +| `AUTH_REQUIRED` | Whether guests can watch | +| `CHAT_ENABLED`, `CHAT_*` | Chat, rate limits, slow mode, link handling | +| `HLS_VIEWER_SECRET`, `HLS_EMBED_SECRET` | Playback tokens the edges verify | +| `AWS_*`, `DVR_AWS_*` | Archive and DVR buckets | +| `HETZNER_TOKEN`, `DNS_*` | Server provisioning and DNS records | +| `STREAM_SYSTEM_STREAMKEY` | Shared secret between the app and the video stack | + +Branding is not an env var. Convention name, copy, footer links, logo, login background and accent colour are stored in the `branding_settings` table and edited at `/manage` > Settings, or scripted: + +```bash +php artisan branding:set convention_name="Example Con" primary_color="#7c5cff" +``` + +The accent colour is applied as CSS custom properties at runtime, so changing it takes effect without a rebuild. + +## Deployment + +The root `Dockerfile` builds the app image. `docker/` holds the images for the video path: origin SRS, origin and edge nginx and caddy, the ABR transcoder, and the DVR uploader. Queues run under Horizon, websockets under Reverb, and the scheduler needs `php artisan schedule:run` every minute. + +## Documentation + +- [docs/dev-stack.md](docs/dev-stack.md): the local video stack +- [docs/admin/pretalx-import.md](docs/admin/pretalx-import.md): importing the programme +- [docs/admin/auto-mode.md](docs/admin/auto-mode.md): starting and stopping shows without a human +- [docs/dvr-archive-plan.md](docs/dvr-archive-plan.md): how the archive and cutting work + +## Contributing + +Issues and pull requests are welcome. For anything larger, or for help running this at your own convention, contact @Thiritin on Telegram. + +## Licence + +GPL-3.0. See [LICENSE](LICENSE). diff --git a/app/Console/Commands/BrandingSetCommand.php b/app/Console/Commands/BrandingSetCommand.php new file mode 100644 index 0000000..8df8edf --- /dev/null +++ b/app/Console/Commands/BrandingSetCommand.php @@ -0,0 +1,125 @@ + Settings. This command is the same write from a shell, + * so provisioning can set it once without a second source of truth that would + * silently stop applying the first time somebody saves the form. + */ +class BrandingSetCommand extends Command +{ + protected $signature = 'branding:set + {pairs?* : key=value pairs, e.g. primary_color=#0e7490 site_name="My Con"} + {--list : Show every key with its current value and shipped default}'; + + /** + * Repeater fields take JSON on the command line, e.g. + * footer_links=\'[{"label":"Privacy","url":"https://example.org/privacy"}]\' + */ + private const JSON_TYPES = ['links']; + + protected $description = 'Set branding values (name, copy, links, accent colour) from the command line'; + + public function handle(Settings $settings): int + { + $fields = collect($settings->groups()) + ->flatMap(fn (array $group) => $group['fields']) + ->keyBy('key'); + + if ($this->option('list') || $this->argument('pairs') === []) { + $this->table( + ['Key', 'Current', 'Default'], + $fields->map(fn (array $field) => [ + $field['key'], + $this->truncate($field['value']), + $this->truncate($field['default']), + ])->values()->all(), + ); + + return self::SUCCESS; + } + + $parsed = []; + + foreach ($this->argument('pairs') as $pair) { + if (! str_contains($pair, '=')) { + $this->error("Expected key=value, got \"{$pair}\"."); + + return self::FAILURE; + } + + [$key, $value] = explode('=', $pair, 2); + $key = trim($key); + + if (! $fields->has($key)) { + $this->error("Unknown branding key \"{$key}\". Run with --list to see them all."); + + return self::FAILURE; + } + + if (in_array($fields[$key]['type'], self::JSON_TYPES, true)) { + $decoded = json_decode($value, true); + + if (! is_array($decoded)) { + $this->error("\"{$key}\" takes a JSON list, e.g. ". + '\'[{"label":"Privacy","url":"https://example.org/privacy"}]\''); + + return self::FAILURE; + } + + $value = $decoded; + } + + $parsed[$key] = $value; + } + + // The same rules the form applies, so a scripted write cannot store + // something the panel would have rejected. + $rules = collect($settings->rules()) + ->only(array_map(fn ($key) => 'values.'.$key, array_keys($parsed))) + ->all(); + + $validator = Validator::make( + ['values' => $parsed], + $rules, + [], + $settings->attributes(), + ); + + if ($validator->fails()) { + foreach ($validator->errors()->all() as $error) { + $this->error($error); + } + + return self::FAILURE; + } + + $settings->save($parsed); + + foreach (array_keys($parsed) as $key) { + $stored = BrandingSetting::where('key', $key)->exists(); + + $this->line($stored + ? " {$key} set" + : " {$key} back to the shipped default"); + } + + return self::SUCCESS; + } + + private function truncate(mixed $value): string + { + $value = is_array($value) ? json_encode($value) : (string) $value; + + return mb_strlen($value) > 40 ? mb_substr($value, 0, 39).'…' : $value; + } +} diff --git a/app/Console/Commands/Chat/AbstractChatCommand.php b/app/Console/Commands/Chat/AbstractChatCommand.php index f965fed..289b3f2 100644 --- a/app/Console/Commands/Chat/AbstractChatCommand.php +++ b/app/Console/Commands/Chat/AbstractChatCommand.php @@ -5,6 +5,7 @@ use App\Contracts\CommandInterface; use App\Events\CommandFeedbackEvent; use App\Models\User; +use App\Support\Chat\Broadcast; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; @@ -15,6 +16,11 @@ abstract class AbstractChatCommand implements CommandInterface */ protected string $rawInput; + /** + * The source (stream) the command was issued from, when known. + */ + protected ?int $sourceId = null; + /** * Parsed parameters from the command. */ @@ -24,9 +30,13 @@ abstract class AbstractChatCommand implements CommandInterface * Command properties that can be overridden. */ protected string $name = ''; + protected string $signature = ''; + protected string $description = ''; + protected array $aliases = []; + protected array $parameters = []; /** @@ -35,6 +45,17 @@ abstract class AbstractChatCommand implements CommandInterface public function setRawInput(string $input): self { $this->rawInput = $input; + + return $this; + } + + /** + * Set the source the command was issued from. + */ + public function setSourceId(?int $sourceId): self + { + $this->sourceId = $sourceId; + return $this; } @@ -54,11 +75,12 @@ public function name(): string if ($this->name) { return $this->name; } - + // Extract from signature if not set $signature = $this->signature(); $parts = explode(' ', $signature); $name = $parts[0] ?? ''; + return trim($name, '/!'); } @@ -89,12 +111,13 @@ public function rules(): array if (isset($config['required']) && $config['required']) { $rules[$key] = 'required'; if (isset($config['type'])) { - $rules[$key] .= '|' . $config['type']; + $rules[$key] .= '|'.$config['type']; } } elseif (isset($config['type'])) { - $rules[$key] = 'nullable|' . $config['type']; + $rules[$key] = 'nullable|'.$config['type']; } } + return $rules; } @@ -120,7 +143,7 @@ public function permission(): ?string public function authorize(User $user): bool { $permission = $this->permission(); - + if ($permission === null) { return true; } @@ -133,22 +156,22 @@ public function authorize(User $user): bool */ protected function parseParameters(): array { - if (!empty($this->parsedParameters)) { + if (! empty($this->parsedParameters)) { return $this->parsedParameters; } // Remove command name from input $input = trim($this->rawInput); $commandName = $this->name(); - + // Check if input starts with command name or any alias $allNames = array_merge([$commandName], $this->aliases()); foreach ($allNames as $name) { - if (Str::startsWith($input, '/' . $name)) { - $input = Str::after($input, '/' . $name); + if (Str::startsWith($input, '/'.$name)) { + $input = Str::after($input, '/'.$name); break; - } elseif (Str::startsWith($input, '!' . $name)) { - $input = Str::after($input, '!' . $name); + } elseif (Str::startsWith($input, '!'.$name)) { + $input = Str::after($input, '!'.$name); break; } } @@ -157,7 +180,7 @@ protected function parseParameters(): array // Parse quoted strings and regular arguments preg_match_all('/"([^"]+)"|\'([^\']+)\'|(\S+)/', $input, $matches); - + $parameters = []; foreach ($matches[0] as $match) { // Remove quotes if present @@ -167,7 +190,7 @@ protected function parseParameters(): array // Map parameters to signature $this->parsedParameters = $this->mapToSignature($parameters); - + return $this->parsedParameters; } @@ -177,16 +200,16 @@ protected function parseParameters(): array protected function mapToSignature(array $rawParams): array { $signature = $this->signature(); - + // Extract parameter names from signature - support both {} and <> brackets preg_match_all('/[{<]([^}>]+)[}>]/', $signature, $matches); $paramNames = $matches[1] ?? []; - + $mapped = []; foreach ($paramNames as $index => $name) { // Remove optional indicator $cleanName = str_replace('?', '', $name); - + // Check if parameter has default value if (str_contains($cleanName, '=')) { [$cleanName, $default] = explode('=', $cleanName, 2); @@ -201,7 +224,7 @@ protected function mapToSignature(array $rawParams): array } } } - + return $mapped; } @@ -225,7 +248,7 @@ public function handle(User $user, array $parameters): void // Validate parameters $validator = $this->validateParameters($this->parsedParameters); - + if ($validator->fails()) { $errors = $validator->errors()->all(); $this->feedback($user, implode("\n", $errors), 'error'); @@ -233,7 +256,7 @@ public function handle(User $user, array $parameters): void } // Check authorization - if (!$this->authorize($user)) { + if (! $this->authorize($user)) { $this->feedback($user, 'You do not have permission to use this command.', 'error'); throw new \Illuminate\Auth\Access\AuthorizationException('You do not have permission to use this command.'); } @@ -252,26 +275,17 @@ abstract protected function execute(User $user, array $parameters): void; */ public function feedback(User $user, string $message, string $type = 'info', array $data = []): void { - broadcast(new CommandFeedbackEvent($user, $message, $type, $data)) - ->toOthers(); - - // Also send to the user themselves on their private channel - broadcast(new CommandFeedbackEvent($user, $message, $type, $data)) - ->via('private-command-feedback.' . $user->id); + // Goes to the recipient's own private channel, so `toOthers()` would drop it + // for the very user it is meant for. + Broadcast::send(new CommandFeedbackEvent($user, $message, $type, $data)); } /** - * Broadcast a system message to all users. + * Broadcast an inline notice into the chat the command came from. */ protected function broadcastSystemMessage(string $message, string $type = 'info'): void { - broadcast(new \App\Events\SystemMessageEvent([ - 'id' => uniqid('system_'), - 'type' => 'system', - 'content' => $message, - 'timestamp' => now()->toIso8601String(), - 'system_type' => $type, - ]))->toOthers(); + Broadcast::send(new \App\Events\Chat\Broadcasts\ChatNoticeEvent($message, $this->sourceId, $type)); } /** @@ -288,4 +302,4 @@ public function toArray(): array 'permission' => $this->permission(), ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/BadgeCommand.php b/app/Console/Commands/Chat/BadgeCommand.php index e4439fe..2c9cb69 100644 --- a/app/Console/Commands/Chat/BadgeCommand.php +++ b/app/Console/Commands/Chat/BadgeCommand.php @@ -2,16 +2,19 @@ namespace App\Console\Commands\Chat; -use App\Models\User; -use App\Models\Role; use App\Events\UserRoleUpdatedEvent; +use App\Models\Role; +use App\Models\User; use Illuminate\Support\Facades\Log; class BadgeCommand extends AbstractChatCommand { protected string $name = 'badge'; + protected array $aliases = []; + protected string $description = 'Grant or revoke role-based badges for users'; + protected string $signature = '/badge '; protected array $parameters = [ @@ -43,7 +46,7 @@ public function rules(): array public function authorize(User $user): bool { - return $user->hasPermission('role.assign') || + return $user->hasPermission('role.assign') || $user->hasRole('admin'); } @@ -54,22 +57,25 @@ protected function execute(User $user, array $parameters): void $roleSlug = strtolower($parameters['role']); // Validate action - if (!in_array($action, ['grant', 'revoke'])) { + if (! in_array($action, ['grant', 'revoke'])) { $this->feedback($user, "Invalid action. Use 'grant' or 'revoke'.", 'error'); + return; } // Find role $role = Role::where('slug', $roleSlug)->first(); - if (!$role) { + if (! $role) { $this->feedback($user, "Role '{$roleSlug}' not found.", 'error'); + return; } // Find target user $targetUser = User::where('name', $username)->first(); - if (!$targetUser) { + if (! $targetUser) { $this->feedback($user, "User '{$username}' not found.", 'error'); + return; } @@ -85,14 +91,16 @@ private function grantRole(User $grantor, User $targetUser, Role $role): void // Check if user already has the role if ($targetUser->hasRole($role->slug)) { $this->feedback($grantor, "User already has the {$role->name} role.", 'warning'); + return; } // Attach role to user $targetUser->roles()->attach($role->id); - // Clear user's role cache + // Clear cached role/badge data used by chat \Cache::forget("user_roles_{$targetUser->id}"); + \App\Services\Chat\MessagePresenter::forgetAuthor($targetUser->id); // Broadcast update (using existing UserRoleUpdatedEvent if it exists) if (class_exists('App\Events\UserRoleUpdatedEvent')) { @@ -114,16 +122,18 @@ private function grantRole(User $grantor, User $targetUser, Role $role): void private function revokeRole(User $revoker, User $targetUser, Role $role): void { // Check if user has the role - if (!$targetUser->hasRole($role->slug)) { + if (! $targetUser->hasRole($role->slug)) { $this->feedback($revoker, "User does not have the {$role->name} role.", 'warning'); + return; } // Detach role from user $targetUser->roles()->detach($role->id); - // Clear user's role cache + // Clear cached role/badge data used by chat \Cache::forget("user_roles_{$targetUser->id}"); + \App\Services\Chat\MessagePresenter::forgetAuthor($targetUser->id); // Broadcast update (using existing UserRoleUpdatedEvent if it exists) if (class_exists('App\Events\UserRoleUpdatedEvent')) { @@ -150,4 +160,4 @@ public function examples(): array '/badge grant ArtistName staff' => 'Grant staff role to ArtistName', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/BroadcastCommand.php b/app/Console/Commands/Chat/BroadcastCommand.php index 6f494b9..1c8d541 100644 --- a/app/Console/Commands/Chat/BroadcastCommand.php +++ b/app/Console/Commands/Chat/BroadcastCommand.php @@ -3,78 +3,59 @@ namespace App\Console\Commands\Chat; use App\Models\User; -use App\Models\Message; -use App\Events\Chat\Broadcasts\SystemAnnouncementEvent; -use Illuminate\Support\Facades\Log; +use App\Services\Chat\ChatModerationService; +use App\Services\ChatMessageSanitizer; +use Illuminate\Auth\Access\AuthorizationException; class BroadcastCommand extends AbstractChatCommand { protected string $name = 'broadcast'; + protected array $aliases = ['announce', 'bc']; - protected string $description = 'Broadcast a system message to all users'; + + protected string $description = 'Post a highlighted announcement in chat'; + protected string $signature = '/broadcast '; protected array $parameters = [ 'message' => [ 'required' => true, 'type' => 'string', - 'description' => 'Message to broadcast', + 'description' => 'Message to announce', ], ]; public function authorize(User $user): bool { - return $user->hasPermission('chat.broadcast') || - $user->hasRole('admin') || - $user->hasRole('moderator'); + return $user->canModerateChat() || $user->hasPermission('chat.broadcast'); } protected function execute(User $user, array $parameters): void { - $messageContent = trim($parameters['message'] ?? ''); + $body = (new ChatMessageSanitizer)->sanitize((string) ($parameters['message'] ?? '')); - if (empty($messageContent)) { - $this->feedback($user, 'Broadcast message cannot be empty.', 'error'); - return; - } + if ($body === '') { + $this->feedback($user, 'Announcement cannot be empty.', 'error'); - // Check message length - if (strlen($messageContent) > 500) { - $this->feedback($user, 'Broadcast message is too long (max 500 characters).', 'error'); return; } - // Create the message in the database - $message = Message::create([ - 'message' => $messageContent, - 'user_id' => null, // System messages don't have a user - 'is_command' => false, - 'type' => 'announcement', - 'priority' => 'high', - 'metadata' => [ - 'sent_by_user_id' => $user->id, - 'sent_by_user_name' => $user->name, - ] - ]); + try { + app(ChatModerationService::class)->announce($user, $body, $this->sourceId); + } catch (AuthorizationException $e) { + $this->feedback($user, $e->getMessage(), 'error'); - // Broadcast the announcement using the dedicated system announcement event - broadcast(new SystemAnnouncementEvent($message)); + return; + } - // Log the broadcast - Log::info('System broadcast sent', [ - 'moderator_id' => $user->id, - 'moderator_name' => $user->name, - 'message' => $messageContent, - 'timestamp' => now(), - ]); + $this->feedback($user, 'Announcement sent.', 'success'); } public function examples(): array { return [ '/broadcast Welcome to the stream!' => 'Send a welcome announcement', - '/announce Stream starting in 5 minutes' => 'Using alias for announcement', - '/bc Technical difficulties, please stand by' => 'Short alias for quick broadcast', + '/bc Technical difficulties, please stand by' => 'Short alias for a quick announcement', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/DeleteCommand.php b/app/Console/Commands/Chat/DeleteCommand.php index 06711d8..7a1d9d7 100644 --- a/app/Console/Commands/Chat/DeleteCommand.php +++ b/app/Console/Commands/Chat/DeleteCommand.php @@ -2,19 +2,17 @@ namespace App\Console\Commands\Chat; -use App\Events\Chat\Broadcasts\BroadcastMessageDeletionIdsEvent; -use App\Models\Message; use App\Models\User; -use Carbon\Carbon; -use Illuminate\Support\Facades\Log; +use App\Services\Chat\ChatModerationService; +use Illuminate\Auth\Access\AuthorizationException; class DeleteCommand extends AbstractChatCommand { protected string $name = 'delete'; - protected array $aliases = ['del', 'remove', 'purge']; + protected array $aliases = ['del', 'remove']; - protected string $description = 'Delete all messages from a user within a time period'; + protected string $description = "Delete a user's messages from the last N minutes"; protected string $signature = '/delete '; @@ -33,142 +31,51 @@ class DeleteCommand extends AbstractChatCommand public function authorize(User $user): bool { - return $user->hasPermission('chat.moderate') || - $user->hasRole('admin') || - $user->hasRole('moderator'); + return $user->canModerateChat(); } protected function execute(User $user, array $parameters): void { - $username = $parameters['username']; - $duration = $parameters['duration']; + $moderation = app(ChatModerationService::class); - // Find the target user - $targetUser = User::where('name', $username)->first(); + $targetUser = User::where('name', $parameters['username'])->first(); if (! $targetUser) { - $this->feedback($user, "User '{$username}' not found.", 'error'); + $this->feedback($user, "User '{$parameters['username']}' not found.", 'error'); return; } - // Parse duration to get the time range - $cutoffTime = $this->parseDurationToTime($duration); - if (! $cutoffTime) { - $this->feedback($user, "Invalid duration format. Use formats like '5m', '1h', '1d'.", 'error'); + $seconds = ChatModerationService::parseDuration((string) $parameters['duration']); + + if (! $seconds) { + $this->feedback($user, "Invalid duration. Use formats like '5m', '1h', '1d'.", 'error'); return; } - // Find all messages from the user within the time period - $messages = Message::where('user_id', $targetUser->id) - ->where('created_at', '>=', $cutoffTime) - ->whereNull('deleted_at') - ->get(); - - if ($messages->isEmpty()) { - $this->feedback($user, "No messages found from '{$username}' in the last {$duration}.", 'info'); + try { + $count = $moderation->purgeUser($user, $targetUser, $this->sourceId, $seconds); + } catch (AuthorizationException $e) { + $this->feedback($user, $e->getMessage(), 'error'); return; } - // Group messages by source_id for broadcasting - $messagesBySource = $messages->groupBy('source_id'); - $messageCount = $messages->count(); - - // Log the IDs being deleted for debugging - Log::info('Deleting messages with IDs', [ - 'message_ids' => $messages->pluck('id')->toArray(), - 'count' => $messageCount, - 'target_user' => $username, - ]); - - // Soft delete all messages - Message::whereIn('id', $messages->pluck('id'))->update([ - 'deleted_at' => now(), - 'deleted_by_user_id' => $user->id, - ]); - - // Broadcast deletion event to each source channel - foreach ($messagesBySource as $sourceId => $sourceMessages) { - broadcast(new BroadcastMessageDeletionIdsEvent( - $sourceMessages->pluck('id')->toArray(), - $sourceId - )); - } - - // Calculate human-readable duration - $durationText = $this->humanizeDuration($duration); - - // Send feedback to moderator - $this->feedback($user, "Deleted {$messageCount} messages from '{$username}' from the last {$durationText}.", 'success'); - - // Notify the target user - $this->feedback($targetUser, "{$messageCount} of your messages from the last {$durationText} have been deleted by a moderator.", 'warning'); - - // Log the deletion - Log::info('Bulk message deletion by moderator', [ - 'moderator_id' => $user->id, - 'target_user_id' => $targetUser->id, - 'message_count' => $messageCount, - 'duration' => $duration, - 'cutoff_time' => $cutoffTime, - ]); - - // Broadcast system message to chat - $this->broadcastSystemMessage( - "Messages from {$username} in the last {$durationText} have been deleted", - 'moderation' + $this->feedback( + $user, + $count === 0 + ? "No messages from {$targetUser->name} in the last ".$moderation->humanizeSeconds($seconds).'.' + : "Deleted {$count} message".($count === 1 ? '' : 's')." from {$targetUser->name}.", + $count === 0 ? 'info' : 'success', ); } - private function parseDurationToTime(string $duration): ?Carbon - { - $matches = []; - if (! preg_match('/^(\d+)([smhd])$/i', $duration, $matches)) { - return null; - } - - $value = (int) $matches[1]; - $unit = strtolower($matches[2]); - - $now = now(); - - return match ($unit) { - 's' => $now->subSeconds($value), - 'm' => $now->subMinutes($value), - 'h' => $now->subHours($value), - 'd' => $now->subDays($value), - default => null, - }; - } - - private function humanizeDuration(string $duration): string - { - $matches = []; - if (! preg_match('/^(\d+)([smhd])$/i', $duration, $matches)) { - return $duration; - } - - $value = (int) $matches[1]; - $unit = strtolower($matches[2]); - - return match ($unit) { - 's' => $value.' second'.($value > 1 ? 's' : ''), - 'm' => $value.' minute'.($value > 1 ? 's' : ''), - 'h' => $value.' hour'.($value > 1 ? 's' : ''), - 'd' => $value.' day'.($value > 1 ? 's' : ''), - default => $duration, - }; - } - public function examples(): array { return [ '/delete JohnDoe 5m' => 'Delete all messages from JohnDoe in the last 5 minutes', - '/delete JohnDoe 1h' => 'Delete all messages from JohnDoe in the last hour', - '/delete JohnDoe 1d' => 'Delete all messages from JohnDoe in the last day', - '/purge SpamUser 30m' => 'Using alias to purge messages from last 30 minutes', + '/del SpamUser 30m' => 'Using alias to delete messages from the last 30 minutes', ]; } } diff --git a/app/Console/Commands/Chat/HelpCommand.php b/app/Console/Commands/Chat/HelpCommand.php index 5918c89..2841c5c 100644 --- a/app/Console/Commands/Chat/HelpCommand.php +++ b/app/Console/Commands/Chat/HelpCommand.php @@ -8,8 +8,11 @@ class HelpCommand extends AbstractChatCommand { protected string $name = 'help'; + protected array $aliases = ['h', 'commands']; + protected string $description = 'Show available commands and their usage'; + protected string $signature = '/help [command]'; protected array $parameters = [ @@ -24,9 +27,10 @@ class HelpCommand extends AbstractChatCommand protected function getRegistry(): CommandRegistry { - if (!$this->registry) { + if (! $this->registry) { $this->registry = app(CommandRegistry::class); } + return $this->registry; } @@ -50,13 +54,15 @@ private function showCommandHelp(User $user, string $commandName): void { $command = $this->getRegistry()->get($commandName); - if (!$command) { + if (! $command) { $this->feedback($user, "Command '/{$commandName}' not found.", 'error'); + return; } - if (!$command->authorize($user)) { + if (! $command->authorize($user)) { $this->feedback($user, "You don't have permission to use '/{$commandName}'.", 'error'); + return; } @@ -68,12 +74,12 @@ private function showCommandHelp(User $user, string $commandName): void $message .= "**Description:** {$info['description']}\n"; $message .= "**Usage:** {$info['signature']}\n"; - if (!empty($info['aliases'])) { - $aliases = array_map(fn($a) => "/{$a}", $info['aliases']); - $message .= "**Aliases:** " . implode(', ', $aliases) . "\n"; + if (! empty($info['aliases'])) { + $aliases = array_map(fn ($a) => "/{$a}", $info['aliases']); + $message .= '**Aliases:** '.implode(', ', $aliases)."\n"; } - if (!empty($examples)) { + if (! empty($examples)) { $message .= "\n**Examples:**\n"; foreach ($examples as $example => $description) { $message .= "• `{$example}` - {$description}\n"; @@ -89,6 +95,7 @@ private function showAllCommands(User $user): void if (empty($availableCommands)) { $this->feedback($user, 'No commands available for your permission level.', 'info'); + return; } @@ -110,14 +117,14 @@ private function showAllCommands(User $user): void $message .= "\n"; } - $message .= "_Use `/help ` for detailed information about a specific command._"; + $message .= '_Use `/help ` for detailed information about a specific command._'; $this->feedback($user, $message, 'info', ['format' => 'markdown']); } private function getCommandCategory(string $commandName): string { - return match($commandName) { + return match ($commandName) { 'timeout', 'slowmode', 'delete', 'nuke' => 'Moderation', 'badge' => 'User Management', 'broadcast' => 'Communication', @@ -134,4 +141,4 @@ public function examples(): array '/commands' => 'Using alias to show all commands', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/NukeCommand.php b/app/Console/Commands/Chat/NukeCommand.php index 9726bf0..e76f122 100644 --- a/app/Console/Commands/Chat/NukeCommand.php +++ b/app/Console/Commands/Chat/NukeCommand.php @@ -3,23 +3,24 @@ namespace App\Console\Commands\Chat; use App\Models\User; -use App\Models\Message; -use App\Events\MessageDeletedEvent; -use Illuminate\Support\Facades\Log; -use Carbon\Carbon; +use App\Services\Chat\ChatModerationService; +use Illuminate\Auth\Access\AuthorizationException; class NukeCommand extends AbstractChatCommand { protected string $name = 'nuke'; + protected array $aliases = ['purge']; - protected string $description = 'Delete multiple messages from a user or time period'; + + protected string $description = 'Delete recent messages from one user or from the whole chat'; + protected string $signature = '/nuke [duration]'; protected array $parameters = [ 'target' => [ 'required' => true, 'type' => 'string', - 'description' => 'Username or "all" for all messages', + 'description' => 'Username or "all" for the whole chat', ], 'duration' => [ 'required' => false, @@ -30,154 +31,50 @@ class NukeCommand extends AbstractChatCommand public function authorize(User $user): bool { - return $user->hasPermission('chat.nuke') || - $user->hasRole('admin'); + return $user->hasPermission('chat.nuke') || $user->isAdmin(); } protected function execute(User $user, array $parameters): void { - $target = $parameters['target']; - $duration = $parameters['duration'] ?? '5m'; + $moderation = app(ChatModerationService::class); + $target = (string) $parameters['target']; + $seconds = ChatModerationService::parseDuration((string) ($parameters['duration'] ?? '5m')); - // Parse duration - $since = $this->parseDuration($duration); - if (!$since) { - $this->feedback($user, "Invalid duration format. Use formats like '5m', '1h'.", 'error'); - return; - } - - // Build query - $query = Message::where('created_at', '>=', $since) - ->whereNull('deleted_at'); - - // If target is not "all", filter by user - if (strtolower($target) !== 'all') { - $targetUser = User::where('name', $target)->first(); - - if (!$targetUser) { - $this->feedback($user, "User '{$target}' not found.", 'error'); - return; - } + if (! $seconds) { + $this->feedback($user, "Invalid duration. Use formats like '5m', '1h'.", 'error'); - // Prevent nuking admin/moderator messages - if ($targetUser->hasRole('admin') || $targetUser->hasRole('moderator')) { - $this->feedback($user, 'Cannot nuke messages from administrators or moderators.', 'error'); - return; - } - - $query->where('user_id', $targetUser->id); - } - - // Get messages to delete - $messages = $query->get(); - $messageCount = $messages->count(); - - if ($messageCount === 0) { - $this->feedback($user, 'No messages found to delete.', 'info'); return; } - // Confirm if large number of messages - if ($messageCount > 100) { - // In a real implementation, you might want to add a confirmation step - Log::warning('Large nuke operation attempted', [ - 'moderator_id' => $user->id, - 'message_count' => $messageCount, - ]); - } - - // Delete messages - $deletedUuids = []; - foreach ($messages as $message) { - $deletedUuids[] = $message->uuid; - - $message->deleted_at = now(); - $message->deleted_by_user_id = $user->id; - $message->save(); - } + try { + if (strtolower($target) === 'all') { + $count = $moderation->clearChat($user, $this->sourceId); + } else { + $targetUser = User::where('name', $target)->first(); - // Broadcast deletion events (batch for efficiency) - foreach (array_chunk($deletedUuids, 50) as $uuidBatch) { - broadcast(new MessageDeletedEvent($uuidBatch))->toOthers(); - } + if (! $targetUser) { + $this->feedback($user, "User '{$target}' not found.", 'error'); - // Send feedback - $targetDesc = strtolower($target) === 'all' ? 'all users' : $target; - $durationText = $this->humanizeDuration($duration); - $this->feedback( - $user, - "Nuked {$messageCount} messages from {$targetDesc} in the last {$durationText}.", - 'success' - ); - - // Notify affected users - if (strtolower($target) !== 'all' && isset($targetUser)) { - $this->feedback( - $targetUser, - "Your recent messages have been removed by a moderator.", - 'warning' - ); - } + return; + } - // Log the nuke operation - Log::info('Chat nuke executed', [ - 'moderator_id' => $user->id, - 'target' => $target, - 'duration' => $duration, - 'message_count' => $messageCount, - 'since' => $since, - ]); - - // Broadcast system message - $this->broadcastSystemMessage( - "Chat has been cleaned by a moderator", - 'moderation' - ); - } + $count = $moderation->purgeUser($user, $targetUser, $this->sourceId, $seconds); + } + } catch (AuthorizationException $e) { + $this->feedback($user, $e->getMessage(), 'error'); - private function parseDuration(string $duration): ?Carbon - { - $matches = []; - if (!preg_match('/^(\d+)([smh])$/i', $duration, $matches)) { - return null; + return; } - $value = (int) $matches[1]; - $unit = strtolower($matches[2]); - - return match($unit) { - 's' => now()->subSeconds($value), - 'm' => now()->subMinutes($value), - 'h' => now()->subHours($value), - default => null, - }; - } - - private function humanizeDuration(string $duration): string - { - $matches = []; - if (preg_match('/^(\d+)([smh])$/i', $duration, $matches)) { - $value = (int) $matches[1]; - $unit = strtolower($matches[2]); - - return match($unit) { - 's' => $value . ' second' . ($value > 1 ? 's' : ''), - 'm' => $value . ' minute' . ($value > 1 ? 's' : ''), - 'h' => $value . ' hour' . ($value > 1 ? 's' : ''), - default => $duration, - }; - } - - return $duration; + $this->feedback($user, "Removed {$count} message".($count === 1 ? '' : 's').'.', 'success'); } public function examples(): array { return [ - '/nuke JohnDoe' => 'Delete last 5 minutes of messages from JohnDoe', - '/nuke JohnDoe 10m' => 'Delete last 10 minutes of messages from JohnDoe', - '/nuke all 1m' => 'Delete all messages from last minute', - '/purge spammer 1h' => 'Using alias to purge last hour of messages', + '/nuke JohnDoe' => 'Delete the last 5 minutes of messages from JohnDoe', + '/nuke JohnDoe 10m' => 'Delete the last 10 minutes of messages from JohnDoe', + '/nuke all' => 'Clear the chat for this stream', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/SlowModeCommand.php b/app/Console/Commands/Chat/SlowModeCommand.php index 2909875..4fdb9fa 100644 --- a/app/Console/Commands/Chat/SlowModeCommand.php +++ b/app/Console/Commands/Chat/SlowModeCommand.php @@ -3,108 +3,78 @@ namespace App\Console\Commands\Chat; use App\Models\User; -use App\Models\ChatSetting; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Log; +use App\Services\Chat\ChatModerationService; +use App\Services\Chat\ChatSettingsService; +use Illuminate\Auth\Access\AuthorizationException; class SlowModeCommand extends AbstractChatCommand { protected string $name = 'slowmode'; + protected array $aliases = ['slow']; - protected string $description = 'Enable or configure slow mode for chat'; + + protected string $description = 'Enable, configure or disable slow mode'; + protected string $signature = '/slowmode [seconds|off]'; protected array $parameters = [ 'duration' => [ 'required' => false, 'type' => 'string', - 'description' => 'Seconds between messages or "off" to disable', + 'description' => 'Seconds between messages, or "off" to disable', ], ]; public function authorize(User $user): bool { - return $user->hasPermission('chat.moderate') || - $user->hasRole('admin') || - $user->hasRole('moderator'); + return $user->canModerateChat(); } protected function execute(User $user, array $parameters): void { + $settings = app(ChatSettingsService::class); $duration = $parameters['duration'] ?? null; + $current = $settings->slowModeSeconds($this->sourceId); - // Get current slow mode setting - $currentSetting = ChatSetting::where('key', 'slow_mode_seconds')->first(); - $currentValue = $currentSetting ? (int) $currentSetting->value : 0; - - // If no parameter provided, show current status if ($duration === null) { - if ($currentValue > 0) { - $this->feedback($user, "Slow mode is currently set to {$currentValue} seconds.", 'info'); - } else { - $this->feedback($user, 'Slow mode is currently disabled.', 'info'); - } - return; - } + $this->feedback( + $user, + $current > 0 ? "Slow mode is set to {$current} seconds." : 'Slow mode is disabled.', + 'info', + ); - // Handle turning off slow mode - if (strtolower($duration) === 'off' || $duration === '0') { - if ($currentSetting) { - $currentSetting->update(['value' => '0']); - } - - Cache::forget('chat.slow_mode'); - - $this->feedback($user, 'Slow mode has been disabled.', 'success'); - $this->broadcastSystemMessage('Slow mode has been disabled', 'info'); - - Log::info('Slow mode disabled', [ - 'moderator_id' => $user->id, - ]); return; } - // Validate duration is a positive integer - if (!is_numeric($duration) || $duration < 1) { - $this->feedback($user, 'Duration must be a positive number of seconds or "off".', 'error'); + $seconds = strtolower((string) $duration) === 'off' ? 0 : ChatModerationService::parseDuration((string) $duration); + + if ($seconds === null || $seconds < 0 || $seconds > 300) { + $this->feedback($user, 'Use a number of seconds between 1 and 300, or "off".', 'error'); + return; } - $seconds = (int) $duration; + try { + app(ChatModerationService::class)->updateSettings($user, ['slow_mode_seconds' => $seconds], $this->sourceId); + } catch (AuthorizationException $e) { + $this->feedback($user, $e->getMessage(), 'error'); - // Validate reasonable limits (1 second to 5 minutes) - if ($seconds < 1 || $seconds > 300) { - $this->feedback($user, 'Duration must be between 1 and 300 seconds.', 'error'); return; } - // Update or create setting - ChatSetting::updateOrCreate( - ['key' => 'slow_mode_seconds'], - ['value' => (string) $seconds] + $this->feedback( + $user, + $seconds === 0 ? 'Slow mode disabled.' : "Slow mode enabled: {$seconds} seconds between messages.", + 'success', ); - - // Clear cache to apply immediately - Cache::forget('chat.slow_mode'); - Cache::put('chat.slow_mode', $seconds, now()->addHours(24)); - - $this->feedback($user, "Slow mode enabled: {$seconds} seconds between messages.", 'success'); - $this->broadcastSystemMessage("Slow mode enabled: {$seconds} seconds between messages", 'warning'); - - Log::info('Slow mode enabled', [ - 'moderator_id' => $user->id, - 'seconds' => $seconds, - ]); } public function examples(): array { return [ - '/slowmode' => 'Check current slow mode status', + '/slowmode' => 'Check the current slow mode setting', '/slowmode 10' => 'Enable 10 second slow mode', - '/slowmode 30' => 'Enable 30 second slow mode', '/slowmode off' => 'Disable slow mode', - '/slow 5' => 'Using alias to enable 5 second slow mode', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/Chat/TimeoutCommand.php b/app/Console/Commands/Chat/TimeoutCommand.php index f8c836c..3a554ea 100644 --- a/app/Console/Commands/Chat/TimeoutCommand.php +++ b/app/Console/Commands/Chat/TimeoutCommand.php @@ -3,15 +3,17 @@ namespace App\Console\Commands\Chat; use App\Models\User; -use App\Models\Timeout; -use Carbon\Carbon; -use Illuminate\Support\Facades\Log; +use App\Services\Chat\ChatModerationService; +use Illuminate\Auth\Access\AuthorizationException; class TimeoutCommand extends AbstractChatCommand { protected string $name = 'timeout'; + protected array $aliases = ['to', 'mute']; + protected string $description = 'Timeout a user from sending messages'; + protected string $signature = '/timeout [reason]'; protected array $parameters = [ @@ -34,133 +36,42 @@ class TimeoutCommand extends AbstractChatCommand public function authorize(User $user): bool { - return $user->hasPermission('chat.moderate') || - $user->hasRole('admin') || - $user->hasRole('moderator'); + return $user->canModerateChat(); } protected function execute(User $user, array $parameters): void { - $targetUsername = $parameters['username']; - $duration = $parameters['duration']; - $reason = $parameters['reason'] ?? null; - - // Find the target user - $targetUser = User::where('name', $targetUsername)->first(); - - if (!$targetUser) { - $this->feedback($user, "User '{$targetUsername}' not found.", 'error'); - return; - } + $moderation = app(ChatModerationService::class); - // Check if trying to timeout self - if ($targetUser->id === $user->id) { - $this->feedback($user, 'You cannot timeout yourself.', 'error'); - return; - } + $targetUser = User::where('name', $parameters['username'])->first(); - // Parse duration - $expiresAt = $this->parseDuration($duration); - if (!$expiresAt) { - $this->feedback($user, "Invalid duration format. Use formats like '5m', '1h', '1d'.", 'error'); - return; - } + if (! $targetUser) { + $this->feedback($user, "User '{$parameters['username']}' not found.", 'error'); - // Check for existing active timeout - $existingTimeout = Timeout::where('user_id', $targetUser->id) - ->where('expires_at', '>', now()) - ->first(); - - if ($existingTimeout) { - // Update existing timeout - $existingTimeout->update([ - 'expires_at' => $expiresAt, - 'reason' => $reason, - 'issued_by_user_id' => $user->id, - ]); - } else { - // Create new timeout - Timeout::create([ - 'user_id' => $targetUser->id, - 'issued_by_user_id' => $user->id, - 'expires_at' => $expiresAt, - 'reason' => $reason, - ]); + return; } - // Calculate human-readable duration - $durationText = $this->humanizeDuration($expiresAt); - - // Send feedback to moderator - $message = "User '{$targetUsername}' has been timed out for {$durationText}"; - if ($reason) { - $message .= " (Reason: {$reason})"; - } - $this->feedback($user, $message, 'success'); + $seconds = ChatModerationService::parseDuration((string) $parameters['duration']); - // Send notification to timed out user - $targetMessage = "You have been timed out for {$durationText}"; - if ($reason) { - $targetMessage .= " (Reason: {$reason})"; - } - $this->feedback($targetUser, $targetMessage, 'warning'); - - // Log the timeout - Log::info('User timeout', [ - 'moderator_id' => $user->id, - 'target_user_id' => $targetUser->id, - 'duration' => $duration, - 'expires_at' => $expiresAt, - 'reason' => $reason, - ]); - - // Broadcast system message to chat - $this->broadcastSystemMessage( - "{$targetUsername} has been timed out for {$durationText}", - 'timeout' - ); - } + if (! $seconds) { + $this->feedback($user, "Invalid duration. Use formats like '5m', '1h', '1d'.", 'error'); - private function parseDuration(string $duration): ?Carbon - { - $matches = []; - if (!preg_match('/^(\d+)([smhd])$/i', $duration, $matches)) { - return null; + return; } - $value = (int) $matches[1]; - $unit = strtolower($matches[2]); - - $now = now(); - - return match($unit) { - 's' => $now->addSeconds($value), - 'm' => $now->addMinutes($value), - 'h' => $now->addHours($value), - 'd' => $now->addDays($value), - default => null, - }; - } + try { + $moderation->timeout($user, $targetUser, $seconds, $parameters['reason'] ?? null, $this->sourceId); + } catch (AuthorizationException $e) { + $this->feedback($user, $e->getMessage(), 'error'); - private function humanizeDuration(Carbon $expiresAt): string - { - $diff = now()->diff($expiresAt); - - $parts = []; - if ($diff->days > 0) { - $parts[] = $diff->days . ' day' . ($diff->days > 1 ? 's' : ''); - } - if ($diff->h > 0) { - $parts[] = $diff->h . ' hour' . ($diff->h > 1 ? 's' : ''); - } - if ($diff->i > 0) { - $parts[] = $diff->i . ' minute' . ($diff->i > 1 ? 's' : ''); - } - if (empty($parts) && $diff->s > 0) { - $parts[] = $diff->s . ' second' . ($diff->s > 1 ? 's' : ''); + return; } - return implode(', ', $parts) ?: 'a moment'; + $this->feedback( + $user, + "{$targetUser->name} was timed out for ".$moderation->humanizeSeconds($seconds).'.', + 'success', + ); } public function examples(): array @@ -168,8 +79,7 @@ public function examples(): array return [ '/timeout JohnDoe 5m' => 'Timeout JohnDoe for 5 minutes', '/timeout JohnDoe 1h Spamming' => 'Timeout JohnDoe for 1 hour with reason', - '/timeout JohnDoe 1d Inappropriate behavior' => 'Timeout for 1 day with reason', '/to JohnDoe 30m' => 'Using alias to timeout for 30 minutes', ]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/CheckAutoModeShows.php b/app/Console/Commands/CheckAutoModeShows.php index 26defa6..b54c3ad 100644 --- a/app/Console/Commands/CheckAutoModeShows.php +++ b/app/Console/Commands/CheckAutoModeShows.php @@ -7,110 +7,91 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; +/** + * Drives auto mode, once a minute. + * + * Two independent rules, documented in full in docs/admin/auto-mode.md: + * + * 1. Start - a scheduled show goes live once its scheduled start has passed *and* its + * source is actually online. Without the source check an auto show would go + * live to an empty stream. + * 2. Stop - a live show ends at its hard stop, whatever the source is doing. The hard + * stop is `auto_stop_at`, falling back to `scheduled_end`. This is the safety + * net: a dance nobody remembers to end stops itself instead of recording all + * night. + * + * Only shows with `auto_mode` on are touched. Everything else is the operator's to drive. + */ class CheckAutoModeShows extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'shows:check-auto-mode'; - /** - * The console command description. - * - * @var string - */ - protected $description = 'Check and start/end auto mode shows based on schedule (ends at scheduled time regardless of source status)'; + protected $description = 'Start auto mode shows when their source comes online, and stop them at their hard stop time'; - /** - * Execute the console command. - */ - public function handle() + public function handle(): int { - $this->info('Checking auto mode shows...'); - - // Check for shows that should start $this->checkShowsToStart(); - - // Check for shows that should end $this->checkShowsToEnd(); - $this->info('Auto mode check completed.'); + return self::SUCCESS; } /** - * Check for scheduled shows that should start automatically. + * Scheduled, auto mode, start time passed, source online. */ - private function checkShowsToStart() + private function checkShowsToStart(): void { - // Find scheduled shows in auto mode where: - // 1. The scheduled start time has passed - // 2. The source is online - // 3. The show is still in scheduled status - $showsToStart = Show::where('auto_mode', true) + $shows = Show::query() + ->where('auto_mode', true) ->where('status', 'scheduled') ->where('scheduled_start', '<=', now()) - ->whereHas('source', function ($query) { - $query->where('status', SourceStatusEnum::ONLINE); - }) + ->whereHas('source', fn ($query) => $query->where('status', SourceStatusEnum::ONLINE)) ->get(); - foreach ($showsToStart as $show) { - $this->info("Starting auto mode show: {$show->title}"); - - Log::info('CheckAutoModeShows: Auto-starting show at scheduled time', [ + foreach ($shows as $show) { + Log::info('Auto mode: starting show', [ 'show_id' => $show->id, 'show_title' => $show->title, - 'scheduled_start' => $show->scheduled_start, - 'source_status' => $show->source->status->value, + 'scheduled_start' => $show->scheduled_start?->toIso8601String(), + 'source_status' => $show->source?->status?->value, ]); $show->goLive(); - $this->info("✓ Show '{$show->title}' started successfully"); - } - - if ($showsToStart->isEmpty()) { - $this->info('No shows to auto-start at this time.'); + $this->info("Started '{$show->title}'"); } } /** - * Check for live shows that should end automatically. - * Shows end when their scheduled end time is reached, regardless of source status. - * This ensures shows don't run indefinitely even if the source stays online. + * Live, auto mode, hard stop reached. + * + * The hard stop is filtered in PHP rather than SQL because it is `auto_stop_at` with a + * fallback to `scheduled_end`, and expressing that as a COALESCE would tie this to one + * database's date handling. The candidate set is only the live auto-mode shows, so it + * is a handful of rows at most. */ - private function checkShowsToEnd() + private function checkShowsToEnd(): void { - // Find all live auto mode shows where the scheduled end time has passed - // These shows will be ended regardless of whether the source is online, offline, or in error - $showsToEnd = Show::where('auto_mode', true) + $shows = Show::query() + ->where('auto_mode', true) ->where('status', 'live') - ->where('scheduled_end', '<=', now()) - ->get(); + ->get() + ->filter(fn (Show $show) => $show->isPastAutoStop()); - foreach ($showsToEnd as $show) { - $sourceStatus = $show->source ? $show->source->status->value : 'unknown'; - - $this->info("Ending auto mode show: {$show->title}"); - - Log::info('CheckAutoModeShows: Auto-ending show at scheduled end time', [ + foreach ($shows as $show) { + Log::info('Auto mode: hard stop reached, ending show', [ 'show_id' => $show->id, 'show_title' => $show->title, - 'scheduled_end' => $show->scheduled_end, - 'current_time' => now(), - 'source_status' => $sourceStatus, - 'reason' => 'Scheduled end time reached', + 'hard_stop' => $show->autoStopAt()?->toIso8601String(), + // Recorded because an explicit hard stop that is not the scheduled end is + // the case worth being able to explain after the fact. + 'explicit_hard_stop' => $show->auto_stop_at !== null, + 'source_status' => $show->source?->status?->value, ]); $show->endLivestream(); - $this->info("✓ Show '{$show->title}' ended successfully (scheduled end reached)"); - } - - if ($showsToEnd->isEmpty()) { - $this->info('No shows to auto-end at this time.'); + $this->info("Ended '{$show->title}' (hard stop reached)"); } } -} \ No newline at end of file +} diff --git a/app/Console/Commands/DevStreamKeys.php b/app/Console/Commands/DevStreamKeys.php new file mode 100644 index 0000000..df9f135 --- /dev/null +++ b/app/Console/Commands/DevStreamKeys.php @@ -0,0 +1,46 @@ +isLocal()) { + $this->error('dev:stream-keys only runs in the local environment.'); + + return self::FAILURE; + } + + $limit = (int) $this->option('limit'); + + $sources = Source::ordered() + ->when($limit > 0, fn ($query) => $query->limit($limit)) + ->get(); + + if ($sources->isEmpty()) { + $this->error('No sources found. Run: php artisan db:seed --class=DevStreamChannelsSeeder'); + + return self::FAILURE; + } + + // Space separated so the shell can pass it straight through as one env var. + $this->line($sources->map(fn (Source $source) => $source->slug.':'.$source->stream_key)->implode(' ')); + + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/ExtractDvrSegments.php b/app/Console/Commands/ExtractDvrSegments.php index 12c261d..0a8a775 100644 --- a/app/Console/Commands/ExtractDvrSegments.php +++ b/app/Console/Commands/ExtractDvrSegments.php @@ -3,8 +3,8 @@ namespace App\Console\Commands; use App\Services\DvrExtractorService; -use Illuminate\Console\Command; use Carbon\Carbon; +use Illuminate\Console\Command; class ExtractDvrSegments extends Command { @@ -42,18 +42,21 @@ public function __construct(DvrExtractorService $extractor) public function handle() { // Validate required options - if (!$this->option('stream')) { + if (! $this->option('stream')) { $this->error('The --stream option is required.'); + return 1; } - if (!$this->option('start')) { + if (! $this->option('start')) { $this->error('The --start option is required.'); + return 1; } - if (!$this->option('end')) { + if (! $this->option('end')) { $this->error('The --end option is required.'); + return 1; } @@ -66,12 +69,14 @@ public function handle() $endTime = Carbon::parse($this->option('end'), 'Europe/Berlin'); } catch (\Exception $e) { $this->error('Invalid date format. Please use format: Y-m-d H:i:s'); + return 1; } // Validate time range if ($endTime->lessThanOrEqualTo($startTime)) { $this->error('End time must be after start time.'); + return 1; } @@ -83,7 +88,7 @@ public function handle() $hours = floor($duration / 3600); $minutes = floor(($duration % 3600) / 60); $seconds = $duration % 60; - $this->info(sprintf("Duration: %02d:%02d:%02d", $hours, $minutes, $seconds)); + $this->info(sprintf('Duration: %02d:%02d:%02d', $hours, $minutes, $seconds)); $this->newLine(); // Generate default output filename if not provided @@ -99,32 +104,33 @@ public function handle() try { if ($dryRun) { $this->info('🔍 DRY RUN MODE - Previewing segments...'); - $this->info('Looking for segments between ' . ($startTime->timestamp * 1000) . ' and ' . ($endTime->timestamp * 1000)); + $this->info('Looking for segments between '.($startTime->timestamp * 1000).' and '.($endTime->timestamp * 1000)); $segments = $this->extractor->findSegments($stream, $startTime, $endTime); - + if (empty($segments)) { $this->warn('No segments found in the specified time range.'); + return 0; } - $this->info("Found " . count($segments) . " segments:"); + $this->info('Found '.count($segments).' segments:'); $this->table( ['Segment', 'Size', 'Timestamp'], array_map(function ($segment) { return [ basename($segment['path']), $this->formatBytes($segment['size']), - Carbon::createFromTimestampMs($segment['timestamp'])->format('Y-m-d H:i:s') + Carbon::createFromTimestampMs($segment['timestamp'])->format('Y-m-d H:i:s'), ]; }, $segments) ); $totalSize = array_sum(array_column($segments, 'size')); - $this->info("Total size to download: " . $this->formatBytes($totalSize)); + $this->info('Total size to download: '.$this->formatBytes($totalSize)); } else { // Perform actual extraction $this->info('Starting extraction process...'); - + $outputPath = $this->extractor->extract( $stream, $startTime, @@ -132,33 +138,33 @@ public function handle() $outputFilename, $targetStorage, function ($message, $type = 'info') { - match($type) { + match ($type) { 'error' => $this->error($message), 'warn' => $this->warn($message), - 'success' => $this->info("✅ " . $message), + 'success' => $this->info('✅ '.$message), default => $this->info($message), }; } ); $this->newLine(); - $this->info("✅ Extraction complete!"); + $this->info('✅ Extraction complete!'); $this->info("Output file: {$outputPath}"); - + // Show file size if (file_exists($outputPath)) { - $this->info("File size: " . $this->formatBytes(filesize($outputPath))); + $this->info('File size: '.$this->formatBytes(filesize($outputPath))); } } return 0; } catch (\Exception $e) { - $this->error('Extraction failed: ' . $e->getMessage()); - + $this->error('Extraction failed: '.$e->getMessage()); + if ($this->output->isVerbose()) { $this->error($e->getTraceAsString()); } - + return 1; } } @@ -171,6 +177,6 @@ private function formatBytes($bytes, $precision = 2) $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); - return round($bytes, $precision) . ' ' . $units[$pow]; + return round($bytes, $precision).' '.$units[$pow]; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/RecordShowStatistics.php b/app/Console/Commands/RecordShowStatistics.php index f195ad1..edc2579 100644 --- a/app/Console/Commands/RecordShowStatistics.php +++ b/app/Console/Commands/RecordShowStatistics.php @@ -27,29 +27,30 @@ class RecordShowStatistics extends Command */ public function handle() { - $service = new ShowStatisticsService(); - + $service = new ShowStatisticsService; + $liveShows = Show::live()->get(); - + if ($liveShows->isEmpty()) { $this->info('No live shows to record statistics for.'); + return Command::SUCCESS; } - + foreach ($liveShows as $show) { try { // First refresh the viewer count from source_users table $show->updateViewerCount(); - + // Then record statistics $service->recordStatistics($show); - + $this->info("Recorded statistics for show: {$show->title} (Viewers: {$show->viewer_count})"); } catch (\Exception $e) { $this->error("Failed to record statistics for show {$show->title}: {$e->getMessage()}"); } } - + return Command::SUCCESS; } } diff --git a/app/Console/Commands/ShowDnsDetails.php b/app/Console/Commands/ShowDnsDetails.php index 6e7657a..98fef75 100644 --- a/app/Console/Commands/ShowDnsDetails.php +++ b/app/Console/Commands/ShowDnsDetails.php @@ -45,8 +45,8 @@ public function handle() $this->info(' Contents:'); $this->line(file_get_contents($keyFile)); - // Show example nsupdate command for creating test222.stream.eurofurence.org - $hostname = 'test222.stream.eurofurence.org'; + // Show an example nsupdate command against the configured zone + $hostname = trim('test222.'.config('dns.zone'), '.'); $testIp = '127.0.0.1'; $this->info("\n3. Example nsupdate Command (for creating $hostname):"); diff --git a/app/Console/Commands/TestDockerUrls.php b/app/Console/Commands/TestDockerUrls.php index 1e289bc..5d6107c 100644 --- a/app/Console/Commands/TestDockerUrls.php +++ b/app/Console/Commands/TestDockerUrls.php @@ -28,7 +28,7 @@ public function handle() { $this->info('🐳 Testing Docker URL Configuration'); $this->newLine(); - + // Display environment info $this->table( ['Setting', 'Value'], @@ -42,9 +42,9 @@ public function handle() ['Regular HLS Port', env('HLS_EDGE_PORT')], ] ); - + $this->newLine(); - + // Get a source to test with $sourceId = $this->argument('source'); if ($sourceId) { @@ -54,52 +54,53 @@ public function handle() } else { $source = Source::first(); } - - if (!$source) { + + if (! $source) { $this->error('No source found. Please create a source first.'); + return 1; } - + $this->info("Testing with source: {$source->name} (slug: {$source->slug})"); $this->newLine(); - + // Test regular URL $this->info('📡 Regular HLS URL (for browser access):'); $regularUrl = $source->getHlsUrl(); $this->line(" Master: {$regularUrl}"); - + $this->newLine(); - + // Test internal URLs $this->info('🔧 Internal HLS URLs (for Docker container access):'); $internalUrls = $source->getInternalHlsUrls(); foreach ($internalUrls as $quality => $url) { $this->line(" {$quality}: {$url}"); } - + $this->newLine(); - + // Check Docker detection $isDocker = $source->isRunningInDocker(); $this->info('🔍 Docker Detection:'); - $this->line(' Running in Docker: ' . ($isDocker ? 'Yes' : 'No')); - $this->line(' /.dockerenv exists: ' . (file_exists('/.dockerenv') ? 'Yes' : 'No')); - $this->line(' /proc/1/cgroup check: ' . - ((file_exists('/proc/1/cgroup') && + $this->line(' Running in Docker: '.($isDocker ? 'Yes' : 'No')); + $this->line(' /.dockerenv exists: '.(file_exists('/.dockerenv') ? 'Yes' : 'No')); + $this->line(' /proc/1/cgroup check: '. + ((file_exists('/proc/1/cgroup') && str_contains(file_get_contents('/proc/1/cgroup'), 'docker')) ? 'Yes' : 'No')); - + $this->newLine(); - + // Show what ThumbnailService would use $this->info('📸 ThumbnailService would use:'); - $streamUrl = app()->runningInConsole() + $streamUrl = app()->runningInConsole() ? ($source->getInternalHlsUrls()['stream'] ?? $source->getHlsUrl()) : $source->getHlsUrl(); $this->line(" URL: {$streamUrl}"); - + $this->newLine(); $this->info('✅ Test complete!'); - + return 0; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/TestS3Storage.php b/app/Console/Commands/TestS3Storage.php index 4d42741..1272817 100644 --- a/app/Console/Commands/TestS3Storage.php +++ b/app/Console/Commands/TestS3Storage.php @@ -30,7 +30,7 @@ public function handle() { $this->info('🚀 Starting S3 Storage Test...'); $this->newLine(); - + // Display current configuration $this->info('📋 Current S3 Configuration:'); $this->table( @@ -41,45 +41,47 @@ public function handle() ['Region', config('filesystems.disks.s3.region')], ['Path Style', config('filesystems.disks.s3.use_path_style_endpoint') ? 'Yes' : 'No'], ['URL', config('filesystems.disks.s3.url') ?: 'Not set'], - ['Key ID', substr(config('filesystems.disks.s3.key'), 0, 10) . '...'], + ['Key ID', substr(config('filesystems.disks.s3.key'), 0, 10).'...'], ] ); $this->newLine(); // Test basic connection first $this->info('🔌 Testing S3 Connection...'); - + try { $disk = Storage::disk('s3'); - + // Try to list files in root to test connection $files = $disk->files('/'); $this->info('✅ Successfully connected to S3!'); - $this->line('Found ' . count($files) . ' files in root directory'); - + $this->line('Found '.count($files).' files in root directory'); + } catch (S3Exception $e) { - $this->error('❌ S3 Connection Error: ' . $e->getAwsErrorMessage()); - $this->line('Error Code: ' . $e->getAwsErrorCode()); - $this->line('Request ID: ' . $e->getAwsRequestId()); + $this->error('❌ S3 Connection Error: '.$e->getAwsErrorMessage()); + $this->line('Error Code: '.$e->getAwsErrorCode()); + $this->line('Request ID: '.$e->getAwsRequestId()); + return 1; } catch (\Exception $e) { - $this->error('❌ Connection Error: ' . $e->getMessage()); + $this->error('❌ Connection Error: '.$e->getMessage()); + return 1; } $this->newLine(); - + // Now test file operations - $testFileName = 'test-' . Str::random(10) . '.txt'; - $testContent = "Test file created at " . now()->toDateTimeString(); + $testFileName = 'test-'.Str::random(10).'.txt'; + $testContent = 'Test file created at '.now()->toDateTimeString(); try { // Test 1: Upload a file $this->info('📤 Test 1: Uploading file to S3...'); - $this->line('File: ' . $testFileName); - + $this->line('File: '.$testFileName); + $uploaded = false; - + // Method 1: Simple put try { $uploaded = $disk->put($testFileName, $testContent); @@ -87,11 +89,11 @@ public function handle() $this->info('✅ File uploaded successfully using put()'); } } catch (\Exception $e) { - $this->warn('put() failed: ' . $e->getMessage()); + $this->warn('put() failed: '.$e->getMessage()); } - + // If first method failed, try alternative - if (!$uploaded) { + if (! $uploaded) { try { // Method 2: Using Flysystem Config object $adapter = $disk->getAdapter(); @@ -100,73 +102,75 @@ public function handle() $uploaded = true; $this->info('✅ File uploaded successfully using adapter'); } catch (\Exception $e) { - $this->error('Adapter write failed: ' . $e->getMessage()); + $this->error('Adapter write failed: '.$e->getMessage()); throw $e; } } - - if (!$uploaded) { + + if (! $uploaded) { $this->error('❌ All upload methods failed'); + return 1; } // Test 2: Check if file exists $this->newLine(); $this->info('🔍 Test 2: Checking if file exists...'); - + try { $exists = $disk->exists($testFileName); if ($exists) { $this->info('✅ File exists on S3'); } else { $this->error('❌ File not found on S3'); + return 1; } } catch (\Exception $e) { - $this->error('Error checking file: ' . $e->getMessage()); + $this->error('Error checking file: '.$e->getMessage()); } // Test 3: Read the file $this->newLine(); $this->info('📖 Test 3: Reading file from S3...'); - + try { $readContent = $disk->get($testFileName); if ($readContent === $testContent) { $this->info('✅ File content matches original'); - $this->line('Content: ' . $readContent); + $this->line('Content: '.$readContent); } else { $this->warn('⚠️ File content differs'); - $this->line('Original: ' . $testContent); - $this->line('Read: ' . $readContent); + $this->line('Original: '.$testContent); + $this->line('Read: '.$readContent); } } catch (\Exception $e) { - $this->error('Error reading file: ' . $e->getMessage()); + $this->error('Error reading file: '.$e->getMessage()); } // Test 4: Get file URL $this->newLine(); $this->info('🔗 Test 4: Getting file URL...'); - + try { $url = $disk->url($testFileName); - $this->info('File URL: ' . $url); + $this->info('File URL: '.$url); } catch (\Exception $e) { - $this->warn('URL generation issue: ' . $e->getMessage()); + $this->warn('URL generation issue: '.$e->getMessage()); } - + // Try temporary URL separately try { $tempUrl = $disk->temporaryUrl($testFileName, now()->addMinutes(5)); - $this->info('Temporary URL (5 min): ' . $tempUrl); + $this->info('Temporary URL (5 min): '.$tempUrl); } catch (\Exception $e) { - $this->warn('Temporary URL not supported or configured: ' . get_class($e)); + $this->warn('Temporary URL not supported or configured: '.get_class($e)); } // Test 5: Delete the file $this->newLine(); $this->info('🗑️ Test 5: Deleting file from S3...'); - + try { $deleted = $disk->delete($testFileName); if ($deleted) { @@ -175,47 +179,47 @@ public function handle() $this->error('❌ Failed to delete file'); } } catch (\Exception $e) { - $this->error('Error deleting file: ' . $e->getMessage()); + $this->error('Error deleting file: '.$e->getMessage()); } // Verify deletion $this->newLine(); $this->info('🔍 Verifying deletion...'); - + try { $stillExists = $disk->exists($testFileName); - if (!$stillExists) { + if (! $stillExists) { $this->info('✅ File successfully removed from S3'); } else { $this->error('❌ File still exists after deletion'); } } catch (\Exception $e) { - $this->warn('Error verifying deletion: ' . $e->getMessage()); + $this->warn('Error verifying deletion: '.$e->getMessage()); } $this->newLine(); $this->info('🎉 S3 storage tests completed!'); - + return 0; } catch (S3Exception $e) { $this->newLine(); $this->error('❌ S3 Error occurred:'); - $this->error('Message: ' . $e->getAwsErrorMessage()); - $this->error('Code: ' . $e->getAwsErrorCode()); - $this->error('Type: ' . $e->getAwsErrorType()); - $this->error('Request ID: ' . $e->getAwsRequestId()); - + $this->error('Message: '.$e->getAwsErrorMessage()); + $this->error('Code: '.$e->getAwsErrorCode()); + $this->error('Type: '.$e->getAwsErrorType()); + $this->error('Request ID: '.$e->getAwsRequestId()); + return 1; } catch (\Exception $e) { $this->newLine(); $this->error('❌ Test Failed with error:'); $this->error($e->getMessage()); - $this->line('Class: ' . get_class($e)); - $this->line('File: ' . $e->getFile()); - $this->line('Line: ' . $e->getLine()); - + $this->line('Class: '.get_class($e)); + $this->line('File: '.$e->getFile()); + $this->line('Line: '.$e->getLine()); + return 1; } } -} \ No newline at end of file +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4e6dc03..eeb350a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,14 +15,13 @@ protected function schedule(Schedule $schedule): void // $schedule->command('inspire')->hourly(); $schedule->job(new \App\Jobs\UpdateListenerCountJob)->everyMinute(); $schedule->job(new \App\Jobs\SaveViewCountJob)->everyMinute(); - $schedule->job(new \App\Jobs\Server\ScalingJob)->everyMinute(); $schedule->job(new \App\Jobs\ServerAssignmentJob)->everyFifteenSeconds(); // Disabled: CleanUpInactiveServerAssignmentsJob - clients table has been dropped // $schedule->job(new \App\Jobs\CleanUpInactiveServerAssignmentsJob)->everyFiveMinutes(); - + // Update server viewer counts based on active source_users $schedule->job(new \App\Jobs\UpdateServerViewerCountsJob)->everyThirtySeconds(); - + // Clean up stale viewer sessions that haven't been active for 3+ minutes $schedule->job(new \App\Jobs\CleanupStaleViewerSessionsJob)->everyMinute(); @@ -31,10 +30,10 @@ protected function schedule(Schedule $schedule): void // Capture thumbnails for live streams every minute $schedule->command('thumbnails:capture')->everyMinute(); - + // Record viewer statistics for live shows every minute $schedule->command('statistics:record')->everyMinute(); - + // Check auto mode shows every minute to start/end them based on schedule and source status $schedule->command('shows:check-auto-mode')->everyMinute(); } diff --git a/app/Contracts/CommandInterface.php b/app/Contracts/CommandInterface.php index 3b96203..ed4729f 100644 --- a/app/Contracts/CommandInterface.php +++ b/app/Contracts/CommandInterface.php @@ -9,78 +9,57 @@ interface CommandInterface /** * Execute the command. * - * @param User $user The user executing the command - * @param array $parameters The parsed command parameters - * @return void + * @param User $user The user executing the command + * @param array $parameters The parsed command parameters */ public function handle(User $user, array $parameters): void; /** * Get the command signature. * Example: "timeout {username} {duration}" - * - * @return string */ public function signature(): string; /** * Get the command name. - * - * @return string */ public function name(): string; /** * Get the command description. - * - * @return string */ public function description(): string; /** * Get command aliases. - * - * @return array */ public function aliases(): array; /** * Get validation rules for command parameters. - * - * @return array */ public function rules(): array; /** * Get parameter descriptions for help text. - * - * @return array */ public function parameters(): array; /** * Check if the user can execute this command. - * - * @param User $user - * @return bool */ public function authorize(User $user): bool; /** * Get the permission required to execute this command. - * - * @return string|null */ public function permission(): ?string; /** * Send feedback to the user. * - * @param User $user - * @param string $message - * @param string $type success|error|info|warning - * @param array $data Additional data to send - * @return void + * @param string $type success|error|info|warning + * @param array $data Additional data to send */ public function feedback(User $user, string $message, string $type = 'info', array $data = []): void; -} \ No newline at end of file +} diff --git a/app/Enum/AutoscalerAction.php b/app/Enum/AutoscalerAction.php deleted file mode 100644 index 5f6d3e2..0000000 --- a/app/Enum/AutoscalerAction.php +++ /dev/null @@ -1,10 +0,0 @@ - 'Viewer', + self::EMBED => 'Embed', + }; + } + + /** + * Each type is signed with its own secret, so leaking the viewer secret + * cannot be used to mint long-lived embed keys. + */ + public function secretConfigKey(): string + { + return match ($this) { + self::VIEWER => 'stream.token.viewer_secret', + self::EMBED => 'stream.token.embed_secret', + }; + } + + /** Viewer tokens must expire; embed keys are stable for a baked-in URL. */ + public function requiresExpiry(): bool + { + return $this === self::VIEWER; + } +} diff --git a/app/Events/Chat/Broadcasts/BroadcastRateLimitChangeEvent.php b/app/Events/Chat/Broadcasts/BroadcastRateLimitChangeEvent.php deleted file mode 100644 index fe59e0e..0000000 --- a/app/Events/Chat/Broadcasts/BroadcastRateLimitChangeEvent.php +++ /dev/null @@ -1,42 +0,0 @@ - $this->maxTries, - 'rateDecay' => $this->rateDecay, - 'slowMode' => $this->slowMode, - ]; - } - - public function broadcastAs(): string - { - return 'rateLimit'; - } -} diff --git a/app/Events/Chat/Broadcasts/ChatMessageEvent.php b/app/Events/Chat/Broadcasts/ChatMessageEvent.php index e3ce03b..acaf442 100644 --- a/app/Events/Chat/Broadcasts/ChatMessageEvent.php +++ b/app/Events/Chat/Broadcasts/ChatMessageEvent.php @@ -3,40 +3,27 @@ namespace App\Events\Chat\Broadcasts; use App\Models\Message; -use App\Models\User; +use App\Services\Chat\MessagePresenter; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class ChatMessageEvent implements ShouldBroadcast +class ChatMessageEvent implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public readonly Message $message, public readonly User $user) {} + public function __construct(public readonly Message $message) {} public function broadcastOn(): array { - return [ - new Channel('chat.source.'.$this->message->source_id), - ]; + return [new Channel('chat.source.'.$this->message->source_id)]; } public function broadcastWith(): array { - return [ - 'id' => $this->message->id, - 'name' => $this->user->name, - 'time' => $this->message->created_at->format('H:i'), - 'message' => $this->message->message, - 'role' => $this->user->role, - 'chat_color' => $this->user->chat_color, - 'type' => $this->message->type, - 'priority' => $this->message->priority, - 'metadata' => $this->message->metadata, - 'source_id' => $this->message->source_id, - ]; + return app(MessagePresenter::class)->present($this->message); } public function broadcastAs(): string diff --git a/app/Events/Chat/Broadcasts/ChatMessagesDeletedEvent.php b/app/Events/Chat/Broadcasts/ChatMessagesDeletedEvent.php new file mode 100644 index 0000000..75abaff --- /dev/null +++ b/app/Events/Chat/Broadcasts/ChatMessagesDeletedEvent.php @@ -0,0 +1,44 @@ + $ids Deleted message ids + */ + public function __construct( + public readonly array $ids, + public readonly ?int $sourceId = null, + public readonly ?string $targetName = null, + public readonly ?string $moderatorName = null, + ) {} + + public function broadcastOn(): array + { + return [new Channel('chat.source.'.$this->sourceId)]; + } + + public function broadcastWith(): array + { + return [ + 'ids' => $this->ids, + 'source_id' => $this->sourceId, + 'target_name' => $this->targetName, + 'moderator_name' => $this->moderatorName, + ]; + } + + public function broadcastAs(): string + { + return 'messages.deleted'; + } +} diff --git a/app/Events/Chat/Broadcasts/ChatNoticeEvent.php b/app/Events/Chat/Broadcasts/ChatNoticeEvent.php new file mode 100644 index 0000000..00a11c3 --- /dev/null +++ b/app/Events/Chat/Broadcasts/ChatNoticeEvent.php @@ -0,0 +1,60 @@ +modsOnly + ? new PrivateChannel('chat.source.'.$this->sourceId.'.mods') + : new Channel('chat.source.'.$this->sourceId), + ]; + } + + public function broadcastWith(): array + { + return [ + 'id' => 'notice_'.Str::random(12), + 'type' => 'notice', + 'level' => $this->level, + 'body' => $this->text, + 'time' => now()->format('H:i'), + 'timestamp' => now()->toIso8601String(), + 'source_id' => $this->sourceId, + ]; + } + + public function broadcastAs(): string + { + return 'notice'; + } +} diff --git a/app/Events/Chat/Broadcasts/BroadcastMessageDeletionIdsEvent.php b/app/Events/Chat/Broadcasts/ChatSettingsUpdatedEvent.php similarity index 53% rename from app/Events/Chat/Broadcasts/BroadcastMessageDeletionIdsEvent.php rename to app/Events/Chat/Broadcasts/ChatSettingsUpdatedEvent.php index cc3ca9f..55e938b 100644 --- a/app/Events/Chat/Broadcasts/BroadcastMessageDeletionIdsEvent.php +++ b/app/Events/Chat/Broadcasts/ChatSettingsUpdatedEvent.php @@ -4,33 +4,37 @@ use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class BroadcastMessageDeletionIdsEvent implements ShouldBroadcast +class ChatSettingsUpdatedEvent implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public array $ids, public ?int $sourceId = null) {} + /** + * @param array $settings + */ + public function __construct( + public readonly array $settings, + public readonly ?int $sourceId = null, + ) {} public function broadcastOn(): array { - return [ - new Channel('chat.source.'.$this->sourceId), - ]; + return [new Channel('chat.source.'.$this->sourceId)]; } public function broadcastWith(): array { return [ - 'ids' => $this->ids, + 'settings' => $this->settings, 'source_id' => $this->sourceId, ]; } public function broadcastAs(): string { - return 'messagesDeleted'; + return 'settings.updated'; } } diff --git a/app/Events/Chat/Broadcasts/ChatSystemEvent.php b/app/Events/Chat/Broadcasts/ChatSystemEvent.php deleted file mode 100644 index 9d284ea..0000000 --- a/app/Events/Chat/Broadcasts/ChatSystemEvent.php +++ /dev/null @@ -1,47 +0,0 @@ -message = Message::create([ - 'user_id' => null, - 'message' => $text, - ]); - } - - public function broadcastOn(): array - { - return [ - new Channel('chat'), - ]; - } - - public function broadcastWith(): array - { - return [ - 'name' => 'System', - 'time' => $this->message->created_at->format('H:i'), - 'message' => $this->message->message, - 'role' => null, - ]; - } - - public function broadcastAs(): string - { - return 'message'; - } -} diff --git a/app/Events/Chat/Broadcasts/ChatUserStateEvent.php b/app/Events/Chat/Broadcasts/ChatUserStateEvent.php new file mode 100644 index 0000000..d5abefb --- /dev/null +++ b/app/Events/Chat/Broadcasts/ChatUserStateEvent.php @@ -0,0 +1,47 @@ +user->id)]; + } + + public function broadcastWith(): array + { + return [ + 'state' => $this->state, + 'reason' => $this->reason, + 'seconds_remaining' => $this->secondsRemaining, + 'source_id' => $this->sourceId, + ]; + } + + public function broadcastAs(): string + { + return 'chat.state'; + } +} diff --git a/app/Events/Chat/Broadcasts/SystemAnnouncementEvent.php b/app/Events/Chat/Broadcasts/SystemAnnouncementEvent.php deleted file mode 100644 index b3d5101..0000000 --- a/app/Events/Chat/Broadcasts/SystemAnnouncementEvent.php +++ /dev/null @@ -1,49 +0,0 @@ -message->source_id), - ]; - } - - public function broadcastWith(): array - { - return [ - 'id' => $this->message->id, - 'name' => 'System Announcement', - 'time' => $this->message->created_at->format('H:i'), - 'message' => $this->message->message, - 'role' => (object) [ - 'name' => 'System', - 'slug' => 'system', - 'chat_color' => '#FFD700', - ], - 'chat_color' => '#FFD700', - 'type' => $this->message->type, - 'priority' => $this->message->priority, - 'metadata' => $this->message->metadata, - 'source_id' => $this->message->source_id, - ]; - } - - public function broadcastAs(): string - { - return 'message'; - } -} diff --git a/app/Events/Chat/Commands/SlowModeDisabled.php b/app/Events/Chat/Commands/SlowModeDisabled.php deleted file mode 100644 index cc835e7..0000000 --- a/app/Events/Chat/Commands/SlowModeDisabled.php +++ /dev/null @@ -1,12 +0,0 @@ -user->id), + new PrivateChannel('user.'.$this->user->id), ]; } @@ -54,4 +52,4 @@ public function broadcastAs(): string { return 'command.feedback'; } -} \ No newline at end of file +} diff --git a/app/Events/MessageDeletedEvent.php b/app/Events/MessageDeletedEvent.php deleted file mode 100644 index 3101dfd..0000000 --- a/app/Events/MessageDeletedEvent.php +++ /dev/null @@ -1,52 +0,0 @@ -messageIds = is_array($messageIds) ? $messageIds : [$messageIds]; - } - - /** - * Get the channels the event should broadcast on. - */ - public function broadcastOn(): Channel - { - return new Channel('chat'); - } - - /** - * The event's broadcast name. - */ - public function broadcastAs(): string - { - return 'messagesDeleted'; - } - - /** - * Get the data to broadcast. - */ - public function broadcastWith(): array - { - return [ - 'ids' => $this->messageIds, - ]; - } -} \ No newline at end of file diff --git a/app/Events/ShowCancelled.php b/app/Events/ShowCancelled.php index 37dadaf..d26a7f9 100644 --- a/app/Events/ShowCancelled.php +++ b/app/Events/ShowCancelled.php @@ -68,4 +68,4 @@ public function broadcastAs() { return 'show.cancelled'; } -} \ No newline at end of file +} diff --git a/app/Events/ShowEnded.php b/app/Events/ShowEnded.php index e9ba892..48fb141 100644 --- a/app/Events/ShowEnded.php +++ b/app/Events/ShowEnded.php @@ -70,4 +70,4 @@ public function broadcastAs() { return 'show.ended'; } -} \ No newline at end of file +} diff --git a/app/Events/SourceStatusChangedEvent.php b/app/Events/SourceStatusChangedEvent.php index af51ecf..2c0f9de 100644 --- a/app/Events/SourceStatusChangedEvent.php +++ b/app/Events/SourceStatusChangedEvent.php @@ -14,6 +14,7 @@ class SourceStatusChangedEvent implements ShouldBroadcast use Dispatchable, InteractsWithSockets, SerializesModels; public Source $source; + public string $previousStatus; /** @@ -33,7 +34,7 @@ public function __construct(Source $source, string $previousStatus) public function broadcastOn(): array { return [ - new Channel('source.' . $this->source->id), + new Channel('source.'.$this->source->id), ]; } @@ -61,4 +62,4 @@ public function broadcastWith(): array 'timestamp' => now()->toIso8601String(), ]; } -} \ No newline at end of file +} diff --git a/app/Events/SystemMessageEvent.php b/app/Events/SystemMessageEvent.php deleted file mode 100644 index 17e7867..0000000 --- a/app/Events/SystemMessageEvent.php +++ /dev/null @@ -1,48 +0,0 @@ -message = $message; - } - - /** - * Get the channels the event should broadcast on. - */ - public function broadcastOn(): Channel - { - return new Channel('chat'); - } - - /** - * The event's broadcast name. - */ - public function broadcastAs(): string - { - return 'system.message'; - } - - /** - * Get the data to broadcast. - */ - public function broadcastWith(): array - { - return $this->message; - } -} \ No newline at end of file diff --git a/app/Events/UserRoleUpdatedEvent.php b/app/Events/UserRoleUpdatedEvent.php index 6df3d68..42111bc 100644 --- a/app/Events/UserRoleUpdatedEvent.php +++ b/app/Events/UserRoleUpdatedEvent.php @@ -13,6 +13,7 @@ class UserRoleUpdatedEvent implements ShouldBroadcast use Dispatchable, InteractsWithSockets, SerializesModels; public int $userId; + public array $roles; /** @@ -50,4 +51,4 @@ public function broadcastWith(): array 'roles' => $this->roles, ]; } -} \ No newline at end of file +} diff --git a/app/Exceptions/InvalidPlaybackTokenException.php b/app/Exceptions/InvalidPlaybackTokenException.php new file mode 100644 index 0000000..2697b1c --- /dev/null +++ b/app/Exceptions/InvalidPlaybackTokenException.php @@ -0,0 +1,65 @@ +action(fn () => event(new StreamStatusEvent(StreamStatusEnum::STARTING_SOON))) - ->label('Set Stream Starting Soon (Start Servers)') - ->tooltip('Will start servers, takes around 6 minutes.') - ->requiresConfirmation(), - Action::make('set_online') - ->action(fn () => event(new StreamStatusEvent(StreamStatusEnum::ONLINE))) - ->label('Set Stream Online') - ->tooltip('Set this after you started the stream in obs for the first time.') - ->requiresConfirmation(), - Action::make('set_issue') - ->action(fn () => event(new StreamStatusEvent(StreamStatusEnum::TECHNICAL_ISSUE))) - ->label('Set Stream Technical Issue') - ->tooltip('Set this if you have technical issues with the stream. Will automatically activate upon stream disconnect.') - ->requiresConfirmation(), - Action::make('set_offline') - ->requiresConfirmation() - ->action(fn () => event(new StreamStatusEvent(StreamStatusEnum::OFFLINE))) - ->tooltip('This sets the stream fully offline and deletes ALL Servers.') - ->label('Set Stream Offline (Delete Servers)') - ->color('danger'), - ]; - } - - protected function getHeaderWidgets(): array - { - return [ - ServerActive::class, - Capacity::class, - ViewCountChart::class, - ]; - } -} diff --git a/app/Filament/Resources/EmoteResource.php b/app/Filament/Resources/EmoteResource.php deleted file mode 100644 index ccd80c3..0000000 --- a/app/Filament/Resources/EmoteResource.php +++ /dev/null @@ -1,218 +0,0 @@ -schema([ - Forms\Components\Section::make('Emote Information') - ->schema([ - Forms\Components\TextInput::make('name') - ->required() - ->unique(ignoreRecord: true) - ->regex('/^[a-z0-9_]+$/') - ->maxLength(20), - Forms\Components\FileUpload::make('s3_key') - ->label('Emote Image') - ->image() - ->imageResizeMode('cover') - ->imageCropAspectRatio('1:1') - ->imageResizeTargetWidth(64) - ->imageResizeTargetHeight(64) - ->disk('s3') - ->directory('emotes') - ->visibility('private') - ->preserveFilenames() - ->loadStateFromRelationshipsUsing(static function (Forms\Components\FileUpload $component, ?Emote $record): void { - if ($record && $record->s3_key) { - // Set the stored path value so Filament knows where the file is - $component->state($record->s3_key); - } - }), - Forms\Components\Toggle::make('is_global') - ->label('Available for all users') - ->helperText('If disabled, only the uploader can use this emote'), - Forms\Components\Toggle::make('is_approved') - ->label('Approved') - ->helperText('Approve this emote for use in chat'), - ]) - ->columns(2), - - Forms\Components\Section::make('Metadata') - ->schema([ - Forms\Components\Select::make('uploaded_by_user_id') - ->label('Uploaded By') - ->relationship('uploadedBy', 'name') - ->disabled() - ->dehydrated(false), - Forms\Components\Select::make('approved_by_user_id') - ->label('Approved By') - ->relationship('approvedBy', 'name') - ->disabled() - ->dehydrated(false), - Forms\Components\DateTimePicker::make('approved_at') - ->label('Approved At') - ->disabled() - ->dehydrated(false), - Forms\Components\TextInput::make('usage_count') - ->label('Usage Count') - ->disabled() - ->dehydrated(false) - ->numeric(), - ]) - ->columns(2), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - Tables\Columns\ImageColumn::make('url') - ->label('Emote') - ->size(40) - ->circular(false), - Tables\Columns\TextColumn::make('name') - ->label('Name') - ->searchable() - ->copyable() - ->formatStateUsing(fn ($state) => ':'.$state.':'), - Tables\Columns\TextColumn::make('uploadedBy.name') - ->label('Uploaded By') - ->searchable(), - Tables\Columns\IconColumn::make('is_global') - ->label('Global') - ->boolean(), - Tables\Columns\IconColumn::make('is_approved') - ->label('Approved') - ->boolean() - ->color(fn (bool $state): string => $state ? 'success' : 'warning'), - Tables\Columns\TextColumn::make('usage_count') - ->label('Usage') - ->numeric() - ->sortable(), - Tables\Columns\TextColumn::make('created_at') - ->label('Uploaded') - ->dateTime() - ->sortable() - ->toggleable(), - ]) - ->filters([ - Tables\Filters\SelectFilter::make('approval_status') - ->label('Status') - ->options([ - 'pending' => 'Pending Approval', - 'approved' => 'Approved', - ]) - ->query(function (Builder $query, array $data) { - if ($data['value'] === 'pending') { - return $query->where('is_approved', false); - } elseif ($data['value'] === 'approved') { - return $query->where('is_approved', true); - } - }), - Tables\Filters\TernaryFilter::make('is_global') - ->label('Global'), - ]) - ->actions([ - Tables\Actions\Action::make('approve') - ->label('Approve') - ->icon('heroicon-o-check-circle') - ->color('success') - ->visible(fn (Emote $record) => ! $record->is_approved) - ->requiresConfirmation() - ->action(function (Emote $record) { - $record->approve(auth()->user()); - }), - Tables\Actions\Action::make('reject') - ->label('Reject') - ->icon('heroicon-o-x-circle') - ->color('danger') - ->visible(fn (Emote $record) => ! $record->is_approved) - ->requiresConfirmation() - ->modalDescription('This will permanently delete the emote and its image.') - ->action(function (Emote $record) { - $record->reject(); - }), - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make(), - ]) - ->bulkActions([ - Tables\Actions\BulkAction::make('approve_selected') - ->label('Approve Selected') - ->icon('heroicon-o-check-circle') - ->color('success') - ->requiresConfirmation() - ->action(function ($records) { - foreach ($records as $record) { - if (! $record->is_approved) { - $record->approve(auth()->user()); - } - } - }), - Tables\Actions\BulkAction::make('reject_selected') - ->label('Reject Selected') - ->icon('heroicon-o-x-circle') - ->color('danger') - ->requiresConfirmation() - ->modalDescription('This will permanently delete the selected emotes and their images.') - ->action(function ($records) { - foreach ($records as $record) { - if (! $record->is_approved) { - $record->reject(); - } - } - }), - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DeleteBulkAction::make(), - ]), - ]) - ->defaultSort('created_at', 'desc'); - } - - public static function getRelations(): array - { - return [ - // - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListEmotes::route('/'), - 'create' => Pages\CreateEmote::route('/create'), - 'edit' => Pages\EditEmote::route('/{record}/edit'), - ]; - } - - public static function getNavigationBadge(): ?string - { - return static::$model::pending()->count() ?: null; - } - - public static function getNavigationBadgeColor(): ?string - { - return static::$model::pending()->count() > 0 ? 'warning' : null; - } -} diff --git a/app/Filament/Resources/EmoteResource/Pages/CreateEmote.php b/app/Filament/Resources/EmoteResource/Pages/CreateEmote.php deleted file mode 100644 index 51f1eb2..0000000 --- a/app/Filament/Resources/EmoteResource/Pages/CreateEmote.php +++ /dev/null @@ -1,23 +0,0 @@ -id(); - - if ($data['is_approved'] ?? false) { - $data['approved_by_user_id'] = auth()->id(); - $data['approved_at'] = now(); - } - - return $data; - } -} diff --git a/app/Filament/Resources/EmoteResource/Pages/EditEmote.php b/app/Filament/Resources/EmoteResource/Pages/EditEmote.php deleted file mode 100644 index f986186..0000000 --- a/app/Filament/Resources/EmoteResource/Pages/EditEmote.php +++ /dev/null @@ -1,30 +0,0 @@ -record->is_approved) { - $data['approved_by_user_id'] = auth()->id(); - $data['approved_at'] = now(); - } - - return $data; - } -} diff --git a/app/Filament/Resources/EmoteResource/Pages/ListEmotes.php b/app/Filament/Resources/EmoteResource/Pages/ListEmotes.php deleted file mode 100644 index 85881fc..0000000 --- a/app/Filament/Resources/EmoteResource/Pages/ListEmotes.php +++ /dev/null @@ -1,19 +0,0 @@ -schema([ - Forms\Components\Select::make('show_id') - ->label('Associated Show') - ->options(Show::with('source')->get()->mapWithKeys(function ($show) { - return [$show->id => $show->title.' ('.$show->source->name.')']; - })) - ->searchable() - ->preload() - ->nullable() - ->reactive() - ->afterStateUpdated(function ($state, Forms\Set $set) { - if ($state) { - $show = Show::find($state); - if ($show) { - $set('title', $show->title); - $set('description', $show->description); - if ($show->actual_start) { - $set('date', $show->actual_start); - } - if ($show->actual_start && $show->actual_end) { - $duration = $show->actual_start->diffInSeconds($show->actual_end); - $set('duration', $duration); - } - } - } - }) - ->helperText('Select a show to auto-populate fields'), - Forms\Components\TextInput::make('title') - ->required() - ->maxLength(255) - ->reactive() - ->afterStateUpdated(function ($state, Forms\Set $set, ?Recording $record) { - if (! $record && filled($state)) { - $set('slug', Str::slug($state)); - } - }), - Forms\Components\TextInput::make('slug') - ->required() - ->maxLength(255) - ->unique(Recording::class, 'slug', ignoreRecord: true) - ->helperText('URL-friendly version of the title'), - Forms\Components\Textarea::make('description') - ->rows(3) - ->columnSpanFull(), - Forms\Components\DateTimePicker::make('date') - ->required() - ->native(false), - Forms\Components\TextInput::make('duration') - ->numeric() - ->suffix('seconds') - ->helperText('Duration in seconds (will be auto-filled via ffmpeg if left empty)') - ->nullable(), - Forms\Components\TextInput::make('m3u8_url') - ->label('M3U8 URL') - ->required() - ->url() - ->columnSpanFull() - ->helperText('URL to the HLS playlist file'), - Forms\Components\FileUpload::make('thumbnail_path') - ->label('Thumbnail') - ->image() - ->imageResizeMode('cover') - ->imageResizeTargetWidth(1280) - ->imageResizeTargetHeight(720) - ->disk('s3') - ->directory('recordings/thumbnails') - ->visibility('private') - ->columnSpanFull() - ->helperText('Upload a thumbnail or leave empty to auto-generate from first frame') - ->loadStateFromRelationshipsUsing(static function (Forms\Components\FileUpload $component, ?Recording $record): void { - if ($record && $record->thumbnail_path) { - $component->state($record->thumbnail_path); - } - }), - Forms\Components\Toggle::make('is_published') - ->label('Published') - ->default(true) - ->helperText('Only published recordings will be visible to users'), - Forms\Components\CheckboxList::make('required_roles') - ->label('Access Restriction') - ->options(Role::pluck('name', 'slug')->toArray()) - ->helperText('Leave empty for public access. Select roles that can access this recording.') - ->hint('Users must have at least one of the selected roles to view') - ->columns(2) - ->columnSpanFull(), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - Tables\Columns\ImageColumn::make('thumbnail_url') - ->label('Thumbnail') - ->size(80) - ->height(45), - Tables\Columns\TextColumn::make('title') - ->searchable() - ->sortable(), - Tables\Columns\TextColumn::make('slug') - ->searchable() - ->toggleable(), - Tables\Columns\TextColumn::make('show.title') - ->label('Show') - ->badge() - ->color('primary') - ->searchable() - ->toggleable(), - Tables\Columns\TextColumn::make('date') - ->dateTime('M j, Y H:i') - ->sortable(), - Tables\Columns\TextColumn::make('duration') - ->formatStateUsing(function ($state) { - if (! $state) { - return '-'; - } - $hours = floor($state / 3600); - $minutes = floor(($state % 3600) / 60); - $seconds = $state % 60; - if ($hours > 0) { - return sprintf('%d:%02d:%02d', $hours, $minutes, $seconds); - } - - return sprintf('%d:%02d', $minutes, $seconds); - }) - ->label('Duration'), - Tables\Columns\TextColumn::make('views') - ->numeric() - ->sortable(), - Tables\Columns\IconColumn::make('is_published') - ->boolean() - ->label('Published'), - Tables\Columns\BadgeColumn::make('required_roles') - ->label('Access') - ->getStateUsing(fn ($record) => $record->hasAccessRestriction() ? 'Restricted' : 'Public') - ->colors([ - 'warning' => fn ($state) => $state === 'Restricted', - 'success' => fn ($state) => $state === 'Public', - ]) - ->icons([ - 'heroicon-o-lock-closed' => fn ($state) => $state === 'Restricted', - 'heroicon-o-globe-alt' => fn ($state) => $state === 'Public', - ]) - ->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\TextColumn::make('created_at') - ->dateTime() - ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - ]) - ->filters([ - Tables\Filters\TernaryFilter::make('is_published') - ->label('Published'), - ]) - ->actions([ - Tables\Actions\Action::make('regenerate_thumbnail') - ->label('Regenerate Thumbnail') - ->icon('heroicon-o-photo') - ->color('warning') - ->requiresConfirmation() - ->modalHeading('Regenerate Thumbnail') - ->modalDescription('This will capture a new thumbnail from the video. The old thumbnail will be replaced.') - ->modalSubmitActionLabel('Regenerate') - ->action(function (Recording $record) { - // Clear the current thumbnail path to force regeneration - $record->thumbnail_path = null; - $record->thumbnail_capture_error = null; - $record->save(); - - // Dispatch the job to process the recording - ProcessRecordingJob::dispatch($record); - - Notification::make() - ->title('Thumbnail regeneration started') - ->body('The thumbnail is being regenerated in the background. Please refresh the page in a few moments to see the new thumbnail.') - ->success() - ->send(); - }) - ->visible(fn (Recording $record) => $record->m3u8_url !== null), - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make(), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\BulkAction::make('regenerate_thumbnails') - ->label('Regenerate Thumbnails') - ->icon('heroicon-o-photo') - ->color('warning') - ->requiresConfirmation() - ->modalHeading('Regenerate Thumbnails') - ->modalDescription('This will capture new thumbnails for all selected recordings. The old thumbnails will be replaced.') - ->modalSubmitActionLabel('Regenerate All') - ->action(function ($records) { - $count = 0; - foreach ($records as $record) { - if ($record->m3u8_url) { - $record->thumbnail_path = null; - $record->thumbnail_capture_error = null; - $record->save(); - ProcessRecordingJob::dispatch($record); - $count++; - } - } - - Notification::make() - ->title('Thumbnails regeneration started') - ->body("Regenerating thumbnails for {$count} recording(s) in the background.") - ->success() - ->send(); - }) - ->deselectRecordsAfterCompletion(), - Tables\Actions\DeleteBulkAction::make(), - ]), - ]) - ->defaultSort('date', 'desc'); - } - - public static function getRelations(): array - { - return [ - // - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListRecordings::route('/'), - 'create' => Pages\CreateRecording::route('/create'), - 'edit' => Pages\EditRecording::route('/{record}/edit'), - ]; - } -} diff --git a/app/Filament/Resources/RecordingResource/Pages/CreateRecording.php b/app/Filament/Resources/RecordingResource/Pages/CreateRecording.php deleted file mode 100644 index 461db79..0000000 --- a/app/Filament/Resources/RecordingResource/Pages/CreateRecording.php +++ /dev/null @@ -1,11 +0,0 @@ -label('Regenerate Thumbnail') - ->icon('heroicon-o-photo') - ->color('warning') - ->requiresConfirmation() - ->modalHeading('Regenerate Thumbnail') - ->modalDescription('This will capture a new thumbnail from the video. The old thumbnail will be replaced.') - ->modalSubmitActionLabel('Regenerate') - ->action(function () { - $record = $this->record; - - // Clear the current thumbnail path to force regeneration - $record->thumbnail_path = null; - $record->thumbnail_capture_error = null; - $record->save(); - - // Dispatch the job to process the recording - ProcessRecordingJob::dispatch($record); - - Notification::make() - ->title('Thumbnail regeneration started') - ->body('The thumbnail is being regenerated in the background. The page will refresh automatically.') - ->success() - ->send(); - - // Redirect to refresh the page after a delay - $this->redirect($this->getResource()::getUrl('edit', ['record' => $record])); - }) - ->visible(fn () => $this->record->m3u8_url !== null), - Actions\DeleteAction::make(), - ]; - } -} \ No newline at end of file diff --git a/app/Filament/Resources/RecordingResource/Pages/ListRecordings.php b/app/Filament/Resources/RecordingResource/Pages/ListRecordings.php deleted file mode 100644 index 6c607ce..0000000 --- a/app/Filament/Resources/RecordingResource/Pages/ListRecordings.php +++ /dev/null @@ -1,19 +0,0 @@ -schema([ - Section::make('Role Information') - ->schema([ - TextInput::make('name') - ->required() - ->maxLength(255) - ->live(onBlur: true) - ->afterStateUpdated(fn (string $state, Forms\Set $set) => $set('slug', Str::slug($state)) - ), - TextInput::make('slug') - ->required() - ->unique(ignoreRecord: true) - ->maxLength(255) - ->helperText('Used for system identification'), - Textarea::make('description') - ->rows(2) - ->columnSpanFull(), - ]) - ->columns(2), - - Section::make('Chat Appearance') - ->schema([ - ColorPicker::make('chat_color') - ->label('Chat Color') - ->helperText('Color displayed in chat for users with this role') - ->default('#808080'), - TextInput::make('priority') - ->numeric() - ->default(0) - ->helperText('Higher priority roles are displayed first (100 for admin, 90 for moderator, etc.)'), - Toggle::make('is_visible') - ->label('Show in Chat') - ->default(true) - ->helperText('Whether this role\'s color is visible in chat'), - ]) - ->columns(3), - - Section::make('Settings') - ->schema([ - Toggle::make('assigned_at_login') - ->label('Assigned at Login') - ->default(true) - ->helperText('If enabled, this role is synced from the registration system at login. If disabled, role persists through logins.'), - Toggle::make('is_staff') - ->label('Staff Role') - ->default(false) - ->helperText('Mark this for admin/moderator roles'), - TagsInput::make('permissions') - ->label('Permissions') - ->separator(',') - ->suggestions([ - 'filament.access', - 'admin.access', - 'chat.moderate', - 'chat.delete', - 'chat.timeout', - 'chat.slowmode', - 'stream.manage', - 'user.manage', - ]) - ->helperText('System permissions for this role') - ->columnSpanFull(), - ]) - ->columns(2), - - Section::make('Additional Configuration') - ->schema([ - KeyValue::make('metadata') - ->label('Metadata') - ->keyLabel('Key') - ->valueLabel('Value') - ->addButtonLabel('Add Metadata'), - ]) - ->collapsed(), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('name') - ->searchable() - ->sortable() - ->weight('bold'), - TextColumn::make('slug') - ->searchable() - ->sortable() - ->badge() - ->color('gray'), - ColorColumn::make('chat_color') - ->label('Chat Color') - ->copyable() - ->copyMessage('Color copied') - ->copyMessageDuration(1500), - TextColumn::make('priority') - ->sortable() - ->badge() - ->color(fn ($state) => match (true) { - $state >= 100 => 'danger', - $state >= 90 => 'warning', - $state >= 50 => 'info', - default => 'gray' - }), - TextColumn::make('assigned_at_login') - ->label('Login Sync') - ->badge() - ->formatStateUsing(fn ($state) => $state ? 'Auto-synced' : 'Manual') - ->color(fn ($state) => $state ? 'info' : 'warning') - ->tooltip(fn ($state) => $state - ? 'This role is automatically synced from the registration system at login' - : 'This role persists through logins and must be manually assigned'), - ToggleColumn::make('is_staff') - ->label('Staff') - ->onColor('warning') - ->offColor('gray'), - ToggleColumn::make('is_visible') - ->label('Chat Badge') - ->onColor('success') - ->offColor('gray') - ->tooltip('Shows role color as a badge in chat messages'), - TextColumn::make('users_count') - ->label('Users') - ->counts('users') - ->sortable() - ->badge() - ->color('success'), - TextColumn::make('created_at') - ->dateTime() - ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - ]) - ->defaultSort('priority', 'desc') - ->filters([ - Tables\Filters\TernaryFilter::make('is_staff') - ->label('Staff Roles') - ->boolean() - ->trueLabel('Staff only') - ->falseLabel('Non-staff only') - ->placeholder('All roles'), - Tables\Filters\TernaryFilter::make('assigned_at_login') - ->label('Login Assignment') - ->boolean() - ->trueLabel('Login-synced only') - ->falseLabel('Manually assigned only') - ->placeholder('All roles'), - Tables\Filters\TernaryFilter::make('is_visible') - ->label('Chat Visibility') - ->boolean() - ->trueLabel('Visible only') - ->falseLabel('Hidden only') - ->placeholder('All roles'), - ]) - ->actions([ - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make() - ->before(function (Role $record) { - if ($record->users()->count() > 0) { - Notification::make() - ->title('Cannot delete role') - ->body('This role has assigned users. Remove all users before deleting.') - ->danger() - ->send(); - - return false; - } - }), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DeleteBulkAction::make() - ->before(function ($records) { - foreach ($records as $record) { - if ($record->users()->count() > 0) { - Notification::make() - ->title('Cannot delete roles') - ->body('One or more roles have assigned users.') - ->danger() - ->send(); - - return false; - } - } - }), - ]), - ]) - ->headerActions([ - Tables\Actions\Action::make('create_default_roles') - ->label('Create Default Roles') - ->icon('heroicon-o-sparkles') - ->visible(fn () => Role::count() === 0) - ->requiresConfirmation() - ->modalHeading('Create Default Roles') - ->modalDescription('This will create the default set of roles for the system.') - ->modalSubmitActionLabel('Create Roles') - ->action(function () { - self::createDefaultRoles(); - Notification::make() - ->title('Default roles created') - ->success() - ->send(); - }), - ]); - } - - public static function getRelations(): array - { - return [ - RelationManagers\UsersRelationManager::class, - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListRoles::route('/'), - 'create' => Pages\CreateRole::route('/create'), - 'edit' => Pages\EditRole::route('/{record}/edit'), - ]; - } - - public static function getNavigationBadge(): ?string - { - return static::getModel()::count(); - } - - protected static function createDefaultRoles(): void - { - $roles = [ - [ - 'name' => 'Admin', - 'slug' => 'admin', - 'description' => 'Full system administrator', - 'chat_color' => '#FF0000', - 'priority' => 100, - 'assigned_at_login' => false, - 'is_staff' => true, - 'is_visible' => true, - 'permissions' => ['admin.access', 'filament.access', 'chat.moderate', 'stream.manage', 'user.manage'], - ], - [ - 'name' => 'Moderator', - 'slug' => 'moderator', - 'description' => 'Chat and stream moderator', - 'chat_color' => '#00FF00', - 'priority' => 90, - 'assigned_at_login' => false, - 'is_staff' => true, - 'is_visible' => true, - 'permissions' => ['filament.access', 'chat.moderate', 'chat.delete', 'chat.timeout', 'chat.slowmode'], - ], - [ - 'name' => 'Super Sponsor', - 'slug' => 'super-sponsor', - 'description' => 'Super sponsor with special chat color', - 'chat_color' => '#FFD700', - 'priority' => 50, - 'assigned_at_login' => true, - 'is_staff' => false, - 'is_visible' => true, - 'permissions' => [], - ], - [ - 'name' => 'Sponsor', - 'slug' => 'sponsor', - 'description' => 'Event sponsor with chat color', - 'chat_color' => '#C0C0C0', - 'priority' => 40, - 'assigned_at_login' => true, - 'is_staff' => false, - 'is_visible' => true, - 'permissions' => [], - ], - [ - 'name' => 'Attendee', - 'slug' => 'attendee', - 'description' => 'Regular event attendee', - 'chat_color' => '#808080', - 'priority' => 10, - 'assigned_at_login' => true, - 'is_staff' => false, - 'is_visible' => false, - 'permissions' => [], - ], - ]; - - foreach ($roles as $roleData) { - Role::create($roleData); - } - } -} diff --git a/app/Filament/Resources/RoleResource/Pages/CreateRole.php b/app/Filament/Resources/RoleResource/Pages/CreateRole.php deleted file mode 100644 index 0891e26..0000000 --- a/app/Filament/Resources/RoleResource/Pages/CreateRole.php +++ /dev/null @@ -1,11 +0,0 @@ -schema([ - Forms\Components\TextInput::make('name') - ->required() - ->maxLength(255), - ]); - } - - public function table(Table $table): Table - { - return $table - ->recordTitleAttribute('name') - ->columns([ - Tables\Columns\TextColumn::make('name') - ->searchable() - ->sortable(), - Tables\Columns\TextColumn::make('email') - ->searchable() - ->sortable(), - Tables\Columns\TextColumn::make('pivot.assigned_at') - ->label('Assigned') - ->dateTime() - ->sortable(), - Tables\Columns\TextColumn::make('pivot.expires_at') - ->label('Expires') - ->dateTime() - ->placeholder('Never') - ->sortable(), - Tables\Columns\BadgeColumn::make('pivot.assigned_by') - ->label('Assigned By') - ->colors([ - 'success' => 'manual', - 'warning' => 'login', - 'info' => 'system', - ]), - ]) - ->filters([ - Tables\Filters\Filter::make('active') - ->query(fn (Builder $query): Builder => $query->where(function ($q) { - $q->whereNull('role_user.expires_at') - ->orWhere('role_user.expires_at', '>', now()); - }) - ) - ->label('Active Only'), - Tables\Filters\Filter::make('expired') - ->query(fn (Builder $query): Builder => $query->where('role_user.expires_at', '<=', now()) - ) - ->label('Expired Only'), - ]) - ->headerActions([ - Tables\Actions\AttachAction::make() - ->preloadRecordSelect() - ->form(fn (Tables\Actions\AttachAction $action): array => [ - $action->getRecordSelect(), - Forms\Components\DateTimePicker::make('expires_at') - ->label('Expires At') - ->native(false) - ->timezone('Europe/Berlin') - ->helperText('Leave empty for permanent assignment'), - Forms\Components\Select::make('assigned_by') - ->label('Assignment Type') - ->options([ - 'manual' => 'Manual', - 'system' => 'System', - ]) - ->default('manual') - ->required(), - ]) - ->mutateFormDataUsing(function (array $data): array { - $data['assigned_at'] = now(); - - return $data; - }) - ->successNotification( - Notification::make() - ->success() - ->title('User assigned') - ->body('The user has been assigned to this role.') - ), - ]) - ->actions([ - Tables\Actions\DetachAction::make() - ->successNotification( - Notification::make() - ->success() - ->title('User removed') - ->body('The user has been removed from this role.') - ), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DetachBulkAction::make(), - ]), - ]); - } -} diff --git a/app/Filament/Resources/ServerResource.php b/app/Filament/Resources/ServerResource.php deleted file mode 100644 index 91abd42..0000000 --- a/app/Filament/Resources/ServerResource.php +++ /dev/null @@ -1,254 +0,0 @@ -schema([ - TextInput::make('hetzner_id') - ->disabled(fn ($operation): bool => $operation === 'edit') - ->nullable() - ->helperText('Hetzner server ID (will be auto-filled for cloud servers)'), - - TextInput::make('hostname') - ->required() - ->helperText('Server hostname (e.g., "edge-1.example.com" or Docker container name)'), - - TextInput::make('ip') - ->nullable() - ->helperText('For local Docker: use container IP or "localhost"'), - - TextInput::make('port') - ->numeric() - ->minValue(1) - ->maxValue(65535) - ->default(8080) - ->required() - ->helperText('Server port (80 and 443 will be omitted from URLs)'), - - TextInput::make('shared_secret') - ->disabled(fn ($operation): bool => $operation === 'edit') - ->default(fn () => \Illuminate\Support\Str::random(40)) - ->required() - ->helperText('Secret key for inter-server authentication'), - - Select::make('type')->options([ - 'origin' => 'Origin', - 'edge' => 'Edge', - ])->disabled(fn ($operation): bool => $operation === 'edit') - ->default('edge') - ->required(), - - TextInput::make('max_clients') - ->minValue(0) - ->numeric() - ->hidden(fn (?Server $record): bool => $record?->type !== ServerTypeEnum::EDGE) - ->maxValue('99999') - ->default(100) - ->required(), - - Select::make('status')->options([ - ServerStatusEnum::PROVISIONING->value => 'Provisioning', - ServerStatusEnum::ACTIVE->value => 'Active', - ServerStatusEnum::DEPROVISIONING->value => 'Deprovisioning', - ServerStatusEnum::DELETED->value => 'Deleted', - ServerStatusEnum::ERROR->value => 'Error', - ]) - ->default('active') - ->required() - ->helperText('Manually change the server status'), - - Checkbox::make('immutable') - ->hidden(fn (?Server $record): bool => $record?->type !== ServerTypeEnum::EDGE) - ->reactive() - ->default(true) - ->helperText('Set this if you want to use this server as a stream server. This will prevent the server from being deleted by autoscaling measures.'), - - Placeholder::make('created_at') - ->label('Created Date') - ->content(fn (?Server $record): string => $record?->created_at?->diffForHumans() ?? '-'), - - Placeholder::make('updated_at') - ->label('Last Modified Date') - ->content(fn (?Server $record): string => $record?->updated_at?->diffForHumans() ?? '-'), - - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('hetzner_id') - ->label('Server ID') - ->searchable() - ->placeholder('-') - ->formatStateUsing(fn ($state) => $state ?: '-'), - - TextColumn::make('type') - ->badge() - ->color(fn ($state): string => match ($state?->value ?? $state) { - 'origin' => 'warning', - 'edge' => 'success', - default => 'gray', - }), - - TextColumn::make('hostname') - ->searchable() - ->copyable(), - - TextColumn::make('ip') - ->copyable(), - - TextColumn::make('port') - ->sortable(), - - TextColumn::make('status') - ->badge() - ->color(fn ($state): string => match ($state?->value ?? $state) { - 'active' => 'success', - 'provisioning' => 'warning', - 'deprovisioning' => 'danger', - 'deleted' => 'gray', - 'error' => 'danger', - default => 'secondary', - }), - - TextColumn::make('viewer_count') - ->label('Viewers') - ->sortable() - ->badge() - ->color('info') - ->formatStateUsing(fn ($state, $record) => - $record && $record->type === ServerTypeEnum::EDGE ? $state : '-' - ) - ->description(fn ($record) => - $record && $record->type === ServerTypeEnum::EDGE && $record->max_clients > 0 - ? round(($record->viewer_count / $record->max_clients) * 100) . '% capacity' - : null - ), - - IconColumn::make('last_heartbeat') - ->label('Heartbeat') - ->icon(fn ($record) => $record && $record->hasRecentHeartbeat() ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle') - ->color(fn ($record) => $record && $record->hasRecentHeartbeat() ? 'success' : 'danger') - ->tooltip(fn ($record) => $record && $record->last_heartbeat - ? 'Last heartbeat: ' . $record->last_heartbeat->diffForHumans() - : 'No heartbeat received' - ), - - BadgeColumn::make('health_status') - ->label('Health') - ->color(fn ($state): string => match ($state) { - 'healthy' => 'success', - 'unhealthy' => 'danger', - 'unknown' => 'gray', - default => 'secondary', - }) - ->tooltip(fn ($record) => - $record && $record->last_health_check - ? "Last check: {$record->last_health_check->diffForHumans()}\n{$record->health_check_message}" - : 'No health check performed' - ) - ->visible(fn ($record) => $record && $record->type === ServerTypeEnum::EDGE), - - TextColumn::make('max_clients') - ->label('Max Clients') - ->sortable() - ->visible(fn (): bool => true), - ])->actions([ - EditAction::make(), - Action::make('viewInstallScript') - ->label('Install Script') - ->icon('heroicon-o-code-bracket') - ->color('info') - ->url(fn (Server $record): string => static::getUrl('install-script', ['record' => $record])), - Action::make('Deprovision') - ->icon('heroicon-o-trash') - ->color('danger') - ->requiresConfirmation() - ->visible(fn (?Server $record): bool => $record && !empty($record->hetzner_id)) - ->action(fn (Server $record) => $record->deprovision()), - Action::make('Delete') - ->icon('heroicon-o-x-mark') - ->color('danger') - ->requiresConfirmation() - ->visible(fn (?Server $record): bool => $record && empty($record->hetzner_id)) - ->modalHeading('Delete Manual Server') - ->modalDescription('Are you sure you want to delete this manually managed server?') - ->action(fn (Server $record) => $record->delete()), - ]) - ->filters([ - SelectFilter::make('status') - ->options([ - 'active' => 'Active', - 'provisioning' => 'Provisioning', - 'deprovisioning' => 'Deprovisioning', - 'deleted' => 'Deleted', - 'error' => 'Error', - ]) - ->multiple(), - SelectFilter::make('type') - ->options([ - 'origin' => 'Origin', - 'edge' => 'Edge', - ]), - ]) - ->modifyQueryUsing(fn (Builder $query) => $query->where('status', '!=', 'deleted')) - ->poll(); - } - - public static function getRelations(): array - { - return []; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListServers::route('/'), - 'create' => Pages\CreateServer::route('/create'), - 'edit' => Pages\EditServer::route('/{record}/edit'), - 'install-script' => Pages\ViewInstallScript::route('/{record}/install-script'), - ]; - } - - public static function getGloballySearchableAttributes(): array - { - return []; - } -} diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php deleted file mode 100644 index e40956c..0000000 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ /dev/null @@ -1,11 +0,0 @@ -label('New Manual Server') - ->icon('heroicon-o-plus'), - Action::make('Enable Autoscaler')->action(fn () => AutoscalerService::enableAutoscaler())->hidden(AutoscalerService::isAutoscalerEnabled())->color('success'), - Action::make('Disable Autoscaler')->action(fn () => AutoscalerService::disableAutoscaler())->hidden(! AutoscalerService::isAutoscalerEnabled())->color('danger'), - Action::make('provisionCloudServer') - ->label('Provision Cloud Server') - ->icon('heroicon-o-cloud') - ->color('primary') - ->form([ - Select::make('type') - ->label('Server Type') - ->options([ - ServerTypeEnum::ORIGIN->value => 'Origin Server (ccx43 - High Performance)', - ServerTypeEnum::EDGE->value => 'Edge Server (cpx21 - Standard)', - ]) - ->default(ServerTypeEnum::EDGE->value) - ->required() - ->helperText('Origin servers handle stream ingestion and transcoding. Edge servers cache and distribute content.'), - ]) - ->action(function (array $data): void { - // Check if we can create an origin server - if ($data['type'] === ServerTypeEnum::ORIGIN->value) { - $existingOrigin = Server::where('type', ServerTypeEnum::ORIGIN) - ->whereIn('status', ['active', 'provisioning']) - ->exists(); - - if ($existingOrigin) { - Notification::make() - ->title('Cannot Create Origin Server') - ->body('An origin server already exists or is being provisioned. Only one origin server is allowed.') - ->danger() - ->send(); - return; - } - } - - // Create the server directly with the specified type - $server = Server::create([ - 'type' => $data['type'], - 'status' => 'provisioning', - 'hostname' => 'pending', - 'port' => 443, - 'shared_secret' => \Illuminate\Support\Str::random(40), - 'max_clients' => ($data['type'] === ServerTypeEnum::ORIGIN->value) ? 1000 : 100, - ]); - - // Dispatch provisioning job - \App\Jobs\Server\Provision\CreateVirtualMachineJob::dispatch($server); - - Notification::make() - ->title('Server Provisioning Started') - ->body("A new {$data['type']} server is being provisioned on Hetzner Cloud.") - ->success() - ->send(); - }) - ->modalHeading('Provision New Cloud Server') - ->modalDescription('Select the type of server to provision on Hetzner Cloud.') - ->modalSubmitActionLabel('Start Provisioning'), - ]; - } -} diff --git a/app/Filament/Resources/ServerResource/Pages/ViewInstallScript.php b/app/Filament/Resources/ServerResource/Pages/ViewInstallScript.php deleted file mode 100644 index 72d1daf..0000000 --- a/app/Filament/Resources/ServerResource/Pages/ViewInstallScript.php +++ /dev/null @@ -1,144 +0,0 @@ -record = $record; - $this->generateScripts(); - } - - protected function generateScripts(): void - { - $provisioningService = app(ServerProvisioningService::class); - - $this->installScript = $provisioningService->generateInstallScript($this->record); - $this->cloudInitScript = $provisioningService->generateCloudInit($this->record); - - // Extract configurations from the install script - $this->extractConfigurationsFromScript($this->installScript); - } - - protected function extractConfigurationsFromScript(string $script): void - { - // Extract Docker Compose configuration - if (preg_match("/cat > docker-compose\.yml <<'DOCKERCOMPOSE'(.*?)DOCKERCOMPOSE/s", $script, $matches)) { - $this->dockerComposeConfig = trim($matches[1]); - } - - // Extract SRS configuration - if (preg_match("/cat > srs\.conf <<'SRSCONF'(.*?)SRSCONF/s", $script, $matches)) { - $this->srsConfig = trim($matches[1]); - } - - // Extract Nginx configuration - if (preg_match("/cat > nginx\.conf <<'NGINXCONF'(.*?)NGINXCONF/s", $script, $matches)) { - if ($this->record->type->value === 'origin') { - $this->nginxOriginConfig = trim($matches[1]); - } else { - $this->nginxEdgeConfig = trim($matches[1]); - } - } - - // Extract Caddy configuration - if (preg_match("/cat > Caddyfile <<'CADDYFILE'(.*?)CADDYFILE/s", $script, $matches)) { - if ($this->record->type->value === 'origin') { - $this->caddyOriginConfig = trim($matches[1]); - } else { - $this->caddyEdgeConfig = trim($matches[1]); - } - } - - // For origin servers, extract FFmpeg related configurations - if ($this->record->type->value === 'origin') { - // Note: FFmpeg Dockerfile and script would need to be added to the ServerProvisioningService - // For now, we'll leave these empty as they're not in the current implementation - $this->ffmpegDockerfile = "# FFmpeg Dockerfile not available in current implementation"; - $this->ffmpegScript = "# FFmpeg stream manager script not available in current implementation"; - } - } - - protected function getHeaderActions(): array - { - return [ - Action::make('copyInstallScript') - ->label('Copy Install Script') - ->icon('heroicon-o-clipboard-document') - ->action(function () { - // JavaScript will handle the actual copy - Notification::make() - ->title('Copied!') - ->body('Install script copied to clipboard') - ->success() - ->send(); - }), - - Action::make('downloadInstallScript') - ->label('Download Script') - ->icon('heroicon-o-arrow-down-tray') - ->action(function () { - return response()->streamDownload(function () { - echo $this->installScript; - }, "ef-streaming-install-{$this->record->id}.sh"); - }), - - Action::make('regenerate') - ->label('Regenerate Scripts') - ->icon('heroicon-o-arrow-path') - ->requiresConfirmation() - ->action(function () { - // Regenerate shared secret if needed - if (!$this->record->shared_secret) { - $this->record->update([ - 'shared_secret' => \Illuminate\Support\Str::random(32) - ]); - } - - $this->generateScripts(); - - Notification::make() - ->title('Scripts Regenerated') - ->success() - ->send(); - }), - ]; - } - - public function getTitle(): string - { - return "Install Script - Server #{$this->record->id} ({$this->record->type->value})"; - } - - public function setActiveTab(string $tab): void - { - $this->activeTab = $tab; - } -} \ No newline at end of file diff --git a/app/Filament/Resources/ServerResource/RelationManagers/UserRelationManager.php b/app/Filament/Resources/ServerResource/RelationManagers/UserRelationManager.php deleted file mode 100644 index a082bef..0000000 --- a/app/Filament/Resources/ServerResource/RelationManagers/UserRelationManager.php +++ /dev/null @@ -1,51 +0,0 @@ -schema([ - TextInput::make('sub') - ->required(), - - TextInput::make('name') - ->required(), - - Placeholder::make('created_at') - ->label('Created Date') - ->content(fn (?User $record): string => $record?->created_at?->diffForHumans() ?? '-'), - - Placeholder::make('updated_at') - ->label('Last Modified Date') - ->content(fn (?User $record): string => $record?->updated_at?->diffForHumans() ?? '-'), - ]); - } - - public function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('sub'), - - TextColumn::make('name') - ->searchable() - ->sortable(), - ]); - } -} diff --git a/app/Filament/Resources/ShowResource.php b/app/Filament/Resources/ShowResource.php deleted file mode 100644 index 9ff72e1..0000000 --- a/app/Filament/Resources/ShowResource.php +++ /dev/null @@ -1,438 +0,0 @@ -schema([ - Section::make('Show Information') - ->schema([ - TextInput::make('title') - ->required() - ->maxLength(255) - ->live(onBlur: true) - ->afterStateUpdated(function (string $state, Forms\Set $set, ?Show $record) { - if (! $record) { - $set('slug', Str::slug($state.'-'.now()->format('Y-m-d'))); - } - }), - TextInput::make('slug') - ->required() - ->unique(ignoreRecord: true) - ->maxLength(255), - Select::make('source_id') - ->label('Source') - ->required() - ->options(Source::ordered()->pluck('name', 'id')) - ->searchable() - ->preload() - ->helperText('Select the stream source for this show'), - Select::make('server_id') - ->label('Streaming Server') - ->options(Server::where('status', 'available')->pluck('hostname', 'id')) - ->searchable() - ->preload() - ->helperText('Optional: Assign a specific server'), - Textarea::make('description') - ->rows(4) - ->columnSpanFull(), - ]) - ->columns(2), - - Section::make('Schedule') - ->schema([ - DateTimePicker::make('scheduled_start') - ->label('Scheduled Start') - ->required() - ->seconds(false) - ->timezone('Europe/Berlin'), - DateTimePicker::make('scheduled_end') - ->label('Scheduled End') - ->required() - ->seconds(false) - ->timezone('Europe/Berlin') - ->after('scheduled_start'), - DateTimePicker::make('actual_start') - ->label('Actual Start') - ->seconds(false) - ->timezone('Europe/Berlin') - ->helperText('Set the actual start time when the show went live'), - DateTimePicker::make('actual_end') - ->label('Actual End') - ->seconds(false) - ->timezone('Europe/Berlin') - ->helperText('Set the actual end time when the show ended'), - ]) - ->columns(2), - - Section::make('Status & Settings') - ->schema([ - Select::make('status') - ->options([ - 'scheduled' => 'Scheduled', - 'live' => 'Live', - 'ended' => 'Ended', - 'cancelled' => 'Cancelled', - ]) - ->required() - ->disabled(fn (?Show $record) => $record && $record->status === 'live') - ->helperText('Use the Go Live/End Stream buttons to manage live status'), - Toggle::make('auto_mode') - ->label('Auto Mode') - ->helperText('When enabled, show will automatically start/end based on source status and scheduled times') - ->hint('Show starts when source goes online after scheduled start, ends when source goes offline after scheduled end'), - Toggle::make('recordable') - ->label('Recordable') - ->helperText('Enable recording for this show') - ->hint('When enabled, this show will be available for recording processing'), - CheckboxList::make('required_roles') - ->label('Access Restriction') - ->options(Role::pluck('name', 'slug')->toArray()) - ->helperText('Leave empty for public access. Select roles that can access this show.') - ->hint('Users must have at least one of the selected roles to view') - ->columns(2) - ->columnSpanFull(), - FileUpload::make('thumbnail_path') - ->label('Thumbnail') - ->image() - ->disk('s3') - ->directory('shows/thumbnails') - ->maxSize(5120) // 5MB max - ->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp']) - ->imagePreviewHeight('250') - ->visibility('private') - ->preserveFilenames() - ->loadStateFromRelationshipsUsing(static function (FileUpload $component, ?Show $record): void { - if ($record && $record->thumbnail_path) { - // Set the stored path value so Filament knows where the file is - $component->state($record->thumbnail_path); - } - }) - ->columnSpanFull(), - TagsInput::make('tags') - ->separator(',') - ->suggestions([ - 'Main Stage', - 'Panel', - 'Workshop', - 'Performance', - 'Interview', - 'Opening Ceremony', - 'Closing Ceremony', - ]) - ->columnSpanFull(), - ]) - ->columns(2), - - Section::make('Statistics') - ->schema([ - Placeholder::make('viewer_count') - ->label('Current Viewers') - ->content(fn (?Show $record) => $record ? $record->viewer_count : 0), - Placeholder::make('peak_viewer_count') - ->label('Peak Viewers') - ->content(fn (?Show $record) => $record ? $record->peak_viewer_count : 0), - Placeholder::make('duration') - ->label('Duration') - ->content(fn (?Show $record) => $record ? $record->formatted_duration : '—'), - ]) - ->columns(3) - ->visible(fn (?Show $record) => $record !== null), - - Section::make('Additional Configuration') - ->schema([ - KeyValue::make('metadata') - ->label('Metadata') - ->keyLabel('Key') - ->valueLabel('Value') - ->addButtonLabel('Add Metadata'), - ]) - ->collapsed(), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - ImageColumn::make('thumbnail_url') // Use the accessor that returns signed URL - ->label('Thumbnail') - ->square() - ->size(40), - TextColumn::make('title') - ->searchable() - ->sortable() - ->weight('bold'), - TextColumn::make('source.name') - ->label('Source') - ->searchable() - ->sortable() - ->badge(), - BadgeColumn::make('status') - ->colors([ - 'success' => 'live', - 'warning' => 'scheduled', - 'gray' => 'ended', - 'danger' => 'cancelled', - ]) - ->icons([ - 'heroicon-o-signal' => 'live', - 'heroicon-o-clock' => 'scheduled', - 'heroicon-o-check-circle' => 'ended', - 'heroicon-o-x-circle' => 'cancelled', - ]), - TextColumn::make('scheduled_start') - ->label('Scheduled') - ->dateTime('M j, Y H:i') - ->sortable(), - TextColumn::make('actual_start') - ->label('Went Live') - ->dateTime('M j, Y H:i') - ->sortable() - ->placeholder('Not started') - ->toggleable(), - TextColumn::make('viewer_count') - ->label('Viewers') - ->numeric() - ->sortable() - ->badge() - ->color(fn ($state) => $state > 0 ? 'success' : 'gray'), - TextColumn::make('peak_viewer_count') - ->label('Peak') - ->numeric() - ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - BadgeColumn::make('auto_mode') - ->label('Auto') - ->getStateUsing(fn ($record) => $record->auto_mode ? 'Auto' : 'Manual') - ->colors([ - 'success' => fn ($state) => $state === 'Auto', - 'gray' => fn ($state) => $state === 'Manual', - ]) - ->icons([ - 'heroicon-o-cog' => fn ($state) => $state === 'Auto', - 'heroicon-o-hand-raised' => fn ($state) => $state === 'Manual', - ]), - BadgeColumn::make('required_roles') - ->label('Access') - ->getStateUsing(fn ($record) => $record->hasAccessRestriction() ? 'Restricted' : 'Public') - ->colors([ - 'warning' => fn ($state) => $state === 'Restricted', - 'success' => fn ($state) => $state === 'Public', - ]) - ->icons([ - 'heroicon-o-lock-closed' => fn ($state) => $state === 'Restricted', - 'heroicon-o-globe-alt' => fn ($state) => $state === 'Public', - ]) - ->toggleable(isToggledHiddenByDefault: true), - TextColumn::make('tags') - ->badge() - ->separator(',') - ->toggleable(isToggledHiddenByDefault: true), - ]) - ->defaultSort('scheduled_start', 'asc') - ->filters([ - Tables\Filters\Filter::make('hide_ended') - ->query(fn (Builder $query): Builder => $query->where('status', '!=', 'ended')) - ->label('Hide Ended Shows') - ->default(), - Tables\Filters\SelectFilter::make('status') - ->options([ - 'scheduled' => 'Scheduled', - 'live' => 'Live', - 'ended' => 'Ended', - 'cancelled' => 'Cancelled', - ]) - ->multiple(), - Tables\Filters\SelectFilter::make('source') - ->relationship('source', 'name'), - Tables\Filters\Filter::make('today') - ->query(fn (Builder $query): Builder => $query->today()) - ->label('Today\'s Shows'), - Tables\Filters\Filter::make('upcoming') - ->query(fn (Builder $query): Builder => $query->upcoming()) - ->label('Upcoming Shows'), - ]) - ->actions([ - Action::make('go_live') - ->label('Go Live') - ->icon('heroicon-o-signal') - ->color('success') - ->requiresConfirmation() - ->modalHeading('Start Live Stream') - ->modalDescription('Are you sure you want to start this show? This will mark it as live and notify viewers.') - ->modalSubmitActionLabel('Go Live') - ->visible(fn (Show $record) => $record->status === 'scheduled') - ->action(function (Show $record) { - $record->goLive(); - Notification::make() - ->title('Show is now live!') - ->body("'{$record->title}' is now streaming.") - ->success() - ->send(); - }), - Action::make('end_stream') - ->label('End Stream') - ->icon('heroicon-o-stop') - ->color('danger') - ->requiresConfirmation() - ->modalHeading('End Live Stream') - ->modalDescription('Are you sure you want to end this show? This will stop the stream and disconnect all viewers.') - ->modalSubmitActionLabel('End Stream') - ->visible(fn (Show $record) => $record->status === 'live') - ->action(function (Show $record) { - $record->endLivestream(); - Notification::make() - ->title('Stream ended') - ->body("'{$record->title}' has ended.") - ->success() - ->send(); - }), - Action::make('view_stats') - ->label('View Statistics') - ->icon('heroicon-o-chart-bar') - ->color('info') - ->url(fn (Show $record) => static::getUrl('statistics', ['record' => $record])), - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make() - ->before(function (Show $record) { - if ($record->status === 'live') { - Notification::make() - ->title('Cannot delete live show') - ->body('Please end the stream before deleting.') - ->danger() - ->send(); - - return false; - } - }), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\BulkAction::make('cancel_shows') - ->label('Cancel Shows') - ->icon('heroicon-o-x-circle') - ->color('danger') - ->requiresConfirmation() - ->action(function ($records) { - foreach ($records as $record) { - if ($record->status === 'scheduled') { - $record->cancel(); - } - } - Notification::make() - ->title('Shows cancelled') - ->success() - ->send(); - }), - Tables\Actions\DeleteBulkAction::make() - ->before(function ($records) { - foreach ($records as $record) { - if ($record->status === 'live') { - Notification::make() - ->title('Cannot delete shows') - ->body('One or more shows are currently live.') - ->danger() - ->send(); - - return false; - } - } - }), - ]), - ]) - ->headerActions([ - Action::make('live_dashboard') - ->label('Live Dashboard') - ->icon('heroicon-o-presentation-chart-line') - ->url(route('filament.admin.pages.stream')) - ->openUrlInNewTab(), - ]) - ->poll('5s'); - } - - public static function getRelations(): array - { - return [ - RelationManagers\ViewersRelationManager::class, - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListShows::route('/'), - 'create' => Pages\CreateShow::route('/create'), - 'edit' => Pages\EditShow::route('/{record}/edit'), - 'statistics' => Pages\ViewShowStatistics::route('/{record}/statistics'), - ]; - } - - public static function getNavigationBadge(): ?string - { - $liveCount = static::getModel()::live()->count(); - $upcomingCount = static::getModel()::upcoming()->count(); - - if ($liveCount > 0) { - return $liveCount.' live'; - } - - if ($upcomingCount > 0) { - return $upcomingCount.' upcoming'; - } - - return null; - } - - public static function getNavigationBadgeColor(): ?string - { - $liveCount = static::getModel()::live()->count(); - - if ($liveCount > 0) { - return 'success'; - } - - return 'warning'; - } -} diff --git a/app/Filament/Resources/ShowResource/Pages/CreateShow.php b/app/Filament/Resources/ShowResource/Pages/CreateShow.php deleted file mode 100644 index d37fde9..0000000 --- a/app/Filament/Resources/ShowResource/Pages/CreateShow.php +++ /dev/null @@ -1,11 +0,0 @@ -label('Capture Screenshot') - ->icon('heroicon-o-camera') - ->color('info') - ->disabled(fn () => $this->record->status !== 'live' || ! $this->record->source) - ->tooltip(fn () => $this->record->status !== 'live' - ? 'Show must be live to capture screenshot' - : (! $this->record->source ? 'Show must have a source' : null) - ) - ->action(function () { - if ($this->record->status !== 'live') { - Notification::make() - ->title('Cannot capture screenshot') - ->body('Show must be live to capture a screenshot.') - ->warning() - ->send(); - - return; - } - - if (! $this->record->source) { - Notification::make() - ->title('Cannot capture screenshot') - ->body('Show must have a source assigned.') - ->warning() - ->send(); - - return; - } - - try { - $screenshotPath = $this->record->captureScreenshot(); - if ($screenshotPath) { - // Refresh the form to show the new thumbnail - $this->fillForm(); - - Notification::make() - ->title('Screenshot captured!') - ->body('Thumbnail has been updated for the show.') - ->success() - ->send(); - } else { - Notification::make() - ->title('Screenshot capture failed') - ->body('Could not capture screenshot from the stream.') - ->warning() - ->send(); - } - } catch (\Exception $e) { - Notification::make() - ->title('Screenshot capture error') - ->body($e->getMessage()) - ->danger() - ->send(); - } - }), - Actions\DeleteAction::make(), - ]; - } -} diff --git a/app/Filament/Resources/ShowResource/Pages/ListShows.php b/app/Filament/Resources/ShowResource/Pages/ListShows.php deleted file mode 100644 index 6c8630a..0000000 --- a/app/Filament/Resources/ShowResource/Pages/ListShows.php +++ /dev/null @@ -1,19 +0,0 @@ -record = $this->resolveRecord($record); - } - - public function getTitle(): string | Htmlable - { - return 'Statistics for ' . $this->record->title; - } - - protected function getViewData(): array - { - $service = new ShowStatisticsService(); - $statistics = $service->getShowStatistics($this->record); - - return [ - 'show' => $this->record, - 'statistics' => $statistics, - 'realtimeStats' => $this->record->status === 'live' ? $service->getRealtimeStats($this->record) : null, - ]; - } -} \ No newline at end of file diff --git a/app/Filament/Resources/ShowResource/RelationManagers/ViewersRelationManager.php b/app/Filament/Resources/ShowResource/RelationManagers/ViewersRelationManager.php deleted file mode 100644 index 7465dc0..0000000 --- a/app/Filament/Resources/ShowResource/RelationManagers/ViewersRelationManager.php +++ /dev/null @@ -1,93 +0,0 @@ -schema([ - Forms\Components\TextInput::make('user.name') - ->label('Name') - ->disabled(), - ]); - } - - public function table(Table $table): Table - { - return $table - ->recordTitleAttribute('user.name') - ->columns([ - Tables\Columns\TextColumn::make('user.name') - ->label('Name') - ->searchable() - ->sortable(), - Tables\Columns\TextColumn::make('user.email') - ->label('Email') - ->searchable(), - Tables\Columns\TextColumn::make('joined_at') - ->label('Joined') - ->dateTime() - ->sortable(), - Tables\Columns\TextColumn::make('left_at') - ->label('Left') - ->dateTime() - ->placeholder('Still watching') - ->sortable(), - Tables\Columns\TextColumn::make('watch_duration') - ->label('Duration') - ->getStateUsing(function ($record) { - $duration = $record->watch_duration; - if (! $duration) { - return '—'; - } - - $hours = floor($duration / 3600); - $minutes = floor(($duration % 3600) / 60); - $seconds = $duration % 60; - - if ($hours > 0) { - return sprintf('%dh %dm %ds', $hours, $minutes, $seconds); - } elseif ($minutes > 0) { - return sprintf('%dm %ds', $minutes, $seconds); - } else { - return sprintf('%ds', $seconds); - } - }), - Tables\Columns\TextColumn::make('ip_address') - ->label('IP') - ->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\BadgeColumn::make('is_active') - ->label('Status') - ->getStateUsing(fn ($record) => $record->is_active ? 'Active' : 'Inactive') - ->colors([ - 'success' => fn ($state) => $state === 'Active', - 'gray' => fn ($state) => $state === 'Inactive', - ]), - ]) - ->filters([ - Tables\Filters\Filter::make('active') - ->query(fn (Builder $query): Builder => $query->active()) - ->label('Currently Watching'), - ]) - ->headerActions([ - // - ]) - ->actions([ - // - ]) - ->bulkActions([ - // - ]); - } -} diff --git a/app/Filament/Resources/SourceResource.php b/app/Filament/Resources/SourceResource.php deleted file mode 100644 index 024ee31..0000000 --- a/app/Filament/Resources/SourceResource.php +++ /dev/null @@ -1,288 +0,0 @@ -schema([ - Section::make('Basic Information') - ->schema([ - TextInput::make('name') - ->required() - ->maxLength(255) - ->live(onBlur: true) - ->afterStateUpdated(function (string $state, Forms\Set $set) { - $slug = Str::slug($state); - $set('slug', $slug); - }), - TextInput::make('slug') - ->required() - ->unique(ignoreRecord: true) - ->maxLength(255), - Select::make('status') - ->label('Status') - ->options([ - SourceStatusEnum::ONLINE->value => 'Online', - SourceStatusEnum::OFFLINE->value => 'Offline', - SourceStatusEnum::ERROR->value => 'Error', - ]) - ->default(SourceStatusEnum::OFFLINE->value) - ->required() - ->native(false) - ->helperText('Set the current status of the streaming source'), - TextInput::make('priority') - ->numeric() - ->default(0) - ->minValue(0) - ->maxValue(999) - ->helperText('Higher priority sources appear first on the homepage'), - Textarea::make('description') - ->rows(3) - ->columnSpanFull(), - ]) - ->columns(2), - - Section::make('Stream Configuration') - ->description('OBS Studio Configuration') - ->schema([ - Forms\Components\Placeholder::make('obs_server_url') - ->label('OBS Server URL') - ->content(function (?Source $record) { - if (! $record) { - return 'Will be generated on save'; - } - - return new \Illuminate\Support\HtmlString( - '' - .htmlspecialchars($record->getRtmpServerUrl()). - '' - ); - }) - ->helperText('Click to copy → OBS Settings → Stream → Server'), - Forms\Components\Placeholder::make('obs_stream_key_display') - ->label('OBS Stream Key') - ->content(function (?Source $record) { - if (! $record || ! $record->stream_key) { - return 'Will be generated on save'; - } - - return new \Illuminate\Support\HtmlString( - '' - .htmlspecialchars($record->getObsStreamKey()). - '' - ); - }) - ->helperText('Click to copy → OBS Settings → Stream → Stream Key'), - ]), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - BadgeColumn::make('status') - ->label('Status') - ->getStateUsing(fn ($record) => $record->status->value) - ->colors([ - 'success' => SourceStatusEnum::ONLINE->value, - 'gray' => SourceStatusEnum::OFFLINE->value, - 'danger' => SourceStatusEnum::ERROR->value, - ]) - ->icons([ - 'heroicon-o-signal' => SourceStatusEnum::ONLINE->value, - 'heroicon-o-signal-slash' => SourceStatusEnum::OFFLINE->value, - 'heroicon-o-exclamation-triangle' => SourceStatusEnum::ERROR->value, - ]), - TextColumn::make('name') - ->searchable() - ->sortable() - ->weight('bold'), - TextColumn::make('slug') - ->label('Stream Name') - ->badge() - ->color('info') - ->copyable() - ->searchable(), - TextColumn::make('priority') - ->label('Priority') - ->sortable() - ->badge() - ->color('primary'), - TextColumn::make('shows_count') - ->label('Total Shows') - ->counts('shows') - ->sortable(), - TextColumn::make('live_shows_count') - ->label('Live Now') - ->getStateUsing(fn ($record) => $record->liveShows()->count()) - ->badge() - ->color(fn ($state) => $state > 0 ? 'success' : 'gray'), - TextColumn::make('created_at') - ->dateTime() - ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - TextColumn::make('updated_at') - ->dateTime() - ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - ]) - ->defaultSort('priority', 'desc') - ->filters([ - Tables\Filters\SelectFilter::make('status') - ->label('Status') - ->options([ - SourceStatusEnum::ONLINE->value => 'Online', - SourceStatusEnum::OFFLINE->value => 'Offline', - SourceStatusEnum::ERROR->value => 'Error', - ]) - ->placeholder('All statuses'), - ]) - ->actions([ - Tables\Actions\Action::make('updateStatus') - ->label('Update Status') - ->icon('heroicon-o-arrow-path') - ->color('warning') - ->form([ - Select::make('status') - ->label('New Status') - ->options([ - SourceStatusEnum::ONLINE->value => 'Online', - SourceStatusEnum::OFFLINE->value => 'Offline', - SourceStatusEnum::ERROR->value => 'Error', - ]) - ->default(fn ($record) => $record->status->value) - ->required() - ->native(false), - ]) - ->action(function ($record, array $data) { - $record->update(['status' => $data['status']]); - - // Observer will automatically broadcast the status change event - - Notification::make() - ->title('Status updated') - ->body("Source '{$record->name}' status has been updated to {$data['status']}.") - ->success() - ->send(); - }), - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make() - ->before(function ($record) { - if ($record->liveShows()->exists()) { - Notification::make() - ->title('Cannot delete source') - ->body('This source has active live shows.') - ->danger() - ->send(); - - return false; - } - }), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\BulkAction::make('updateStatus') - ->label('Update Status') - ->icon('heroicon-o-arrow-path') - ->form([ - Select::make('status') - ->label('New Status') - ->options([ - SourceStatusEnum::ONLINE->value => 'Online', - SourceStatusEnum::OFFLINE->value => 'Offline', - SourceStatusEnum::ERROR->value => 'Error', - ]) - ->required() - ->native(false), - ]) - ->action(function ($records, array $data) { - $records->each(function ($record) use ($data) { - $record->update(['status' => $data['status']]); - // Observer will automatically broadcast the status change event - }); - - Notification::make() - ->title('Status updated') - ->body('The selected sources have been updated.') - ->success() - ->send(); - }) - ->deselectRecordsAfterCompletion(), - Tables\Actions\DeleteBulkAction::make() - ->before(function ($records) { - foreach ($records as $record) { - if ($record->liveShows()->exists()) { - Notification::make() - ->title('Cannot delete sources') - ->body('One or more sources have active live shows.') - ->danger() - ->send(); - - return false; - } - } - }), - ]), - ]) - ->poll('10s'); - } - - public static function getRelations(): array - { - return [ - RelationManagers\ShowsRelationManager::class, - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListSources::route('/'), - 'create' => Pages\CreateSource::route('/create'), - 'edit' => Pages\EditSource::route('/{record}/edit'), - ]; - } - - public static function getNavigationBadge(): ?string - { - $onlineCount = static::getModel()::where('status', SourceStatusEnum::ONLINE)->count(); - return $onlineCount > 0 ? (string) $onlineCount : null; - } - - public static function getNavigationBadgeColor(): ?string - { - return 'success'; - } -} \ No newline at end of file diff --git a/app/Filament/Resources/SourceResource/Pages/CreateSource.php b/app/Filament/Resources/SourceResource/Pages/CreateSource.php deleted file mode 100644 index 28bd2bf..0000000 --- a/app/Filament/Resources/SourceResource/Pages/CreateSource.php +++ /dev/null @@ -1,11 +0,0 @@ -label('Regenerate Stream Key') - ->icon('heroicon-o-arrow-path') - ->color('warning') - ->requiresConfirmation() - ->modalHeading('Regenerate Stream Key?') - ->modalDescription('This will invalidate the current stream key. Any active streams will be disconnected.') - ->action(function () { - $newKey = Str::random(32); - $this->record->stream_key = $newKey; - $this->record->save(); - - // Refresh the form to show the new key - $this->fillForm(); - - Notification::make() - ->title('Stream key regenerated') - ->body('The new stream key has been saved and is now active.') - ->success() - ->send(); - }), - Actions\DeleteAction::make(), - ]; - } -} diff --git a/app/Filament/Resources/SourceResource/Pages/ListSources.php b/app/Filament/Resources/SourceResource/Pages/ListSources.php deleted file mode 100644 index 1cc6144..0000000 --- a/app/Filament/Resources/SourceResource/Pages/ListSources.php +++ /dev/null @@ -1,19 +0,0 @@ -schema([ - Forms\Components\TextInput::make('title') - ->required() - ->maxLength(255), - ]); - } - - public function table(Table $table): Table - { - return $table - ->recordTitleAttribute('title') - ->columns([ - Tables\Columns\TextColumn::make('title') - ->searchable() - ->sortable(), - Tables\Columns\BadgeColumn::make('status') - ->colors([ - 'success' => 'live', - 'warning' => 'scheduled', - 'gray' => 'ended', - 'danger' => 'cancelled', - ]), - Tables\Columns\TextColumn::make('scheduled_start') - ->dateTime() - ->sortable(), - Tables\Columns\TextColumn::make('viewer_count') - ->label('Viewers') - ->sortable(), - ]) - ->filters([ - // - ]) - ->headerActions([ - Tables\Actions\CreateAction::make(), - ]) - ->actions([ - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make(), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DeleteBulkAction::make(), - ]), - ]); - } -} diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php deleted file mode 100644 index 0ad9b90..0000000 --- a/app/Filament/Resources/UserResource.php +++ /dev/null @@ -1,104 +0,0 @@ -schema([ - TextInput::make('sub') - ->disabled() - ->required(), - - TextInput::make('name') - ->disabled() - ->required(), - - TextInput::make('reg_id') - ->disabled() - ->integer(), - - Select::make('server_id') - ->relationship('server', 'hostname', - fn (Builder $query) => $query - ->where('type', ServerTypeEnum::EDGE) - ->where('status', ServerStatusEnum::ACTIVE)) - ->nullable(), - - Placeholder::make('updated_at') - ->label('Last Modified Date') - ->content(fn (?User $record): string => $record?->updated_at?->diffForHumans() ?? '-'), - - Placeholder::make('created_at') - ->label('Created Date') - ->content(fn (?User $record): string => $record?->created_at?->diffForHumans() ?? '-'), - ]); - } - - public static function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('sub'), - - TextColumn::make('name') - ->searchable() - ->sortable(), - - TextColumn::make('reg_id'), - ]); - } - - public static function getRelations(): array - { - return [ - RolesRelationManager::class, - MessagesRelationManager::class, - ]; - } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListUsers::route('/'), - 'create' => Pages\CreateUser::route('/create'), - 'edit' => Pages\EditUser::route('/{record}/edit'), - ]; - } - - public static function getGloballySearchableAttributes(): array - { - return ['name']; - } -} diff --git a/app/Filament/Resources/UserResource/Pages/CreateUser.php b/app/Filament/Resources/UserResource/Pages/CreateUser.php deleted file mode 100644 index 78a3894..0000000 --- a/app/Filament/Resources/UserResource/Pages/CreateUser.php +++ /dev/null @@ -1,11 +0,0 @@ -schema([ - Select::make('user_id') - ->relationship('user', 'name') - ->searchable(), - - TextInput::make('message') - ->required(), - - Checkbox::make('is_command'), - - Placeholder::make('created_at') - ->label('Created Date') - ->content(fn (?Message $record): string => $record?->created_at?->diffForHumans() ?? '-'), - - Placeholder::make('updated_at') - ->label('Last Modified Date') - ->content(fn (?Message $record): string => $record?->updated_at?->diffForHumans() ?? '-'), - ]); - } - - public function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('user.name') - ->searchable() - ->sortable(), - - TextColumn::make('message'), - - TextColumn::make('is_command'), - ]); - } -} diff --git a/app/Filament/Resources/UserResource/RelationManagers/RolesRelationManager.php b/app/Filament/Resources/UserResource/RelationManagers/RolesRelationManager.php deleted file mode 100644 index 772f6c5..0000000 --- a/app/Filament/Resources/UserResource/RelationManagers/RolesRelationManager.php +++ /dev/null @@ -1,98 +0,0 @@ -schema([ - Forms\Components\Select::make('role_id') - ->label('Role') - ->options(Role::ordered()->pluck('name', 'id')) - ->required(), - ]); - } - - public function table(Table $table): Table - { - return $table - ->columns([ - Tables\Columns\TextColumn::make('name') - ->label('Role') - ->weight('bold') - ->searchable() - ->sortable(), - Tables\Columns\TextColumn::make('slug') - ->badge() - ->color('gray'), - Tables\Columns\ColorColumn::make('chat_color') - ->label('Chat Color') - ->copyable() - ->copyMessage('Color copied'), - Tables\Columns\TextColumn::make('priority') - ->badge() - ->color(fn ($state) => match (true) { - $state >= 100 => 'danger', - $state >= 90 => 'warning', - $state >= 50 => 'info', - default => 'gray' - }), - Tables\Columns\ToggleColumn::make('assigned_at_login') - ->label('Login Sync') - ->disabled(), - ]) - ->filters([]) - ->headerActions([ - Tables\Actions\AttachAction::make() - ->preloadRecordSelect() - ->form(fn (Tables\Actions\AttachAction $action): array => [ - Forms\Components\Select::make('recordId') - ->label('Role') - ->options(Role::ordered()->pluck('name', 'id')) - ->required() - ->searchable() - ->placeholder('Select a role'), - ]) - ->successNotification( - Notification::make() - ->success() - ->title('Role assigned') - ->body('The role has been assigned to the user.') - ), - ]) - ->actions([ - Tables\Actions\DetachAction::make() - ->successNotification( - Notification::make() - ->success() - ->title('Role removed') - ->body('The role has been removed from the user.') - ) - ->modalHeading('Remove Role') - ->modalDescription('Are you sure you want to remove this role from the user?'), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DetachBulkAction::make() - ->modalHeading('Remove Selected Roles') - ->modalDescription('Are you sure you want to remove the selected roles from this user?'), - ]), - ]) - ->defaultSort('priority', 'desc') - ->paginated([10, 25, 50]); - } -} diff --git a/app/Filament/Widgets/Capacity.php b/app/Filament/Widgets/Capacity.php deleted file mode 100644 index 2495c5b..0000000 --- a/app/Filament/Widgets/Capacity.php +++ /dev/null @@ -1,30 +0,0 @@ -where('type', ServerTypeEnum::EDGE)->sum('max_clients'); - $maxClientsProvisioning = Server::where('status', ServerStatusEnum::PROVISIONING)->where('type', ServerTypeEnum::EDGE)->sum('max_clients'); - $waitingUsers = User::whereNull('server_id')->count(); - - return [ - Card::make('Max clients', $maxClients), - Card::make('Booting Capacity', $maxClientsProvisioning), - Card::make('Waiting Users', $waitingUsers), - ]; - } -} diff --git a/app/Filament/Widgets/ServerActive.php b/app/Filament/Widgets/ServerActive.php deleted file mode 100644 index ab65ceb..0000000 --- a/app/Filament/Widgets/ServerActive.php +++ /dev/null @@ -1,26 +0,0 @@ -where('type', ServerTypeEnum::EDGE)->select(['status', \DB::raw('COUNT(servers.id) AS count')])->get(); - $widget = []; - foreach ($server as $s) { - $widget[] = Card::make('Edge Server '.$s->status->value, $s->count); - } - - return $widget; - } -} diff --git a/app/Filament/Widgets/ViewCountChart.php b/app/Filament/Widgets/ViewCountChart.php deleted file mode 100644 index cbe1e29..0000000 --- a/app/Filament/Widgets/ViewCountChart.php +++ /dev/null @@ -1,77 +0,0 @@ - 'Saturday', - '03-09-2023' => 'Sunday', - '04-09-2023' => 'Monday', - '05-09-2023' => 'Tuesday', - '06-09-2023' => 'Wednesday', - '07-09-2023' => 'Thursday', - '08-09-2023' => 'Friday', - ]; - $datalist = []; - foreach ($days as $k => $v) { - $model = Trend::model(ViewCount::class) - ->between( - start: \Illuminate\Support\Carbon::parse($k), - end: \Illuminate\Support\Carbon::parse($k)->endOfDay(), - ) - ->perHour() - ->average('count'); - $datalist[] = [ - 'label' => $v, - 'data' => $model->map(fn (TrendValue $value) => $value->aggregate), - ]; - } - - return [ - 'datasets' => $datalist, - 'labels' => [ - '00:00', - '01:00', - '02:00', - '03:00', - '04:00', - '05:00', - '06:00', - '07:00', - '08:00', - '09:00', - '10:00', - '11:00', - '12:00', - '13:00', - '14:00', - '15:00', - '16:00', - '17:00', - '18:00', - '19:00', - '20:00', - '21:00', - '22:00', - '23:00', - ], - ]; - } -} diff --git a/app/Helpers/IpSubnetHelper.php b/app/Helpers/IpSubnetHelper.php index e31cf45..6923b8d 100644 --- a/app/Helpers/IpSubnetHelper.php +++ b/app/Helpers/IpSubnetHelper.php @@ -6,10 +6,9 @@ class IpSubnetHelper { /** * Check if an IP address is within a subnet - * - * @param string $ip The IP address to check - * @param string $subnet The subnet in CIDR notation (e.g., 192.168.1.0/24 or 2001:db8::/64) - * @return bool + * + * @param string $ip The IP address to check + * @param string $subnet The subnet in CIDR notation (e.g., 192.168.1.0/24 or 2001:db8::/64) */ public static function isIpInSubnet(string $ip, string $subnet): bool { @@ -18,7 +17,7 @@ public static function isIpInSubnet(string $ip, string $subnet): bool } // Check if subnet contains a slash for CIDR notation - if (!str_contains($subnet, '/')) { + if (! str_contains($subnet, '/')) { return false; } @@ -47,8 +46,8 @@ public static function isIpInSubnet(string $ip, string $subnet): bool private static function isIpv4InSubnet(string $ip, string $subnetAddress, int $prefixLength): bool { // Validate IP addresses - if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || - !filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || + ! filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return false; } @@ -74,8 +73,8 @@ private static function isIpv4InSubnet(string $ip, string $subnetAddress, int $p private static function isIpv6InSubnet(string $ip, string $subnetAddress, int $prefixLength): bool { // Validate IP addresses - if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || - !filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || + ! filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return false; } @@ -108,7 +107,7 @@ private static function isIpv6InSubnet(string $ip, string $subnetAddress, int $p $mask = 0xFF << (8 - $bitsRemaining); $ipByte = ord($ipBinary[$bytesToCompare]); $subnetByte = ord($subnetBinary[$bytesToCompare]); - + if (($ipByte & $mask) !== ($subnetByte & $mask)) { return false; } @@ -116,4 +115,4 @@ private static function isIpv6InSubnet(string $ip, string $subnetAddress, int $p return true; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/CommandController.php b/app/Http/Controllers/Api/CommandController.php index eb9c34c..3eeee4a 100644 --- a/app/Http/Controllers/Api/CommandController.php +++ b/app/Http/Controllers/Api/CommandController.php @@ -26,24 +26,25 @@ public function execute(Request $request) { $request->validate([ 'command' => 'required|string|max:1000', + 'source_id' => 'nullable|integer|exists:sources,id', ]); $user = Auth::user(); $commandInput = $request->input('command'); // Check if user is timed out (unless it's the help command) - if (!str_starts_with(trim($commandInput), '/help')) { + if (! str_starts_with(trim($commandInput), '/help')) { $activeTimeout = Timeout::where('user_id', $user->id) ->where('expires_at', '>', now()) ->first(); - + if ($activeTimeout) { $remainingTime = now()->diffInSeconds($activeTimeout->expires_at); $message = "You are timed out for {$remainingTime} more seconds"; if ($activeTimeout->reason) { $message .= " (Reason: {$activeTimeout->reason})"; } - + return response()->json([ 'success' => false, 'error' => $message, @@ -57,9 +58,10 @@ public function execute(Request $request) } // Rate limiting for commands - $key = 'command-execute:' . $user->id; + $key = 'command-execute:'.$user->id; if (RateLimiter::tooManyAttempts($key, 10)) { $seconds = RateLimiter::availableIn($key); + return response()->json([ 'success' => false, 'error' => "Too many commands. Please wait {$seconds} seconds.", @@ -70,8 +72,8 @@ public function execute(Request $request) // Find the command $command = $this->registry->findByInput($commandInput); - - if (!$command) { + + if (! $command) { return response()->json([ 'success' => false, 'error' => 'Command not found. Type /help for available commands.', @@ -83,8 +85,13 @@ public function execute(Request $request) $command->setRawInput($commandInput); } + // Commands act on the chat they were typed in + if (method_exists($command, 'setSourceId')) { + $command->setSourceId($request->integer('source_id') ?: null); + } + // Check authorization - if (!$command->authorize($user)) { + if (! $command->authorize($user)) { return response()->json([ 'success' => false, 'error' => 'You do not have permission to use this command.', @@ -147,7 +154,7 @@ public function execute(Request $request) ]); // Send error feedback to user - $command->feedback($user, 'Command failed: ' . $e->getMessage(), 'error'); + $command->feedback($user, 'Command failed: '.$e->getMessage(), 'error'); return response()->json([ 'success' => false, @@ -230,13 +237,13 @@ public function help(Request $request) $command = $this->registry->get($commandName); - if (!$command) { + if (! $command) { return response()->json([ 'error' => 'Command not found.', ], 404); } - if (!$command->authorize($user)) { + if (! $command->authorize($user)) { return response()->json([ 'error' => 'You do not have permission to view this command.', ], 403); @@ -247,4 +254,4 @@ public function help(Request $request) 'examples' => method_exists($command, 'examples') ? $command->examples() : [], ]); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/FfmpegCallbackController.php b/app/Http/Controllers/Api/FfmpegCallbackController.php index ed81cad..b9f6f95 100644 --- a/app/Http/Controllers/Api/FfmpegCallbackController.php +++ b/app/Http/Controllers/Api/FfmpegCallbackController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers\Api; -use Illuminate\Http\Request; use App\Http\Controllers\Controller; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Process; @@ -16,29 +16,29 @@ public function onPublish(Request $request) { $app = $request->input('app'); $stream = $request->input('stream'); - + // Only process 'live' app (transcoded outputs) if ($app !== 'live') { return response()->json(['code' => 0]); } - + // Skip quality variants (only process base streams) if (preg_match('/_(fhd|hd|sd|ld)$/', $stream)) { return response()->json(['code' => 0]); } - + Log::info('Starting FFmpeg HLS generation', [ 'app' => $app, 'stream' => $stream, ]); - + // Signal the FFmpeg manager to check for new streams // Or directly start FFmpeg process here if preferred $this->signalFfmpegManager(); - + return response()->json(['code' => 0]); } - + /** * Handle stream unpublish event - stop FFmpeg HLS generation */ @@ -46,28 +46,28 @@ public function onUnpublish(Request $request) { $app = $request->input('app'); $stream = $request->input('stream'); - + // Only process 'live' app if ($app !== 'live') { return response()->json(['code' => 0]); } - + // Skip quality variants if (preg_match('/_(fhd|hd|sd|ld)$/', $stream)) { return response()->json(['code' => 0]); } - + Log::info('Stopping FFmpeg HLS generation', [ 'app' => $app, 'stream' => $stream, ]); - + // Signal the FFmpeg manager to check for removed streams $this->signalFfmpegManager(); - + return response()->json(['code' => 0]); } - + /** * Signal the FFmpeg manager to recheck streams */ @@ -76,8 +76,8 @@ private function signalFfmpegManager() // Touch a file that the manager watches, or send a signal $signalFile = '/var/www/html/hls/check_streams'; @touch($signalFile); - + // Alternative: Send HTTP request to FFmpeg manager if it has an API // Or use Redis pub/sub for real-time notifications } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/HlsSessionController.php b/app/Http/Controllers/Api/HlsSessionController.php index bf09e3c..8ae1375 100644 --- a/app/Http/Controllers/Api/HlsSessionController.php +++ b/app/Http/Controllers/Api/HlsSessionController.php @@ -6,9 +6,9 @@ use App\Models\Server; use App\Models\Source; use Illuminate\Http\Request; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; class HlsSessionController extends Controller { @@ -22,25 +22,26 @@ public function auth(Request $request) $originalUri = $request->header('X-Original-URI'); $realIp = $request->header('X-Real-IP', $request->ip()); $edgeServerId = $request->header('X-Edge-Server'); - + $userId = null; $userName = null; $user = null; $hlsContext = null; - + // Parse stream slug from URI first to create cache keys // Format: /live/{slug}_quality.m3u8 or /live/{slug}_quality_segment.ts or /live/{slug}_master.m3u8 - if (!preg_match('#^/live/([^/_]+?)(?:_(?:master|fhd|hd|sd|ld))?(?:\.|_)#', $originalUri, $slugMatches)) { + if (! preg_match('#^/live/([^/_]+?)(?:_(?:master|fhd|hd|sd|ld))?(?:\.|_)#', $originalUri, $slugMatches)) { Log::warning('Invalid HLS URI format', ['uri' => $originalUri]); + return response()->json(['error' => 'Invalid URI'], 403); } - + $streamSlug = $slugMatches[1]; - + // Extract HLS context if present (from SRS edge server) if (preg_match('/[?&]hls_ctx=([^&]+)/', $originalUri, $ctxMatches)) { $hlsContext = $ctxMatches[1]; - + // Check if we have stored user info for this HLS context $storedUserInfo = Cache::get("hls_context:{$hlsContext}"); if ($storedUserInfo) { @@ -48,12 +49,12 @@ public function auth(Request $request) $userName = $storedUserInfo['user_name']; } } - + // Check for session ID or streamkey in the URL parameters if (preg_match('/[?&](session_id|streamkey)=([^&]+)/', $originalUri, $matches)) { $paramName = $matches[1]; $tokenValue = $matches[2]; - + // Check if it's the system streamkey (for thumbnails, monitoring, etc.) $systemStreamkey = config('stream.system_streamkey'); if ($paramName === 'streamkey' && $systemStreamkey && $tokenValue === $systemStreamkey) { @@ -61,17 +62,17 @@ public function auth(Request $request) return response('', 200) ->header('X-Session-Id', 'system'); } - + // Check if it's a user's streamkey if ($paramName === 'streamkey' || $paramName === 'session_id') { // Cache user lookup for 5 minutes to avoid database queries - $user = Cache::remember("user:streamkey:{$tokenValue}", 300, function() use ($tokenValue) { + $user = Cache::remember("user:streamkey:{$tokenValue}", 300, function () use ($tokenValue) { return \App\Models\User::where('streamkey', $tokenValue)->first(); }); if ($user) { $userId = $user->id; $userName = $user->name; - + // Store user info with HLS context for segment requests if ($hlsContext) { Cache::put("hls_context:{$hlsContext}", [ @@ -80,7 +81,7 @@ public function auth(Request $request) 'streamkey' => $tokenValue, ], now()->addHours(2)); } - + // Also store by IP as fallback $sessionKey = "hls_user:{$realIp}:{$streamSlug}"; Cache::put($sessionKey, [ @@ -90,7 +91,7 @@ public function auth(Request $request) ], now()->addMinutes(10)); } } - } else if (!$userId) { + } elseif (! $userId) { // For segment requests without streamkey and no hls_ctx match, check cache by IP+stream $sessionKey = "hls_user:{$realIp}:{$streamSlug}"; $storedUserInfo = Cache::get($sessionKey); @@ -101,34 +102,36 @@ public function auth(Request $request) Cache::put($sessionKey, $storedUserInfo, now()->addMinutes(10)); } } - + // Check if source exists and is online (cached for 10 seconds) - $source = Cache::remember("source:slug:{$streamSlug}", 10, function() use ($streamSlug) { + $source = Cache::remember("source:slug:{$streamSlug}", 10, function () use ($streamSlug) { return Source::where('slug', $streamSlug)->first(); }); - - if (!$source) { + + if (! $source) { Log::warning('Source not found for HLS request', ['slug' => $streamSlug]); + return response()->json(['error' => 'Stream not found'], 404); } - + if ($source->status !== \App\Enum\SourceStatusEnum::ONLINE) { Log::info('Source offline for HLS request', [ 'slug' => $streamSlug, 'status' => $source->status->value, ]); + return response()->json(['error' => 'Stream offline'], 404); } - + // Create or retrieve session ID for this viewer $sessionId = $this->getOrCreateSession($realIp, $streamSlug, $edgeServerId, $userId); - + // Return success with session ID return response() ->json(['status' => 'ok']) ->header('X-Session-Id', $sessionId); } - + /** * Handle heartbeat from edge server with viewer counts */ @@ -140,39 +143,40 @@ public function heartbeat(Request $request) 'streams' => 'array', 'timestamp' => 'required|date', ]); - + $serverId = $request->input('server_id'); $viewerCount = $request->input('viewer_count'); $streams = $request->input('streams', []); - + Log::info('Edge server heartbeat', [ 'server_id' => $serverId, 'viewer_count' => $viewerCount, 'streams' => $streams, ]); - + // Find server by hostname or hetzner_id $server = Server::where('hostname', $serverId) ->orWhere('hetzner_id', $serverId) ->first(); - - if (!$server) { + + if (! $server) { // Try to find by container name for local development if (str_contains($serverId, 'docker')) { $server = Server::where('hetzner_id', 'manual') ->where('type', \App\Enum\ServerTypeEnum::EDGE) ->first(); } - - if (!$server) { + + if (! $server) { Log::warning('Unknown edge server in heartbeat', ['server_id' => $serverId]); + return response()->json(['error' => 'Unknown server'], 404); } } - + // Update server viewer count $server->updateViewerCount($viewerCount); - + // Store stream-specific viewer counts in cache foreach ($streams as $streamSlug => $count) { Cache::put( @@ -181,12 +185,12 @@ public function heartbeat(Request $request) now()->addMinutes(2) ); } - + // Calculate total viewers across all edges for each stream foreach ($streams as $streamSlug => $count) { $this->updateStreamViewerCount($streamSlug); } - + return response()->json([ 'status' => 'ok', 'server' => [ @@ -195,36 +199,36 @@ public function heartbeat(Request $request) ], ]); } - + /** * Get or create a session for a viewer */ private function getOrCreateSession($ip, $streamSlug, $edgeServerId, $userId = null) { // Generate session key - include user ID if available for unique sessions per user - $sessionKey = $userId + $sessionKey = $userId ? "hls_session:user:{$userId}:{$streamSlug}:{$edgeServerId}" : "hls_session:{$ip}:{$streamSlug}:{$edgeServerId}"; - + // Check cache for existing session $sessionId = Cache::get($sessionKey); - - if (!$sessionId) { + + if (! $sessionId) { // Generate new session ID $sessionId = Str::uuid()->toString(); - + // Store in cache with 5 minute TTL (will be refreshed on each request) Cache::put($sessionKey, $sessionId, now()->addMinutes(5)); - + // New session created (no logging for performance) } else { // Refresh TTL Cache::put($sessionKey, $sessionId, now()->addMinutes(5)); } - + return $sessionId; } - + /** * Update total viewer count for a stream across all edges */ @@ -232,21 +236,20 @@ private function updateStreamViewerCount($streamSlug) { // Get all edge servers $edges = Server::getActiveEdges(); - + $totalViewers = 0; foreach ($edges as $edge) { $count = Cache::get("stream_viewers:{$streamSlug}:{$edge->id}", 0); $totalViewers += $count; } - + // Store total count Cache::put("stream_total_viewers:{$streamSlug}", $totalViewers, now()->addMinutes(2)); - + Log::info('Stream viewer count updated', [ 'stream' => $streamSlug, 'total_viewers' => $totalViewers, 'edge_count' => $edges->count(), ]); } - -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/RecordingApiController.php b/app/Http/Controllers/Api/RecordingApiController.php index 49f41f7..5c6a6f1 100644 --- a/app/Http/Controllers/Api/RecordingApiController.php +++ b/app/Http/Controllers/Api/RecordingApiController.php @@ -11,16 +11,22 @@ class RecordingApiController extends Controller { /** - * Get shows that need to be recorded. - * Returns shows where recordable=true, actual_start and actual_end are set, and no recording exists. + * Shows still awaiting a published recording. + * + * Legacy: this was the work queue for an external processing server, back when + * producing a recording meant extracting and re-encoding MP4s. Cutting is now + * internal and instant (see ArchivePlaylistService), so nothing consumes this to do + * work any more. Kept because the endpoint is an external API contract, and narrowed + * to the honest question it can still answer: which announced shows have not been + * published yet. */ public function shows() { $shows = Show::with('source') - ->where('recordable', true) + ->where('announce_recording', true) ->whereNotNull('actual_start') ->whereNotNull('actual_end') - ->whereDoesntHave('recording') + ->whereDoesntHave('recordings', fn ($q) => $q->where('is_published', true)) ->get() ->map(function ($show) { return [ @@ -31,13 +37,14 @@ public function shows() 'end' => $show->actual_end->toIso8601String(), 'title' => $show->title, 'description' => $show->description, + 'description_html' => $show->description_html, ]; }); return response()->json([ 'success' => true, 'data' => $shows, - 'count' => $shows->count() + 'count' => $shows->count(), ]); } @@ -58,7 +65,7 @@ public function create(Request $request) ]); // If show_id is provided, get the show details - if (!empty($validated['show_id'])) { + if (! empty($validated['show_id'])) { $show = Show::find($validated['show_id']); // Use show details if not provided @@ -84,7 +91,7 @@ public function create(Request $request) $count = 1; while (Recording::where('slug', $slug)->exists()) { - $slug = $baseSlug . '-' . $count; + $slug = $baseSlug.'-'.$count; $count++; } @@ -92,7 +99,7 @@ public function create(Request $request) } // Default to published - if (!isset($validated['is_published'])) { + if (! isset($validated['is_published'])) { $validated['is_published'] = true; } @@ -107,7 +114,7 @@ public function create(Request $request) return response()->json([ 'success' => true, 'data' => $recording, - 'message' => 'Recording created successfully' + 'message' => 'Recording created successfully', ], 201); } @@ -122,7 +129,7 @@ public function getBySlug($slug) return response()->json([ 'success' => true, - 'data' => $recording + 'data' => $recording, ]); } } diff --git a/app/Http/Controllers/Api/ServerProvisionController.php b/app/Http/Controllers/Api/ServerProvisionController.php index 8d81e88..5be25b0 100644 --- a/app/Http/Controllers/Api/ServerProvisionController.php +++ b/app/Http/Controllers/Api/ServerProvisionController.php @@ -26,7 +26,7 @@ public function config(Request $request, string $type) // Find server by shared secret $server = Server::where('shared_secret', $sharedSecret)->first(); - if (!$server) { + if (! $server) { return response('Unauthorized', 401); } @@ -37,15 +37,15 @@ public function config(Request $request, string $type) case 'nginx-origin': $content = $this->provisioningService->generateNginxOriginConfig($server); break; - + case 'nginx-edge': $content = $this->provisioningService->generateNginxEdgeConfig($server); break; - + case 'caddy-origin': $content = $this->provisioningService->generateCaddyOriginConfig($server); break; - + case 'caddy-edge': $content = $this->provisioningService->generateCaddyEdgeConfig($server); break; @@ -59,7 +59,18 @@ public function config(Request $request, string $type) $content = $this->provisioningService->generateDockerCompose($server); $contentType = 'application/x-yaml'; break; - + + // Edge nginx needs the njs module to verify playback tokens locally, + // so it is built on the host from this Dockerfile rather than pulled. + case 'dockerfile-edge': + $content = $this->provisioningService->generateConfig($server, 'edge-dockerfile'); + break; + + case 'hls-auth-js': + $content = $this->provisioningService->generateConfig($server, 'hls-auth-js'); + $contentType = 'application/javascript'; + break; + default: return response('Not found', 404); } @@ -77,7 +88,7 @@ public function script(Request $request, string $script) // Find server by shared secret $server = Server::where('shared_secret', $sharedSecret)->first(); - if (!$server) { + if (! $server) { return response('Unauthorized', 401); } @@ -115,19 +126,19 @@ public function register(Request $request) ]); $server = Server::find($data['server_id']); - if (!$server || $server->shared_secret !== $sharedSecret) { + if (! $server || $server->shared_secret !== $sharedSecret) { return response()->json(['error' => 'Unauthorized'], 401); } // Check if this server can become origin (if it's an origin type) - if ($server->type === \App\Enum\ServerTypeEnum::ORIGIN && + if ($server->type === \App\Enum\ServerTypeEnum::ORIGIN && $data['status'] === \App\Enum\ServerStatusEnum::ACTIVE->value && - !$server->canBecomeOrigin()) { + ! $server->canBecomeOrigin()) { Log::warning('Cannot activate origin server - another origin is already active', [ 'server_id' => $server->id, 'hostname' => $data['hostname'], ]); - + return response()->json([ 'error' => 'Another origin server is already active', 'status' => 'conflict', @@ -184,7 +195,7 @@ public function heartbeat(Request $request, Server $server) // Optional: Include health data in request $health = $request->input('health', []); - if (!empty($health)) { + if (! empty($health)) { $server->update([ 'metadata' => array_merge($server->metadata ?? [], [ 'health' => $health, diff --git a/app/Http/Controllers/Api/SrsCallbackController.php b/app/Http/Controllers/Api/SrsCallbackController.php index a2cfd93..865480a 100644 --- a/app/Http/Controllers/Api/SrsCallbackController.php +++ b/app/Http/Controllers/Api/SrsCallbackController.php @@ -30,7 +30,7 @@ public function auth(Request $request) $tcUrl = $request->input('tcUrl'); $param = $request->input('param'); // Query string from RTMP URL (e.g., "?secret=xyz") $clientIp = $request->input('ip'); // The actual client IP from SRS - + // Allow internal transcoding to publish to "live" app // Block external clients from publishing directly to "live" app if ($app === 'live') { @@ -42,108 +42,108 @@ public function auth(Request $request) 'stream' => $stream, 'ip' => $clientIp, ]); - + // Allow internal transcoding without authentication return response()->json(['code' => 0]); } - + // External client trying to publish to live app - reject Log::warning('External publishing to live app rejected - use ingress app', [ 'app' => $app, 'stream' => $stream, 'ip' => $clientIp, ]); - + return response()->json(['code' => 403, 'msg' => 'Publishing to live app not allowed - use ingress app'], 403); } - + // Remove the leading '?' if present and parse parameters $param = ltrim($param, '?'); parse_str($param, $params); - + // Check for 'secret' parameter (which is the stream_key) $streamKey = $params['secret'] ?? null; - + // Also check for shared_secret for server-to-server auth $sharedSecret = $params['shared_secret'] ?? null; - + // Get the server making the request (for edge->origin auth) $serverIp = $request->ip(); - + // First check if this is a server-to-server forward (edge to origin) if ($sharedSecret) { $server = Server::where('shared_secret', $sharedSecret) ->where('status', \App\Enum\ServerStatusEnum::ACTIVE) ->first(); - + if ($server) { Log::info('Server-to-server auth successful', [ 'server_id' => $server->id, 'hostname' => $server->hostname, ]); - + // For server-to-server, still update the source status based on stream name $source = Source::where('slug', $stream)->first(); if ($source) { $previousStatus = $source->status->value; $source->status = SourceStatusEnum::ONLINE; $source->save(); - + // Broadcast status change event if ($previousStatus !== SourceStatusEnum::ONLINE->value) { broadcast(new SourceStatusChangedEvent($source, $previousStatus)); } } - + return response()->json([ 'code' => 0, 'server' => [ 'id' => (string) $server->id, - 'signature' => md5($server->id . ':' . $server->shared_secret), - ] + 'signature' => md5($server->id.':'.$server->shared_secret), + ], ]); } - + Log::warning('Server-to-server auth failed - invalid shared_secret', [ 'ip' => $serverIp, ]); - + return response()->json(['code' => 403], 403); } - + // Regular source authentication - if (!$streamKey) { + if (! $streamKey) { Log::warning('No stream key provided in publish request', [ 'app' => $app, 'stream' => $stream, 'param' => $param, ]); - + return response()->json(['code' => 403], 403); } - + // Find source by slug (stream name) $source = Source::where('slug', $stream)->first(); - - if (!$source) { + + if (! $source) { Log::warning('No source found for stream', [ 'stream' => $stream, ]); - + return response()->json(['code' => 403], 403); } - + // Verify the stream_key matches (encrypted field will auto-decrypt on access) if ($source->stream_key !== $streamKey) { Log::warning('Invalid stream key for source', [ 'stream' => $stream, 'source_id' => $source->id, - 'provided_key' => substr($streamKey, 0, 8) . '...', + 'provided_key' => substr($streamKey, 0, 8).'...', ]); - + return response()->json(['code' => 403], 403); } - + Log::info('Stream auth successful', [ 'source_id' => $source->id, 'source_name' => $source->name, @@ -151,12 +151,12 @@ public function auth(Request $request) 'stream' => $stream, 'previous_status' => $source->status->value, ]); - + // Update source status to online $previousStatus = $source->status->value; $source->status = SourceStatusEnum::ONLINE; $source->save(); - + // Log recovery if coming from error state if ($previousStatus === SourceStatusEnum::ERROR->value) { Log::info('Source recovered from error state', [ @@ -164,22 +164,22 @@ public function auth(Request $request) 'source_name' => $source->name, ]); } - + // Broadcast status change event if ($previousStatus !== SourceStatusEnum::ONLINE->value) { broadcast(new SourceStatusChangedEvent($source, $previousStatus)); } - + // Return success with source info return response()->json([ 'code' => 0, 'client' => [ 'id' => (string) $source->id, - 'signature' => md5($source->id . ':' . $streamKey), - ] + 'signature' => md5($source->id.':'.$streamKey), + ], ]); } - + /** * Handle SRS on_play webhook * Called when a client wants to play a stream @@ -190,13 +190,13 @@ public function play(Request $request) Log::info('SRS play webhook called', [ 'data' => $request->all(), ]); - + // For now, allow all play requests // You could add viewer authentication here if needed - + return response()->json(['code' => 0]); } - + /** * Handle SRS on_stop webhook * Called when a client stops playing or publishing @@ -207,12 +207,12 @@ public function stop(Request $request) Log::info('SRS stop webhook called', [ 'data' => $request->all(), ]); - + // Clean up any session data if needed - + return response()->json(['code' => 0]); } - + /** * Handle SRS on_unpublish webhook * Called when a stream stops publishing @@ -222,23 +222,23 @@ public function unpublish(Request $request) Log::info('SRS unpublish webhook called', [ 'data' => $request->all(), ]); - + $stream = $request->input('stream'); $app = $request->input('app'); - + // Find the source $source = Source::where('slug', $stream)->first(); if ($source) { $previousStatus = $source->status->value; - + // Check if there's still a live show for this source - $hasLiveShow = \App\Models\Show::where(function($query) use ($source) { - $query->where('source_id', $source->id) - ->orWhere('source_id', $source->slug); - }) + $hasLiveShow = \App\Models\Show::where(function ($query) use ($source) { + $query->where('source_id', $source->id) + ->orWhere('source_id', $source->slug); + }) ->where('status', 'live') ->exists(); - + // If there's a live show, this is an unexpected disconnect (error) // If no live show, this is an expected shutdown (offline) if ($hasLiveShow) { @@ -256,9 +256,9 @@ public function unpublish(Request $request) 'previous_status' => $previousStatus, ]); } - + $source->save(); - + // Broadcast status change event if ($previousStatus !== $source->status->value) { broadcast(new SourceStatusChangedEvent($source, $previousStatus)); @@ -268,16 +268,16 @@ public function unpublish(Request $request) 'stream' => $stream, ]); } - + Log::info('Stream unpublish processed', [ 'app' => $app, 'stream' => $stream, 'final_status' => $source ? $source->status->value : 'unknown', ]); - + return response()->json(['code' => 0]); } - + /** * Handle SRS on_error webhook * Called when there's a stream error or connection interruption @@ -287,28 +287,28 @@ public function error(Request $request) Log::info('SRS error webhook called', [ 'data' => $request->all(), ]); - + $stream = $request->input('stream'); $error = $request->input('error'); $description = $request->input('description'); - + // Update source status to error $source = Source::where('slug', $stream)->first(); if ($source) { $previousStatus = $source->status->value; - + // Only set to error if currently online (to avoid overriding offline status) if ($source->status === SourceStatusEnum::ONLINE) { $source->status = SourceStatusEnum::ERROR; $source->save(); - + Log::warning('Source status updated to error', [ 'source_id' => $source->id, 'name' => $source->name, 'error' => $error, 'description' => $description, ]); - + // Broadcast status change event broadcast(new SourceStatusChangedEvent($source, $previousStatus)); } @@ -318,10 +318,10 @@ public function error(Request $request) 'error' => $error, ]); } - + return response()->json(['code' => 0]); } - + /** * Handle SRS on_hls webhook * Called when a client requests an HLS stream @@ -330,35 +330,35 @@ public function error(Request $request) public function onHls(Request $request) { $data = $request->all(); - + Log::info('SRS on_hls callback', $data); - + // Extract parameters from SRS callback $stream = $data['stream'] ?? ''; $param = $data['param'] ?? ''; $clientId = $data['client_id'] ?? ''; $ip = $data['ip'] ?? ''; - + // Parse query parameters from param field parse_str(ltrim($param, '?'), $params); - + // Check for streamkey or session_id $streamkey = $params['streamkey'] ?? $params['session_id'] ?? null; - + // Check if it's the internal system session if ($streamkey === config('stream.internal_session_id')) { Log::info('SRS auth bypassed for internal session', [ 'stream' => $stream, 'client_id' => $clientId, ]); - + return response()->json(['code' => 0]); } - + // Validate streamkey if provided if ($streamkey) { $user = \App\Models\User::where('streamkey', $streamkey)->first(); - + if ($user) { // Store user info with client_id for tracking \Illuminate\Support\Facades\Cache::put("srs_client:{$clientId}", [ @@ -367,34 +367,36 @@ public function onHls(Request $request) 'stream' => $stream, 'ip' => $ip, ], now()->addHours(2)); - + Log::info('SRS HLS access granted', [ 'user_id' => $user->id, 'user_name' => $user->name, 'stream' => $stream, 'client_id' => $clientId, ]); - + return response()->json(['code' => 0]); } } - + // Check if source exists and is online $source = Source::where('slug', $stream)->first(); - - if (!$source) { + + if (! $source) { Log::warning('Source not found for SRS HLS request', ['stream' => $stream]); + return response()->json(['code' => 404, 'msg' => 'Stream not found']); } - + if ($source->status !== SourceStatusEnum::ONLINE) { Log::info('Source offline for SRS HLS request', [ 'stream' => $stream, 'status' => $source->status->value, ]); + return response()->json(['code' => 404, 'msg' => 'Stream offline']); } - + // If no auth but stream is online, allow access (public stream) // You might want to make this configurable per source Log::info('SRS HLS public access granted', [ @@ -402,7 +404,7 @@ public function onHls(Request $request) 'client_id' => $clientId, 'ip' => $ip, ]); - + return response()->json(['code' => 0]); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/SrsDvrController.php b/app/Http/Controllers/Api/SrsDvrController.php index 4d40fe0..58c8b3f 100644 --- a/app/Http/Controllers/Api/SrsDvrController.php +++ b/app/Http/Controllers/Api/SrsDvrController.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers\Api; +use App\Http\Controllers\Controller; use App\Models\DvrRecording; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; -use App\Http\Controllers\Controller; class SrsDvrController extends Controller { @@ -18,10 +18,10 @@ public function handleDvrCallback(Request $request) try { // Log the DVR event Log::info('DVR callback received', $request->all()); - + // Extract DVR information $data = $request->all(); - + // Parse the file path to extract metadata $filePath = $data['file'] ?? ''; $app = $data['app'] ?? ''; @@ -30,7 +30,7 @@ public function handleDvrCallback(Request $request) $clientId = $data['client_id'] ?? null; $ip = $data['ip'] ?? ''; $action = $data['action'] ?? ''; - + // Store DVR recording information in database (optional) if ($filePath) { $this->storeDvrRecording([ @@ -44,27 +44,27 @@ public function handleDvrCallback(Request $request) 'created_at' => now(), ]); } - + // Return success response return response()->json([ 'code' => 0, - 'msg' => 'ok' + 'msg' => 'ok', ]); - + } catch (\Exception $e) { - Log::error('DVR callback error: ' . $e->getMessage(), [ + Log::error('DVR callback error: '.$e->getMessage(), [ 'request' => $request->all(), - 'exception' => $e + 'exception' => $e, ]); - + // SRS expects code 0 for success, non-zero for error return response()->json([ 'code' => 1, - 'msg' => 'error: ' . $e->getMessage() + 'msg' => 'error: '.$e->getMessage(), ]); } } - + /** * Store DVR recording information */ @@ -74,18 +74,18 @@ private function storeDvrRecording(array $data) // You can create a DvrRecording model to track recordings // For now, just log it Log::info('DVR recording created', $data); - + // Optional: Store in database // DvrRecording::create($data); - + // Optional: Dispatch job for post-processing // ProcessDvrRecording::dispatch($data); - + } catch (\Exception $e) { - Log::error('Failed to store DVR recording: ' . $e->getMessage()); + Log::error('Failed to store DVR recording: '.$e->getMessage()); } } - + /** * Handle S3 upload webhook from DVR uploader service */ @@ -93,9 +93,9 @@ public function handleUploadWebhook(Request $request) { try { Log::info('DVR S3 upload webhook received', $request->all()); - + $data = $request->all(); - + // Extract information $s3Bucket = $data['s3_bucket'] ?? ''; $s3Key = $data['s3_key'] ?? ''; @@ -103,23 +103,23 @@ public function handleUploadWebhook(Request $request) $app = $data['app'] ?? ''; $stream = $data['stream'] ?? ''; $date = $data['date'] ?? ''; - + // Optional: Update recording status in database // Optional: Send notifications // Optional: Trigger VOD processing - + return response()->json([ 'status' => 'success', - 'message' => 'Upload webhook processed' + 'message' => 'Upload webhook processed', ]); - + } catch (\Exception $e) { - Log::error('DVR upload webhook error: ' . $e->getMessage()); - + Log::error('DVR upload webhook error: '.$e->getMessage()); + return response()->json([ 'status' => 'error', - 'message' => $e->getMessage() + 'message' => $e->getMessage(), ], 500); } } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Auth/FrontChannelLogoutController.php b/app/Http/Controllers/Auth/FrontChannelLogoutController.php index 075a219..caa5373 100644 --- a/app/Http/Controllers/Auth/FrontChannelLogoutController.php +++ b/app/Http/Controllers/Auth/FrontChannelLogoutController.php @@ -3,12 +3,13 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Services\BrandingService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; class FrontChannelLogoutController extends Controller { - public function __invoke(Request $request) + public function __invoke(Request $request, BrandingService $branding) { \Auth::logout(); @@ -16,6 +17,16 @@ public function __invoke(Request $request) $request->session()->invalidate(); $request->session()->regenerateToken(); - return redirect("https://identity.eurofurence.org/oauth2/sessions/logout?id_token_hint={$idToken}"); + // The provider endpoint is per installation. Without one configured the + // local session is already gone, so there is nowhere else to send them. + $logoutUrl = trim((string) $branding->get('identity_logout_url')); + + if ($logoutUrl === '') { + return redirect('/'); + } + + $separator = str_contains($logoutUrl, '?') ? '&' : '?'; + + return redirect($logoutUrl.$separator.'id_token_hint='.urlencode((string) $idToken)); } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3633ffd..c94c465 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -3,12 +3,65 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Models\Show; +use Illuminate\Contracts\Database\Query\Builder; +use Illuminate\Support\Collection; use Inertia\Inertia; class LoginController extends Controller { + /** + * How far ahead the schedule rail on the login screen looks. + */ + private const SCHEDULE_WINDOW_HOURS = 20; + + /** + * How many rows it shows, however much fits in that window. + */ + private const SCHEDULE_LENGTH = 6; + public function __invoke() { - return Inertia::render('Auth/Login'); + return Inertia::render('Auth/Login', [ + 'schedule' => $this->schedule(), + ]); + } + + /** + * Whatever is on now, plus anything starting in the next 20 hours, in clock + * order. Ended and cancelled shows never appear. + */ + private function schedule(): Collection + { + return Show::query() + // accessibleBy(null) keeps role-restricted shows out of the rail, + // which is rendered before anyone has signed in. + ->accessibleBy(null) + ->with('source') + ->where(function (Builder $query) { + // Live shows stay listed no matter when they were scheduled to + // start, since they are on right now. + $query->where('status', 'live') + ->orWhere(fn (Builder $upcoming) => $upcoming + ->where('status', 'scheduled') + ->whereBetween('scheduled_start', [ + now(), + now()->addHours(self::SCHEDULE_WINDOW_HOURS), + ])); + }) + // Live shows sort by when they actually started, so a stream that has + // been running since yesterday stays above tonight's line-up. + ->orderByRaw('COALESCE(actual_start, scheduled_start)') + ->limit(self::SCHEDULE_LENGTH) + ->get() + ->map(fn (Show $show) => [ + 'id' => $show->id, + 'title' => $show->title, + 'source' => $show->source?->name, + 'time' => ($show->actual_start ?? $show->scheduled_start)?->format('H:i'), + // Drives both the highlighted row and the LIVE marker. + 'current' => $show->status === 'live', + ]) + ->values(); } } diff --git a/app/Http/Controllers/Auth/OidcClientController.php b/app/Http/Controllers/Auth/OidcClientController.php index 482ae5c..e504777 100644 --- a/app/Http/Controllers/Auth/OidcClientController.php +++ b/app/Http/Controllers/Auth/OidcClientController.php @@ -3,7 +3,9 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Models\Role; use App\Models\User; +use App\Services\BrandingService; use App\Services\Hydra\Client; use App\Services\OpenIDService; use Illuminate\Http\RedirectResponse; @@ -35,8 +37,15 @@ public function callback(Request $request) /** * Only Identity Client - Redirects to error page if scope is invalid */ + Log::info('OIDC DEBUG callback entered', ['query' => $request->query(), 'session_id' => Session::getId()]); if (isset($data['error'])) { - return Redirect::route('auth.login'); + Log::warning('OIDC callback returned an error from the identity provider', $data); + + // The provider rejected the authorize request (a replayed flow, a rotated CSRF + // cookie on its side, an expired flow). Bouncing to auth.login would start yet + // another authorize round and loop, hiding the error, so stop at the sign-in + // screen and say so. + return $this->failed('Sign-in was refused. Start again from this page, in a single tab.'); } /** @@ -45,9 +54,13 @@ public function callback(Request $request) * otherwise null === null and it would pass the check falsely. */ if ($request->get('state') !== Session::get('login.oauth2state', false)) { + Log::warning('OIDC callback state did not match the session', [ + 'got' => $request->get('state'), + 'expected' => Session::get('login.oauth2state', false), + ]); Session::remove('login.oauth2state'); - return Redirect::route('auth.login'); + return $this->failed('The sign-in request expired or was started in another session.'); } Session::flush(); /** @@ -59,9 +72,15 @@ public function callback(Request $request) ]); $userinfoRequest = Http::identity()->withToken($accessToken->getToken())->get('/api/v1/userinfo'); if ($userinfoRequest->successful() === false) { - return Redirect::route('auth.login'); + Log::warning('OIDC userinfo request failed', ['status' => $userinfoRequest->status(), 'body' => $userinfoRequest->body()]); + + // Named via BrandingService so a saved override wins over the config default. + $identity = app(BrandingService::class)->all()['identity_name'] ?? 'the identity provider'; + + return $this->failed("Your account details could not be read from {$identity}."); } $userinfo = $userinfoRequest->json(); + Log::info('OIDC DEBUG userinfo ok', ['keys' => array_keys($userinfo ?? [])]); if (! isset($userinfo['sub'])) { throw new UnexpectedValueException('Could not request user id from freshly fetched token.'); @@ -75,16 +94,24 @@ public function callback(Request $request) ]); $user = $user->fresh(); - // Fetch attendee packages from EF registration API + // Fetch attendee packages from the registration API $packages = $this->fetchAttendeePackages($userid); // Sync roles from registration system (groups and packages) $roleSlugs = $this->mapGroupsAndPackagesToRoles($userinfo['groups'] ?? [], $packages); $user->syncRolesFromLogin($roleSlugs); - Auth::loginUsingId($user->id); + // Remembered, so the sign-in survives the session cookie expiring or the session + // store being cleared. Attendees should not be bounced back to the identity + // provider mid-convention. + Auth::loginUsingId($user->id, remember: true); Session::put('access_token', $accessToken); - Session::put('avatar', $userinfo['avatar']); + Session::put('avatar', $userinfo['avatar'] ?? null); + Log::info('OIDC DEBUG logged in', [ + 'user_id' => $user->id, + 'auth_check' => Auth::check(), + 'session_id' => Session::getId(), + ]); // Middleware will handle server assignment and redirect if needed return $this->redirectDestination($request); @@ -92,6 +119,17 @@ public function callback(Request $request) public function login(Request $request): RedirectResponse { + if ($rejection = $this->redirectUriRejection()) { + Log::error('OIDC redirect URI will be rejected by the provider', [ + 'redirect_uri' => route('auth.callback'), + 'reason' => $rejection, + ]); + + if (! app()->isProduction()) { + return $this->failed($rejection); + } + } + $provider = $this->openIDService->setupOIDC($request, $this->clientIsAdmin($request)); $authorizationUrl = $provider->getAuthorizationUrl(); Session::put('login.oauth2state', $provider->getState()); @@ -104,13 +142,62 @@ public function clientIsAdmin(Request $request) return false; } + /** + * End a broken sign-in at the sign-in screen rather than at the flow initiator. + * + * `auth.login` immediately redirects to the provider's authorize endpoint, so + * redirecting a failure there restarts the flow: the operator sees a redirect loop + * instead of a message, and the underlying error stays invisible. + * + * The provider's own description is kept out of the response on purpose; it leaks + * internals ("the CSRF value from the token does not match ...") and means nothing to + * an attendee. It is in the log for whoever is debugging. + */ + /** + * OAuth2 providers refuse a plain-http redirect URI unless the host is localhost or a + * `*.localhost` subdomain. Ory Hydra answers with + * `invalid_request: Redirect URL is using an insecure protocol ...` only *after* a full + * round trip through the authorize endpoint, which reads like a login failure rather + * than a misconfigured APP_URL. Catch it before leaving the app. + * + * @return string|null The reason, or null when the redirect URI is acceptable. + */ + private function redirectUriRejection(): ?string + { + $uri = route('auth.callback'); + $parts = parse_url($uri); + + if (($parts['scheme'] ?? 'http') === 'https') { + return null; + } + + $host = $parts['host'] ?? ''; + + if ($host === 'localhost' || str_ends_with($host, '.localhost')) { + return null; + } + + return "Sign-in is misconfigured: the callback URL {$uri} uses http, which the identity " + .'provider only accepts for localhost hosts. Set APP_URL to an https URL, or to a ' + .'*.localhost host, and make sure that callback URL is registered for this client.'; + } + + private function failed(?string $reason = null): RedirectResponse + { + Session::remove('login.oauth2state'); + + return Redirect::route('login')->withErrors([ + 'oidc' => $reason ?? 'Sign-in could not be completed. Please try again.', + ]); + } + private function redirectDestination(Request $request) { return Redirect::route('shows.grid'); } /** - * Fetch attendee packages from EF registration API + * Fetch attendee packages from the registration API * This is optional - if the registration system is offline, we silently continue without packages */ private function fetchAttendeePackages(string $userId): array @@ -177,50 +264,60 @@ private function fetchAttendeePackages(string $userId): array } /** - * Map registration system groups and packages to role slugs + * Collect the external identifiers this sign-in grants. + * + * Nothing is translated to a role here. A role claims an identifier by + * putting it in its `external_id`, so which role a group ID or a package + * maps to is a question for the roles table, editable in /manage, rather + * than something wired into this class. + * + * @param array $groups Group IDs from the userinfo claim. + * @param array $packages Package names from the registration system. + * @return array */ private function mapGroupsAndPackagesToRoles(array $groups, array $packages): array { - $roles = []; + // Group IDs are already the identifier, so they pass through untouched. + $identifiers = array_values($groups); - // Check packages for sponsor/supersponsor - foreach ($packages as $package) { - $packageName = strtolower($package); + /* + * Packages are not. A package reads like "day-supersponsor-2026", so a + * role claims one by declaring the part it recognises. Longest first, or + * the sponsor role would swallow every supersponsor package. + */ + $claimed = Role::loginAssigned() + ->pluck('external_id') + ->filter() + ->sortByDesc(fn (string $id) => strlen($id)) + ->values(); - if (str_contains($packageName, 'supersponsor')) { - $roles[] = 'supersponsor'; - } elseif (str_contains($packageName, 'sponsor')) { - $roles[] = 'sponsor'; - } - } + foreach ($packages as $package) { + $package = strtolower((string) $package); - // Map groups to roles - // Group IDs from identity provider userinfo groups array - $groupMapping = [ - 'KVJ7GW275683NMZL' => 'admin', // Streaming Admin group - '54ZYODX15G2K1M76' => 'staff', // General EF Staff - ]; + foreach ($claimed as $identifier) { + if (str_contains($package, strtolower($identifier))) { + $identifiers[] = $identifier; - foreach ($groups as $group) { - if (isset($groupMapping[$group])) { - $roles[] = $groupMapping[$group]; + // One role per package: the longest match already won. + break; + } } } - // Add attendee role as base role if not already included - if (! in_array('attendee', $roles)) { - $roles[] = 'attendee'; - } + /* + * Everyone who got this far signed in successfully, so a role that + * declares itself the baseline gets handed out unconditionally. + */ + $identifiers[] = 'attendee'; - // Remove duplicates - $roles = array_unique($roles); + $identifiers = array_values(array_unique($identifiers)); - Log::info('Mapped roles for user', [ + Log::info('Mapped external identifiers for user', [ 'groups' => $groups, 'packages' => $packages, - 'roles' => $roles, + 'identifiers' => $identifiers, ]); - return $roles; + return $identifiers; } } diff --git a/app/Http/Controllers/Chat/ChatUserController.php b/app/Http/Controllers/Chat/ChatUserController.php new file mode 100644 index 0000000..0d79686 --- /dev/null +++ b/app/Http/Controllers/Chat/ChatUserController.php @@ -0,0 +1,69 @@ +user(); + $sourceId = $request->integer('source_id') ?: null; + + $payload = [ + 'id' => $user->id, + 'name' => $user->name, + 'color' => $user->chat_color, + 'badges' => $user->chatBadges(), + 'member_since' => $user->created_at?->toIso8601String(), + 'is_self' => $viewer->id === $user->id, + 'can_moderate' => $this->moderation->canActOn($viewer, $user), + 'can_ban' => $viewer->canBanFromChat() && $this->moderation->canActOn($viewer, $user), + ]; + + if (! $viewer->canModerateChat()) { + return response()->json($payload); + } + + $messages = Message::with('user') + ->where('user_id', $user->id) + ->when($sourceId, fn ($query) => $query->where('source_id', $sourceId)) + ->orderByDesc('id') + ->limit(10) + ->get(); + + $timeout = $user->activeTimeout(); + $ban = $user->activeChatBan(); + + return response()->json(array_merge($payload, [ + 'message_count' => Message::where('user_id', $user->id) + ->when($sourceId, fn ($query) => $query->where('source_id', $sourceId)) + ->count(), + 'recent_messages' => $this->presenter->presentMany($messages->reverse()), + 'timeout' => $timeout ? [ + 'expires_at' => $timeout->expires_at->toIso8601String(), + 'seconds_remaining' => (int) now()->diffInSeconds($timeout->expires_at), + 'reason' => $timeout->reason, + ] : null, + 'ban' => $ban ? [ + 'permanent' => $ban->isPermanent(), + 'expires_at' => $ban->expires_at?->toIso8601String(), + 'reason' => $ban->reason, + ] : null, + ])); + } +} diff --git a/app/Http/Controllers/Chat/ModerationController.php b/app/Http/Controllers/Chat/ModerationController.php new file mode 100644 index 0000000..337b5b9 --- /dev/null +++ b/app/Http/Controllers/Chat/ModerationController.php @@ -0,0 +1,203 @@ +validate([ + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'seconds' => ['required', 'integer', 'min:1', 'max:1209600'], + 'reason' => ['nullable', 'string', 'max:200'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $target = User::findOrFail($data['user_id']); + + $this->moderation->timeout( + $request->user(), + $target, + $data['seconds'], + $data['reason'] ?? null, + $data['source_id'] ?? null, + ); + + return response()->json([ + 'success' => true, + 'message' => "{$target->name} timed out for ".$this->moderation->humanizeSeconds($data['seconds']).'.', + ]); + } + + public function untimeout(Request $request) + { + $data = $request->validate([ + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $target = User::findOrFail($data['user_id']); + $this->moderation->removeTimeout($request->user(), $target, $data['source_id'] ?? null); + + return response()->json(['success' => true, 'message' => "Timeout removed for {$target->name}."]); + } + + public function ban(Request $request) + { + $data = $request->validate([ + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'reason' => ['nullable', 'string', 'max:200'], + 'seconds' => ['nullable', 'integer', 'min:60'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $target = User::findOrFail($data['user_id']); + + $this->moderation->ban( + $request->user(), + $target, + $data['reason'] ?? null, + isset($data['seconds']) ? now()->addSeconds($data['seconds']) : null, + $data['source_id'] ?? null, + ); + + return response()->json(['success' => true, 'message' => "{$target->name} banned from chat."]); + } + + public function unban(Request $request) + { + $data = $request->validate([ + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $target = User::findOrFail($data['user_id']); + $this->moderation->unban($request->user(), $target, $data['source_id'] ?? null); + + return response()->json(['success' => true, 'message' => "{$target->name} unbanned."]); + } + + public function purge(Request $request) + { + $data = $request->validate([ + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + 'within_seconds' => ['nullable', 'integer', 'min:1'], + ]); + + $target = User::findOrFail($data['user_id']); + + $count = $this->moderation->purgeUser( + $request->user(), + $target, + $data['source_id'] ?? null, + $data['within_seconds'] ?? null, + ); + + return response()->json([ + 'success' => true, + 'message' => $count === 0 + ? "No messages from {$target->name} to remove." + : "Removed {$count} message".($count === 1 ? '' : 's')." from {$target->name}.", + ]); + } + + public function clear(Request $request) + { + $data = $request->validate([ + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $count = $this->moderation->clearChat($request->user(), $data['source_id'] ?? null); + + return response()->json(['success' => true, 'message' => "Cleared {$count} messages."]); + } + + public function announce(Request $request) + { + $data = $request->validate([ + 'message' => ['required', 'string', 'min:1', 'max:500'], + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + ]); + + $body = (new ChatMessageSanitizer)->sanitize($data['message']); + $message = $this->moderation->announce($request->user(), $body, $data['source_id'] ?? null); + + return response()->json(['success' => true, 'message' => 'Announcement sent.', 'announcement' => $this->presenter->present($message)]); + } + + public function updateSettings(Request $request) + { + $data = $request->validate([ + 'source_id' => ['nullable', 'integer', 'exists:sources,id'], + 'slow_mode_seconds' => ['nullable', 'integer', 'min:0', 'max:300'], + 'emote_only' => ['nullable', 'boolean'], + 'sponsors_only' => ['nullable', 'boolean'], + ]); + + $sourceId = $data['source_id'] ?? null; + unset($data['source_id']); + + return response()->json([ + 'success' => true, + 'settings' => $this->moderation->updateSettings($request->user(), $data, $sourceId), + ]); + } + + /** + * Everything the mod menu shows: who is timed out, who is banned. + */ + public function index(Request $request) + { + abort_unless($request->user()->canModerateChat(), 403); + + return response()->json([ + 'timeouts' => Timeout::with('user:id,name', 'issuedBy:id,name') + ->active() + ->latest('expires_at') + ->limit(50) + ->get() + ->map(fn (Timeout $timeout) => [ + 'user_id' => $timeout->user_id, + 'name' => $timeout->user?->name, + 'expires_at' => $timeout->expires_at->toIso8601String(), + 'seconds_remaining' => (int) now()->diffInSeconds($timeout->expires_at), + 'reason' => $timeout->reason, + 'issued_by' => $timeout->issuedBy?->name, + ]), + 'bans' => ChatBan::with('user:id,name', 'bannedBy:id,name') + ->active() + ->latest('id') + ->limit(50) + ->get() + ->map(fn (ChatBan $ban) => [ + 'user_id' => $ban->user_id, + 'name' => $ban->user?->name, + 'reason' => $ban->reason, + 'permanent' => $ban->isPermanent(), + 'expires_at' => $ban->expires_at?->toIso8601String(), + 'issued_by' => $ban->bannedBy?->name, + ]), + ]); + } +} diff --git a/app/Http/Controllers/HlsController.php b/app/Http/Controllers/HlsController.php index 6216e56..a4875ef 100644 --- a/app/Http/Controllers/HlsController.php +++ b/app/Http/Controllers/HlsController.php @@ -2,16 +2,18 @@ namespace App\Http\Controllers; +use App\Enum\ServerStatusEnum; +use App\Enum\ServerTypeEnum; +use App\Helpers\IpSubnetHelper; use App\Models\Server; use App\Models\Source; use App\Models\SourceUser; use App\Models\User; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Cache; -use App\Helpers\IpSubnetHelper; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; class HlsController extends Controller { @@ -24,7 +26,7 @@ public function master(Request $request, $stream) // Find the source by slug $source = Source::where('slug', $stream)->first(); - if (!$source) { + if (! $source) { return response('Stream not found', 404) ->header('Content-Type', 'text/plain'); } @@ -38,21 +40,21 @@ public function master(Request $request, $stream) $systemStreamkey = config('stream.system_streamkey'); if ($systemStreamkey && $streamkey === $systemStreamkey) { // For system operations, create a minimal user object - $user = new User(); + $user = new User; $user->id = 0; $user->name = 'System'; $user->streamkey = $streamkey; } else { // Look up user by streamkey $user = User::where('streamkey', $streamkey)->first(); - if (!$user) { + if (! $user) { return response('Invalid streamkey', 401) ->header('Content-Type', 'text/plain'); } } } else { $user = Auth::user(); - if (!$user) { + if (! $user && config('auth.required')) { return response('Authentication required', 401) ->header('Content-Type', 'text/plain'); } @@ -62,17 +64,17 @@ public function master(Request $request, $stream) // Check for IP-based server override $server = $this->getServerForRequest($request, $user); - - if (!$server) { + + if (! $server) { return response('No server available', 503) ->header('Content-Type', 'text/plain'); } - + $port = $server->port ?? 8080; // Build cache key based on stream, server, and streamkey - $cacheKey = "hls_master:{$stream}:{$server->hostname}:{$port}:" . ($streamkey ?? 'auth'); - + $cacheKey = "hls_master:{$stream}:{$server->hostname}:{$port}:".($streamkey ?? 'auth'); + // Try to get cached response $cachedResponse = Cache::get($cacheKey); if ($cachedResponse) { @@ -92,7 +94,7 @@ public function master(Request $request, $stream) try { // Fetch the master playlist from the server // For HTTPS, allow self-signed certificates in development - $httpClient = Http::timeout(3); + $httpClient = Http::timeout(3)->withHeaders($this->systemAuthHeaders()); if (str_starts_with($masterUrl, 'https://')) { $httpClient = $httpClient->withOptions(['verify' => false]); } @@ -103,13 +105,14 @@ public function master(Request $request, $stream) // Rewrite variant URLs to use our Laravel routes and preserve streamkey $playlist = preg_replace_callback( - '/^(' . preg_quote($stream, '/') . '_(sd|hd|fhd)\.m3u8)$/m', - function($matches) use ($streamkey) { - $url = '/hls/' . $matches[1]; + '/^('.preg_quote($stream, '/').'_(sd|hd|fhd)\.m3u8)$/m', + function ($matches) use ($streamkey) { + $url = '/hls/'.$matches[1]; // Add streamkey parameter if present if ($streamkey) { - $url .= '?streamkey=' . $streamkey; + $url .= '?streamkey='.$streamkey; } + return $url; }, $playlist @@ -131,7 +134,7 @@ function($matches) use ($streamkey) { 'url' => $masterUrl, 'status_code' => $response->status(), 'response_body' => $response->body(), - 'user_id' => $user->id, + 'user_id' => $user?->id, 'streamkey' => $streamkey ?? null, ]); @@ -156,7 +159,7 @@ function($matches) use ($streamkey) { public function variant(Request $request, $variant) { // Extract stream name and quality from variant (e.g., "test-stream_fhd") - if (!preg_match('/^(.+)_(fhd|hd|sd)$/', $variant, $matches)) { + if (! preg_match('/^(.+)_(fhd|hd|sd)$/', $variant, $matches)) { return response('Invalid variant format', 400) ->header('Content-Type', 'text/plain'); } @@ -167,7 +170,7 @@ public function variant(Request $request, $variant) // Find the source $source = Source::where('slug', $streamSlug)->first(); - if (!$source) { + if (! $source) { return response('Stream not found', 404) ->header('Content-Type', 'text/plain'); } @@ -181,25 +184,25 @@ public function variant(Request $request, $variant) $systemStreamkey = config('stream.system_streamkey'); if ($systemStreamkey && $streamkey === $systemStreamkey) { // For system operations, create a minimal user object - $user = new User(); + $user = new User; $user->id = 0; $user->name = 'System'; $user->streamkey = $streamkey; } else { // Look up user by streamkey $user = User::where('streamkey', $streamkey)->first(); - if (!$user) { + if (! $user) { return response('Invalid streamkey', 401) ->header('Content-Type', 'text/plain'); } } } else { $user = Auth::user(); - if (!$user) { + if (! $user && config('auth.required')) { return response('Authentication required', 401) ->header('Content-Type', 'text/plain'); } - $streamkey = $user->streamkey; + $streamkey = $user?->streamkey; } $this->trackUserAccess($source, $user, $request); @@ -207,7 +210,7 @@ public function variant(Request $request, $variant) // Check for IP-based server override $server = $this->getServerForRequest($request, $user); - if (!$server || !$server->hostname) { + if (! $server || ! $server->hostname) { return response('No server available', 503) ->header('Content-Type', 'text/plain'); } @@ -216,8 +219,8 @@ public function variant(Request $request, $variant) $port = $server->port ?? 8080; // Build cache key based on variant, server, and streamkey - $cacheKey = "hls_variant:{$variant}:{$hostname}:{$port}:" . ($streamkey ?? 'auth'); - + $cacheKey = "hls_variant:{$variant}:{$hostname}:{$port}:".($streamkey ?? 'auth'); + // Try to get cached response $cachedResponse = Cache::get($cacheKey); if ($cachedResponse) { @@ -237,7 +240,7 @@ public function variant(Request $request, $variant) try { // For HTTPS, allow self-signed certificates in development - $httpClient = Http::timeout(3); + $httpClient = Http::timeout(3)->withHeaders($this->systemAuthHeaders()); if (str_starts_with($edgeUrl, 'https://')) { $httpClient = $httpClient->withOptions(['verify' => false]); } @@ -249,7 +252,7 @@ public function variant(Request $request, $variant) // Rewrite .ts segment URLs to use full edge server URL with streamkey $playlist = preg_replace_callback( '/^([^#\s]+\.ts)$/m', - function($matches) use ($hostname, $port, $streamkey) { + function ($matches) use ($hostname, $port, $streamkey) { $segment = $matches[1]; // Use HTTPS for port 443, HTTP for other ports if ($port == 443) { @@ -258,8 +261,9 @@ function($matches) use ($hostname, $port, $streamkey) { $url = "http://{$hostname}:{$port}/live/{$segment}"; } if ($streamkey) { - $url .= '?streamkey=' . $streamkey; + $url .= '?streamkey='.$streamkey; } + return $url; }, $playlist @@ -283,7 +287,7 @@ function($matches) use ($hostname, $port, $streamkey) { 'url' => $edgeUrl, 'status_code' => $response->status(), 'response_body' => $response->body(), - 'user_id' => $user->id, + 'user_id' => $user?->id, 'streamkey' => $streamkey ?? null, ]); @@ -303,13 +307,33 @@ function($matches) use ($hostname, $port, $streamkey) { } } + /** + * Identify this proxy to an edge server. + * + * Edge nginx authenticates .m3u8 as well as .ts now, so these internal + * fetches need a credential. It goes in a header rather than the query + * string so the URL stays byte-identical, which keeps the edge's playlist + * cache key shared across viewers and keeps the key out of edge access logs. + * njs recognises it locally, with no round trip back here. + * + * @return array + */ + private function systemAuthHeaders(): array + { + $systemStreamkey = config('stream.system_streamkey'); + + return $systemStreamkey ? ['X-Stream-Key' => $systemStreamkey] : []; + } + /** * Track user access to streams */ private function trackUserAccess($source, $user, $request) { - // Skip tracking for system user - if ($user->id === 0) { + // Skip tracking for the system user, and for signed-out viewers on an + // installation with optional login: SourceUser is keyed by user_id, so + // there is nothing to attribute a guest session to. + if (! $user || $user->id === 0) { return; } @@ -379,10 +403,20 @@ private function getServerForRequest(Request $request, $user) } // For system users, just return the first available edge server - if ($user->id === 0) { + if ($user && $user->id === 0) { return Server::getActiveEdges()->first(); } + // Signed-out viewers have no stored assignment to reuse, so they get + // the least loaded edge on every request instead. Same ordering as + // User::assignServerToUser, minus the persistence. + if (! $user) { + return Server::where('status', ServerStatusEnum::ACTIVE) + ->where('type', ServerTypeEnum::EDGE) + ->orderBy('viewer_count', 'asc') + ->first(); + } + return $user->getOrAssignServer($clientIp); } } diff --git a/app/Http/Controllers/Local/DebugController.php b/app/Http/Controllers/Local/DebugController.php new file mode 100644 index 0000000..b1599a8 --- /dev/null +++ b/app/Http/Controllers/Local/DebugController.php @@ -0,0 +1,146 @@ +isLocal()` and is additionally + * guarded by the LocalOnly middleware. + */ +class DebugController extends Controller +{ + /** + * Personas that can be spawned on demand, in the order they are shown. + */ + private const PERSONAS = [ + 'admin' => 'Admin', + 'moderator' => 'Moderator', + 'staff' => 'Staff', + 'supersponsor' => 'Super Sponsor', + 'sponsor' => 'Sponsor', + 'attendee' => 'Attendee', + ]; + + public function index() + { + $users = User::with('roles') + ->orderBy('id') + ->get() + ->map(fn (User $user) => [ + 'id' => $user->id, + 'name' => $user->name, + 'color' => $user->chat_color, + 'badges' => $user->chatBadges(), + 'roles' => $user->roles->pluck('slug')->all(), + 'is_test' => str_starts_with((string) $user->sub, 'debug|'), + ]); + + $current = Auth::user(); + + return Inertia::render('Debug/Users', [ + 'users' => $users, + 'current' => $current ? [ + 'id' => $current->id, + 'name' => $current->name, + 'badges' => $current->chatBadges(), + ] : null, + 'personas' => collect(self::PERSONAS) + ->map(fn (string $label, string $slug) => ['slug' => $slug, 'label' => $label]) + ->values(), + 'shows' => Show::whereNotNull('source_id') + ->whereIn('status', ['live', 'scheduled']) + ->orderByDesc('status') + ->limit(8) + ->get() + ->map(fn (Show $show) => [ + 'title' => $show->title, + 'status' => $show->status, + 'url' => route('show.view', $show), + 'chat_url' => route('show.chat', $show), + ]), + ]); + } + + /** + * Become an existing user. + */ + public function loginAs(User $user) + { + Auth::login($user); + request()->session()->regenerate(); + + return back()->with('status', "Now signed in as {$user->name}."); + } + + /** + * Create (or reuse) a throwaway user holding a single role. + */ + public function persona(Request $request) + { + $data = $request->validate([ + 'role' => ['required', 'string', 'in:'.implode(',', array_keys(self::PERSONAS))], + ]); + + $slug = $data['role']; + $role = Role::where('slug', $slug)->first(); + + if (! $role) { + return back()->with('status', "Role '{$slug}' does not exist in this database."); + } + + $user = User::create([ + 'sub' => 'debug|'.$slug.'|'.Str::lower(Str::random(6)), + 'name' => self::PERSONAS[$slug].' '.random_int(100, 999), + ]); + + $user->roles()->attach($role->id); + + MessagePresenter::forgetAuthor($user->id); + + Auth::login($user->fresh()); + $request->session()->regenerate(); + + return back()->with('status', "Created and signed in as {$user->name}."); + } + + /** + * Delete every generated persona. The account in use is signed out first. + */ + public function reset(Request $request) + { + $ids = User::where('sub', 'like', 'debug|%')->pluck('id'); + + if ($ids->isEmpty()) { + return back()->with('status', 'No test users to remove.'); + } + + if (in_array(Auth::id(), $ids->all(), true)) { + Auth::logout(); + $request->session()->regenerate(); + } + + User::whereIn('id', $ids)->delete(); + + return back()->with('status', "Removed {$ids->count()} test users."); + } + + public function logout(Request $request) + { + Auth::logout(); + $request->session()->regenerate(); + + return back()->with('status', 'Signed out.'); + } +} diff --git a/app/Http/Controllers/Manage/DashboardController.php b/app/Http/Controllers/Manage/DashboardController.php new file mode 100644 index 0000000..200dbd8 --- /dev/null +++ b/app/Http/Controllers/Manage/DashboardController.php @@ -0,0 +1,34 @@ + fn () => $overview->capacityCards(), + 'edgeServers' => fn () => $overview->edgeServerCards(), + 'viewers' => fn () => $overview->viewers(), + 'servers' => fn () => $overview->servers(), + 'alerts' => fn () => $overview->alerts(), + 'schedule' => fn () => $overview->schedule(self::SCHEDULE_HOURS), + 'scheduleHours' => self::SCHEDULE_HOURS, + ]); + } +} diff --git a/app/Http/Controllers/Manage/EmoteController.php b/app/Http/Controllers/Manage/EmoteController.php new file mode 100644 index 0000000..70d460b --- /dev/null +++ b/app/Http/Controllers/Manage/EmoteController.php @@ -0,0 +1,297 @@ +authorize('viewAny', Emote::class); + + $table = Table::make(Emote::query()->with('uploadedBy')) + ->name('emotes') + ->columns([ + Column::image('image', 'Emote'), + Column::copyable('name', 'Name')->searchable('name'), + Column::text('uploaded_by', 'Uploaded by'), + Column::badge('is_approved', 'Approved'), + Column::badge('is_global', 'Global'), + Column::number('usage_count', 'Usage')->sortable(), + Column::datetime('created_at', 'Uploaded')->sortable()->toggleable(), + ]) + ->filters([ + Filter::select('approval_status', 'Status') + ->options(['pending' => 'Pending approval', 'approved' => 'Approved']) + ->placeholder('All statuses') + ->apply(fn ($query, $value) => $query->where('is_approved', $value === 'approved')), + Filter::ternary('is_global', 'Global') + ->trueLabel('Global only') + ->falseLabel('Personal only') + ->placeholder('All emotes'), + ]) + ->defaultSort('created_at', 'desc') + ->rows(fn (Emote $emote) => $this->row($emote)) + ->recordUrl(fn (Emote $emote) => route('manage.emotes.edit', $emote)) + ->rowActions(fn (Emote $emote) => $this->rowActions($emote)) + ->bulkActions($this->bulkActions()) + ->pageActions($this->pageActions()); + + return inertia('Manage/Emotes/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Emote::class); + + return inertia('Manage/Emotes/Form', [ + 'emote' => null, + 'defaults' => [ + 'name' => '', + 's3_key' => '', + 'is_global' => true, + 'is_approved' => true, + ], + ]); + } + + public function store(EmoteRequest $request): RedirectResponse + { + $this->authorize('create', Emote::class); + + $emote = Emote::create($request->validated() + [ + 'uploaded_by_user_id' => $request->user()->id, + ]); + + // Uploaded from the panel by a moderator, so it is approved on the spot + // rather than queued behind whoever just approved it. + if ($request->boolean('is_approved')) { + $emote->approve($request->user()); + } + + Toast::flashSuccess('Emote created', "':{$emote->name}:' is ready to use."); + + return to_route('manage.emotes.edit', $emote); + } + + public function edit(Emote $emote): Response + { + $this->authorize('view', $emote); + + return inertia('Manage/Emotes/Form', [ + 'emote' => [ + 'id' => $emote->id, + 'name' => $emote->name, + 's3_key' => $emote->s3_key, + 'is_global' => (bool) $emote->is_global, + 'is_approved' => (bool) $emote->is_approved, + 'usage_count' => $emote->usage_count, + 'preview_url' => $emote->url, + 'uploaded_by' => $emote->uploadedBy?->name ?? '-', + 'approved_by' => $emote->approvedBy?->name ?? '-', + 'approved_at' => $emote->approved_at?->format('M j, Y H:i') ?? '-', + ], + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($emote), + ), + ]); + } + + public function update(EmoteRequest $request, Emote $emote): RedirectResponse + { + $this->authorize('update', $emote); + + $validated = $request->validated(); + $approve = $request->boolean('is_approved'); + unset($validated['is_approved']); + + $emote->update($validated); + + // Going through approve() rather than setting the flag keeps the approver + // and the timestamp in step with the row. + if ($approve && ! $emote->is_approved) { + $emote->approve($request->user()); + } + + Toast::flashSuccess('Emote updated'); + + return back(); + } + + public function destroy(Emote $emote): RedirectResponse + { + $this->authorize('delete', $emote); + + $name = $emote->name; + // reject() removes the stored image along with the row. + $emote->reject(); + + Toast::flashSuccess('Emote deleted', "':{$name}:' and its image have been removed."); + + return to_route('manage.emotes.index'); + } + + public function approve(Emote $emote): RedirectResponse + { + $this->authorize('approve', $emote); + + $emote->approve(request()->user()); + + Toast::flashSuccess('Emote approved', "':{$emote->name}:' is now usable in chat."); + + return back(); + } + + public function bulkApprove(Request $request): RedirectResponse + { + $this->authorize('create', Emote::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $emotes = Emote::whereIn('id', $validated['ids'])->where('is_approved', false)->get(); + $emotes->each(fn (Emote $emote) => $emote->approve($request->user())); + + Toast::flashSuccess('Emotes approved', $emotes->count().' approved.'); + + return back(); + } + + public function bulkDestroy(Request $request): RedirectResponse + { + $this->authorize('create', Emote::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $emotes = Emote::whereIn('id', $validated['ids'])->get(); + $emotes->each->reject(); + + Toast::flashSuccess('Emotes deleted', $emotes->count().' removed with their images.'); + + return back(); + } + + /** + * @return array + */ + private function row(Emote $emote): array + { + return [ + 'image' => $emote->url, + 'name' => ':'.$emote->name.':', + 'uploaded_by' => $emote->uploadedBy?->name ?? '-', + 'is_approved' => $emote->is_approved + ? Status::make('Approved', Status::OK) + : Status::make('Pending', Status::WARN), + 'is_global' => $emote->is_global + ? Status::make('Global', Status::INFO) + : Status::make('Personal', Status::IDLE), + 'usage_count' => $emote->usage_count, + 'created_at' => $emote->created_at?->format('M j, Y H:i'), + ]; + } + + /** + * @return array + */ + private function rowActions(Emote $emote): array + { + $user = request()->user(); + $actions = []; + + if (! $emote->is_approved && $user->can('approve', $emote)) { + $actions[] = Action::post('approve', 'Approve', route('manage.emotes.approve', $emote)) + ->icon('check-circle') + ->tone(Status::OK); + } + + $actions[] = Action::link('edit', 'Edit', route('manage.emotes.edit', $emote))->icon('pencil'); + + if ($user->can('delete', $emote)) { + $actions[] = $this->deleteAction($emote); + } + + return $actions; + } + + /** + * @return array + */ + private function recordActions(Emote $emote): array + { + return $this->rowActions($emote); + } + + private function deleteAction(Emote $emote): Action + { + return Action::delete('delete', 'Delete', route('manage.emotes.destroy', $emote)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm( + 'Delete emote', + "':{$emote->name}:' and its uploaded image are removed for good.", + 'Delete', + ); + } + + /** + * @return array + */ + private function bulkActions(): array + { + if (! request()->user()->can('create', Emote::class)) { + return []; + } + + return [ + Action::post('bulk_approve', 'Approve', route('manage.emotes.bulk.approve')) + ->icon('check-circle') + ->tone(Status::OK) + ->confirm('Approve selected emotes', 'Already approved emotes are skipped.', 'Approve'), + Action::delete('bulk_delete', 'Delete', route('manage.emotes.bulk.destroy')) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm('Delete selected emotes', 'Their uploaded images go too.', 'Delete'), + ]; + } + + /** + * @return array + */ + private function pageActions(): array + { + if (! request()->user()->can('create', Emote::class)) { + return []; + } + + return [ + Action::link('create', 'New Emote', route('manage.emotes.create'))->icon('plus'), + ]; + } +} diff --git a/app/Http/Controllers/Manage/PretalxConnectionController.php b/app/Http/Controllers/Manage/PretalxConnectionController.php new file mode 100644 index 0000000..d1b7b7b --- /dev/null +++ b/app/Http/Controllers/Manage/PretalxConnectionController.php @@ -0,0 +1,92 @@ +user()?->hasPermission('admin.access'), 403); + + $validated = $request->validate([ + 'url' => ['required', 'url', 'max:2048'], + 'event' => ['nullable', 'string', 'max:255'], + 'token' => ['nullable', 'string', 'max:255'], + ]); + + $token = trim((string) ($validated['token'] ?? '')); + + if ($token === Settings::MASK_SECRET || $token === Settings::CLEAR_SECRET) { + $token = ''; + } + + $client = $pretalx->using([ + 'pretalx_url' => $validated['url'], + 'pretalx_event' => $validated['event'] ?? null, + // Empty falls through to the stored token inside the service. + 'pretalx_token' => $token, + ]); + + try { + $probe = $client->probe(); + } catch (RuntimeException $e) { + Toast::flashDanger('Could not reach pretalx', $e->getMessage()); + + return back(); + } catch (Throwable $e) { + Toast::flashDanger('Could not reach pretalx', $e->getMessage()); + + return back(); + } + + $pretalx->rememberEvents($validated['url'], $probe['events']); + + // The URL being tested is usually not saved yet, so the settings page is told + // which instance to read the remembered list from on the way back. + session()->flash('pretalx.tested_url', $validated['url']); + + $this->report($probe); + + return back(); + } + + /** + * @param array $probe + */ + private function report(array $probe): void + { + $seen = count($probe['events']).' '.str('event')->plural(count($probe['events'])).' visible'; + + if ($probe['warning'] !== null) { + Toast::flashWarning('Connected to pretalx', $probe['warning'].' '.$seen.'.'); + + return; + } + + Toast::flashSuccess( + 'Connected to pretalx', + $probe['eventName'].': '.$probe['slots'].' '.str('session')->plural($probe['slots'] ?? 0). + ' in the published schedule. '.$seen.'.', + ); + } +} diff --git a/app/Http/Controllers/Manage/PretalxImportController.php b/app/Http/Controllers/Manage/PretalxImportController.php new file mode 100644 index 0000000..8bc2487 --- /dev/null +++ b/app/Http/Controllers/Manage/PretalxImportController.php @@ -0,0 +1,219 @@ +authorize('create', Show::class); + + $configured = $pretalx->isConfigured(); + $error = null; + $rooms = []; + $slots = []; + + if ($configured) { + try { + $rooms = $pretalx->rooms(); + $slots = $pretalx->slots(); + } catch (RuntimeException $e) { + $error = $e->getMessage(); + $rooms = []; + $slots = []; + } + } + + $event = $pretalx->event(); + $mapping = $event !== null ? PretalxRoomSource::mapFor($event) : []; + $imported = Show::whereNotNull('pretalx_slot_id') + ->pluck('id', 'pretalx_slot_id'); + + // Rooms pretalx knows about, plus any room a slot names that is not in the room + // list (a private room the token cannot see, for instance). + $roomNames = collect($rooms)->pluck('name', 'id'); + + foreach ($slots as $slot) { + if (! $roomNames->has($slot['room_id'])) { + $roomNames->put($slot['room_id'], 'Room '.$slot['room_id']); + $rooms[] = ['id' => $slot['room_id'], 'name' => 'Room '.$slot['room_id']]; + } + } + + $sessionCounts = collect($slots)->countBy('room_id'); + + /* + * A con has far more rooms than channels - 46 of them here against a handful of + * sources - and most hold nothing that is ever streamed. Only rooms with sessions + * are offered, plus any room already mapped, so the mapping list stays readable. + */ + $rooms = array_values(array_filter( + $rooms, + fn (array $room) => $sessionCounts->has($room['id']) || isset($mapping[$room['id']]), + )); + + return inertia('Manage/Shows/Import', [ + 'configured' => $configured, + 'event' => $event, + 'instance' => $pretalx->baseUrl(), + 'error' => $error, + 'settingsUrl' => route('manage.settings'), + 'sources' => Source::ordered() + ->get(['id', 'name']) + ->map(fn (Source $source) => ['value' => $source->id, 'label' => $source->name]) + ->all(), + 'rooms' => array_map(fn (array $room) => [ + 'id' => $room['id'], + 'name' => $room['name'], + 'source_id' => $mapping[$room['id']] ?? null, + 'sessions' => $sessionCounts->get($room['id'], 0), + ], $rooms), + 'slots' => array_map(fn (array $slot) => [ + 'id' => $slot['id'], + 'title' => $slot['title'], + 'speakers' => $slot['speakers'], + 'room_id' => $slot['room_id'], + 'start' => $slot['start']->toIso8601String(), + 'end' => $slot['end']->toIso8601String(), + 'day' => $slot['start']->format('D j M'), + 'time' => $slot['start']->format('H:i').' - '.$slot['end']->format('H:i'), + 'past' => $slot['end']->isPast(), + 'showUrl' => isset($imported[$slot['id']]) + ? route('manage.shows.edit', $imported[$slot['id']]) + : null, + ], $slots), + ]); + } + + /** + * Save the room mapping and import the ticked slots. Both in one post: the mapping is + * what decides where a slot can go, so a selection is only meaningful together with it. + */ + public function store(Request $request, PretalxService $pretalx, PretalxImporter $importer): RedirectResponse + { + $this->authorize('create', Show::class); + + $validated = $request->validate([ + 'rooms' => ['array'], + 'rooms.*.id' => ['required', 'integer'], + 'rooms.*.name' => ['nullable', 'string', 'max:255'], + 'rooms.*.source_id' => ['nullable', 'integer', Rule::exists('sources', 'id')], + 'slots' => ['array'], + 'slots.*' => ['string', 'max:255'], + ]); + + $event = $pretalx->event(); + + if ($event === null) { + Toast::flashDanger('Pretalx is not configured', 'Set the instance URL and event slug in Settings first.'); + + return back(); + } + + $this->saveRooms($validated['rooms'] ?? [], $event); + + $slots = $validated['slots'] ?? []; + + if ($slots === []) { + Toast::flashSuccess('Room mapping saved', 'No sessions were selected, so nothing was imported.'); + + return back(); + } + + try { + $result = $importer->import($slots, $event); + } catch (RuntimeException $e) { + Toast::flashDanger('Import failed', $e->getMessage()); + + return back(); + } + + $this->reportImport($result); + + return back(); + } + + /** + * Drop the cached schedule, for when pretalx published a new version mid-event. + */ + public function refresh(PretalxService $pretalx): RedirectResponse + { + $this->authorize('create', Show::class); + + $pretalx->forget(); + + Toast::flashSuccess('Schedule reloaded', 'The next read comes straight from pretalx.'); + + return back(); + } + + /** + * @param array> $rooms + */ + private function saveRooms(array $rooms, string $event): void + { + foreach ($rooms as $room) { + PretalxRoomSource::updateOrCreate( + ['event_slug' => $event, 'room_id' => (int) $room['id']], + ['room_name' => $room['name'] ?? null, 'source_id' => $room['source_id'] ?? null], + ); + } + } + + /** + * @param array{imported: int, existing: int, unmapped: int, missing: int} $result + */ + private function reportImport(array $result): void + { + $skipped = []; + + if ($result['existing'] > 0) { + $skipped[] = $result['existing'].' already imported'; + } + + if ($result['unmapped'] > 0) { + $skipped[] = $result['unmapped'].' in a room with no channel'; + } + + if ($result['missing'] > 0) { + $skipped[] = $result['missing'].' no longer in the pretalx schedule'; + } + + $detail = $skipped === [] ? null : 'Skipped: '.implode(', ', $skipped).'.'; + + if ($result['imported'] === 0) { + Toast::flashDanger('Nothing imported', $detail ?? 'None of the selected sessions could be imported.'); + + return; + } + + Toast::flashSuccess( + $result['imported'].' '.str('session')->plural($result['imported']).' imported', + trim(('They are scheduled and can be edited like any other show. '.($detail ?? ''))), + ); + } +} diff --git a/app/Http/Controllers/Manage/RecordingController.php b/app/Http/Controllers/Manage/RecordingController.php new file mode 100644 index 0000000..5bcefbd --- /dev/null +++ b/app/Http/Controllers/Manage/RecordingController.php @@ -0,0 +1,564 @@ +authorize('viewAny', Recording::class); + + $table = Table::make(Recording::query()->with('show')) + ->name('recordings') + ->columns([ + Column::image('thumbnail', 'Thumbnail'), + Column::text('title', 'Title')->searchable()->sortable(), + Column::copyable('slug', 'Slug')->searchable()->toggleable(hiddenByDefault: true), + Column::text('show', 'Show')->toggleable(), + Column::datetime('date', 'Date')->sortable(), + Column::duration('duration', 'Duration'), + Column::number('views', 'Views')->sortable(), + Column::badge('is_published', 'Published'), + Column::badge('access', 'Access')->toggleable(hiddenByDefault: true), + ]) + ->filters([ + Filter::ternary('is_published', 'Published') + ->trueLabel('Published only') + ->falseLabel('Unpublished only') + ->placeholder('All recordings'), + ]) + ->defaultSort('date', 'desc') + ->rows(fn (Recording $recording) => $this->row($recording)) + ->recordUrl(fn (Recording $recording) => route('manage.recordings.edit', $recording)) + ->rowActions(fn (Recording $recording) => $this->rowActions($recording)) + ->bulkActions($this->bulkActions()) + ->pageActions($this->pageActions()); + + return inertia('Manage/Recordings/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Recording::class); + + return inertia('Manage/Recordings/Form', [ + 'recording' => null, + 'options' => [ + 'shows' => $this->showOptions(), + 'roles' => $this->roleOptions(), + ], + 'defaults' => [ + 'show_id' => '', + 'title' => '', + 'slug' => '', + 'description' => '', + 'date' => now()->format('Y-m-d\TH:i'), + 'duration' => '', + 'm3u8_url' => '', + 'thumbnail_path' => '', + 'is_published' => true, + 'required_roles' => [], + ], + ]); + } + + public function store(RecordingRequest $request): RedirectResponse + { + $this->authorize('create', Recording::class); + + $recording = Recording::create($request->validated()); + + // Fills in whatever was left blank: duration from the playlist, and a + // thumbnail from the first frame when none was uploaded. + ProcessRecordingJob::dispatch($recording); + + Toast::flashSuccess('Recording created', "'{$recording->title}' is being processed."); + + return to_route('manage.recordings.edit', $recording); + } + + public function edit(Recording $recording): Response + { + $this->authorize('view', $recording); + + return inertia('Manage/Recordings/Form', [ + 'recording' => [ + 'id' => $recording->id, + 'show_id' => $recording->show_id, + 'title' => $recording->title, + 'slug' => $recording->slug, + 'description' => $recording->description, + 'date' => $recording->date?->format('Y-m-d\TH:i'), + 'duration' => $recording->duration, + 'm3u8_url' => $recording->m3u8_url, + 'thumbnail_path' => $recording->thumbnail_path, + 'thumbnail_url' => $recording->thumbnail_url, + 'thumbnail_error' => $recording->thumbnail_capture_error, + 'is_published' => (bool) $recording->is_published, + 'required_roles' => $recording->required_roles ?? [], + 'views' => $recording->views, + // A cut carries these; a recording registered from outside does not, and + // the form uses their presence to decide which fields it owns. + 'starts_at' => $recording->starts_at?->toIso8601String(), + 'ends_at' => $recording->ends_at?->toIso8601String(), + 'status' => $recording->status, + 'build_error' => $recording->build_error, + 'segment_count' => $recording->segment_count, + 'playlist_built_at' => $recording->playlist_built_at?->diffForHumans(), + ], + // Bounds the scrubber. Without it a cut can run past the end of the archive + // (segments not uploaded yet) or before its start (already expired), and the + // resulting empty range reads as data loss rather than a range mistake. + 'available' => $this->archiveBounds($recording), + 'options' => [ + 'shows' => $this->showOptions(), + 'roles' => $this->roleOptions(), + ], + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($recording), + ), + ]); + } + + public function update(RecordingRequest $request, Recording $recording): RedirectResponse + { + $this->authorize('update', $recording); + + $recording->update($request->validated()); + + // A cut is derived state: the archive is truth and the playlist is generated + // from the markers, so every save rebuilds rather than mutating media. That is + // what makes trimming repeatable and non-destructive, months after the fact. + if ($recording->hasCut()) { + if (! app(ArchivePlaylistService::class)->rebuild($recording->fresh())) { + Toast::flashError('Playlist not built', $recording->fresh()->build_error); + + return back(); + } + } + + Toast::flashSuccess('Recording updated'); + + return back(); + } + + /** + * Cut a draft recording from a show, copying its title and markers. + * + * Deliberately does not require the show to have ended. The archive is a continuous + * per-source timeline, so a range can be cut, published and re-cut while the show is + * still running; the main source stays online for the whole event and never produces + * an end to wait for. What it does require is an explicit end marker, since there is + * no natural one. + */ + public function storeFromShow(Request $request, Show $show): RedirectResponse + { + $this->authorize('create', Recording::class); + + if (! $show->source) { + Toast::flashError('No source', 'That show has no source, so there is no archive to cut from.'); + + return back(); + } + + if (! $show->actual_start) { + Toast::flashError('Not started', 'That show has not gone live yet, so there is nothing to cut.'); + + return back(); + } + + $endsAt = $request->filled('ends_at') + ? CarbonImmutable::parse($request->string('ends_at')->toString()) + : $show->actual_end; + + if (! $endsAt) { + Toast::flashError( + 'End marker needed', + 'That show is still live. Set an end marker to cut a recording from it.' + ); + + return back(); + } + + $recording = Recording::create([ + 'show_id' => $show->id, + 'source_id' => $show->source->id, + 'title' => $show->title, + 'slug' => $this->uniqueSlug($show->title), + 'description' => $show->description, + 'date' => $show->actual_start, + 'starts_at' => $show->actual_start, + 'ends_at' => $endsAt, + 'archive_prefix' => "archive/{$show->source->slug}", + 'status' => 'draft', + 'is_published' => false, + ]); + + // Building reads a couple of hour indexes and writes four playlists, so it runs + // inline and the operator lands on a finished recording rather than a spinner. + $built = app(ArchivePlaylistService::class)->rebuild($recording); + + if (! $built) { + Toast::flashError('Draft created, playlist not built', $recording->fresh()->build_error); + } else { + Toast::flashSuccess('Recording drafted', "Cut from '{$show->title}'. Adjust the markers before publishing."); + } + + return to_route('manage.recordings.edit', $recording); + } + + /** + * Rebuild without changing the markers. + * + * Useful when the archive has caught up since the last build: the uploader runs a few + * seconds behind live, so a cut whose end was at the live edge will have been short + * by a segment or two. + */ + public function rebuild(Recording $recording): RedirectResponse + { + $this->authorize('update', $recording); + + if (! $recording->hasCut()) { + Toast::flashError('Nothing to rebuild', 'This recording has no cut markers.'); + + return back(); + } + + if (! app(ArchivePlaylistService::class)->rebuild($recording)) { + Toast::flashError('Playlist not built', $recording->fresh()->build_error); + + return back(); + } + + $fresh = $recording->fresh(); + Toast::flashSuccess('Playlist rebuilt', "{$fresh->segment_count} segments, {$fresh->formatted_duration}."); + + return back(); + } + + /** + * Playlist for an arbitrary window of the source archive, for the trim editor. + * + * Separate from the recording's own playlist because the editor has to show material + * outside the current markers; that is how an operator finds where the show actually + * starts. The window is bounded so a careless request cannot ask for seven days of a + * continuously running source in one playlist. + */ + public function preview(Request $request, Recording $recording) + { + $this->authorize('view', $recording); + + $source = $recording->archiveSourceSlug(); + abort_unless($source, 404); + + $rendition = $request->string('rendition', 'hd')->toString(); + $service = app(ArchivePlaylistService::class); + + abort_unless(in_array($rendition, $service->renditions(), true), 404); + + $from = CarbonImmutable::parse($request->string('from')->toString()); + $to = CarbonImmutable::parse($request->string('to')->toString()); + + if ($to->diffInHours($from) > 4) { + $to = $from->addHours(4); + } + + try { + $body = $service->renderRange($source, $from, $to, $rendition); + } catch (\Throwable $e) { + abort(410, $e->getMessage()); + } + + return response($body, 200, [ + 'Content-Type' => 'application/vnd.apple.mpegurl', + // Segment URLs inside are signed and time limited. + 'Cache-Control' => 'private, no-store', + ]); + } + + /** + * How much of the source's archive is still cuttable. + * + * Bounded at both ends for different reasons: the uploader runs a few seconds behind + * live, and old hours eventually expire out of the archive. + */ + protected function archiveBounds(Recording $recording): array + { + $source = $recording->archiveSourceSlug(); + + if (! $source) { + return ['from' => null, 'to' => null]; + } + + $range = app(ArchivePlaylistService::class)->availableRange($source); + + return [ + 'from' => $range['from']?->toIso8601String(), + 'to' => $range['to']?->toIso8601String(), + ]; + } + + protected function uniqueSlug(string $title): string + { + $base = \Illuminate\Support\Str::slug($title); + $slug = $base; + $i = 1; + + while (Recording::where('slug', $slug)->exists()) { + $slug = $base.'-'.$i++; + } + + return $slug; + } + + public function destroy(Recording $recording): RedirectResponse + { + $this->authorize('delete', $recording); + + $title = $recording->title; + $recording->delete(); + + Toast::flashSuccess('Recording deleted', "'{$title}' has been removed."); + + return to_route('manage.recordings.index'); + } + + /** + * Clearing the path is what makes the job capture a new frame rather than skip + * a recording that already has one. + */ + public function regenerateThumbnail(Recording $recording): RedirectResponse + { + $this->authorize('update', $recording); + + $recording->update([ + 'thumbnail_path' => null, + 'thumbnail_capture_error' => null, + ]); + + ProcessRecordingJob::dispatch($recording); + + Toast::flashSuccess( + 'Thumbnail regeneration started', + 'It is captured in the background; reload in a moment.', + ); + + return back(); + } + + public function bulkRegenerateThumbnails(Request $request): RedirectResponse + { + $this->authorize('create', Recording::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $recordings = Recording::whereIn('id', $validated['ids']) + ->whereNotNull('m3u8_url') + ->get(); + + foreach ($recordings as $recording) { + $recording->update(['thumbnail_path' => null, 'thumbnail_capture_error' => null]); + ProcessRecordingJob::dispatch($recording); + } + + Toast::flashSuccess( + 'Thumbnail regeneration started', + $recordings->count().' queued.', + ); + + return back(); + } + + public function bulkDestroy(Request $request): RedirectResponse + { + $this->authorize('create', Recording::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $recordings = Recording::whereIn('id', $validated['ids'])->get(); + $recordings->each->delete(); + + Toast::flashSuccess('Recordings deleted', $recordings->count().' removed.'); + + return back(); + } + + /** + * @return array + */ + private function row(Recording $recording): array + { + return [ + 'thumbnail' => $recording->thumbnail_url, + 'title' => $recording->title, + 'slug' => $recording->slug, + 'show' => $recording->show?->title ?? '-', + 'date' => $recording->date?->format('M j, Y H:i'), + 'duration' => $recording->duration, + 'views' => $recording->views, + 'is_published' => $recording->is_published + ? Status::make('Published', Status::OK) + : Status::make('Draft', Status::IDLE), + 'access' => $recording->hasAccessRestriction() + ? Status::make('Restricted', Status::WARN) + : Status::make('Public', Status::OK), + ]; + } + + /** + * @return array + */ + private function rowActions(Recording $recording): array + { + $actions = [ + Action::link('edit', 'Edit', route('manage.recordings.edit', $recording))->icon('pencil'), + ]; + + if (request()->user()->can('update', $recording)) { + $actions[] = $this->deleteAction($recording); + } + + return $actions; + } + + /** + * @return array + */ + private function recordActions(Recording $recording): array + { + $user = request()->user(); + $actions = []; + + if ($user->can('update', $recording)) { + $actions[] = Action::post( + 'regenerate_thumbnail', + 'Regenerate Thumbnail', + route('manage.recordings.thumbnail', $recording), + ) + ->icon('image') + ->tone(Status::WARN) + ->disabled($recording->m3u8_url ? null : 'There is no playlist to capture from.') + ->confirm( + 'Regenerate thumbnail', + 'A new frame is captured from the video and replaces the current thumbnail.', + 'Regenerate', + ); + + $actions[] = $this->deleteAction($recording); + } + + return $actions; + } + + private function deleteAction(Recording $recording): Action + { + return Action::delete('delete', 'Delete', route('manage.recordings.destroy', $recording)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm('Delete recording', "'{$recording->title}' will no longer be watchable.", 'Delete'); + } + + /** + * @return array + */ + private function bulkActions(): array + { + if (! request()->user()->can('create', Recording::class)) { + return []; + } + + return [ + Action::post('bulk_thumbnails', 'Regenerate Thumbnails', route('manage.recordings.bulk.thumbnail')) + ->icon('image') + ->tone(Status::WARN) + ->confirm( + 'Regenerate thumbnails', + 'Recordings without a playlist are skipped.', + 'Regenerate', + ), + Action::delete('bulk_delete', 'Delete', route('manage.recordings.bulk.destroy')) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm('Delete selected recordings', 'This cannot be undone.', 'Delete'), + ]; + } + + /** + * @return array + */ + private function pageActions(): array + { + if (! request()->user()->can('create', Recording::class)) { + return []; + } + + return [ + Action::link('create', 'New Recording', route('manage.recordings.create'))->icon('plus'), + ]; + } + + /** + * Shows a recording can be attached to, newest first: a recording is almost + * always of something that just ended. + * + * @return array + */ + private function showOptions(): array + { + $options = [['value' => '', 'label' => 'Not linked to a show']]; + + foreach (Show::with('source')->orderByDesc('scheduled_start')->limit(200)->get() as $show) { + $options[] = [ + 'value' => $show->id, + 'label' => $show->title.($show->source ? ' ('.$show->source->name.')' : ''), + ]; + } + + return $options; + } + + /** + * @return array + */ + private function roleOptions(): array + { + return Role::orderByDesc('priority') + ->get() + ->map(fn (Role $role) => ['value' => $role->slug, 'label' => $role->name]) + ->all(); + } +} diff --git a/app/Http/Controllers/Manage/RoleController.php b/app/Http/Controllers/Manage/RoleController.php new file mode 100644 index 0000000..8fce550 --- /dev/null +++ b/app/Http/Controllers/Manage/RoleController.php @@ -0,0 +1,350 @@ + 'Admin access — full run of /manage', + 'filament.access' => 'Legacy admin access — still honoured on existing rows', + 'stream.manage' => 'Manage sources, shows, servers and recordings', + 'user.manage' => 'Manage users and roles', + 'chat.moderate' => 'Moderate chat', + 'chat.delete' => 'Delete chat messages', + 'chat.timeout' => 'Time users out', + 'chat.slowmode' => 'Toggle slow mode', + ]; + + public function index(Request $request): Response + { + $this->authorize('viewAny', Role::class); + + $table = Table::make(Role::query()->withCount('users')) + ->name('roles') + ->columns([ + Column::text('name', 'Name')->searchable()->sortable(), + Column::copyable('slug', 'Slug')->searchable()->sortable(), + Column::copyable('external_id', 'External ID')->searchable()->fallback('—'), + Column::color('chat_color', 'Chat colour'), + Column::number('priority', 'Priority')->sortable(), + Column::badge('sync', 'Login sync'), + Column::badge('is_visible', 'Chat badge'), + Column::number('users_count', 'Users')->sortable('users_count'), + Column::datetime('created_at', 'Created')->sortable()->toggleable(hiddenByDefault: true), + ]) + ->filters([ + Filter::ternary('synced', 'Login assignment') + ->trueLabel('Login-synced only') + ->falseLabel('Manually assigned only') + ->placeholder('All roles') + ->apply(fn ($query, $value) => $value === '1' + ? $query->loginAssigned() + : $query->manuallyAssigned()), + Filter::ternary('is_visible', 'Chat visibility') + ->trueLabel('Visible only') + ->falseLabel('Hidden only') + ->placeholder('All roles'), + ]) + ->defaultSort('priority', 'desc') + ->rows(fn (Role $role) => $this->row($role)) + ->recordUrl(fn (Role $role) => route('manage.roles.edit', $role)) + ->rowActions(fn (Role $role) => $this->rowActions($role)) + ->pageActions($this->pageActions()); + + return inertia('Manage/Roles/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Role::class); + + return inertia('Manage/Roles/Form', [ + 'role' => null, + 'options' => ['permissions' => $this->permissionOptions()], + 'defaults' => [ + 'name' => '', + 'slug' => '', + 'external_id' => '', + 'description' => '', + 'chat_color' => '#808080', + 'priority' => 0, + 'is_visible' => true, + 'permissions' => [], + ], + ]); + } + + public function store(RoleRequest $request): RedirectResponse + { + $this->authorize('create', Role::class); + + $role = Role::create($request->validated()); + + Toast::flashSuccess('Role created', "'{$role->name}' is ready to assign."); + + return to_route('manage.roles.index'); + } + + public function edit(Role $role): Response + { + $this->authorize('view', $role); + + return inertia('Manage/Roles/Form', [ + 'role' => [ + 'id' => $role->id, + 'name' => $role->name, + 'slug' => $role->slug, + 'external_id' => $role->external_id, + 'description' => $role->description, + 'chat_color' => $role->chat_color, + 'priority' => $role->priority, + 'is_visible' => (bool) $role->is_visible, + 'permissions' => $role->permissions ?? [], + 'users_count' => $role->users()->count(), + ], + 'options' => ['permissions' => $this->permissionOptions()], + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + request()->user()->can('update', $role) ? [$this->deleteAction($role)] : [], + ), + 'members' => $role->users() + ->orderBy('name') + ->limit(50) + ->get() + ->map(fn ($user) => [ + 'id' => $user->id, + 'name' => $user->name, + 'url' => route('manage.users.edit', $user), + ]) + ->all(), + ]); + } + + public function update(RoleRequest $request, Role $role): RedirectResponse + { + $this->authorize('update', $role); + + $role->update($request->validated()); + + Toast::flashSuccess('Role updated'); + + return to_route('manage.roles.index'); + } + + public function destroy(Role $role): RedirectResponse + { + // RolePolicy refuses while the role still has members. + if (! request()->user()->can('delete', $role)) { + Toast::flashDanger('Cannot delete role', 'This role still has members. Remove them first.'); + + return back(); + } + + $name = $role->name; + $role->delete(); + + Toast::flashSuccess('Role deleted', "'{$name}' has been removed."); + + return to_route('manage.roles.index'); + } + + /** + * Bootstrap for a fresh install: without at least one role carrying + * `admin.access` nobody can reach /manage at all. + */ + public function seedDefaults(): RedirectResponse + { + $this->authorize('create', Role::class); + + if (Role::query()->exists()) { + Toast::flashDanger('Roles already exist', 'The defaults are only offered on an empty install.'); + + return back(); + } + + foreach ($this->defaultRoles() as $role) { + Role::create($role); + } + + Toast::flashSuccess('Default roles created'); + + return back(); + } + + /** + * @return array + */ + private function row(Role $role): array + { + return [ + 'name' => $role->name, + 'slug' => $role->slug, + 'chat_color' => $role->chat_color, + 'priority' => $role->priority, + 'external_id' => $role->external_id, + 'sync' => $role->external_id + ? Status::make('Auto-synced', Status::OK) + : Status::make('Manual', Status::IDLE), + 'is_visible' => $role->is_visible + ? Status::make('Shown', Status::OK) + : Status::make('Hidden', Status::IDLE), + 'users_count' => $role->users_count, + 'created_at' => $role->created_at?->format('M j, Y H:i'), + ]; + } + + /** + * @return array + */ + private function rowActions(Role $role): array + { + $actions = [ + Action::link('edit', 'Edit', route('manage.roles.edit', $role))->icon('pencil'), + ]; + + if (request()->user()->can('update', $role)) { + $actions[] = $this->deleteAction($role); + } + + return $actions; + } + + private function deleteAction(Role $role): Action + { + $members = $role->users()->count(); + + return Action::delete('delete', 'Delete', route('manage.roles.destroy', $role)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->disabled($members > 0 ? "This role still has {$members} member(s)." : null) + ->confirm('Delete role', "'{$role->name}' will no longer grant anything.", 'Delete'); + } + + /** + * @return array + */ + private function pageActions(): array + { + if (! request()->user()->can('create', Role::class)) { + return []; + } + + $actions = [ + Action::link('create', 'New Role', route('manage.roles.create'))->icon('plus'), + ]; + + if (! Role::query()->exists()) { + $actions[] = Action::post('seed', 'Create Default Roles', route('manage.roles.seed')) + ->icon('sparkles') + ->confirm( + 'Create default roles', + 'Creates Admin, Moderator, Super Sponsor, Sponsor and Attendee.', + 'Create', + ); + } + + return $actions; + } + + /** + * @return array + */ + private function permissionOptions(): array + { + $options = []; + + foreach (self::PERMISSIONS as $value => $label) { + $options[] = ['value' => $value, 'label' => $label]; + } + + return $options; + } + + /** + * Only the tiers whose identifier is predictable get one; admin and + * moderator map to a group ID this installation has to supply, so they start + * manual and someone fills the identifier in. + * + * @return array> + */ + private function defaultRoles(): array + { + return [ + [ + 'name' => 'Admin', + 'slug' => 'admin', + 'description' => 'Full system administrator', + 'chat_color' => '#FF0000', + 'priority' => 100, + 'is_visible' => true, + 'permissions' => ['admin.access', 'stream.manage', 'user.manage', 'chat.moderate'], + ], + [ + 'name' => 'Moderator', + 'slug' => 'moderator', + 'description' => 'Chat and stream moderator', + 'chat_color' => '#00FF00', + 'priority' => 90, + 'is_visible' => true, + 'permissions' => ['chat.moderate', 'chat.delete', 'chat.timeout', 'chat.slowmode'], + ], + [ + 'name' => 'Super Sponsor', + 'slug' => 'super-sponsor', + 'external_id' => 'supersponsor', + 'description' => 'Super sponsor with a special chat colour', + 'chat_color' => '#FFD700', + 'priority' => 50, + 'is_visible' => true, + 'permissions' => [], + ], + [ + 'name' => 'Sponsor', + 'slug' => 'sponsor', + 'external_id' => 'sponsor', + 'description' => 'Sponsor with a chat colour', + 'chat_color' => '#C0C0C0', + 'priority' => 40, + 'is_visible' => true, + 'permissions' => [], + ], + [ + 'name' => 'Attendee', + 'slug' => 'attendee', + 'external_id' => 'attendee', + 'description' => 'Regular attendee', + 'chat_color' => '#808080', + 'priority' => 10, + 'is_visible' => false, + 'permissions' => [], + ], + ]; + } +} diff --git a/app/Http/Controllers/Manage/ServerController.php b/app/Http/Controllers/Manage/ServerController.php new file mode 100644 index 0000000..1fed39f --- /dev/null +++ b/app/Http/Controllers/Manage/ServerController.php @@ -0,0 +1,346 @@ +value, + ServerStatusEnum::PROVISIONING->value, + ServerStatusEnum::DEPROVISIONING->value, + ServerStatusEnum::ERROR->value, + ]; + + /** + * Deleted servers are hidden by default, as they were in Filament. There it was a hard + * query scope, which made the "Deleted" choice in the status filter dead - selecting it + * could only ever return nothing. Here the same default is expressed as the status + * filter's default value instead, so the option actually works. + */ + public function index(Request $request): Response + { + $this->authorize('viewAny', Server::class); + + $table = Table::make(Server::query()) + ->name('servers') + ->columns([ + Column::text('hetzner_id', 'Server ID')->searchable()->fallback('-'), + Column::badge('type', 'Type'), + Column::copyable('hostname', 'Hostname')->searchable()->sortable(), + Column::copyable('ip', 'IP'), + Column::number('port', 'Port')->sortable(), + Column::badge('status', 'Status'), + Column::number('viewer_count', 'Viewers')->sortable(), + Column::icon('heartbeat', 'Heartbeat'), + Column::badge('health_status', 'Health'), + Column::number('max_clients', 'Max clients')->sortable(), + ]) + ->filters([ + Filter::select('status', 'Status') + ->options([ + ServerStatusEnum::ACTIVE->value => 'Active', + ServerStatusEnum::PROVISIONING->value => 'Provisioning', + ServerStatusEnum::DEPROVISIONING->value => 'Deprovisioning', + ServerStatusEnum::DELETED->value => 'Deleted', + ServerStatusEnum::ERROR->value => 'Error', + ]) + ->multiple() + ->default(self::VISIBLE_STATUSES) + ->apply(fn (Builder $query, array $value) => $query->whereIn('status', $value)), + Filter::select('type', 'Type') + ->options([ + ServerTypeEnum::ORIGIN->value => 'Origin', + ServerTypeEnum::EDGE->value => 'Edge', + ]), + ]) + ->defaultSort('created_at', 'desc') + ->rows(fn (Server $server) => $this->row($server)) + ->recordUrl(fn (Server $server) => route('manage.servers.edit', $server)) + ->rowActions(fn (Server $server) => $this->rowActions($server)) + ->pageActions($this->pageActions()); + + return inertia('Manage/Servers/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Server::class); + + return inertia('Manage/Servers/Form', [ + 'server' => null, + 'options' => $this->formOptions(), + 'defaults' => [ + 'hostname' => '', + 'ip' => '', + 'port' => 8080, + 'type' => ServerTypeEnum::EDGE->value, + 'status' => ServerStatusEnum::ACTIVE->value, + 'shared_secret' => Str::random(40), + 'max_clients' => 100, + 'hetzner_id' => '', + ], + ]); + } + + public function store(ServerRequest $request): RedirectResponse + { + $this->authorize('create', Server::class); + + $server = Server::create($request->serverData()); + + Toast::flashSuccess('Server created', "'{$server->hostname}' is now managed here."); + + return to_route('manage.servers.edit', $server); + } + + public function edit(Server $server): Response + { + $this->authorize('view', $server); + + return inertia('Manage/Servers/Form', [ + 'server' => [ + 'id' => $server->id, + 'hetzner_id' => $server->hetzner_id, + 'hostname' => $server->hostname, + 'ip' => $server->ip, + 'port' => $server->port, + 'type' => $server->type?->value, + 'status' => $server->status?->value, + 'shared_secret' => $server->shared_secret, + 'max_clients' => $server->max_clients, + 'viewer_count' => $server->viewer_count, + 'health_status' => $server->health_status, + 'health_check_message' => $server->health_check_message, + 'created_at' => $server->created_at?->diffForHumans() ?? '-', + 'updated_at' => $server->updated_at?->diffForHumans() ?? '-', + 'is_cloud' => $server->isHetznerServer(), + 'is_edge' => $server->type === ServerTypeEnum::EDGE, + ], + 'options' => $this->formOptions(), + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($server, includeEdit: false), + ), + 'users' => $server->users() + ->select(['id', 'name', 'sub', 'reg_id']) + ->orderBy('name') + ->get() + ->all(), + ]); + } + + public function update(ServerRequest $request, Server $server): RedirectResponse + { + $this->authorize('update', $server); + + $server->update($request->serverData($server->type)); + + Toast::flashSuccess('Server updated'); + + return back(); + } + + /** + * Only for manually managed servers; the policy blocks anything with a hetzner_id. + * Server::delete() unassigns its users first. + */ + public function destroy(Server $server): RedirectResponse + { + $this->authorize('delete', $server); + + $hostname = $server->hostname; + $server->delete(); + + Toast::flashSuccess('Server deleted', "'{$hostname}' has been removed."); + + return to_route('manage.servers.index'); + } + + public function deprovision(Server $server): RedirectResponse + { + $this->authorize('deprovision', $server); + + $server->deprovision(); + + Toast::flashSuccess( + 'Deprovisioning started', + "'{$server->hostname}' is being torn down on Hetzner Cloud.", + ); + + return back(); + } + + /** + * @return array + */ + private function row(Server $server): array + { + $isEdge = $server->type === ServerTypeEnum::EDGE; + $capacity = $isEdge && $server->max_clients > 0 + ? round($server->viewer_count / $server->max_clients * 100).'% capacity' + : null; + + return [ + 'hetzner_id' => $server->hetzner_id, + 'type' => Status::serverType($server->type), + 'hostname' => $server->hostname, + 'ip' => $server->ip, + 'port' => $server->port, + 'status' => Status::server($server->status), + 'viewer_count' => $isEdge + ? ['display' => number_format($server->viewer_count, 0, '.', ' '), 'description' => $capacity] + : null, + 'heartbeat' => $server->hasRecentHeartbeat() + ? Status::make('Recent', Status::OK, 'circle-check') + [ + 'title' => 'Last heartbeat: '.$server->last_heartbeat->diffForHumans(), + ] + : Status::make('Stale', Status::DANGER, 'circle-x') + [ + 'title' => $server->last_heartbeat + ? 'Last heartbeat: '.$server->last_heartbeat->diffForHumans() + : 'No heartbeat received', + ], + // Health checks only run against edge servers, so an origin shows nothing + // rather than a misleading "unknown". + 'health_status' => $isEdge ? Status::health($server->health_status) : null, + 'max_clients' => $server->max_clients, + ]; + } + + /** + * @return array + */ + private function rowActions(Server $server): array + { + return $this->recordActions($server, includeEdit: true); + } + + /** + * Deprovision and Delete are mutually exclusive by design: a cloud server has to go + * through Hetzner teardown, a manual one is just a row. + * + * @return array + */ + private function recordActions(Server $server, bool $includeEdit): array + { + $user = request()->user(); + $actions = []; + + if ($includeEdit) { + $actions[] = Action::link('edit', 'Edit', route('manage.servers.edit', $server)) + ->icon('pencil'); + } + + if ($user->can('viewInstallScript', $server)) { + $actions[] = Action::link('install_script', 'Install Script', route('manage.servers.install-script', $server)) + ->icon('code'); + } + + if ($user->can('deprovision', $server)) { + $actions[] = Action::post('deprovision', 'Deprovision', route('manage.servers.deprovision', $server)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm( + 'Deprovision server', + 'The Hetzner server and its DNS record are deleted. Viewers on it are moved away.', + 'Deprovision', + ); + } + + if ($user->can('delete', $server)) { + $actions[] = Action::delete('delete', 'Delete', route('manage.servers.destroy', $server)) + ->icon('x') + ->tone(Status::DANGER) + ->confirm( + 'Delete Manual Server', + 'Are you sure you want to delete this manually managed server?', + 'Delete', + ); + } + + return $actions; + } + + /** + * @return array + */ + private function pageActions(): array + { + $user = request()->user(); + $actions = []; + + if ($user->can('create', Server::class)) { + $actions[] = Action::link('create', 'New Manual Server', route('manage.servers.create')) + ->icon('plus'); + } + + if ($user->can('provision', Server::class)) { + $actions[] = Action::post('provision', 'Provision Cloud Server', route('manage.servers.provision')) + ->icon('cloud') + ->tone(Status::INFO) + ->confirm( + 'Provision New Cloud Server', + 'Select the type of server to provision on Hetzner Cloud.', + 'Start Provisioning', + ) + ->fields([ + [ + 'key' => 'type', + 'label' => 'Server Type', + 'type' => 'select', + 'default' => ServerTypeEnum::EDGE->value, + 'required' => true, + 'helper' => 'Origin servers handle stream ingestion and transcoding. Edge servers cache and distribute content.', + 'options' => [ + ['value' => ServerTypeEnum::ORIGIN->value, 'label' => 'Origin Server (ccx43 - High Performance)'], + ['value' => ServerTypeEnum::EDGE->value, 'label' => 'Edge Server (cpx21 - Standard)'], + ], + ], + ]); + } + + return $actions; + } + + /** + * @return array + */ + private function formOptions(): array + { + return [ + 'types' => [ + ['value' => ServerTypeEnum::ORIGIN->value, 'label' => 'Origin'], + ['value' => ServerTypeEnum::EDGE->value, 'label' => 'Edge'], + ], + 'statuses' => [ + ['value' => ServerStatusEnum::PROVISIONING->value, 'label' => 'Provisioning'], + ['value' => ServerStatusEnum::ACTIVE->value, 'label' => 'Active'], + ['value' => ServerStatusEnum::DEPROVISIONING->value, 'label' => 'Deprovisioning'], + ['value' => ServerStatusEnum::DELETED->value, 'label' => 'Deleted'], + ['value' => ServerStatusEnum::ERROR->value, 'label' => 'Error'], + ], + ]; + } +} diff --git a/app/Http/Controllers/Manage/ServerInstallScriptController.php b/app/Http/Controllers/Manage/ServerInstallScriptController.php new file mode 100644 index 0000000..ee6396e --- /dev/null +++ b/app/Http/Controllers/Manage/ServerInstallScriptController.php @@ -0,0 +1,148 @@ + x <<'EOF'` blocks back out of the generated + * shell script to fill its tabs. Each config is asked for directly instead, which is what + * the service already exposes. The FFmpeg tabs, which only ever rendered a + * "# not available in current implementation" comment, are gone. + * See docs/admin/rebuild-plan.md 2.9. + */ +class ServerInstallScriptController extends Controller +{ + public function show(Server $server, ServerProvisioningService $provisioning) + { + $this->authorize('viewInstallScript', $server); + + return inertia('Manage/Servers/InstallScript', [ + 'server' => [ + 'id' => $server->id, + 'hostname' => $server->hostname, + 'type' => $server->type?->value, + ], + 'tabs' => $this->tabs($server, $provisioning), + 'downloadUrl' => route('manage.servers.install-script.download', $server), + 'regenerateUrl' => route('manage.servers.install-script.regenerate', $server), + ]); + } + + public function download(Server $server, ServerProvisioningService $provisioning): StreamedResponse + { + $this->authorize('viewInstallScript', $server); + + $script = $provisioning->generateInstallScript($server); + + return response()->streamDownload( + fn () => print ($script), + "install-{$server->id}.sh", + ['Content-Type' => 'text/x-shellscript'], + ); + } + + /** + * Backfills a missing shared secret and re-renders. The scripts are generated on every + * request, so there is nothing else to invalidate. + */ + public function regenerate(Server $server): RedirectResponse + { + $this->authorize('viewInstallScript', $server); + + if (! $server->shared_secret) { + $server->update(['shared_secret' => Str::random(40)]); + } + + Toast::flashSuccess('Scripts Regenerated'); + + return back(); + } + + /** + * @return array + */ + private function tabs(Server $server, ServerProvisioningService $provisioning): array + { + $isOrigin = $server->type === ServerTypeEnum::ORIGIN; + + $tabs = [ + [ + 'key' => 'install', + 'label' => 'Install script', + 'language' => 'bash', + 'filename' => "install-{$server->id}.sh", + 'content' => $provisioning->generateInstallScript($server), + ], + [ + 'key' => 'cloud-init', + 'label' => 'Cloud-init', + 'language' => 'yaml', + 'filename' => 'cloud-init.yaml', + 'content' => $provisioning->generateCloudInit($server), + ], + [ + 'key' => 'docker-compose', + 'label' => 'Docker Compose', + 'language' => 'yaml', + 'filename' => 'docker-compose.yml', + 'content' => $provisioning->generateConfig($server, 'docker-compose'), + ], + [ + 'key' => 'nginx', + 'label' => $isOrigin ? 'nginx (origin)' : 'nginx (edge)', + 'language' => 'nginx', + 'filename' => 'nginx.conf', + 'content' => $provisioning->generateConfig($server, 'nginx'), + ], + [ + 'key' => 'caddy', + 'label' => $isOrigin ? 'Caddyfile (origin)' : 'Caddyfile (edge)', + 'language' => 'caddy', + 'filename' => 'Caddyfile', + 'content' => $provisioning->generateConfig($server, 'caddy'), + ], + ]; + + if ($isOrigin) { + $tabs[] = [ + 'key' => 'srs', + 'label' => 'SRS', + 'language' => 'conf', + 'filename' => 'srs.conf', + 'content' => $provisioning->generateConfig($server, 'srs'), + ]; + } else { + // Edges verify playback tokens themselves, which needs the njs + // module in the image and the verifier alongside nginx.conf. + $tabs[] = [ + 'key' => 'edge-dockerfile', + 'label' => 'Dockerfile (edge nginx)', + 'language' => 'docker', + 'filename' => 'Dockerfile.edge-nginx', + 'content' => $provisioning->generateConfig($server, 'edge-dockerfile'), + ]; + + $tabs[] = [ + 'key' => 'hls-auth-js', + 'label' => 'Token verifier (njs)', + 'language' => 'javascript', + 'filename' => 'hls-auth.js', + 'content' => $provisioning->generateConfig($server, 'hls-auth-js'), + ]; + } + + // A template that renders empty would otherwise show as a blank tab with a copy + // button, which reads as a bug rather than as "not applicable here". + return array_values(array_filter($tabs, fn (array $tab) => trim($tab['content']) !== '')); + } +} diff --git a/app/Http/Controllers/Manage/ServerProvisionController.php b/app/Http/Controllers/Manage/ServerProvisionController.php new file mode 100644 index 0000000..58e8701 --- /dev/null +++ b/app/Http/Controllers/Manage/ServerProvisionController.php @@ -0,0 +1,74 @@ +authorize('provision', Server::class); + + $validated = $request->validate([ + 'type' => ['required', Rule::enum(ServerTypeEnum::class)], + ]); + + $type = ServerTypeEnum::from($validated['type']); + + if ($type === ServerTypeEnum::ORIGIN && $this->originExists()) { + Toast::flashDanger( + 'Cannot Create Origin Server', + 'An origin server already exists or is being provisioned. Only one origin server is allowed.', + ); + + return back(); + } + + $server = Server::create([ + 'type' => $type, + 'status' => ServerStatusEnum::PROVISIONING, + 'hostname' => 'pending', + 'port' => 443, + 'shared_secret' => Str::random(40), + 'max_clients' => $type === ServerTypeEnum::ORIGIN ? 1000 : 100, + ]); + + CreateVirtualMachineJob::dispatch($server); + + Toast::flashSuccess( + 'Server Provisioning Started', + "A new {$type->value} server is being provisioned on Hetzner Cloud.", + ); + + return back(); + } + + private function originExists(): bool + { + return Server::query() + ->where('type', ServerTypeEnum::ORIGIN) + ->whereIn('status', [ServerStatusEnum::ACTIVE, ServerStatusEnum::PROVISIONING]) + ->exists(); + } +} diff --git a/app/Http/Controllers/Manage/SettingsController.php b/app/Http/Controllers/Manage/SettingsController.php new file mode 100644 index 0000000..b2264ad --- /dev/null +++ b/app/Http/Controllers/Manage/SettingsController.php @@ -0,0 +1,75 @@ +authorizeSettings(); + + return inertia('Manage/Settings', [ + 'groups' => $settings->groups(), + // Filled by the last successful connection test, so the event slug can be + // picked rather than typed. Empty until then, and the field stays free text. + // A test that just ran names the instance it used, which is normally still + // unsaved at that point. + 'pretalxEvents' => $pretalx->rememberedEvents(session('pretalx.tested_url')), + ]); + } + + public function update(Request $request, Settings $settings): RedirectResponse + { + $this->authorizeSettings(); + + $validated = $request->validate( + $settings->rules() + ['values' => ['required', 'array']], + [], + $settings->attributes(), + ); + + $settings->save($validated['values']); + + Toast::flashSuccess('Settings saved', 'The public site picks the change up immediately.'); + + return back(); + } + + public function reset(Settings $settings): RedirectResponse + { + $this->authorizeSettings(); + + $settings->reset(); + + Toast::flashSuccess( + 'Settings reset to defaults', + 'Uploaded files are kept in case another setting still points at them.', + ); + + return back(); + } + + /** + * Changing the login copy and accent colour of the public site is an + * administrator's job, not every staff member's. + */ + private function authorizeSettings(): void + { + abort_unless(request()->user()?->hasPermission('admin.access'), 403); + } +} diff --git a/app/Http/Controllers/Manage/ShowController.php b/app/Http/Controllers/Manage/ShowController.php new file mode 100644 index 0000000..92bf584 --- /dev/null +++ b/app/Http/Controllers/Manage/ShowController.php @@ -0,0 +1,438 @@ +authorize('viewAny', Show::class); + + $table = Table::make(Show::query()->with('source')) + ->name('shows') + ->columns([ + Column::image('thumbnail', 'Thumbnail')->width('72px'), + Column::text('title', 'Title')->searchable()->sortable(), + Column::badge('source', 'Source')->searchable('source.name'), + Column::badge('status', 'Status'), + Column::datetime('scheduled_start', 'Scheduled')->sortable(), + Column::datetime('actual_start', 'Went Live') + ->sortable() + ->fallback('Not started') + ->toggleable(), + Column::number('viewer_count', 'Viewers')->sortable(), + Column::number('peak_viewer_count', 'Peak')->sortable()->toggleable(hiddenByDefault: true), + Column::badge('auto_mode', 'Auto'), + Column::badge('access', 'Access')->toggleable(hiddenByDefault: true), + ]) + ->filters([ + // On by default, as in Filament: an operator almost never wants the archive + // in the way of today's running order. + Filter::boolean('hide_ended', 'Hide ended') + ->default(true) + ->apply(fn (Builder $query) => $query->where('status', '!=', 'ended')), + Filter::select('status', 'Status') + ->options(array_combine( + ShowRequest::STATUSES, + array_map('ucfirst', ShowRequest::STATUSES), + )) + ->multiple(), + Filter::select('source', 'Source') + ->options(Source::ordered()->pluck('name', 'id')->all()) + ->apply(fn (Builder $query, string $value) => $query->where('source_id', $value)), + Filter::boolean('today', 'Today') + ->apply(fn (Builder $query) => $query->today()), + Filter::boolean('upcoming', 'Upcoming') + ->apply(fn (Builder $query) => $query->upcoming()), + ]) + ->defaultSort('scheduled_start', 'asc') + ->rows(fn (Show $show) => $this->row($show)) + ->recordUrl(fn (Show $show) => route('manage.shows.edit', $show)) + ->rowActions(fn (Show $show) => $this->recordActions($show, includeEdit: true)) + ->bulkActions($this->bulkActions()) + ->pageActions($this->pageActions()); + + return inertia('Manage/Shows/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Show::class); + + return inertia('Manage/Shows/Form', [ + 'show' => null, + 'options' => $this->formOptions(), + 'defaults' => [ + 'title' => '', + 'slug' => '', + 'source_id' => Source::ordered()->value('id'), + 'description' => '', + 'scheduled_start' => now()->format('Y-m-d\TH:i'), + 'scheduled_end' => now()->addHour()->format('Y-m-d\TH:i'), + 'actual_start' => null, + 'actual_end' => null, + 'auto_mode' => false, + 'auto_stop_at' => null, + 'announce_recording' => false, + 'visibility' => 'public', + 'required_roles' => [], + ], + ]); + } + + public function store(ShowRequest $request): RedirectResponse + { + $this->authorize('create', Show::class); + + $show = Show::create($request->showData()); + + Toast::flashSuccess('Show created', "'{$show->title}' is scheduled."); + + return to_route('manage.shows.edit', $show); + } + + public function edit(Show $show): Response + { + $this->authorize('view', $show); + + return inertia('Manage/Shows/Form', [ + 'show' => [ + 'id' => $show->id, + 'title' => $show->title, + 'slug' => $show->slug, + 'source_id' => $show->source_id, + 'description' => $show->description, + // datetime-local wants minutes and no timezone suffix. + 'scheduled_start' => $show->scheduled_start?->format('Y-m-d\TH:i'), + 'scheduled_end' => $show->scheduled_end?->format('Y-m-d\TH:i'), + 'actual_start' => $show->actual_start?->format('Y-m-d\TH:i:s'), + 'actual_end' => $show->actual_end?->format('Y-m-d\TH:i:s'), + 'auto_mode' => (bool) $show->auto_mode, + 'auto_stop_at' => $show->auto_stop_at?->format('Y-m-d\TH:i'), + 'announce_recording' => (bool) $show->announce_recording, + 'visibility' => $show->isPrivate() ? 'private' : 'public', + 'required_roles' => $show->required_roles ?? [], + // Captured off the stream while it runs; never set by hand here. A + // recording carries its own, separate thumbnail. + 'thumbnail_url' => $show->thumbnail_url, + 'status' => Status::show($show->status), + 'is_live' => $show->status === 'live', + 'viewer_count' => $show->viewer_count, + 'peak_viewer_count' => $show->peak_viewer_count, + 'formatted_duration' => $show->formatted_duration, + 'statistics_url' => route('manage.shows.statistics', $show), + ], + 'options' => $this->formOptions(), + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($show, includeEdit: false), + ), + ]); + } + + public function update(ShowRequest $request, Show $show): RedirectResponse + { + $this->authorize('update', $show); + + $show->update($request->showData($show)); + + Toast::flashSuccess('Show updated'); + + return back(); + } + + public function destroy(Show $show): RedirectResponse + { + if (! request()->user()->can('delete', $show)) { + Toast::flashDanger('Cannot delete live show', 'Please end the stream before deleting.'); + + return back(); + } + + $title = $show->title; + $show->delete(); + + Toast::flashSuccess('Show deleted', "'{$title}' has been removed."); + + return to_route('manage.shows.index'); + } + + public function goLive(Show $show): RedirectResponse + { + $this->authorize('goLive', $show); + + $show->goLive(); + + Toast::flashSuccess('Show is now live!', "'{$show->title}' is now streaming."); + + return back(); + } + + public function endStream(Show $show): RedirectResponse + { + $this->authorize('endStream', $show); + + $show->endLivestream(); + + Toast::flashSuccess('Stream ended', "'{$show->title}' has ended."); + + return back(); + } + + public function cancel(Show $show): RedirectResponse + { + $this->authorize('cancel', $show); + + $show->cancel(); + + Toast::flashSuccess('Show cancelled', "'{$show->title}' will not be broadcast."); + + return back(); + } + + /** + * Cancelling only applies to shows that have not started; anything else is skipped + * rather than failing the whole batch, which is what Filament's bulk action did. + */ + public function bulkCancel(Request $request): RedirectResponse + { + $this->authorize('create', Show::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $cancelled = Show::whereIn('id', $validated['ids']) + ->where('status', 'scheduled') + ->get() + ->each->cancel() + ->count(); + + Toast::flashSuccess('Shows cancelled', $cancelled.' of '.count($validated['ids']).' were still scheduled.'); + + return back(); + } + + /** + * All-or-nothing: one live show in the selection blocks the batch, as in Filament. + */ + public function bulkDestroy(Request $request): RedirectResponse + { + $this->authorize('create', Show::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $shows = Show::whereIn('id', $validated['ids'])->get(); + + foreach ($shows as $show) { + if (! $request->user()->can('delete', $show)) { + Toast::flashDanger('Cannot delete shows', 'One or more shows are currently live.'); + + return back(); + } + } + + $shows->each->delete(); + + Toast::flashSuccess('Shows deleted', $shows->count().' removed.'); + + return back(); + } + + /** + * @return array + */ + private function row(Show $show): array + { + return [ + 'thumbnail' => $show->thumbnail_url, + 'title' => $show->title, + 'source' => $show->source + ? Status::make($show->source->name, Status::INFO) + : null, + 'status' => Status::show($show->status), + 'scheduled_start' => $show->scheduled_start?->format('M j, Y H:i'), + 'actual_start' => $show->actual_start?->format('M j, Y H:i'), + 'viewer_count' => $show->viewer_count, + 'peak_viewer_count' => $show->peak_viewer_count, + 'auto_mode' => Status::toggle( + (bool) $show->auto_mode, + 'Auto', + 'Manual', + trueTone: Status::OK, + falseTone: Status::IDLE, + trueIcon: 'cog', + falseIcon: 'hand', + ), + 'access' => Status::toggle( + $show->hasAccessRestriction(), + 'Restricted', + 'Public', + trueTone: Status::WARN, + falseTone: Status::OK, + trueIcon: 'lock', + falseIcon: 'globe', + ), + ]; + } + + /** + * @return array + */ + private function recordActions(Show $show, bool $includeEdit): array + { + $user = request()->user(); + $actions = []; + + if ($includeEdit) { + $actions[] = Action::link('edit', 'Edit', route('manage.shows.edit', $show))->icon('pencil'); + } + + if ($user->can('goLive', $show)) { + $actions[] = Action::post('go_live', 'Go Live', route('manage.shows.go-live', $show)) + ->icon('play') + ->tone(Status::OK) + ->confirm( + 'Start Live Stream', + 'Are you sure you want to start this show? This will mark it as live and notify viewers.', + 'Go Live', + ); + } + + if ($user->can('cancel', $show)) { + $actions[] = Action::post('cancel', 'Cancel', route('manage.shows.cancel', $show)) + ->icon('circle-x') + ->tone(Status::IDLE) + ->confirm( + 'Cancel Show', + 'The show will not be broadcast. It stays on the schedule as cancelled.', + 'Cancel Show', + ); + } + + if ($user->can('endStream', $show)) { + $actions[] = Action::post('end_stream', 'End Stream', route('manage.shows.end', $show)) + ->icon('square') + ->tone(Status::DANGER) + ->confirm( + 'End Live Stream', + 'Are you sure you want to end this show? This will stop the stream and disconnect all viewers.', + 'End Stream', + ); + } + + // Cutting a recording deliberately does not wait for the show to end. The archive + // is a continuous per-source timeline, so any range can be cut while the show is + // still running, which is the only workable option for a source that stays online + // for the whole event. What it does need is an end marker, so the action is only + // offered once one exists. + if ($user->can('create', \App\Models\Recording::class) && $show->actual_start) { + $actions[] = Action::post('create_recording', 'Create Recording', route('manage.shows.recording.store', $show)) + ->icon('film') + ->disabled($show->actual_end + ? null + : 'Still live. End the show, or set an end marker on the recording.') + ->confirm( + 'Create recording', + "Cuts '{$show->title}' from the archive as an unpublished draft. " + .'The markers can be adjusted afterwards and the playlist is rebuilt each time.', + 'Create draft', + ); + } + + $actions[] = Action::link('statistics', 'View Statistics', route('manage.shows.statistics', $show)) + ->icon('bar-chart'); + + if ($user->can('update', $show)) { + $actions[] = Action::delete('delete', 'Delete', route('manage.shows.destroy', $show)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->disabled($show->status === 'live' ? 'End the stream before deleting.' : null) + ->confirm('Delete show', "'{$show->title}' and its viewer history are removed.", 'Delete'); + } + + return $actions; + } + + /** + * @return array + */ + private function bulkActions(): array + { + if (! request()->user()->can('create', Show::class)) { + return []; + } + + return [ + Action::post('bulk_cancel', 'Cancel Shows', route('manage.shows.bulk.cancel')) + ->icon('circle-x') + ->tone(Status::IDLE) + ->confirm('Cancel selected shows', 'Only shows that have not started yet are cancelled.', 'Cancel shows'), + Action::delete('bulk_delete', 'Delete', route('manage.shows.bulk.destroy')) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm('Delete selected shows', 'A live show in the selection blocks the whole batch.', 'Delete'), + ]; + } + + /** + * @return array + */ + private function pageActions(): array + { + $actions = []; + + if (request()->user()->can('create', Show::class)) { + // Only offered once there is an instance and an event to import from; + // otherwise the button leads to a screen that can only say "not configured". + if (app(PretalxService::class)->isConfigured()) { + $actions[] = Action::link('import', 'Import from pretalx', route('manage.shows.import')) + ->icon('download'); + } + + $actions[] = Action::link('create', 'New Show', route('manage.shows.create'))->icon('plus'); + } + + return $actions; + } + + /** + * @return array + */ + private function formOptions(): array + { + return [ + 'sources' => Source::ordered() + ->get(['id', 'name']) + ->map(fn (Source $source) => ['value' => $source->id, 'label' => $source->name]) + ->all(), + 'statuses' => array_map( + fn (string $status) => ['value' => $status, 'label' => ucfirst($status)], + ShowRequest::STATUSES, + ), + 'roles' => ShowRequest::roleOptions(), + ]; + } +} diff --git a/app/Http/Controllers/Manage/ShowPlannerController.php b/app/Http/Controllers/Manage/ShowPlannerController.php new file mode 100644 index 0000000..43edf76 --- /dev/null +++ b/app/Http/Controllers/Manage/ShowPlannerController.php @@ -0,0 +1,180 @@ +authorize('viewAny', Show::class); + + $from = $this->from($request); + $days = $this->days($request); + $to = $from->clone()->addDays($days); + + $shows = Show::query() + ->with('source') + ->whereNotNull('scheduled_start') + // Overlap, not containment: a dance running past midnight has to appear on the + // day it starts even when the window ends mid-show. + ->where('scheduled_start', '<', $to) + ->where(function ($query) use ($from) { + $query->where('scheduled_end', '>', $from) + ->orWhereNull('scheduled_end'); + }) + ->get(); + + $grouped = $shows->groupBy('source_id'); + + return inertia('Manage/Shows/Planner', [ + 'range' => [ + 'from' => $from->toIso8601String(), + 'to' => $to->toIso8601String(), + 'days' => $days, + // Pre-formatted so the client never has to guess at locale or timezone. + 'dayLabels' => collect(range(0, $days - 1)) + ->map(fn (int $offset) => [ + 'iso' => $from->clone()->addDays($offset)->toIso8601String(), + 'label' => $from->clone()->addDays($offset)->format('D j M'), + 'isToday' => $from->clone()->addDays($offset)->isToday(), + ]) + ->all(), + ], + 'now' => now()->toIso8601String(), + 'lanes' => Source::ordered() + ->get(['id', 'name']) + ->map(fn (Source $source) => [ + 'id' => $source->id, + 'name' => $source->name, + 'shows' => $grouped->get($source->id, collect()) + ->sortBy('scheduled_start') + ->map(fn (Show $show) => $this->block($show)) + ->values() + ->all(), + ]) + ->all(), + 'can' => [ + 'edit' => $request->user()->can('create', Show::class), + ], + ]); + } + + /** + * Move or resize one block. Only the times: everything else keeps its value. + */ + public function reschedule(Request $request, Show $show): RedirectResponse + { + $this->authorize('update', $show); + + $validated = $request->validate([ + 'scheduled_start' => ['required', 'date'], + 'scheduled_end' => ['required', 'date', 'after:scheduled_start'], + ]); + + $show->update($validated); + + Toast::flashSuccess( + 'Show rescheduled', + "'{$show->title}' now runs ".$show->scheduled_start->format('D j M H:i'). + ' to '.$show->scheduled_end->format('H:i').'.', + ); + + return back(); + } + + /** + * Quick-create from an empty stretch of track: enough to hold the slot, nothing more. + * Access, auto mode and recording are decided on the form afterwards. + */ + public function store(Request $request): RedirectResponse + { + $this->authorize('create', Show::class); + + $validated = $request->validate([ + 'title' => ['required', 'string', 'max:255'], + 'source_id' => ['required', 'integer', 'exists:sources,id'], + 'scheduled_start' => ['required', 'date'], + 'scheduled_end' => ['required', 'date', 'after:scheduled_start'], + ]); + + // The model's creating hook builds the dated slug from title + scheduled_start. + $show = Show::create($validated + [ + 'status' => 'scheduled', + 'auto_mode' => false, + 'announce_recording' => false, + 'required_roles' => [], + ]); + + Toast::flashSuccess('Show created', "'{$show->title}' holds the slot. Open it to finish setting it up."); + + return back(); + } + + /** + * @return array + */ + private function block(Show $show): array + { + // A show with no end has no length to draw, so it gets a nominal hour - the same + // fallback the public schedule uses. + $end = $show->scheduled_end ?? $show->scheduled_start->clone()->addHour(); + + return [ + 'id' => $show->id, + 'title' => $show->title, + 'start' => $show->scheduled_start->toIso8601String(), + 'end' => $end->toIso8601String(), + 'status' => Status::show($show->status), + // A live show must not be dragged out from under its viewers. + 'locked' => $show->status === 'live', + 'autoMode' => (bool) $show->auto_mode, + 'url' => route('manage.shows.edit', $show), + ]; + } + + private function from(Request $request): Carbon + { + $raw = $request->string('from')->toString(); + + if ($raw !== '') { + try { + return Carbon::parse($raw)->startOfDay(); + } catch (\Throwable) { + // Fall through to today rather than 500 on a hand-edited query string. + } + } + + return now()->startOfDay(); + } + + private function days(Request $request): int + { + $days = (int) $request->input('days', self::DEFAULT_DAYS); + + return max(1, min($days, self::MAX_DAYS)); + } +} diff --git a/app/Http/Controllers/Manage/ShowStatisticsController.php b/app/Http/Controllers/Manage/ShowStatisticsController.php new file mode 100644 index 0000000..681aacc --- /dev/null +++ b/app/Http/Controllers/Manage/ShowStatisticsController.php @@ -0,0 +1,172 @@ +authorize('view', $show); + + $samples = ShowStatistic::query() + ->where('show_id', $show->id) + ->orderBy('recorded_at') + ->get(['recorded_at', 'viewer_count', 'unique_viewers']); + + return inertia('Manage/Shows/Statistics', [ + 'show' => [ + 'id' => $show->id, + 'title' => $show->title, + 'source' => $show->source?->name, + 'status' => Status::show($show->status), + 'is_live' => $show->status === 'live', + 'scheduled_start' => $show->scheduled_start?->format('M j, Y H:i'), + 'scheduled_end' => $show->scheduled_end?->format('M j, Y H:i'), + 'actual_start' => $show->actual_start?->format('M j, Y H:i:s'), + 'actual_end' => $show->actual_end?->format('M j, Y H:i:s'), + 'formatted_duration' => $show->formatted_duration, + 'edit_url' => route('manage.shows.edit', $show), + ], + 'live' => $show->status === 'live' ? $this->live($show, $samples) : null, + 'report' => $this->report($show, $samples), + 'viewers' => $this->viewers($show), + ]); + } + + /** + * @param Collection $samples + * @return array + */ + private function live(Show $show, Collection $samples): array + { + $since = now()->subMinutes(30); + $recent = $samples->filter(fn (ShowStatistic $sample) => $sample->recorded_at->gte($since)); + + $sessions = $show->viewerSessions(); + + return [ + 'current' => (int) $show->viewer_count, + 'peak' => (int) $samples->max('viewer_count'), + // Arrivals and departures over the last five minutes: the number that says + // whether a dip is people leaving or the stream dropping. + 'joins' => (clone $sessions)->where('joined_at', '>=', now()->subMinutes(5))->count(), + 'leaves' => (clone $sessions)->where('left_at', '>=', now()->subMinutes(5))->count(), + 'watching' => (clone $sessions)->whereNull('left_at')->count(), + 'sparkline' => $this->points($recent, cap: 30), + ]; + } + + /** + * @param Collection $samples + * @return array + */ + private function report(Show $show, Collection $samples): array + { + $average = $samples->avg('viewer_count') ?? 0; + $minutes = $samples->count(); + + return [ + 'peak' => (int) $samples->max('viewer_count'), + 'average' => (int) round($average), + 'unique' => (int) $samples->max('unique_viewers'), + // Samples land once a minute, so summing them is the watch-minutes total + // directly - no need to multiply an average by a duration and hope they agree. + 'watch_hours' => round($samples->sum('viewer_count') / 60, 1), + 'sampled_minutes' => $minutes, + 'chart' => $this->points($samples, cap: self::MAX_POINTS), + ]; + } + + /** + * Samples to chart points, averaged into at most `cap` buckets. + * + * @param Collection $samples + * @return array + */ + private function points(Collection $samples, int $cap): array + { + if ($samples->isEmpty()) { + return []; + } + + $size = (int) max(1, ceil($samples->count() / $cap)); + + return $samples + ->chunk($size) + ->map(fn (Collection $chunk) => [ + 'label' => $this->label($chunk->first()->recorded_at), + 'value' => (int) round($chunk->avg('viewer_count')), + ]) + ->values() + ->all(); + } + + private function label(Carbon $at): string + { + return $at->format('H:i'); + } + + /** + * @return array> + */ + private function viewers(Show $show): array + { + return $show->viewerSessions() + ->with('user:id,name') + ->orderByDesc('joined_at') + ->limit(100) + ->get() + ->map(fn ($session) => [ + 'id' => $session->id, + 'name' => $session->user?->name ?? 'Unknown', + 'joined_at' => $session->joined_at?->format('M j, H:i'), + 'left_at' => $session->left_at?->format('M j, H:i'), + 'duration' => $this->duration($session->watch_duration), + 'active' => (bool) $session->is_active, + ]) + ->all(); + } + + /** + * `1h 04m 12s`, matching the Filament viewers relation manager. + */ + private function duration(?int $seconds): string + { + if (! $seconds) { + return '—'; + } + + $hours = intdiv($seconds, 3600); + $minutes = intdiv($seconds % 3600, 60); + $remaining = $seconds % 60; + + return match (true) { + $hours > 0 => sprintf('%dh %dm %ds', $hours, $minutes, $remaining), + $minutes > 0 => sprintf('%dm %ds', $minutes, $remaining), + default => sprintf('%ds', $remaining), + }; + } +} diff --git a/app/Http/Controllers/Manage/SourceController.php b/app/Http/Controllers/Manage/SourceController.php new file mode 100644 index 0000000..5e4d844 --- /dev/null +++ b/app/Http/Controllers/Manage/SourceController.php @@ -0,0 +1,414 @@ +authorize('viewAny', Source::class); + + $table = Table::make(Source::query()->withCount('shows')) + ->name('sources') + ->columns([ + Column::badge('status', 'Status'), + Column::text('name', 'Name')->searchable()->sortable(), + Column::copyable('slug', 'Stream Name')->searchable()->sortable(), + Column::number('priority', 'Priority')->sortable(), + Column::number('shows_count', 'Total Shows')->sortable('shows_count'), + Column::number('live_shows_count', 'Live Now'), + Column::datetime('created_at', 'Created')->sortable()->toggleable(hiddenByDefault: true), + Column::datetime('updated_at', 'Updated')->sortable()->toggleable(hiddenByDefault: true), + ]) + ->filters([ + Filter::select('status', 'Status') + ->options($this->statusOptions()) + ->placeholder('All statuses'), + ]) + ->defaultSort('priority', 'desc') + ->rows(fn (Source $source) => $this->row($source)) + ->recordUrl(fn (Source $source) => route('manage.sources.edit', $source)) + ->rowActions(fn (Source $source) => $this->rowActions($source)) + ->bulkActions($this->bulkActions()) + ->pageActions($this->pageActions()); + + return inertia('Manage/Sources/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function create(): Response + { + $this->authorize('create', Source::class); + + return inertia('Manage/Sources/Form', [ + 'source' => null, + 'options' => ['statuses' => $this->statusOptionList()], + /* + * No `status` here: a new source starts offline (the column default) and is + * moved only by the Update Status action. + */ + 'defaults' => [ + 'name' => '', + 'slug' => '', + 'priority' => 0, + 'description' => '', + ], + ]); + } + + public function store(SourceRequest $request): RedirectResponse + { + $this->authorize('create', Source::class); + + // The model boot hook generates the stream key, so it is never posted from a form. + $source = Source::create($request->validated()); + + Toast::flashSuccess('Source created', "'{$source->name}' is ready to receive a stream."); + + return to_route('manage.sources.edit', $source); + } + + public function edit(Source $source): Response + { + $this->authorize('view', $source); + + return inertia('Manage/Sources/Form', [ + 'source' => [ + 'id' => $source->id, + 'name' => $source->name, + 'slug' => $source->slug, + 'status' => $source->status?->value, + 'priority' => $source->priority, + 'description' => $source->description, + 'rtmp_url' => $source->getRtmpServerUrl(), + 'stream_key' => $source->getObsStreamKey(), + 'shows_count' => $source->shows()->count(), + 'live_shows_count' => $source->liveShows()->count(), + 'created_at' => $source->created_at?->diffForHumans() ?? '-', + 'updated_at' => $source->updated_at?->diffForHumans() ?? '-', + ], + 'options' => ['statuses' => $this->statusOptionList()], + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($source), + ), + 'shows' => $source->shows() + ->orderByDesc('scheduled_start') + ->limit(50) + ->get() + ->map(fn ($show) => [ + 'id' => $show->id, + 'title' => $show->title, + 'status' => Status::show($show->status), + 'scheduled_start' => $show->scheduled_start?->format('M j, Y H:i'), + 'viewer_count' => $show->viewer_count, + 'url' => route('manage.shows.edit', $show), + ]) + ->all(), + ]); + } + + public function update(SourceRequest $request, Source $source): RedirectResponse + { + $this->authorize('update', $source); + + $source->update($request->validated()); + + Toast::flashSuccess('Source updated'); + + return back(); + } + + public function destroy(Source $source): RedirectResponse + { + // SourcePolicy refuses while the source has a live show. + if (! request()->user()->can('delete', $source)) { + Toast::flashDanger('Cannot delete source', 'This source has active live shows.'); + + return back(); + } + + $name = $source->name; + $source->delete(); + + Toast::flashSuccess('Source deleted', "'{$name}' has been removed."); + + return to_route('manage.sources.index'); + } + + /** + * The observer broadcasts the change, so nothing else has to be nudged here. + */ + public function updateStatus(Request $request, Source $source): RedirectResponse + { + $this->authorize('update', $source); + + $validated = $request->validate([ + 'status' => ['required', Rule::enum(SourceStatusEnum::class)], + ]); + + $source->update(['status' => $validated['status']]); + + Toast::flashSuccess( + 'Status updated', + "Source '{$source->name}' status has been updated to {$validated['status']}.", + ); + + return back(); + } + + /** + * @param array $ids + */ + public function bulkUpdateStatus(Request $request): RedirectResponse + { + $this->authorize('create', Source::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + 'status' => ['required', Rule::enum(SourceStatusEnum::class)], + ]); + + Source::whereIn('id', $validated['ids']) + ->get() + // One update per model rather than a mass update, so the observer fires and + // each change is broadcast. + ->each(fn (Source $source) => $source->update(['status' => $validated['status']])); + + Toast::flashSuccess('Status updated', 'The selected sources have been updated.'); + + return back(); + } + + /** + * All-or-nothing, matching Filament: if any selected source is live, none are deleted. + */ + public function bulkDestroy(Request $request): RedirectResponse + { + $this->authorize('create', Source::class); + + $validated = $request->validate([ + 'ids' => ['required', 'array'], + 'ids.*' => ['integer'], + ]); + + $sources = Source::whereIn('id', $validated['ids'])->get(); + + foreach ($sources as $source) { + if (! $request->user()->can('delete', $source)) { + Toast::flashDanger('Cannot delete sources', 'One or more sources have active live shows.'); + + return back(); + } + } + + $sources->each->delete(); + + Toast::flashSuccess('Sources deleted', $sources->count().' removed.'); + + return back(); + } + + /** + * Invalidates the key anyone is currently pushing with, so it is a confirmed action. + */ + public function regenerateStreamKey(Source $source): RedirectResponse + { + $this->authorize('regenerateStreamKey', $source); + + $source->update(['stream_key' => Str::random(32)]); + + Toast::flashSuccess( + 'Stream key regenerated', + 'The new stream key has been saved and is now active.', + ); + + return back(); + } + + /** + * @return array + */ + private function row(Source $source): array + { + return [ + 'status' => Status::source($source->status), + 'name' => $source->name, + 'slug' => $source->slug, + 'priority' => $source->priority, + 'shows_count' => $source->shows_count, + 'live_shows_count' => (function () use ($source) { + $live = $source->liveShows()->count(); + + return ['display' => (string) $live, 'description' => $live > 0 ? 'on air' : null]; + })(), + 'created_at' => $source->created_at?->format('M j, Y H:i'), + 'updated_at' => $source->updated_at?->format('M j, Y H:i'), + ]; + } + + /** + * What a table row offers: open it, or remove it. + * + * Overriding a status and rotating a stream key both belong to one source and both have + * consequences beyond the row - a rotated key disconnects whoever is pushing. They live + * on the detail page, where the OBS block they affect is on screen. + * + * @return array + */ + private function rowActions(Source $source): array + { + $actions = [ + Action::link('edit', 'Edit', route('manage.sources.edit', $source))->icon('pencil'), + ]; + + if (request()->user()->can('update', $source)) { + $actions[] = $this->deleteAction($source); + } + + return $actions; + } + + /** + * The detail page header: everything that acts on this one source. + * + * @return array + */ + private function recordActions(Source $source): array + { + $user = request()->user(); + $actions = []; + + if ($user->can('update', $source)) { + $actions[] = Action::post('update_status', 'Update Status', route('manage.sources.status', $source)) + ->icon('refresh-cw') + ->tone(Status::WARN) + ->fields([[ + 'key' => 'status', + 'label' => 'New Status', + 'type' => 'select', + 'default' => $source->status?->value, + 'required' => true, + 'options' => $this->statusOptionList(), + ]]); + } + + if ($user->can('regenerateStreamKey', $source)) { + $actions[] = Action::post('regenerate_key', 'Regenerate Stream Key', route('manage.sources.stream-key', $source)) + ->icon('refresh-cw') + ->tone(Status::WARN) + ->confirm( + 'Regenerate Stream Key?', + 'This will invalidate the current stream key. Any active streams will be disconnected.', + 'Regenerate', + ); + } + + if ($user->can('update', $source)) { + $actions[] = $this->deleteAction($source); + } + + return $actions; + } + + /** + * Offered even while blocked, carrying the reason, so the UI can explain itself. + */ + private function deleteAction(Source $source): Action + { + $live = $source->liveShows()->exists(); + + return Action::delete('delete', 'Delete', route('manage.sources.destroy', $source)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->disabled($live ? 'This source has active live shows.' : null) + ->confirm( + 'Delete source', + "Deleting '{$source->name}' also removes the shows attached to it.", + 'Delete', + ); + } + + /** + * @return array + */ + private function bulkActions(): array + { + if (! request()->user()->can('create', Source::class)) { + return []; + } + + return [ + Action::post('bulk_status', 'Update Status', route('manage.sources.bulk.status')) + ->icon('refresh-cw') + ->tone(Status::WARN) + ->fields([[ + 'key' => 'status', + 'label' => 'New Status', + 'type' => 'select', + 'default' => SourceStatusEnum::OFFLINE->value, + 'required' => true, + 'options' => $this->statusOptionList(), + ]]), + Action::delete('bulk_delete', 'Delete', route('manage.sources.bulk.destroy')) + ->icon('trash-2') + ->tone(Status::DANGER) + ->confirm('Delete selected sources', 'Sources with a live show block the whole batch.', 'Delete'), + ]; + } + + /** + * @return array + */ + private function pageActions(): array + { + if (! request()->user()->can('create', Source::class)) { + return []; + } + + return [ + Action::link('create', 'New Source', route('manage.sources.create'))->icon('plus'), + ]; + } + + /** + * @return array + */ + private function statusOptions(): array + { + return [ + SourceStatusEnum::ONLINE->value => 'Online', + SourceStatusEnum::OFFLINE->value => 'Offline', + SourceStatusEnum::ERROR->value => 'Error', + ]; + } + + /** + * @return array + */ + private function statusOptionList(): array + { + return collect($this->statusOptions()) + ->map(fn (string $label, string $value) => ['value' => $value, 'label' => $label]) + ->values() + ->all(); + } +} diff --git a/app/Http/Controllers/Manage/TableColumnController.php b/app/Http/Controllers/Manage/TableColumnController.php new file mode 100644 index 0000000..295c32a --- /dev/null +++ b/app/Http/Controllers/Manage/TableColumnController.php @@ -0,0 +1,28 @@ +validate([ + 'hidden' => ['present', 'array'], + 'hidden.*' => ['string'], + ]); + + session()->put("manage.table.{$table}.hidden", array_values($validated['hidden'])); + + return back(); + } +} diff --git a/app/Http/Controllers/Manage/UploadController.php b/app/Http/Controllers/Manage/UploadController.php new file mode 100644 index 0000000..8d42db7 --- /dev/null +++ b/app/Http/Controllers/Manage/UploadController.php @@ -0,0 +1,77 @@ +validate([ + 'purpose' => ['required', Rule::in(array_keys($purposes))], + ]); + + $config = $purposes[$request->string('purpose')->toString()]; + + $request->validate([ + 'file' => [ + 'required', + 'file', + 'mimes:'.implode(',', $config['mimes']), + 'max:'.$config['max'], + ], + ]); + + $file = $request->file('file'); + + $name = $config['preserve_filename'] + ? Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME)).'.'.$file->getClientOriginalExtension() + : Str::random(40).'.'.$file->getClientOriginalExtension(); + + $path = $file->storeAs($config['directory'], $name, [ + 'disk' => $config['disk'], + 'visibility' => $config['visibility'], + ]); + + Toast::put('upload', [ + 'purpose' => $request->string('purpose')->toString(), + 'path' => $path, + 'url' => $this->previewUrl($config['disk'], $config['visibility'], $path), + ]); + + Toast::flashSuccess('File uploaded', $name); + + return back(); + } + + private function previewUrl(string $disk, string $visibility, string $path): ?string + { + $storage = Storage::disk($disk); + + if ($visibility === 'private') { + // Local and public drivers have the method but throw when they cannot sign. + try { + return $storage->temporaryUrl($path, now()->addMinutes(30)); + } catch (\Throwable) { + return null; + } + } + + return $storage->url($path); + } +} diff --git a/app/Http/Controllers/Manage/UserController.php b/app/Http/Controllers/Manage/UserController.php new file mode 100644 index 0000000..799cadc --- /dev/null +++ b/app/Http/Controllers/Manage/UserController.php @@ -0,0 +1,226 @@ +authorize('viewAny', User::class); + + $table = Table::make(User::query()->with(['server', 'roles'])) + ->name('users') + ->columns([ + Column::text('name', 'Name')->searchable()->sortable(), + Column::copyable('sub', 'Subject')->searchable()->toggleable(hiddenByDefault: true), + Column::number('reg_id', 'Reg ID')->searchable()->sortable(), + Column::text('roles', 'Roles'), + Column::text('server', 'Edge server'), + Column::datetime('created_at', 'First seen')->sortable()->toggleable(hiddenByDefault: true), + ]) + ->filters([ + Filter::select('role', 'Role') + ->options(Role::orderByDesc('priority')->pluck('name', 'slug')->all()) + ->placeholder('All roles') + ->apply(fn ($query, $value) => $query->whereHas( + 'roles', + fn ($roles) => $roles->where('slug', $value), + )), + ]) + ->defaultSort('name', 'asc') + ->rows(fn (User $user) => $this->row($user)) + ->recordUrl(fn (User $user) => route('manage.users.edit', $user)) + ->rowActions(fn (User $user) => $this->rowActions($user)); + + return inertia('Manage/Users/Index', [ + 'table' => $table->toArray($request), + ]); + } + + public function edit(User $user): Response + { + $this->authorize('view', $user); + + return inertia('Manage/Users/Form', [ + 'user' => [ + 'id' => $user->id, + 'sub' => $user->sub, + 'name' => $user->name, + 'reg_id' => $user->reg_id, + 'server_id' => $user->server_id, + 'roles' => $user->roles->pluck('slug')->all(), + 'created_at' => $user->created_at?->diffForHumans() ?? '-', + 'updated_at' => $user->updated_at?->diffForHumans() ?? '-', + ], + 'options' => [ + 'servers' => $this->serverOptions(), + 'roles' => $this->roleOptions(), + ], + 'actions' => array_map( + fn (Action $action) => $action->toArray(), + $this->recordActions($user), + ), + 'messages' => $user->messages() + ->latest() + ->limit(25) + ->get() + ->map(fn ($message) => [ + 'id' => $message->id, + 'message' => $message->message, + 'created_at' => $message->created_at?->format('M j, Y H:i'), + ]) + ->all(), + ]); + } + + public function update(Request $request, User $user): RedirectResponse + { + $this->authorize('update', $user); + + $validated = $request->validate([ + 'server_id' => ['nullable', 'integer', 'exists:servers,id'], + 'roles' => ['array'], + 'roles.*' => ['string', 'exists:roles,slug'], + ]); + + $user->update(['server_id' => $validated['server_id'] ?? null]); + + // Sync by slug so the form posts something stable rather than role ids. + $roleIds = Role::whereIn('slug', $validated['roles'] ?? [])->pluck('id'); + $user->roles()->sync($roleIds); + + Toast::flashSuccess('User updated'); + + return back(); + } + + public function destroy(User $user): RedirectResponse + { + if (! request()->user()->can('delete', $user)) { + Toast::flashDanger('Cannot delete user', 'You cannot delete your own account.'); + + return back(); + } + + $name = $user->name; + $user->delete(); + + Toast::flashSuccess('User deleted', "'{$name}' has been removed."); + + return to_route('manage.users.index'); + } + + /** + * @return array + */ + private function row(User $user): array + { + $roles = $user->roles->sortByDesc('priority'); + + return [ + 'name' => $user->name, + 'sub' => $user->sub, + // Null, not '-': this is a number column, and it renders its own + // placeholder for an empty cell. + 'reg_id' => $user->reg_id, + 'roles' => $roles->isEmpty() ? '-' : $roles->pluck('name')->implode(', '), + 'server' => $user->server?->hostname ?? '-', + 'created_at' => $user->created_at?->format('M j, Y H:i'), + ]; + } + + /** + * @return array + */ + private function rowActions(User $user): array + { + $actions = [ + Action::link('edit', 'Edit', route('manage.users.edit', $user))->icon('pencil'), + ]; + + if (request()->user()->can('update', $user)) { + $actions[] = $this->deleteAction($user); + } + + return $actions; + } + + /** + * @return array + */ + private function recordActions(User $user): array + { + return request()->user()->can('update', $user) ? [$this->deleteAction($user)] : []; + } + + private function deleteAction(User $user): Action + { + $self = request()->user()->id === $user->id; + + return Action::delete('delete', 'Delete', route('manage.users.destroy', $user)) + ->icon('trash-2') + ->tone(Status::DANGER) + ->disabled($self ? 'You cannot delete your own account.' : null) + ->confirm( + 'Delete user', + "Deleting '{$user->name}' also removes their chat history and watch records.", + 'Delete', + ); + } + + /** + * Only active edge servers can take a viewer, which is what the Filament select + * restricted to as well. + * + * @return array + */ + private function serverOptions(): array + { + $options = [['value' => '', 'label' => 'Not assigned']]; + + foreach (Server::query() + ->where('type', ServerTypeEnum::EDGE) + ->where('status', ServerStatusEnum::ACTIVE) + ->orderBy('hostname') + ->get() as $server) { + $options[] = ['value' => $server->id, 'label' => $server->hostname]; + } + + return $options; + } + + /** + * @return array + */ + private function roleOptions(): array + { + return Role::orderByDesc('priority') + ->get() + ->map(fn (Role $role) => ['value' => $role->slug, 'label' => $role->name]) + ->all(); + } +} diff --git a/app/Http/Controllers/MessageController.php b/app/Http/Controllers/MessageController.php index 17acfff..39d72aa 100644 --- a/app/Http/Controllers/MessageController.php +++ b/app/Http/Controllers/MessageController.php @@ -5,175 +5,237 @@ use App\Events\Chat\Broadcasts\ChatMessageEvent; use App\Http\Requests\MessageRequest; use App\Models\Message; -use App\Models\Timeout; +use App\Models\User; +use App\Services\Chat\ChatModerationService; +use App\Services\Chat\ChatSettingsService; +use App\Services\Chat\MessagePresenter; use App\Services\ChatMessageSanitizer; +use App\Services\EmoteService; +use App\Support\Chat\Broadcast; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\RateLimiter; -use Illuminate\Validation\ValidationException; -use Symfony\Component\HttpFoundation\Response as SymphonyResponse; +use Symfony\Component\HttpFoundation\Response; class MessageController extends Controller { + public function __construct( + protected ChatSettingsService $settings, + protected ChatModerationService $moderation, + protected MessagePresenter $presenter, + protected EmoteService $emotes, + ) {} + public function send(MessageRequest $request) { - $message = $request->post('message'); - $sourceId = $request->post('source_id'); + /** @var User $user */ $user = $request->user(); - $maxTries = Cache::get('chat.maxTries', static fn () => config('chat.default.maxTries')); - $rateDecay = Cache::get('chat.rateDecay', static fn () => config('chat.default.rateDecay')); - $slowMode = Cache::get('chat.slowMode', static fn () => config('chat.default.slowMode')); - - // Check if user is timed out - $activeTimeout = Timeout::where('user_id', $user->id) - ->where('expires_at', '>', now()) - ->first(); - - if ($activeTimeout) { - $remainingTime = now()->diffInSeconds($activeTimeout->expires_at); - $message = "You are timed out for {$remainingTime} more seconds"; - if ($activeTimeout->reason) { - $message .= " (Reason: {$activeTimeout->reason})"; - } + $sourceId = (int) $request->post('source_id'); + $settings = $this->settings->all($sourceId); - return response([ - 'success' => false, - 'error' => 'user_timed_out', - 'message' => $message, - 'timeout' => [ - 'expires_at' => $activeTimeout->expires_at, - 'remaining_seconds' => $remainingTime, - 'reason' => $activeTimeout->reason, - ], - ], SymphonyResponse::HTTP_FORBIDDEN); + if ($blocked = $this->blockedResponse($user)) { + return $blocked; + } + + if ($settings['sponsors_only'] && ! $this->canBypassModes($user) && ! $user->hasAnyRole(['sponsor', 'supersponsor'])) { + return $this->refuse('sponsors_only', 'Chat is in sponsors-only mode right now.'); } - if ($user->cant('chat.ignore.ratelimit') && ! $user->isAdmin() && ! $user->isModerator()) { - if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $maxTries)) { - $seconds = RateLimiter::availableIn('send-message:'.$user->id); - - return response([ - 'success' => false, - 'rateLimit' => [ - 'maxTries' => $maxTries, - 'secondsLeft' => $seconds, - 'rateDecay' => $rateDecay, - 'slowMode' => $slowMode, - ], - 'error' => 'rate_limit_hit', - ], SymphonyResponse::HTTP_TOO_MANY_REQUESTS); - } - RateLimiter::hit('send-message:'.$user->id, (int) $rateDecay); + $sanitizer = new ChatMessageSanitizer; + $body = $sanitizer->sanitize((string) $request->post('message'), $user); + + if ($sanitizer->isEffectivelyEmpty($body)) { + return $this->refuse('empty_message', 'Your message is empty.'); } - // Commands are now handled via separate API endpoint - // This endpoint only handles regular messages - if (str_starts_with(trim($message), '/')) { - throw ValidationException::withMessages([ - 'message' => 'Commands should be sent via the command API endpoint.', - ]); + if ($settings['emote_only'] && ! $this->canBypassModes($user) && ! $this->isEmoteOnly($body, $user)) { + return $this->refuse('emote_only', 'Chat is in emote-only mode right now.'); } - // Sanitize message - $sanitizer = new ChatMessageSanitizer; - $message = $sanitizer->sanitize($message, $user); + if ($limited = $this->enforceRateLimit($user, $sourceId, $settings)) { + return $limited; + } - $messageModel = $user->messages()->create([ - 'message' => $message, + $this->emotes->recordUsage($body, $user); + + $message = $user->messages()->create([ + 'message' => $body, 'source_id' => $sourceId, + 'reply_to_id' => $this->resolveReplyTo($request, $sourceId), 'is_command' => false, 'type' => 'user', ]); - broadcast(new ChatMessageEvent($messageModel, $user))->toOthers(); + // Broadcast to everyone, including the sender: clients dedupe on message id, + // which keeps ordering identical for the author and everyone else. + Broadcast::send(new ChatMessageEvent($message)); return response([ 'success' => true, - 'message_id' => $messageModel->id, - 'message' => [ - 'id' => $messageModel->id, - 'name' => $user->name, - 'time' => $messageModel->created_at->format('H:i'), - 'message' => $messageModel->message, // This contains the processed message with emote tags - 'role' => $user->role, - 'chat_color' => $user->chat_color, - 'type' => $messageModel->type, - 'is_command' => false, - 'source_id' => $messageModel->source_id, - ], - 'rateLimit' => [ - 'maxTries' => $maxTries, - 'secondsLeft' => $this->getSecondsLeft($user, $slowMode, $maxTries), - 'rateDecay' => $rateDecay, - 'slowMode' => $slowMode, - ], + 'message' => $this->presenter->present($message), + 'limits' => $this->limits($user, $sourceId, $settings), ]); } public function loadOlder(Request $request) { + $request->validate([ + 'source_id' => ['required', 'integer', 'exists:sources,id'], + 'before_id' => ['nullable', 'integer'], + ]); + $user = $request->user(); - $beforeId = $request->get('before_id'); - $sourceId = $request->get('source_id'); - $limit = 50; - - $query = Message::with('user') - ->where(function ($query) use ($user) { - $query->where('is_command', false) - ->orWhere('type', 'announcement') - ->orWhere('type', 'system') - ->orWhere(fn ($q) => $q->where('is_command', true)->where('user_id', $user->id)); - }); - - // Filter by source_id if provided - if ($sourceId) { - $query->where('source_id', $sourceId); - } + $limit = (int) config('chat.history.page', 50); - if ($beforeId) { + $query = Message::with(['user', 'replyTo.user']) + ->visibleTo($user) + ->where('source_id', $request->integer('source_id')); + + if ($beforeId = $request->integer('before_id')) { $query->where('id', '<', $beforeId); } - $messages = $query->orderBy('created_at', 'desc') - ->limit($limit) - ->get() - ->reverse() - ->map(fn (Message $message) => [ - 'id' => $message->id, - 'message' => $message->message, - 'is_command' => (bool) $message->is_command, - 'name' => $message->user->name ?? null, - 'role' => $message->user?->role, - 'chat_color' => $message->user?->chat_color, - 'time' => $message->created_at->format('H:i'), - 'type' => $message->type, - 'priority' => $message->priority, - 'metadata' => $message->metadata, - 'source_id' => $message->source_id, - ]) - ->values(); + $messages = $query->orderByDesc('id')->limit($limit)->get()->reverse(); return response()->json([ - 'messages' => $messages, + 'messages' => $this->presenter->presentMany($messages), 'hasMore' => $messages->count() === $limit, ]); } - private function getSecondsLeft($user, $slowMode, $maxTries) + /** + * Delete a single message: the author's own, or anyone's for moderators. + */ + public function destroy(Request $request, Message $message) + { + $this->moderation->deleteMessage($request->user(), $message); + + return response()->json(['success' => true]); + } + + /** + * Refuse to send, with a machine-readable reason the client can react to. + */ + protected function refuse(string $error, string $message, int $status = Response::HTTP_FORBIDDEN) + { + return response(['success' => false, 'error' => $error, 'message' => $message], $status); + } + + /** + * Bans and timeouts silence a user everywhere. + */ + protected function blockedResponse(User $user) { - if ($user->can('chat.ignore.ratelimit') || $user->isAdmin() || $user->isModerator()) { - return 0; + if ($ban = $user->activeChatBan()) { + return $this->refuse('user_banned', $ban->isPermanent() + ? 'You are banned from chat.'.($ban->reason ? " Reason: {$ban->reason}" : '') + : 'You are banned from chat until '.$ban->expires_at->format('H:i').'.'); } - // If slow mode is active always return seconds left - if ($slowMode) { - return RateLimiter::availableIn('send-message:'.$user->id); + + if ($timeout = $user->activeTimeout()) { + $remaining = (int) now()->diffInSeconds($timeout->expires_at); + + return response([ + 'success' => false, + 'error' => 'user_timed_out', + 'message' => "You are timed out for {$remaining} more seconds" + .($timeout->reason ? " (Reason: {$timeout->reason})" : ''), + 'timeout' => [ + 'expires_at' => $timeout->expires_at, + 'remaining_seconds' => $remaining, + 'reason' => $timeout->reason, + ], + ], Response::HTTP_FORBIDDEN); } - // In any other case only return if the rate limiter is now hit. - if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $maxTries)) { - return RateLimiter::availableIn('send-message:'.$user->id); + return null; + } + + /** + * Slow mode allows one message per interval; otherwise the burst limiter applies. + * + * @param array $settings + */ + protected function enforceRateLimit(User $user, int $sourceId, array $settings) + { + if ($this->canBypassModes($user)) { + return null; + } + + $key = $this->rateKey($user, $sourceId); + $slowMode = (int) $settings['slow_mode_seconds']; + $maxTries = $slowMode > 0 ? 1 : (int) $settings['max_tries']; + $decay = $slowMode > 0 ? $slowMode : (int) $settings['rate_decay']; + + if (RateLimiter::tooManyAttempts($key, $maxTries)) { + $seconds = RateLimiter::availableIn($key); + + return response([ + 'success' => false, + 'error' => 'rate_limit_hit', + 'message' => $slowMode > 0 + ? "Slow mode is on. You can chat again in {$seconds}s." + : "You are sending messages too quickly. Try again in {$seconds}s.", + 'limits' => array_merge($this->limits($user, $sourceId, $settings), ['seconds_left' => $seconds]), + ], Response::HTTP_TOO_MANY_REQUESTS); + } + + RateLimiter::hit($key, $decay); + + return null; + } + + /** + * @param array $settings + * @return array + */ + protected function limits(User $user, int $sourceId, array $settings): array + { + $slowMode = (int) $settings['slow_mode_seconds']; + $bypass = $this->canBypassModes($user); + + return [ + 'slow_mode_seconds' => $slowMode, + 'max_tries' => $slowMode > 0 ? 1 : (int) $settings['max_tries'], + 'rate_decay' => $slowMode > 0 ? $slowMode : (int) $settings['rate_decay'], + 'seconds_left' => $bypass ? 0 : RateLimiter::availableIn($this->rateKey($user, $sourceId)), + 'can_bypass' => $bypass, + ]; + } + + protected function rateKey(User $user, int $sourceId): string + { + return "send-message:{$user->id}:{$sourceId}"; + } + + protected function canBypassModes(User $user): bool + { + return $user->hasPermission('chat.ignore.ratelimit') || $user->canModerateChat(); + } + + /** + * True when a message is nothing but emotes the user can actually send. + */ + protected function isEmoteOnly(string $body, User $user): bool + { + $available = $this->emotes->getAvailableEmotes($user); + + $stripped = preg_replace_callback( + '/:([a-z0-9_]+):/i', + fn (array $matches) => isset($available[strtolower($matches[1])]) ? '' : $matches[0], + $body, + ); + + return trim($stripped) === ''; + } + + protected function resolveReplyTo(Request $request, int $sourceId): ?int + { + $replyToId = $request->integer('reply_to_id'); + + if (! $replyToId) { + return null; } - return 0; + return Message::where('id', $replyToId)->where('source_id', $sourceId)->value('id'); } } diff --git a/app/Http/Controllers/RecordingController.php b/app/Http/Controllers/RecordingController.php index 7ba6332..92400e3 100644 --- a/app/Http/Controllers/RecordingController.php +++ b/app/Http/Controllers/RecordingController.php @@ -5,51 +5,177 @@ use App\Models\Recording; use App\Models\Show; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Inertia\Inertia; class RecordingController extends Controller { + /** + * Archive landing page: one collection per convention year. + * + * Searching switches the page from collections to flat results, because when you + * are hunting for one show, year boundaries only get in the way. + */ public function index(Request $request) { $user = Auth::user(); $search = $request->get('search'); - // Get published recordings with optional search (filtered by access) - $recordingsQuery = Recording::where('is_published', true) - ->accessibleBy($user); + $recordings = $this->publishedRecordings($user, $search) + ->orderBy('date', 'desc') + ->get(); + + $collections = $recordings + ->groupBy(fn (Recording $recording) => $recording->date?->year ?? 0) + ->map(fn ($yearRecordings, $year) => [ + 'year' => (int) $year, + 'count' => $yearRecordings->count(), + 'total_views' => (int) $yearRecordings->sum('views'), + // Runtime in hours reads better than a raw second count on a card. + 'hours' => (int) round($yearRecordings->sum('duration') / 3600), + 'first_date' => $yearRecordings->min('date'), + 'last_date' => $yearRecordings->max('date'), + // Poster art: the most watched recording of the year that actually has a still. + 'thumbnail_url' => $yearRecordings + ->sortByDesc('views') + ->first(fn (Recording $recording) => (bool) $recording->thumbnail_url) + ?->thumbnail_url, + 'highlights' => $yearRecordings + ->sortByDesc('views') + ->take(3) + ->pluck('title') + ->values(), + ]) + ->sortByDesc('year') + ->values(); + + // Shows still processing sit in the same grid as everything else, newest first, + // as dimmed tiles. They are not their own section: a viewer looking for a show + // should find it where they expect it, told it is not ready yet. + $withPending = $this->withPendingTiles($recordings, $this->pendingTiles($search)); + + return Inertia::render('Archive/Index', [ + 'collections' => $collections, + 'recentRecordings' => $withPending->take(8)->values(), + 'searchResults' => $search ? $withPending->values() : null, + 'totalRecordings' => $recordings->count(), + 'search' => $search, + ]); + } + + /** + * One year's collection, laid out like the browse grid. + */ + public function year(Request $request, int $year) + { + $user = Auth::user(); + $search = $request->get('search'); + + $recordings = $this->publishedRecordings($user, $search) + ->whereYear('date', $year) + ->orderBy('date', 'desc') + ->get(); + + // Shows that ended but whose recording has not been published yet, so the + // year does not look like it is missing something without explanation. + $pending = $this->pendingTiles($search, $year); + + if ($recordings->isEmpty() && $pending->isEmpty() && ! $search) { + abort(404); + } + + $years = Recording::where('is_published', true) + ->accessibleBy($user) + ->orderBy('date', 'desc') + ->get() + ->map(fn (Recording $recording) => $recording->date?->year) + ->filter() + ->unique() + ->sortDesc() + ->values(); + + return Inertia::render('Archive/Year', [ + 'year' => $year, + 'years' => $years, + 'recordings' => $this->withPendingTiles($recordings, $pending)->values(), + 'totalViews' => (int) $recordings->sum('views'), + 'hours' => (int) round($recordings->sum('duration') / 3600), + 'search' => $search, + ]); + } + + private function publishedRecordings($user, ?string $search) + { + $query = Recording::where('is_published', true)->accessibleBy($user); if ($search) { - $recordingsQuery->where(function ($query) use ($search) { + $query->where(function ($query) use ($search) { $query->where('title', 'like', '%'.$search.'%') ->orWhere('description', 'like', '%'.$search.'%'); }); } - $recordings = $recordingsQuery->orderBy('date', 'desc')->get(); + return $query; + } - // Get pending recordings (shows that are recordable but don't have a recording yet) - $pendingShowsQuery = Show::where('recordable', true) + private function pendingShows(?string $search) + { + // Shows the audience was promised would be available afterwards, but which have + // not been published yet. `announce_recording` is the promise; it says nothing + // about capture, which happens for every source unconditionally. + $query = Show::where('announce_recording', true) ->where('status', 'ended') - ->doesntHave('recording'); + ->whereDoesntHave('recordings', fn ($q) => $q->where('is_published', true)); if ($search) { - $pendingShowsQuery->where(function ($query) use ($search) { + $query->where(function ($query) use ($search) { $query->where('title', 'like', '%'.$search.'%') ->orWhere('description', 'like', '%'.$search.'%'); }); } - $pendingShows = $pendingShowsQuery + return $query ->orderBy('actual_end', 'desc') ->orderBy('scheduled_end', 'desc') ->get(); + } - return Inertia::render('Recordings', [ - 'recordings' => $recordings, - 'pendingShows' => $pendingShows, - 'search' => $search, - ]); + /** + * Pending shows shaped like recordings, so the same tile renders both. `is_pending` + * is what the tile keys off to dim itself and drop its link. + */ + private function pendingTiles(?string $search, ?int $year = null): Collection + { + return $this->pendingShows($search) + ->filter(fn (Show $show) => $year === null + || ($show->actual_end ?? $show->scheduled_end)?->year === $year) + // Dates go out as ISO strings, matching how the models serialise theirs, so + // the merged list sorts on one comparable type. + ->map(fn (Show $show) => [ + 'id' => 'pending-'.$show->id, + 'title' => $show->title, + 'description' => $show->description, + 'description_html' => $show->description_html, + 'date' => ($show->actual_end ?? $show->scheduled_end)?->toJSON(), + 'thumbnail_url' => null, + 'duration' => null, + 'views' => 0, + 'is_pending' => true, + ]) + ->values(); + } + + /** + * One date-ordered list of published recordings and pending tiles. + */ + private function withPendingTiles(Collection $recordings, Collection $pending): Collection + { + return $recordings + ->map(fn (Recording $recording) => $recording->toArray() + ['is_pending' => false]) + ->concat($pending) + ->sortByDesc('date') + ->values(); } public function show(Recording $recording) diff --git a/app/Http/Controllers/RecordingPlaylistController.php b/app/Http/Controllers/RecordingPlaylistController.php new file mode 100644 index 0000000..4b99cc7 --- /dev/null +++ b/app/Http/Controllers/RecordingPlaylistController.php @@ -0,0 +1,82 @@ +authorized($slug); + + return $this->playlist($this->playlists->renderMaster($recording)); + } + + public function media(Request $request, string $slug, string $rendition) + { + $recording = $this->authorized($slug); + + if (! in_array($rendition, $this->playlists->renditions(), true)) { + abort(404); + } + + try { + $body = $this->playlists->renderMedia($recording, $rendition); + } catch (\Throwable $e) { + // A cut whose segments have expired out of the archive, most likely. + abort(Response::HTTP_GONE, $e->getMessage()); + } + + return $this->playlist($body); + } + + /** + * Unpublished recordings are invisible to everyone but an operator who may edit them, + * so a draft can be previewed in the manage panel before it goes out. + */ + protected function authorized(string $slug): Recording + { + $recording = Recording::where('slug', $slug)->firstOrFail(); + $user = Auth::user(); + + if (! $recording->is_published) { + abort_unless($user?->can('update', $recording), 404); + + return $recording; + } + + abort_unless($recording->canBeAccessedBy($user), 403); + + return $recording; + } + + protected function playlist(string $body) + { + return response($body, 200, [ + 'Content-Type' => 'application/vnd.apple.mpegurl', + // The URLs inside are signed and time-limited, so a cached copy would hand + // out credentials and would also outlive them. + 'Cache-Control' => 'private, no-store', + ]); + } +} diff --git a/app/Http/Controllers/ScheduleController.php b/app/Http/Controllers/ScheduleController.php new file mode 100644 index 0000000..8f0d25a --- /dev/null +++ b/app/Http/Controllers/ScheduleController.php @@ -0,0 +1,95 @@ +accessibleBy($user) + ->whereNotNull('scheduled_start') + ->whereIn('status', ['scheduled', 'live', 'ended']) + // Today through the next six days, plus anything still on air that + // started before midnight so a long show never drops off the guide. + ->where(function ($query) { + $query->whereBetween('scheduled_start', [ + now()->startOfDay(), + now()->addDays(6)->endOfDay(), + ])->orWhere(function ($query) { + $query->where('status', 'live') + ->where('scheduled_start', '>=', now()->subDay()); + }); + }) + ->orderBy('scheduled_start') + ->get(); + + // Channel order follows source priority so the primary channel is the top row. + $sourceOrder = Source::ordered()->pluck('id')->values()->all(); + + $days = $shows + ->groupBy(fn (Show $show) => $show->scheduled_start->toDateString()) + ->map(function ($dayShows, $date) use ($sourceOrder) { + $day = $dayShows->first()->scheduled_start->copy()->startOfDay(); + + $channels = $dayShows + ->groupBy('source_id') + ->map(fn ($channelShows) => [ + 'id' => $channelShows->first()->source_id, + 'name' => $channelShows->first()->source?->name ?? 'Unassigned', + 'shows' => $channelShows + ->sortBy('scheduled_start') + ->map(fn (Show $show) => [ + 'id' => $show->id, + 'title' => $show->title, + 'slug' => $show->slug, + 'status' => $show->status, + 'scheduled_start' => $show->scheduled_start->toIso8601String(), + // Blocks without an end time get a one hour default so the + // grid still has something to span. + 'scheduled_end' => ($show->scheduled_end ?? $show->scheduled_start->copy()->addHour())->toIso8601String(), + 'viewer_count' => $show->viewer_count, + 'is_restricted' => $show->hasAccessRestriction(), + // Tells a viewer they can catch this later if they miss + // it. Purely a promise: capture happens for every source + // regardless, and whether a recording actually appears is + // decided by publishing it. + 'will_be_available' => (bool) $show->announce_recording, + ]) + ->values(), + ]) + ->sortBy(fn ($channel) => array_search($channel['id'], $sourceOrder, true) === false + ? PHP_INT_MAX + : array_search($channel['id'], $sourceOrder, true)) + ->values(); + + return [ + 'date' => $date, + 'label' => $day->isToday() ? 'Today' : $day->format('D j M'), + 'sub_label' => $day->format('D j M'), + 'is_today' => $day->isToday(), + 'channels' => $channels, + ]; + }) + ->sortBy('date') + ->values(); + + return Inertia::render('Schedule', [ + 'days' => $days, + 'primaryChannel' => Source::ordered()->first()?->name, + 'currentTime' => now()->toIso8601String(), + ]); + } +} diff --git a/app/Http/Controllers/StreamController.php b/app/Http/Controllers/StreamController.php index ba08fdd..2b54e23 100644 --- a/app/Http/Controllers/StreamController.php +++ b/app/Http/Controllers/StreamController.php @@ -8,6 +8,9 @@ use App\Models\Show; use App\Models\Source; use App\Models\User; +use App\Services\Chat\ChatSettingsService; +use App\Services\Chat\MessagePresenter; +use App\Services\PlaybackTokenService; use App\Services\StreamInfoService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -16,12 +19,109 @@ class StreamController extends Controller { + /** + * Backlog plus live chat state for a source, shared by the player and the popout. + * + * A guest (only possible when login is optional) reads along but has no + * limits, timeout or ban of their own, so those come back empty and the + * client renders a sign-in prompt in place of the composer. + * + * @return array + */ + protected function chatProps(?int $sourceId, ?User $user): array + { + if (! config('chat.enabled')) { + return $this->emptyChatProps(); + } + + $messages = Message::with(['user', 'replyTo.user']) + ->visibleTo($user) + ->where('source_id', $sourceId) + ->orderByDesc('id') + ->limit((int) config('chat.history.initial', 60)) + ->get() + ->reverse(); + + $settings = app(ChatSettingsService::class)->all($sourceId); + + if (! $user) { + return [ + 'chatMessages' => app(MessagePresenter::class)->presentMany($messages), + 'chatSettings' => $settings, + 'chatState' => [ + 'limits' => [ + 'slow_mode_seconds' => (int) $settings['slow_mode_seconds'], + 'max_tries' => (int) $settings['max_tries'], + 'rate_decay' => (int) $settings['rate_decay'], + 'seconds_left' => 0, + 'can_bypass' => false, + ], + 'timeout' => null, + 'ban' => null, + ], + ]; + } + + $canBypass = $user->canModerateChat() || $user->hasPermission('chat.ignore.ratelimit'); + $slowMode = (int) $settings['slow_mode_seconds']; + $timeout = $user->activeTimeout(); + $ban = $user->activeChatBan(); + + return [ + 'chatMessages' => app(MessagePresenter::class)->presentMany($messages), + 'chatSettings' => $settings, + 'chatState' => [ + 'limits' => [ + 'slow_mode_seconds' => $slowMode, + 'max_tries' => $slowMode > 0 ? 1 : (int) $settings['max_tries'], + 'rate_decay' => $slowMode > 0 ? $slowMode : (int) $settings['rate_decay'], + 'seconds_left' => $canBypass ? 0 : RateLimiter::availableIn("send-message:{$user->id}:{$sourceId}"), + 'can_bypass' => $canBypass, + ], + 'timeout' => $timeout ? [ + 'seconds_remaining' => (int) now()->diffInSeconds($timeout->expires_at), + 'reason' => $timeout->reason, + ] : null, + 'ban' => $ban ? [ + 'permanent' => $ban->isPermanent(), + 'expires_at' => $ban->expires_at?->toIso8601String(), + 'reason' => $ban->reason, + ] : null, + ], + ]; + } + + /** + * What the player gets when chat is switched off, so the page shape stays + * the same and the client only has to check one flag. + * + * @return array + */ + protected function emptyChatProps(): array + { + return [ + 'chatMessages' => [], + 'chatSettings' => app(ChatSettingsService::class)->all(null), + 'chatState' => [ + 'limits' => [ + 'slow_mode_seconds' => 0, + 'max_tries' => 0, + 'rate_decay' => 0, + 'seconds_left' => 0, + 'can_bypass' => false, + ], + 'timeout' => null, + 'ban' => null, + ], + ]; + } + /** * Shows grid - main landing page */ public function index() { - /** @var User $user */ + /** @var User|null $user */ $user = Auth::user(); // Get live shows (filtered by access) @@ -39,6 +139,7 @@ public function index() 'title' => $show->title, 'slug' => $show->slug, 'description' => $show->description, + 'description_html' => $show->description_html, 'source' => $show->source ? $show->source->name : null, 'status' => $show->status, 'thumbnail_url' => $show->thumbnail_url, @@ -62,6 +163,7 @@ public function index() 'title' => $show->title, 'slug' => $show->slug, 'description' => $show->description, + 'description_html' => $show->description_html, 'source' => $show->source ? $show->source->name : null, 'status' => 'starting_soon', // Override status to indicate starting soon 'thumbnail_url' => $show->thumbnail_url, @@ -85,6 +187,7 @@ public function index() 'title' => $show->title, 'slug' => $show->slug, 'description' => $show->description, + 'description_html' => $show->description_html, 'source' => $show->source ? $show->source->name : null, 'status' => $show->status, 'thumbnail_url' => $show->thumbnail_url, @@ -96,11 +199,13 @@ public function index() // Get popular recordings (prefer latest year, then by views) // First, find the most recent year with recordings + // Ordering by `date` and reading the year off the model keeps this working on + // both MySQL and Postgres; YEAR() is MySQL-only. $latestYear = Recording::accessibleBy($user) ->where('is_published', true) - ->selectRaw('YEAR(date) as year') - ->orderBy('year', 'desc') + ->orderBy('date', 'desc') ->first() + ?->date ?->year; $popularRecordings = collect(); @@ -127,21 +232,219 @@ public function index() }); } + // Archive: everything that already happened, newest first. The browse page + // shows a slice of it inline so the grid never looks empty between shows; + // the full list lives on the archive page. + $archiveRecordings = Recording::accessibleBy($user) + ->where('is_published', true) + ->orderBy('date', 'desc') + ->limit(12) + ->get() + ->map(fn ($recording) => $this->mapRecording($recording)); + + $archiveTotal = Recording::accessibleBy($user) + ->where('is_published', true) + ->count(); + + $primarySource = Source::ordered()->first(); + $featured = $this->resolveFeaturedShow($user, $primarySource); + + // Channel chips: only sources that actually have something in the grid. + $channels = $liveShows->concat($startingSoonShows)->concat($upcomingShows) + ->pluck('source') + ->filter() + ->unique() + ->values(); + return Inertia::render('ShowsGrid', [ 'liveShows' => $liveShows, 'startingSoonShows' => $startingSoonShows, 'upcomingShows' => $upcomingShows, 'popularRecordings' => $popularRecordings, + 'archiveRecordings' => $archiveRecordings, + 'archiveTotal' => $archiveTotal, + 'featured' => $featured, + 'featuredChat' => $this->featuredChatExcerpt($user, $featured), + 'primaryChannel' => $primarySource?->name, + 'channels' => $channels, 'currentTime' => now()->toIso8601String(), ]); } + /** + * Resolve the featured show for the stage hero. + * + * The primary channel (highest source priority, e.g. Prime) always owns the + * hero: live if it is on air, otherwise its next scheduled show. Only when that + * channel has nothing at all do we fall back to the busiest live show. + */ + private function resolveFeaturedShow(?User $user, ?Source $primarySource): ?array + { + $show = null; + + if ($primarySource) { + $show = Show::with('source') + ->accessibleBy($user) + ->where('source_id', $primarySource->id) + ->where('status', 'live') + ->orderBy('viewer_count', 'desc') + ->first() + ?? Show::with('source') + ->accessibleBy($user) + ->where('source_id', $primarySource->id) + ->scheduled() + ->where('scheduled_start', '>=', now()->subHours(2)) + ->orderBy('scheduled_start') + ->first(); + } + + $show ??= Show::with('source') + ->accessibleBy($user) + ->where('status', 'live') + ->orderBy('viewer_count', 'desc') + ->first(); + + if (! $show) { + return null; + } + + $upNext = Show::with('source') + ->accessibleBy($user) + ->where('source_id', $show->source_id) + ->where('id', '!=', $show->id) + ->scheduled() + ->where('scheduled_start', '>=', now()) + ->orderBy('scheduled_start') + ->first(); + + return [ + 'id' => $show->id, + 'title' => $show->title, + 'slug' => $show->slug, + 'description' => $show->description, + 'description_html' => $show->description_html, + 'source' => $show->source?->name, + 'source_id' => $show->source_id, + 'status' => $show->status, + 'thumbnail_url' => $show->thumbnail_url, + 'hls_url' => $show->status === 'live' ? $show->getHlsUrl() : null, + 'viewer_count' => $show->viewer_count, + 'started_at' => $show->actual_start ?? $show->scheduled_start, + 'scheduled_start' => $show->scheduled_start, + 'scheduled_end' => $show->scheduled_end, + 'is_restricted' => $show->hasAccessRestriction(), + 'is_primary_channel' => $primarySource && $show->source_id === $primarySource->id, + 'up_next' => $upNext ? [ + 'title' => $upNext->title, + 'slug' => $upNext->slug, + 'scheduled_start' => $upNext->scheduled_start, + ] : null, + ]; + } + + /** + * The last few chat lines for the featured channel. + * + * Chat is keyed by source, not by show, so the excerpt follows the channel and + * survives a show ending mid-conversation. + */ + private function featuredChatExcerpt(?User $user, ?array $featured): array + { + if (! config('chat.enabled')) { + return ['source_id' => null, 'messages' => []]; + } + + if (! $featured || ! ($featured['source_id'] ?? null)) { + return ['source_id' => null, 'messages' => []]; + } + + $messages = Message::with(['user', 'replyTo.user']) + ->visibleTo($user) + ->where('source_id', $featured['source_id']) + ->orderByDesc('id') + ->limit((int) config('chat.history.excerpt', 8)) + ->get() + ->reverse(); + + return [ + 'source_id' => $featured['source_id'], + 'messages' => app(MessagePresenter::class)->presentMany($messages), + ]; + } + + /** + * Shape a recording for the browse grid. + */ + private function mapRecording(Recording $recording): array + { + return [ + 'id' => $recording->id, + 'title' => $recording->title, + 'slug' => $recording->slug, + 'description' => $recording->description, + 'date' => $recording->date, + 'duration' => $recording->duration, + 'formatted_duration' => $recording->formatted_duration, + 'thumbnail_url' => $recording->thumbnail_url, + 'views' => $recording->views, + 'is_restricted' => $recording->hasAccessRestriction(), + ]; + } + + /** + * Mint a playback token for this page render. + * + * Issued alongside the existing streamkey and not yet consumed by the + * player; the edges do not enforce it until njs verification lands. Callers + * must already have checked canBeAccessedBy(), because the source binding on + * the token is what carries that decision to the edge. + * + * The edge claim reads the user's current assignment without triggering one, + * so this stays free of side effects. It is replaced by a capacity-weighted + * pick once server assignment comes out of the users table. + * + * See docs/streaming-auth-redesign.md. + */ + private function playbackProps(?User $user, Show $show): ?array + { + if (! $show->source) { + return null; + } + + // A guest only gets here when login is optional, and only for a show + // canBeAccessedBy() already cleared, which for them means an + // unrestricted one. + if (! $user && config('auth.required')) { + return null; + } + + $tokens = app(PlaybackTokenService::class); + + // No secret configured yet means this whole path is inert, which is the + // expected state until the edges are ready to verify. + if (! $tokens->isConfigured()) { + return null; + } + + return [ + 'token' => $user + ? $tokens->issueViewer( + user: $user, + source: $show->source, + edge: $user->server?->hostname, + ) + : $tokens->issueGuest(source: $show->source), + 'expires_in' => $tokens->ttl(), + 'refresh_after' => $tokens->refreshAfter(), + ]; + } + public function external(Request $request, Show $show) { // Load show with source relationship $show->load('source'); - /** @var User $user */ + /** @var User|null $user */ $user = Auth::user(); // Check access restrictions @@ -172,17 +475,19 @@ public function external(Request $request, Show $show) 'title' => $show->title, 'slug' => $show->slug, 'description' => $show->description, + 'description_html' => $show->description_html, 'source' => $show->source ? $show->source->name : null, 'status' => $show->status, 'can_watch' => $show->canWatch(), 'hls_url' => $hlsUrl, ], + 'playback' => $this->playbackProps($user, $show), ]); } public function show(Request $request, Show $show) { - /** @var User $user */ + /** @var User|null $user */ $user = Auth::user(); // Load show with source relationship @@ -215,6 +520,10 @@ public function show(Request $request, Show $show) 'source' => $s->source ? $s->source->name : null, 'status' => $s->status, 'scheduled_start' => $s->scheduled_start, + // The same tile as the browse grid renders these, so without them + // every "other live show" below the player is a blank placeholder. + 'thumbnail_url' => $s->thumbnail_url, + 'viewer_count' => $s->viewer_count, 'can_watch' => $s->canWatch(), 'is_restricted' => $s->hasAccessRestriction(), ]; @@ -230,6 +539,7 @@ public function show(Request $request, Show $show) 'title' => $show->title, 'slug' => $show->slug, 'description' => $show->description, + 'description_html' => $show->description_html, 'source' => $show->source ? [ 'id' => $show->source->id, 'name' => $show->source->name, @@ -246,41 +556,12 @@ public function show(Request $request, Show $show) ], 'availableShows' => $availableShows, 'initialHlsUrl' => $hlsUrl, + 'playback' => $this->playbackProps($user, $show), 'initialStatus' => $show->isLive() ? 'online' : \Cache::get('stream.status', static fn () => StreamStatusEnum::OFFLINE->value), 'initialListeners' => $show->viewer_count ?? StreamInfoService::getUserCount(), 'initialOtherDevice' => false, // This feature has been removed with Client model 'sourceId' => $show->source_id, - 'chatMessages' => array_values(Message::with('user') - ->where('source_id', $show->source_id) - ->where(function ($query) use ($user) { - $query->where('is_command', false) - ->orWhere('type', 'announcement') - ->orWhere('type', 'system') - ->orWhere(fn ($q) => $q->where('is_command', true)->where('user_id', $user->id)); // show users own commands - }) - ->orderBy('created_at', 'desc') - ->limit(50) - ->get() - ->reverse() - ->map(fn (Message $message) => [ - 'id' => $message->id, - 'message' => $message->message, - 'is_command' => (bool) $message->is_command, - 'name' => $message->user->name ?? null, - 'role' => $message->user?->role, - 'chat_color' => $message->user?->chat_color, - 'time' => $message->created_at->format('H:i'), - 'type' => $message->type, - 'priority' => $message->priority, - 'metadata' => $message->metadata, - 'source_id' => $message->source_id, - ])->toArray()), - 'rateLimit' => [ - 'maxTries' => \Cache::get('chat.maxTries', static fn () => config('chat.default.maxTries')), - 'rateDecay' => \Cache::get('chat.rateDecay', static fn () => config('chat.default.rateDecay')), - 'slowMode' => \Cache::get('chat.slowMode', static fn () => config('chat.default.slowMode')), - 'secondsLeft' => (! $user->isStaff()) ? RateLimiter::availableIn('send-message:'.$user->id) : 0, - ], + ...$this->chatProps($show->source_id, $user), ]); } @@ -289,7 +570,7 @@ public function show(Request $request, Show $show) */ public function chat(Request $request, Show $show) { - /** @var User $user */ + /** @var User|null $user */ $user = Auth::user(); // Load show with source relationship @@ -313,37 +594,7 @@ public function chat(Request $request, Show $show) 'status' => $show->status, ], 'sourceId' => $show->source_id, - 'chatMessages' => array_values(Message::with('user') - ->where('source_id', $show->source_id) - ->where(function ($query) use ($user) { - $query->where('is_command', false) - ->orWhere('type', 'announcement') - ->orWhere('type', 'system') - ->orWhere(fn ($q) => $q->where('is_command', true)->where('user_id', $user->id)); - }) - ->orderBy('created_at', 'desc') - ->limit(50) - ->get() - ->reverse() - ->map(fn (Message $message) => [ - 'id' => $message->id, - 'message' => $message->message, - 'is_command' => (bool) $message->is_command, - 'name' => $message->user->name ?? null, - 'role' => $message->user?->role, - 'chat_color' => $message->user?->chat_color, - 'time' => $message->created_at->format('H:i'), - 'type' => $message->type, - 'priority' => $message->priority, - 'metadata' => $message->metadata, - 'source_id' => $message->source_id, - ])->toArray()), - 'rateLimit' => [ - 'maxTries' => \Cache::get('chat.maxTries', static fn () => config('chat.default.maxTries')), - 'rateDecay' => \Cache::get('chat.rateDecay', static fn () => config('chat.default.rateDecay')), - 'slowMode' => \Cache::get('chat.slowMode', static fn () => config('chat.default.slowMode')), - 'secondsLeft' => (! $user->isStaff()) ? RateLimiter::availableIn('send-message:'.$user->id) : 0, - ], + ...$this->chatProps($show->source_id, $user), ]); } } diff --git a/app/Http/Controllers/TestChatController.php b/app/Http/Controllers/TestChatController.php deleted file mode 100644 index c506633..0000000 --- a/app/Http/Controllers/TestChatController.php +++ /dev/null @@ -1,57 +0,0 @@ - [ - [ - 'name' => 'timeout', - 'description' => 'Timeout a user from chatting', - 'syntax' => '/timeout "username" "duration"', - 'parameters' => [ - ['name' => 'username', 'description' => 'The user to timeout', 'required' => true], - ['name' => 'duration', 'description' => 'Duration (e.g., 5s, 5m, 5h)', 'required' => true] - ], - 'aliases' => [] - ], - [ - 'name' => 'slowmode', - 'description' => 'Enable or disable slow mode', - 'syntax' => '/slowmode on|off [seconds]', - 'aliases' => ['slow'] - ], - [ - 'name' => 'delete', - 'description' => 'Delete messages', - 'syntax' => '/delete "username" [count]', - 'aliases' => [] - ], - [ - 'name' => 'broadcast', - 'description' => 'Send a system broadcast', - 'syntax' => '/broadcast "message"', - 'aliases' => [] - ], - [ - 'name' => 'badge', - 'description' => 'Manage user badges', - 'syntax' => '/badge ', - 'aliases' => [] - ] - ], - 'rateLimit' => [ - 'secondsLeft' => 0, - 'maxTries' => 10, - 'rateDecay' => 60, - 'slowMode' => false - ] - ]); - } -} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4028934..587da55 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -57,7 +57,10 @@ class Kernel extends HttpKernel */ protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, + // Authenticates only when config('auth.required') is on; see config/auth.php. + 'auth.optional' => \App\Http\Middleware\AuthenticateIfRequired::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'chat.enabled' => \App\Http\Middleware\EnsureChatIsEnabled::class, 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index d4ef644..96b7985 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -12,6 +12,12 @@ class Authenticate extends Middleware */ protected function redirectTo(Request $request): ?string { + \Illuminate\Support\Facades\Log::info('AUTH DEBUG unauthenticated redirect', [ + 'url' => $request->fullUrl(), + 'session_id' => $request->hasSession() ? $request->session()->getId() : null, + 'session_keys' => $request->hasSession() ? array_keys($request->session()->all()) : [], + ]); + return $request->expectsJson() ? null : route('login'); } } diff --git a/app/Http/Middleware/AuthenticateIfRequired.php b/app/Http/Middleware/AuthenticateIfRequired.php new file mode 100644 index 0000000..7faa868 --- /dev/null +++ b/app/Http/Middleware/AuthenticateIfRequired.php @@ -0,0 +1,28 @@ +header('X-Recording-Api-Key') ?: $request->get('api_key'); - + // Get the expected API key from environment $expectedApiKey = config('app.recording_api_key', env('RECORDING_API_KEY')); - + // Check if API key matches if (empty($expectedApiKey) || $apiKey !== $expectedApiKey) { return response()->json([ 'error' => 'Unauthorized', - 'message' => 'Invalid or missing API key' + 'message' => 'Invalid or missing API key', ], 401); } return $next($request); } -} \ No newline at end of file +} diff --git a/app/Http/Middleware/CheckSharedSecretMiddleware.php b/app/Http/Middleware/CheckSharedSecretMiddleware.php index 11695e0..da014b1 100644 --- a/app/Http/Middleware/CheckSharedSecretMiddleware.php +++ b/app/Http/Middleware/CheckSharedSecretMiddleware.php @@ -16,7 +16,7 @@ public function handle(Request $request, Closure $next) { // Check for shared secret in header first, then fall back to request parameter $sharedSecret = $request->header('X-Shared-Secret') ?: $request->get('shared_secret'); - + $server = Server::where('shared_secret', $sharedSecret)->first(); // Throw auth exception if (is_null($server)) { diff --git a/app/Http/Middleware/EnsureAttendeeHasTicketMiddleware.php b/app/Http/Middleware/EnsureAttendeeHasTicketMiddleware.php index d5080b1..6bbb60a 100644 --- a/app/Http/Middleware/EnsureAttendeeHasTicketMiddleware.php +++ b/app/Http/Middleware/EnsureAttendeeHasTicketMiddleware.php @@ -63,6 +63,7 @@ public function abort(Request $request, Closure $next) // Always assign Digital Pass role to authenticated users without tickets // This ensures they can still chat and view streams Auth::user()->assignRole('Digital Pass'); + return $next($request); } } diff --git a/app/Http/Middleware/EnsureChatIsEnabled.php b/app/Http/Middleware/EnsureChatIsEnabled.php new file mode 100644 index 0000000..862db73 --- /dev/null +++ b/app/Http/Middleware/EnsureChatIsEnabled.php @@ -0,0 +1,23 @@ +user(); + $chatEnabled = (bool) config('chat.enabled'); $chatCommands = []; $chatConfig = []; - $emotes = []; + $emotes = ['map' => (object) [], 'list' => []]; + $chatPermissions = []; - if ($user) { + if ($user && $chatEnabled) { // Use new CommandRegistry for commands $commandRegistry = app(CommandRegistry::class); $availableCommands = $commandRegistry->availableFor($user); @@ -55,31 +58,53 @@ public function share(Request $request): array $chatConfig = [ 'maxMessageLength' => $sanitizer->getMaxLength(), 'allowedDomains' => $sanitizer->getAllowedDomains(), + 'bufferSize' => (int) config('chat.history.buffer', 300), ]; - // Get emotes available for user - $emoteService = app(\App\Services\EmoteService::class); - $emotes = [ - 'available' => $emoteService->getAvailableEmotes($user), - 'global' => $emoteService->getGlobalEmotes(), - 'favorites' => $emoteService->getUserFavorites($user), - ]; + $emotes = app(\App\Services\EmoteService::class)->clientPayload($user); + $chatPermissions = [ + 'moderate' => $user->canModerateChat(), + 'ban' => $user->canBanFromChat(), + 'announce' => $user->canModerateChat() || $user->hasPermission('chat.broadcast'), + 'bypass_limits' => $user->canModerateChat() || $user->hasPermission('chat.ignore.ratelimit'), + ]; } return array_merge(parent::share($request), [ + 'flash' => [ + 'status' => fn () => $request->session()->get('status'), + ], + 'branding' => app(BrandingService::class)->forFrontend(), + // Deployment-wide switches the client needs to know about: whether + // chat exists at all, and whether a guest is allowed to browse + // without signing in. Both come from the env, see config/chat.php + // and config/auth.php. + 'features' => [ + 'chat' => $chatEnabled, + 'authRequired' => (bool) config('auth.required'), + 'loginUrl' => route('login'), + ], 'auth' => [ 'user' => $user ? array_merge( $user->only('id', 'name', 'role'), - ['is_staff' => $user->isStaff()] + [ + 'is_staff' => $user->isStaff(), + 'chat_color' => $user->chat_color, + 'badges' => $user->chatBadges(), + ] ) : null, + 'can_access_manage' => $user ? \Illuminate\Support\Facades\Gate::forUser($user)->allows('access-manage') : false, + // Kept until /admin is removed; see docs/admin/rebuild-plan.md part 5. 'can_access_filament' => $user?->can('filament.access'), 'has_server_assignment' => $user ? ($user->server_id && $user->streamkey ? true : false) : false, ], 'chat' => [ + 'enabled' => $chatEnabled, 'commands' => $chatCommands, 'config' => $chatConfig, 'emotes' => $emotes, + 'permissions' => $chatPermissions, ], ]); } diff --git a/app/Http/Middleware/LocalOnly.php b/app/Http/Middleware/LocalOnly.php new file mode 100644 index 0000000..31e685a --- /dev/null +++ b/app/Http/Middleware/LocalOnly.php @@ -0,0 +1,25 @@ +isLocal(), 404); + + return $next($request); + } +} diff --git a/app/Http/Middleware/ShareManageProps.php b/app/Http/Middleware/ShareManageProps.php new file mode 100644 index 0000000..2de6614 --- /dev/null +++ b/app/Http/Middleware/ShareManageProps.php @@ -0,0 +1,28 @@ + fn () => app(Navigation::class)->groups(), + 'manageStatus' => fn () => app(Overview::class)->statusStrip(), + ]); + + return $next($request); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 58a7fed..a4b990b 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - 'hls/*' + 'hls/*', ]; } diff --git a/app/Http/Requests/Manage/EmoteRequest.php b/app/Http/Requests/Manage/EmoteRequest.php new file mode 100644 index 0000000..ff87e42 --- /dev/null +++ b/app/Http/Requests/Manage/EmoteRequest.php @@ -0,0 +1,51 @@ + + */ + public function rules(): array + { + $emote = $this->route('emote'); + + return [ + // Typed in chat as :name:, so the character set is deliberately narrow. + 'name' => [ + 'required', + 'string', + 'max:20', + 'regex:/^[a-z0-9_]+$/', + Rule::unique('emotes', 'name')->ignore($emote?->id), + ], + // The key the upload endpoint returned; the file itself is already on S3. + 's3_key' => ['required', 'string', 'max:2048'], + 'is_global' => ['boolean'], + 'is_approved' => ['boolean'], + ]; + } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'name.regex' => 'The name may only contain lowercase letters, numbers and underscores.', + 's3_key.required' => 'Upload an image for the emote.', + ]; + } +} diff --git a/app/Http/Requests/Manage/RecordingRequest.php b/app/Http/Requests/Manage/RecordingRequest.php new file mode 100644 index 0000000..77f7973 --- /dev/null +++ b/app/Http/Requests/Manage/RecordingRequest.php @@ -0,0 +1,83 @@ + + */ + public function rules(): array + { + $recording = $this->route('recording'); + + return [ + 'show_id' => ['nullable', 'integer', 'exists:shows,id'], + 'title' => ['required', 'string', 'max:255'], + 'slug' => [ + 'required', + 'string', + 'max:255', + 'regex:/^[a-z0-9-]+$/', + Rule::unique('recordings', 'slug')->ignore($recording?->id), + ], + 'description' => ['nullable', 'string', 'max:5000'], + 'date' => ['required', 'date'], + // Left blank, ProcessRecordingJob reads it off the playlist. + 'duration' => ['nullable', 'integer', 'min:0'], + + // The cut: which slice of the source's archive this recording shows. + // Distinct from the show's actual_start/actual_end, which record when it + // aired. An end is required rather than optional, because a source that + // stays online for the whole event never supplies one. + 'starts_at' => ['nullable', 'date', 'required_with:ends_at'], + 'ends_at' => ['nullable', 'date', 'required_with:starts_at', 'after:starts_at'], + + // Generated by ArchivePlaylistService for a cut, so it can only be required + // when the recording is not one. Recordings registered from outside still + // have to supply a playlist. + 'm3u8_url' => [ + Rule::requiredIf(fn () => ! $this->filled('starts_at')), + 'nullable', 'url', 'max:2048', + ], + 'thumbnail_path' => ['nullable', 'string', 'max:2048'], + 'is_published' => ['boolean'], + 'required_roles' => ['array'], + 'required_roles.*' => ['string', 'exists:roles,slug'], + ]; + } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'slug.regex' => 'The slug may only contain lowercase letters, numbers and dashes.', + ]; + } + + protected function prepareForValidation(): void + { + // An empty select posts "", which would fail the integer rule rather than + // reading as "no show". + if ($this->input('show_id') === '') { + $this->merge(['show_id' => null]); + } + + if ($this->input('duration') === '') { + $this->merge(['duration' => null]); + } + } +} diff --git a/app/Http/Requests/Manage/RoleRequest.php b/app/Http/Requests/Manage/RoleRequest.php new file mode 100644 index 0000000..a6abc6e --- /dev/null +++ b/app/Http/Requests/Manage/RoleRequest.php @@ -0,0 +1,76 @@ + + */ + public function rules(): array + { + $role = $this->route('role'); + + return [ + 'name' => ['required', 'string', 'max:255'], + 'slug' => [ + 'required', + 'string', + 'max:255', + 'regex:/^[a-z0-9-]+$/', + Rule::unique('roles', 'slug')->ignore($role?->id), + ], + /* + * The identifier the identity provider knows this role by. Unique, + * because two roles claiming the same group would both be granted + * and the sync would have no way to choose. + */ + 'external_id' => [ + 'nullable', + 'string', + 'max:255', + Rule::unique('roles', 'external_id')->ignore($role?->id), + ], + 'description' => ['nullable', 'string', 'max:1000'], + // Rendered as a chat badge, so it has to be a colour the browser accepts. + 'chat_color' => ['nullable', 'string', 'regex:/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/'], + 'priority' => ['required', 'integer', 'min:0', 'max:999'], + 'is_visible' => ['boolean'], + 'permissions' => ['array'], + 'permissions.*' => ['string', 'max:255'], + ]; + } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'slug.regex' => 'The slug may only contain lowercase letters, numbers and dashes.', + 'external_id.unique' => 'Another role already syncs from that identifier.', + ]; + } + + /** + * An empty field posts "", and the unique index would let exactly one role + * hold that. Every unsynced role has to be null instead. + */ + protected function prepareForValidation(): void + { + if (trim((string) $this->input('external_id')) === '') { + $this->merge(['external_id' => null]); + } + } +} diff --git a/app/Http/Requests/Manage/ServerRequest.php b/app/Http/Requests/Manage/ServerRequest.php new file mode 100644 index 0000000..6acf104 --- /dev/null +++ b/app/Http/Requests/Manage/ServerRequest.php @@ -0,0 +1,84 @@ + + */ + public function rules(): array + { + $rules = [ + 'hostname' => ['required', 'string', 'max:255'], + 'ip' => ['nullable', 'string', 'max:255'], + 'port' => ['required', 'integer', 'min:1', 'max:65535'], + 'status' => ['required', Rule::enum(ServerStatusEnum::class)], + 'max_clients' => ['nullable', 'integer', 'min:0', 'max:99999'], + ]; + + if ($this->isCreating()) { + $rules['hetzner_id'] = ['nullable', 'string', 'max:255']; + $rules['type'] = ['required', Rule::enum(ServerTypeEnum::class)]; + $rules['shared_secret'] = ['required', 'string', 'min:16', 'max:255']; + } + + return $rules; + } + + /** + * @return array + */ + public function attributes(): array + { + return [ + 'hetzner_id' => 'Hetzner ID', + 'ip' => 'IP address', + 'max_clients' => 'max clients', + ]; + } + + /** + * The validated payload, with `max_clients` stripped for an origin server so a hidden + * control cannot write a value the form never showed. + * + * @return array + */ + public function serverData(?ServerTypeEnum $type = null): array + { + $data = $this->validated(); + + $type ??= ServerTypeEnum::from($data['type']); + + if ($type !== ServerTypeEnum::EDGE) { + unset($data['max_clients']); + } + + return $data; + } + + private function isCreating(): bool + { + return $this->route('server') === null; + } +} diff --git a/app/Http/Requests/Manage/ShowRequest.php b/app/Http/Requests/Manage/ShowRequest.php new file mode 100644 index 0000000..abb4a8e --- /dev/null +++ b/app/Http/Requests/Manage/ShowRequest.php @@ -0,0 +1,114 @@ + + */ + public function rules(): array + { + $show = $this->route('show'); + + return [ + 'title' => ['required', 'string', 'max:255'], + 'slug' => ['required', 'string', 'max:255', Rule::unique('shows', 'slug')->ignore($show)], + 'source_id' => ['required', 'integer', Rule::exists('sources', 'id')], + 'description' => ['nullable', 'string'], + + 'scheduled_start' => ['required', 'date'], + 'scheduled_end' => ['required', 'date', 'after:scheduled_start'], + 'actual_start' => ['nullable', 'date'], + 'actual_end' => ['nullable', 'date', 'after_or_equal:actual_start'], + + 'auto_mode' => ['boolean'], + 'auto_stop_at' => ['nullable', 'date'], + 'announce_recording' => ['boolean'], + + 'visibility' => ['required', Rule::in(['public', 'private'])], + // Only read when visibility is private; required then, because a private show + // nobody can watch is a mistake, not a configuration. + 'required_roles' => ['array', 'required_if:visibility,private'], + 'required_roles.*' => [Rule::exists('roles', 'slug')], + ]; + } + + /** + * The payload to persist. + * + * `status` is deliberately absent: it only moves through Go Live, End Stream and + * Cancel, each of which does more than write a column (timestamps, viewer + * notification). A form can neither set nor clear it. + * + * @return array + */ + public function showData(?Show $show = null): array + { + $data = $this->validated(); + + // The slug is the public URL of a stream people are watching right now. + if ($show?->status === 'live') { + $data['slug'] = $show->slug; + } + + $data['auto_mode'] = (bool) ($data['auto_mode'] ?? false); + $data['announce_recording'] = (bool) ($data['announce_recording'] ?? false); + + // Public is stored as an empty role list, which is what canBeAccessedBy() reads. + $data['required_roles'] = $data['visibility'] === 'private' + ? array_values($data['required_roles'] ?? []) + : []; + + unset($data['visibility']); + + // The hard stop only means something in auto mode, and defaults to the scheduled + // end so the safe behaviour needs no thought. See docs/admin/auto-mode.md. + if (! $data['auto_mode']) { + $data['auto_stop_at'] = null; + } elseif (empty($data['auto_stop_at'])) { + $data['auto_stop_at'] = $data['scheduled_end']; + } + + return $data; + } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'scheduled_end.after' => 'The scheduled end must be later than the scheduled start.', + 'required_roles.*.exists' => 'One of the selected roles no longer exists.', + ]; + } + + /** + * Role slugs, for the access-restriction checkbox list. + * + * @return array + */ + public static function roleOptions(): array + { + return Role::orderByDesc('priority') + ->get(['name', 'slug']) + ->map(fn (Role $role) => ['value' => $role->slug, 'label' => $role->name]) + ->all(); + } +} diff --git a/app/Http/Requests/Manage/SourceRequest.php b/app/Http/Requests/Manage/SourceRequest.php new file mode 100644 index 0000000..bd4e78a --- /dev/null +++ b/app/Http/Requests/Manage/SourceRequest.php @@ -0,0 +1,61 @@ + + */ + public function rules(): array + { + $source = $this->route('source'); + + $rules = [ + 'name' => ['required', 'string', 'max:255'], + // Higher first on the public grid; the ceiling matches the Filament form. + 'priority' => ['required', 'integer', 'min:0', 'max:999'], + 'description' => ['nullable', 'string'], + ]; + + /* + * The slug is the RTMP ingress path and the HLS route key. It is set once, on + * create, and never accepted again: changing it disconnects the encoder and + * breaks playback. `Source::updating()` reverts it as a second line of defence. + * + * `status` is not here at all. It is changed only through the Update Status + * action, so there is exactly one way to do it. + */ + if ($source === null) { + $rules['slug'] = [ + 'required', + 'string', + 'max:255', + Rule::unique('sources', 'slug'), + ]; + } + + return $rules; + } + + /** + * @return array + */ + public function attributes(): array + { + return [ + 'slug' => 'stream name', + ]; + } +} diff --git a/app/Http/Requests/MessageRequest.php b/app/Http/Requests/MessageRequest.php index 3e00f27..708ff37 100644 --- a/app/Http/Requests/MessageRequest.php +++ b/app/Http/Requests/MessageRequest.php @@ -9,8 +9,9 @@ class MessageRequest extends FormRequest public function rules(): array { return [ - 'message' => ['required', 'string', 'max:500', 'min:1'], + 'message' => ['required', 'string', 'min:1', 'max:'.config('chat.default.maxMessageLength', 500)], 'source_id' => ['required', 'integer', 'exists:sources,id'], + 'reply_to_id' => ['nullable', 'integer', 'exists:messages,id'], ]; } diff --git a/app/Jobs/CaptureThumbnailJob.php b/app/Jobs/CaptureThumbnailJob.php index e568b50..1fd0a84 100644 --- a/app/Jobs/CaptureThumbnailJob.php +++ b/app/Jobs/CaptureThumbnailJob.php @@ -50,7 +50,7 @@ public function handle(ThumbnailService $thumbnailService): void if ($thumbnailPath) { // Refresh the show to get updated thumbnail_path $this->show->refresh(); - + // Broadcast the update (event will use accessor for signed URL) broadcast(new ShowThumbnailUpdated($this->show)); diff --git a/app/Jobs/CleanupStaleViewerSessionsJob.php b/app/Jobs/CleanupStaleViewerSessionsJob.php index 856d78b..cbfdc10 100644 --- a/app/Jobs/CleanupStaleViewerSessionsJob.php +++ b/app/Jobs/CleanupStaleViewerSessionsJob.php @@ -44,4 +44,4 @@ public function handle(): void ]); } } -} \ No newline at end of file +} diff --git a/app/Jobs/ProcessRecordingJob.php b/app/Jobs/ProcessRecordingJob.php index c96a92b..2db4364 100644 --- a/app/Jobs/ProcessRecordingJob.php +++ b/app/Jobs/ProcessRecordingJob.php @@ -5,14 +5,14 @@ use App\Models\Recording; use App\Services\RecordingService; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; -use Illuminate\Contracts\Queue\ShouldBeUnique; -class ProcessRecordingJob implements ShouldQueue, ShouldBeUnique +class ProcessRecordingJob implements ShouldBeUnique, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -45,31 +45,32 @@ public function __construct(Recording $recording) public function handle(RecordingService $recordingService): void { $startTime = microtime(true); - + // Reload the recording to get the latest data $this->recording->refresh(); - + Log::info("Processing recording {$this->recording->id}: {$this->recording->title}", [ 'recording_id' => $this->recording->id, 'm3u8_url' => $this->recording->m3u8_url, 'current_duration' => $this->recording->duration, - 'has_duration' => !is_null($this->recording->duration), - 'has_thumbnail' => !is_null($this->recording->thumbnail_path), + 'has_duration' => ! is_null($this->recording->duration), + 'has_thumbnail' => ! is_null($this->recording->thumbnail_path), ]); // Skip if already processed (unless force reprocess is set) - if ($this->recording->duration && $this->recording->thumbnail_path && !$this->recording->force_reprocess) { + if ($this->recording->duration && $this->recording->thumbnail_path && ! $this->recording->force_reprocess) { Log::info("Recording {$this->recording->id} already fully processed, skipping", [ 'recording_id' => $this->recording->id, 'duration' => $this->recording->duration, 'thumbnail_path' => $this->recording->thumbnail_path, ]); + return; } try { $recordingService->processRecording($this->recording); - + $processingTime = round(microtime(true) - $startTime, 2); $freshRecording = $this->recording->fresh(); Log::info("Successfully processed recording {$this->recording->id} in {$processingTime} seconds", [ @@ -96,7 +97,7 @@ public function handle(RecordingService $recordingService): void */ public function uniqueId(): string { - return 'process-recording-' . $this->recording->id; + return 'process-recording-'.$this->recording->id; } /** diff --git a/app/Jobs/Server/Deprovision/InitializeDeprovisioningJob.php b/app/Jobs/Server/Deprovision/InitializeDeprovisioningJob.php index 6cea676..619fb40 100644 --- a/app/Jobs/Server/Deprovision/InitializeDeprovisioningJob.php +++ b/app/Jobs/Server/Deprovision/InitializeDeprovisioningJob.php @@ -24,17 +24,17 @@ public function handle(): void // Get all users currently assigned to this server $usersToReassign = $this->server->users()->get(); $userCount = $usersToReassign->count(); - + if ($userCount > 0) { Log::info('Reassigning users from deprovisioning server', [ 'server_id' => $this->server->id, 'server_hostname' => $this->server->hostname, 'user_count' => $userCount, ]); - + $reassignedCount = 0; $failedCount = 0; - + // Attempt to reassign each user to another available server foreach ($usersToReassign as $user) { // The assignServerToUser method will find the best available server @@ -54,7 +54,7 @@ public function handle(): void ]); } } - + Log::info('User reassignment complete', [ 'server_id' => $this->server->id, 'total_users' => $userCount, @@ -62,7 +62,7 @@ public function handle(): void 'failed' => $failedCount, ]); } - + // Update server status to deprovisioning $this->server->update([ 'status' => ServerStatusEnum::DEPROVISIONING, diff --git a/app/Jobs/Server/Provision/CreateDnsRecordJob.php b/app/Jobs/Server/Provision/CreateDnsRecordJob.php index 73c0d60..fb1a5e0 100644 --- a/app/Jobs/Server/Provision/CreateDnsRecordJob.php +++ b/app/Jobs/Server/Provision/CreateDnsRecordJob.php @@ -2,7 +2,6 @@ namespace App\Jobs\Server\Provision; -use App\Enum\ServerTypeEnum; use App\Models\Server; use App\Services\DnsKeyService; use Illuminate\Bus\Queueable; diff --git a/app/Jobs/Server/Provision/CreateVirtualMachineJob.php b/app/Jobs/Server/Provision/CreateVirtualMachineJob.php index 51a3980..1dc2436 100644 --- a/app/Jobs/Server/Provision/CreateVirtualMachineJob.php +++ b/app/Jobs/Server/Provision/CreateVirtualMachineJob.php @@ -27,11 +27,11 @@ public function handle(): void $hetznerServerType = ($this->server->type === ServerTypeEnum::ORIGIN) ? 'ccx43' : 'cpx21'; $hetznerClient = Hetzner::client(); $name = $this->server->type->value.'-'.$this->server->id.'-'.Str::random(12); - + // Generate cloud-init script using the provisioning service $provisioningService = app(ServerProvisioningService::class); $cloudInitScript = $provisioningService->generateCloudInit($this->server); - + // Prepare SSH keys array - only add if available $sshKeys = []; try { @@ -60,7 +60,7 @@ public function handle(): void $serverType = $hetznerClient->serverTypes()->getByName($hetznerServerType); $image = $hetznerClient->images()->getByName('ubuntu-22.04'); $location = $hetznerClient->locations()->getByName('nbg1'); - + $payload = [ 'name' => $name, 'server_type' => $serverType->id, @@ -74,34 +74,34 @@ public function handle(): void 'type' => $this->server->type->value, ], ]; - + // Make direct API call using Guzzle - $httpClient = new \GuzzleHttp\Client(); + $httpClient = new \GuzzleHttp\Client; $response = $httpClient->post('https://api.hetzner.cloud/v1/servers', [ 'headers' => [ - 'Authorization' => 'Bearer ' . config('services.hetzner.token'), + 'Authorization' => 'Bearer '.config('services.hetzner.token'), 'Content-Type' => 'application/json', ], 'json' => $payload, ]); - + $responseBody = json_decode($response->getBody()->getContents()); $server = $responseBody->server; - // Wait for server to be ready with network info + // Wait for server to be ready with network info $maxAttempts = 12; // 2 minutes max wait $attempts = 0; while ($attempts < $maxAttempts) { sleep(10); - + // Fetch updated server info $getResponse = $httpClient->get("https://api.hetzner.cloud/v1/servers/{$server->id}", [ 'headers' => [ - 'Authorization' => 'Bearer ' . config('services.hetzner.token'), + 'Authorization' => 'Bearer '.config('services.hetzner.token'), ], ]); $server = json_decode($getResponse->getBody()->getContents())->server; - + // Check if we have the public IP (always required) if (isset($server->public_net->ipv4->ip)) { break; @@ -114,14 +114,14 @@ public function handle(): void $this->server->update([ 'hetzner_id' => $server->id, - 'hostname' => $name.'.stream.eurofurence.org', + 'hostname' => trim($name.'.'.config('dns.zone'), '.'), 'ip' => $server->public_net->ipv4->ip, 'internal_ip' => $internalIp, 'port' => 443, 'max_clients' => ($this->server->type === ServerTypeEnum::EDGE) ? 100 : 1000, 'status' => ServerStatusEnum::PROVISIONING, ]); - + // Chain the DNS creation and wait for ready jobs \Illuminate\Support\Facades\Bus::chain([ new CreateDnsRecordJob($this->server), diff --git a/app/Jobs/Server/ScalingJob.php b/app/Jobs/Server/ScalingJob.php deleted file mode 100644 index 706aba4..0000000 --- a/app/Jobs/Server/ScalingJob.php +++ /dev/null @@ -1,80 +0,0 @@ - StreamStatusEnum::OFFLINE->value)); - - if (! AutoscalerService::isAutoscalerEnabled()) { - return; - } - - if ($cacheStatus === StreamStatusEnum::OFFLINE) { - return; - } - - // Determine Needed Servers - $action = AutoscalerService::determineAction(); - - if ($action === AutoscalerAction::SCALE_UP) { - CreateServerJob::dispatch(); - } - - if ($action === AutoscalerAction::SCALE_DOWN) { - $serverCount = Server::where('status', ServerStatusEnum::ACTIVE->value) - ->where('type', ServerTypeEnum::EDGE) - ->where('hetzner_id', '!=', 'manual') // Don't count manual servers - ->count(); - - if ($serverCount > 1) { - // Delete Server with lowest user count and not immutable or manual - $server = Server::where('status', ServerStatusEnum::ACTIVE) - ->where('type', ServerTypeEnum::EDGE) - ->where('servers.created_at', '<=', now()->subHour()) - ->where('immutable', false) - ->where('hetzner_id', '!=', 'manual') // Never deprovision manual servers - ->leftJoin('clients', function (JoinClause $join) { - $join->on('clients.server_id', '=', 'servers.id'); - $join->on('clients.stop', \DB::raw('NULL')); - $join->on('clients.start', 'IS NOT', \DB::raw('NULL')); - }) - ->groupBy('servers.id') - ->orderBy('client_counts', 'desc') - ->selectRaw('servers.id, count(clients.id) as client_counts') - ->first(); - - if (! is_null($server)) { - $server = Server::find($server->id); - if ($server->immutable) { - return; - } - DeleteServerJob::dispatch($server); - } - } - } - } -} diff --git a/app/Jobs/Server/ServerHealthCheckJob.php b/app/Jobs/Server/ServerHealthCheckJob.php index 508d809..8cb5568 100644 --- a/app/Jobs/Server/ServerHealthCheckJob.php +++ b/app/Jobs/Server/ServerHealthCheckJob.php @@ -2,9 +2,9 @@ namespace App\Jobs\Server; -use App\Models\Server; -use App\Enum\ServerTypeEnum; use App\Enum\ServerStatusEnum; +use App\Enum\ServerTypeEnum; +use App\Models\Server; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; use Illuminate\Support\Facades\Log; @@ -34,8 +34,8 @@ public function handle(): void foreach ($edgeServers as $server) { try { $healthy = $server->performHealthCheck(); - - if (!$healthy) { + + if (! $healthy) { Log::warning('Server health check failed', [ 'server_id' => $server->id, 'hostname' => $server->hostname, diff --git a/app/Jobs/UpdateServerViewerCountsJob.php b/app/Jobs/UpdateServerViewerCountsJob.php index 7e45169..4287d42 100644 --- a/app/Jobs/UpdateServerViewerCountsJob.php +++ b/app/Jobs/UpdateServerViewerCountsJob.php @@ -3,8 +3,6 @@ namespace App\Jobs; use App\Models\Server; -use App\Models\User; -use App\Models\SourceUser; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -37,7 +35,7 @@ public function handle(): void 'viewer_count' => $count, 'last_heartbeat' => now(), ]); - + Log::debug('Updated server viewer count', [ 'server_id' => $serverId, 'viewer_count' => $count, @@ -56,11 +54,11 @@ public function handle(): void // Log summary $totalViewers = $viewerCounts->sum(); $activeServers = $viewerCounts->count(); - + Log::info('Server viewer counts updated', [ 'total_viewers' => $totalViewers, 'active_servers' => $activeServers, 'server_counts' => $viewerCounts->toArray(), ]); } -} \ No newline at end of file +} diff --git a/app/Listeners/Chat/DeleteMessages/DeleteMessagesListener.php b/app/Listeners/Chat/DeleteMessages/DeleteMessagesListener.php deleted file mode 100644 index 345f013..0000000 --- a/app/Listeners/Chat/DeleteMessages/DeleteMessagesListener.php +++ /dev/null @@ -1,36 +0,0 @@ -user->messages() - ->where('is_command', false) - ->where('created_at', '>', $event->since) - ->get(['id', 'source_id']); - - // Group messages by source_id for broadcasting - $messagesBySource = $messages->groupBy('source_id'); - - // Broadcast deletion event to each source channel - foreach ($messagesBySource as $sourceId => $sourceMessages) { - broadcast(new BroadcastMessageDeletionIdsEvent( - $sourceMessages->pluck('id')->toArray(), - $sourceId - )); - } - - // Delete the messages - $event->user->messages() - ->where('is_command', false) - ->where('created_at', '>', $event->since) - ->delete(); - } -} diff --git a/app/Listeners/Chat/SlowMode/AnnounceSlowModeDeactivationListener.php b/app/Listeners/Chat/SlowMode/AnnounceSlowModeDeactivationListener.php deleted file mode 100644 index e4a4e55..0000000 --- a/app/Listeners/Chat/SlowMode/AnnounceSlowModeDeactivationListener.php +++ /dev/null @@ -1,16 +0,0 @@ -seconds} seconds.")); - } -} diff --git a/app/Listeners/Chat/SlowMode/SlowModeDisableListener.php b/app/Listeners/Chat/SlowMode/SlowModeDisableListener.php deleted file mode 100644 index 5fa211e..0000000 --- a/app/Listeners/Chat/SlowMode/SlowModeDisableListener.php +++ /dev/null @@ -1,25 +0,0 @@ -seconds); - Cache::set('chat.slowMode', true); - - broadcast(new BroadcastRateLimitChangeEvent( - slowMode: true, - maxTries: 1, - rateDecay: $event->seconds - )); - } -} diff --git a/app/Listeners/HandleAutoModeShowsListener.php b/app/Listeners/HandleAutoModeShowsListener.php index 4c88459..cdbabd3 100644 --- a/app/Listeners/HandleAutoModeShowsListener.php +++ b/app/Listeners/HandleAutoModeShowsListener.php @@ -39,6 +39,7 @@ public function handle(SourceStatusChangedEvent $event): void Log::info('HandleAutoModeShowsListener: No auto mode shows found for source', [ 'source_id' => $source->id, ]); + return; } @@ -96,7 +97,7 @@ private function handleSourceOnline(Show $show): void // 2. It's within the scheduled time window OR past the scheduled start time if ($show->status === 'scheduled') { $now = now(); - + // Check if we should start the show if ($show->scheduled_start <= $now) { Log::info('HandleAutoModeShowsListener: Auto-starting show - source online and past scheduled start', [ @@ -144,7 +145,7 @@ private function handleSourceOffline(Show $show): void 'show_id' => $show->id, 'show_title' => $show->title, ]); - } else if ($show->status === 'live' && $show->isWithinScheduledTime()) { + } elseif ($show->status === 'live' && $show->isWithinScheduledTime()) { Log::info('HandleAutoModeShowsListener: Source offline during scheduled time - keeping show live', [ 'show_id' => $show->id, 'show_title' => $show->title, @@ -152,4 +153,4 @@ private function handleSourceOffline(Show $show): void ]); } } -} \ No newline at end of file +} diff --git a/app/Models/BrandingSetting.php b/app/Models/BrandingSetting.php new file mode 100644 index 0000000..e268915 --- /dev/null +++ b/app/Models/BrandingSetting.php @@ -0,0 +1,65 @@ +value('value') ?? '__unset__'; + }); + + if ($stored === '__unset__') { + return $default ?? config("branding.{$key}"); + } + + return $stored; + } + + /** + * Set a branding value. + */ + public static function setValue(string $key, $value, ?string $description = null): self + { + $setting = self::updateOrCreate( + ['key' => $key], + [ + 'value' => $value, + 'description' => $description, + ] + ); + + Cache::forget("branding_setting_{$key}"); + + return $setting; + } + + /** + * Clear the cache when saving. + */ + protected static function booted() + { + static::saved(function ($setting) { + Cache::forget("branding_setting_{$setting->key}"); + }); + + static::deleted(function ($setting) { + Cache::forget("branding_setting_{$setting->key}"); + }); + } +} diff --git a/app/Models/ChatBan.php b/app/Models/ChatBan.php new file mode 100644 index 0000000..59a61c4 --- /dev/null +++ b/app/Models/ChatBan.php @@ -0,0 +1,53 @@ + 'datetime', + 'lifted_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function bannedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'banned_by_user_id'); + } + + public function scopeActive(Builder $query): Builder + { + return $query->whereNull('lifted_at') + ->where(fn (Builder $q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now())); + } + + public function isPermanent(): bool + { + return $this->expires_at === null; + } + + public function lift(?User $moderator = null): void + { + $this->update([ + 'lifted_at' => now(), + 'lifted_by_user_id' => $moderator?->id, + ]); + } +} diff --git a/app/Models/ChatModerationLog.php b/app/Models/ChatModerationLog.php new file mode 100644 index 0000000..7d163fa --- /dev/null +++ b/app/Models/ChatModerationLog.php @@ -0,0 +1,37 @@ + 'array', + ]; + + public function moderator(): BelongsTo + { + return $this->belongsTo(User::class, 'moderator_id'); + } + + public function targetUser(): BelongsTo + { + return $this->belongsTo(User::class, 'target_user_id'); + } + + public function source(): BelongsTo + { + return $this->belongsTo(Source::class); + } +} diff --git a/app/Models/ChatSetting.php b/app/Models/ChatSetting.php index 11aaff6..f29acc7 100644 --- a/app/Models/ChatSetting.php +++ b/app/Models/ChatSetting.php @@ -12,50 +12,63 @@ class ChatSetting extends Model protected $fillable = [ 'key', + 'source_id', 'value', 'description', ]; /** - * Get a setting value by key. + * Get a setting value, falling back from the source-scoped row to the global row. */ - public static function getValue(string $key, $default = null) + public static function getValue(string $key, $default = null, ?int $sourceId = null) { - return Cache::remember("chat_setting_{$key}", 3600, function () use ($key, $default) { - $setting = self::where('key', $key)->first(); - return $setting ? $setting->value : $default; + return Cache::remember(self::cacheKey($key, $sourceId), 3600, function () use ($key, $default, $sourceId) { + if ($sourceId !== null) { + $scoped = self::where('key', $key)->where('source_id', $sourceId)->first(); + + if ($scoped) { + return $scoped->value; + } + } + + $global = self::where('key', $key)->whereNull('source_id')->first(); + + return $global ? $global->value : $default; }); } - /** - * Set a setting value. - */ - public static function setValue(string $key, $value, ?string $description = null): self + public static function setValue(string $key, $value, ?string $description = null, ?int $sourceId = null): self { $setting = self::updateOrCreate( - ['key' => $key], - [ - 'value' => $value, - 'description' => $description, - ] + ['key' => $key, 'source_id' => $sourceId], + ['value' => (string) $value, 'description' => $description], ); - Cache::forget("chat_setting_{$key}"); + self::forget($key, $sourceId); return $setting; } - /** - * Clear the cache when saving. - */ - protected static function booted() + public static function forget(string $key, ?int $sourceId = null): void { - static::saved(function ($setting) { - Cache::forget("chat_setting_{$setting->key}"); - }); + Cache::forget(self::cacheKey($key, $sourceId)); - static::deleted(function ($setting) { - Cache::forget("chat_setting_{$setting->key}"); - }); + if ($sourceId !== null) { + return; + } + + // A changed global default invalidates every source-scoped read of the same key. + Cache::forget(self::cacheKey($key, null)); + } + + protected static function cacheKey(string $key, ?int $sourceId): string + { + return 'chat_setting_'.$key.'_'.($sourceId ?? 'global'); + } + + protected static function booted(): void + { + static::saved(fn (self $setting) => self::forget($setting->key, $setting->source_id)); + static::deleted(fn (self $setting) => self::forget($setting->key, $setting->source_id)); } -} \ No newline at end of file +} diff --git a/app/Models/Emote.php b/app/Models/Emote.php index adb636e..49b1ed3 100644 --- a/app/Models/Emote.php +++ b/app/Models/Emote.php @@ -162,7 +162,7 @@ public function getUrlAttribute($value) } // If no S3 key, return null - if (!$this->s3_key) { + if (! $this->s3_key) { return null; } diff --git a/app/Models/Message.php b/app/Models/Message.php index 153f706..31ab18c 100644 --- a/app/Models/Message.php +++ b/app/Models/Message.php @@ -2,7 +2,9 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; class Message extends Model @@ -13,6 +15,7 @@ class Message extends Model 'message', 'user_id', 'source_id', + 'reply_to_id', 'is_command', 'type', 'priority', @@ -22,15 +25,62 @@ class Message extends Model protected $casts = [ 'metadata' => 'array', + 'is_command' => 'boolean', ]; - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } - public function source(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function source(): BelongsTo { return $this->belongsTo(Source::class); } + + public function replyTo(): BelongsTo + { + return $this->belongsTo(self::class, 'reply_to_id'); + } + + public function deletedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'deleted_by_user_id'); + } + + /** + * Messages that belong in the visible chat log for the given user. + */ + public function scopeVisibleTo(Builder $query, ?User $user = null): Builder + { + return $query->where(function (Builder $query) use ($user) { + $query->where('is_command', false) + ->orWhereIn('type', ['announcement', 'system']); + + if ($user) { + $query->orWhere(fn (Builder $q) => $q->where('is_command', true)->where('user_id', $user->id)); + } + }); + } + + /** + * The raw message text, with legacy stored markup converted back to plain text. + * + * Messages used to be stored HTML-escaped with `` tags baked in. The client + * now receives raw text and renders emotes/mentions/links itself, so old rows are + * normalised on the way out instead of being migrated in place. + */ + public function getBodyAttribute(): string + { + $body = (string) $this->message; + + if (! str_contains($body, ']*><\/emote>/', ':$1:', $body); + $body = str_replace('​', '', $body); + + return html_entity_decode($body, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } } diff --git a/app/Models/PretalxRoomSource.php b/app/Models/PretalxRoomSource.php new file mode 100644 index 0000000..030c12e --- /dev/null +++ b/app/Models/PretalxRoomSource.php @@ -0,0 +1,45 @@ + 'integer', + 'source_id' => 'integer', + ]; + + public function source(): BelongsTo + { + return $this->belongsTo(Source::class); + } + + /** + * The saved mapping for an event as room id => source id, with unmapped rooms absent. + * + * @return array + */ + public static function mapFor(string $eventSlug): array + { + return static::query() + ->where('event_slug', $eventSlug) + ->whereNotNull('source_id') + ->pluck('source_id', 'room_id') + ->map(fn ($id) => (int) $id) + ->all(); + } +} diff --git a/app/Models/Recording.php b/app/Models/Recording.php index bf7819b..13086f1 100644 --- a/app/Models/Recording.php +++ b/app/Models/Recording.php @@ -2,6 +2,8 @@ namespace App\Models; +use Carbon\Carbon; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; @@ -13,10 +15,15 @@ class Recording extends Model protected $fillable = [ 'show_id', + 'source_id', 'title', 'slug', 'description', 'date', + 'starts_at', + 'ends_at', + 'archive_prefix', + 'status', 'duration', 'm3u8_url', 'thumbnail_path', @@ -29,7 +36,11 @@ class Recording extends Model protected $casts = [ 'date' => 'datetime', + 'starts_at' => 'datetime', + 'ends_at' => 'datetime', + 'playlist_built_at' => 'datetime', 'duration' => 'integer', + 'segment_count' => 'integer', 'views' => 'integer', 'is_published' => 'boolean', 'thumbnail_updated_at' => 'datetime', @@ -60,6 +71,37 @@ protected static function boot() }); } + /** + * Cut markers are normalised to the app timezone before they are stored. + * + * starts_at and ends_at are `timestamp without time zone`, matching the rest of the + * schema, and Laravel writes them with `Y-m-d H:i:s` and no offset. The digits that + * reach Postgres are therefore whatever timezone the Carbon happened to be in, and a + * UTC one lands as local time on read: the instant silently moves by the offset. + * + * It only shows up later, as a cut that resolves to zero segments and reports the + * archive as expired, so normalise here rather than expecting every caller to + * remember. Callers passing app-local values are unaffected. + */ + protected function startsAt(): Attribute + { + return $this->localDateTime(); + } + + protected function endsAt(): Attribute + { + return $this->localDateTime(); + } + + protected function localDateTime(): Attribute + { + return Attribute::make( + set: fn ($value) => $value === null + ? null + : Carbon::parse($value)->setTimezone(config('app.timezone')), + ); + } + /** * Get the show associated with this recording. */ @@ -68,6 +110,42 @@ public function show() return $this->belongsTo(Show::class); } + /** + * The source whose archive this recording is a view of. + */ + public function source() + { + return $this->belongsTo(Source::class); + } + + /** + * Slug of the archive this cut reads from. + * + * Prefers the stored prefix so an existing recording keeps resolving if its source is + * later renamed, and falls back to the live relation for recordings created before + * the prefix was recorded. + */ + public function archiveSourceSlug(): ?string + { + if ($this->archive_prefix) { + return str_contains($this->archive_prefix, '/') + ? substr(strrchr($this->archive_prefix, '/'), 1) + : $this->archive_prefix; + } + + return $this->source?->slug ?? $this->show?->source?->slug; + } + + /** + * Whether the cut is fully specified. An end marker is required: the archive is a + * continuous timeline with no natural end for a source that stays online for the + * whole event, so something has to say where the recording stops. + */ + public function hasCut(): bool + { + return $this->starts_at !== null && $this->ends_at !== null; + } + /** * Get the full URL for the thumbnail. * Returns a signed URL for S3 access. diff --git a/app/Models/Role.php b/app/Models/Role.php index 8995c74..fcef5db 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -13,17 +13,19 @@ class Role extends Model protected $fillable = [ 'name', 'slug', + // What the identity provider calls this role: a group ID from the + // userinfo claim, or a registration package name. Set, the role is + // synced at every login; empty, it is only ever assigned by hand. + 'external_id', 'description', 'chat_color', 'priority', - 'assigned_at_login', 'is_visible', 'permissions', 'metadata', ]; protected $casts = [ - 'assigned_at_login' => 'boolean', 'is_visible' => 'boolean', 'permissions' => 'array', 'metadata' => 'array', @@ -136,19 +138,22 @@ public function scopeVisible($query) } /** - * Scope for roles assigned at login. + * Roles the identity provider owns: they carry the identifier it knows them + * by, so login rewrites them. */ public function scopeLoginAssigned($query) { - return $query->where('assigned_at_login', true); + return $query->whereNotNull('external_id')->where('external_id', '!=', ''); } /** - * Scope for manually assigned roles. + * Roles login never touches, because nothing external names them. */ public function scopeManuallyAssigned($query) { - return $query->where('assigned_at_login', false); + return $query->where(fn ($query) => $query + ->whereNull('external_id') + ->orWhere('external_id', '')); } /** diff --git a/app/Models/Server.php b/app/Models/Server.php index 543775d..a17146b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -7,6 +7,7 @@ use App\Jobs\Server\DeleteServerJob; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; @@ -14,6 +15,8 @@ class Server extends Model { + use HasFactory; + protected $guarded = []; protected $casts = [ @@ -39,13 +42,13 @@ class Server extends Model protected static function boot() { parent::boot(); - + static::creating(function ($server) { // Auto-generate shared secret if not provided if (empty($server->shared_secret)) { $server->shared_secret = Str::random(40); } - + // Set default status if not provided if (empty($server->status)) { $server->status = ServerStatusEnum::PROVISIONING; @@ -88,7 +91,7 @@ public function canBecomeOrigin(): bool ->where('id', '!=', $this->id) ->exists(); - return !$existingOrigin; + return ! $existingOrigin; } /** @@ -99,12 +102,14 @@ public function getHlsBaseUrl(): string if ($this->type === ServerTypeEnum::ORIGIN) { // Origin server serves HLS directly from SRS $protocol = $this->port === 443 ? 'https' : 'http'; - $port = in_array($this->port, [80, 443]) ? '' : ':' . $this->port; + $port = in_array($this->port, [80, 443]) ? '' : ':'.$this->port; + return "{$protocol}://{$this->hostname}{$port}"; } else { // Edge server proxies from origin $protocol = $this->port === 443 ? 'https' : 'http'; - $port = in_array($this->port, [80, 443]) ? '' : ':' . $this->port; + $port = in_array($this->port, [80, 443]) ? '' : ':'.$this->port; + return "{$protocol}://{$this->hostname}{$port}"; } } @@ -115,10 +120,11 @@ public function getHlsBaseUrl(): string public function getHlsUrl(string $streamSlug, string $quality = 'fhd'): string { $baseUrl = $this->getHlsBaseUrl(); - + if ($this->type === ServerTypeEnum::ORIGIN) { // Origin server path structure from SRS $hlsPath = $this->hls_path ?: '/live'; + return "{$baseUrl}{$hlsPath}/{$streamSlug}_{$quality}/index.m3u8"; } else { // Edge server proxies the same path @@ -137,6 +143,7 @@ public function getOriginUrl(): ?string return $origin->getHlsBaseUrl(); } } + return null; } @@ -156,10 +163,10 @@ public function updateViewerCount(int $count): void */ public function hasRecentHeartbeat(): bool { - if (!$this->last_heartbeat) { + if (! $this->last_heartbeat) { return false; } - + // Consider heartbeat stale after 1 minute return $this->last_heartbeat->gt(now()->subMinute()); } @@ -192,7 +199,7 @@ public function delete() { // Unassign all users from this server before deletion $this->users()->update(['server_id' => null]); - + return parent::delete(); } @@ -201,7 +208,7 @@ public function delete() */ public function isHetznerServer(): bool { - return !empty($this->hetzner_id); + return ! empty($this->hetzner_id); } /** @@ -209,12 +216,12 @@ public function isHetznerServer(): bool */ public function canUseInternalNetworkWith(?Server $otherServer): bool { - if (!$otherServer) { + if (! $otherServer) { return false; } // Both servers must be Hetzner servers - if (!$this->isHetznerServer() || !$otherServer->isHetznerServer()) { + if (! $this->isHetznerServer() || ! $otherServer->isHetznerServer()) { return false; } @@ -229,7 +236,7 @@ public function canUseInternalNetworkWith(?Server $otherServer): bool public function isReady(): bool { // For manual/local servers (null hetzner_id), assume they're ready if active - if (!$this->hetzner_id && $this->status === ServerStatusEnum::ACTIVE) { + if (! $this->hetzner_id && $this->status === ServerStatusEnum::ACTIVE) { return true; } @@ -239,20 +246,20 @@ public function isReady(): bool if ($this->type === ServerTypeEnum::ORIGIN) { // Origin servers run SRS with API on port 1985 $proto = 'http'; - $hostname = $this->ip . ':1985'; + $hostname = $this->ip.':1985'; } // For local Docker containers (null hetzner_id), use http - if (!$this->hetzner_id) { + if (! $this->hetzner_id) { $proto = 'http'; // Use the hostname directly for Docker containers if ($this->type === ServerTypeEnum::EDGE) { - $hostname = $this->hostname . ':' . $this->port; + $hostname = $this->hostname.':'.$this->port; } } try { - $request = Http::timeout(5)->get($proto . '://' . $hostname . '/ready'); + $request = Http::timeout(5)->get($proto.'://'.$hostname.'/ready'); } catch (ClientException|ServerException|ConnectionException $e) { return false; } @@ -266,7 +273,7 @@ public function isInUse(): bool // Origin is in use if any streams are live return \App\Models\Source::where('status', \App\Enum\SourceStatusEnum::ONLINE)->exists(); } - + // Edge is in use if it has viewers return $this->viewer_count > 0; } @@ -279,7 +286,8 @@ public function getHostWithPort(): string if (in_array($this->port, [80, 443])) { return $this->hostname; } - return $this->hostname . ':' . $this->port; + + return $this->hostname.':'.$this->port; } /** @@ -295,19 +303,20 @@ public function performHealthCheck(): bool try { $protocol = in_array($this->port, [443]) ? 'https' : 'http'; $url = "{$protocol}://{$this->getHostWithPort()}/health"; - + $response = Http::timeout(5)->get($url); - + if ($response->successful()) { $this->update([ 'health_status' => 'healthy', 'last_health_check' => now(), 'health_check_message' => 'Health check passed', ]); + return true; } else { $errorMessage = "HTTP {$response->status()}: {$response->body()}"; - + // Log the health check failure \Log::error('Server health check failed', [ 'server_id' => $this->id, @@ -317,17 +326,18 @@ public function performHealthCheck(): bool 'response_body' => $response->body(), 'message' => $errorMessage, ]); - + $this->update([ 'health_status' => 'unhealthy', 'last_health_check' => now(), 'health_check_message' => $errorMessage, ]); + return false; } } catch (\Exception $e) { - $errorMessage = 'Health check failed: ' . $e->getMessage(); - + $errorMessage = 'Health check failed: '.$e->getMessage(); + // Log the health check exception \Log::error('Server health check exception', [ 'server_id' => $this->id, @@ -336,12 +346,13 @@ public function performHealthCheck(): bool 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); - + $this->update([ 'health_status' => 'unhealthy', 'last_health_check' => now(), 'health_check_message' => $errorMessage, ]); + return false; } } @@ -351,11 +362,11 @@ public function performHealthCheck(): bool */ public function hasRecentHealthCheck(): bool { - if (!$this->last_health_check) { + if (! $this->last_health_check) { return false; } - + // Consider health check stale after 2 minutes return $this->last_health_check->gt(now()->subMinutes(2)); } -} \ No newline at end of file +} diff --git a/app/Models/Show.php b/app/Models/Show.php index 343e423..52981d6 100644 --- a/app/Models/Show.php +++ b/app/Models/Show.php @@ -23,7 +23,8 @@ class Show extends Model 'actual_end', 'status', 'auto_mode', - 'recordable', + 'auto_stop_at', + 'announce_recording', 'thumbnail_path', 'thumbnail_updated_at', 'thumbnail_capture_error', @@ -34,6 +35,8 @@ class Show extends Model 'metadata', 'required_roles', 'server_id', + // Set by the pretalx import; its presence is what stops a slot being imported twice. + 'pretalx_slot_id', ]; protected $casts = [ @@ -42,8 +45,9 @@ class Show extends Model 'actual_start' => 'datetime', 'actual_end' => 'datetime', 'thumbnail_updated_at' => 'datetime', + 'auto_stop_at' => 'datetime', 'auto_mode' => 'boolean', - 'recordable' => 'boolean', + 'announce_recording' => 'boolean', 'tags' => 'array', 'metadata' => 'array', 'required_roles' => 'array', @@ -116,11 +120,23 @@ public function showStatistics() } /** - * Get the recording for this show. + * Recordings cut from this show's slot. + * + * hasMany rather than hasOne: a recording is a time range over the source's archive, + * so a long block can be sliced into several published pieces without re-recording + * anything. `recording()` stays as the convenience accessor for the common 1:1 case. + */ + public function recordings() + { + return $this->hasMany(Recording::class); + } + + /** + * The primary recording for this show, if one has been cut. */ public function recording() { - return $this->hasOne(Recording::class); + return $this->hasOne(Recording::class)->latestOfMany(); } /** @@ -273,6 +289,18 @@ public function getDurationAttribute() return $this->scheduled_start->diffInMinutes($this->scheduled_end); } + /** + * The description as HTML. + * + * Descriptions are markdown - that is what pretalx abstracts are written in, and what + * the form accepts - and the stored value stays markdown so it can be edited. This is + * the rendered, sanitised form for display. + */ + public function getDescriptionHtmlAttribute(): ?string + { + return \App\Support\Markdown::render($this->description); + } + /** * Get the full URL for the thumbnail path stored in database. * Returns a signed URL for S3 access. @@ -392,6 +420,41 @@ public function isAutoMode() return $this->auto_mode === true; } + /** + * The moment an auto-mode show must stop, whatever the source is doing. + * + * Falls back to `scheduled_end` when no explicit hard stop is set, which is what auto + * mode did before the column existed. See docs/admin/auto-mode.md. + */ + public function autoStopAt(): ?Carbon + { + if (! $this->isAutoMode()) { + return null; + } + + return $this->auto_stop_at ?? $this->scheduled_end; + } + + /** + * Whether the hard stop has passed. This is the dance safety net: a show nobody + * remembered to end stops on its own instead of recording all night. + */ + public function isPastAutoStop(): bool + { + $stop = $this->autoStopAt(); + + return $stop !== null && $stop->lte(now()); + } + + /** + * Private means only listed roles may watch, and nobody else even sees the show. + * Public means anyone signed in. + */ + public function isPrivate(): bool + { + return ! empty($this->required_roles); + } + /** * Check if show is within scheduled time window. */ diff --git a/app/Models/Source.php b/app/Models/Source.php index 6ac7c6d..58a2f13 100644 --- a/app/Models/Source.php +++ b/app/Models/Source.php @@ -58,9 +58,10 @@ protected static function boot() }); static::updating(function ($source) { - // Update slug if name changes - if ($source->isDirty('name') && !$source->isDirty('slug')) { - $source->slug = Str::slug($source->name); + // The slug is the RTMP ingress path and the HLS route key, so it is + // immutable after creation. Renaming a source must not move it. + if ($source->isDirty('slug')) { + $source->slug = $source->getOriginal('slug'); } // Stream key should remain separate from slug for security // Only regenerate if explicitly cleared @@ -142,14 +143,21 @@ public function scopeOrdered($query) /** * Get the base RTMP server URL for OBS configuration. * Returns URL in format: rtmp://server:port/ingress + * + * Null when no origin server is active: there is no address to push to yet. This used + * to read ->hostname off null and take the whole page down with it, which is exactly + * the moment - origin down - when an operator most needs the page. */ - public function getRtmpServerUrl() + public function getRtmpServerUrl(): ?string { - // Get the active origin server $originServer = \App\Models\Server::where('type', \App\Enum\ServerTypeEnum::ORIGIN) ->where('status', \App\Enum\ServerStatusEnum::ACTIVE) ->first(); + if (! $originServer) { + return null; + } + return "rtmp://{$originServer->hostname}:1935/ingress"; } @@ -176,6 +184,11 @@ public function getRtmpPushUrl() */ public function getHlsUrl() { + // Local dev loops bypass the edge proxy entirely; see config/stream.php. + if (config('stream.dev_streams')) { + return asset("dev-streams/{$this->slug}/index.m3u8"); + } + return route('hls.master', ['stream' => $this->slug]); } } diff --git a/app/Models/SystemMessage.php b/app/Models/SystemMessage.php index 18addf9..95bf01d 100644 --- a/app/Models/SystemMessage.php +++ b/app/Models/SystemMessage.php @@ -45,4 +45,4 @@ public function scopeRecent($query, $minutes = 60) { return $query->where('created_at', '>=', now()->subMinutes($minutes)); } -} \ No newline at end of file +} diff --git a/app/Models/Timeout.php b/app/Models/Timeout.php index 33d7a0b..c233461 100644 --- a/app/Models/Timeout.php +++ b/app/Models/Timeout.php @@ -52,4 +52,4 @@ public function isActive(): bool { return $this->expires_at > now(); } -} \ No newline at end of file +} diff --git a/app/Models/User.php b/app/Models/User.php index 9b09355..7d0b590 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,16 +5,15 @@ // use Illuminate\Contracts\Auth\MustVerifyEmail; use App\Enum\ServerStatusEnum; use App\Enum\ServerTypeEnum; -use Filament\Models\Contracts\FilamentUser; +use App\Helpers\IpSubnetHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Laravel\Sanctum\HasApiTokens; -use App\Helpers\IpSubnetHelper; -class User extends Authenticatable implements FilamentUser +class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; @@ -51,7 +50,6 @@ public function server() return $this->belongsTo(Server::class); } - public function getOrAssignServer($clientIp = null) { // Check for subnet-based override first @@ -65,7 +63,7 @@ public function getOrAssignServer($clientIp = null) ($localIpv6Subnet && IpSubnetHelper::isIpInSubnet($clientIp, $localIpv6Subnet)) )) { // Create a virtual server object with the override hostname - $server = new Server(); + $server = new Server; $server->hostname = $localHostname; $server->port = 8080; @@ -146,7 +144,7 @@ public function assignServerToUser(): bool // If no servers have capacity, still try to get the least loaded one // This ensures users can still connect in emergency situations - if (!$server) { + if (! $server) { $server = $query->orderBy('viewer_count', 'asc')->first(); if ($server) { @@ -321,31 +319,32 @@ public function removeRole($role): void } /** - * Sync roles from login (registration system). + * Rewrite the roles the identity provider owns from what it just told us. + * + * Ownership is decided by the role, not by this call: a role carrying an + * `external_id` is the provider's to give and take away, and a role without + * one is never touched here, however it was assigned. + * + * @param array $externalIds Group IDs and package names from the provider. */ - public function syncRolesFromLogin(array $rolesSlugs): void + public function syncRolesFromLogin(array $externalIds): void { - // Log current roles before sync \Log::info('Before sync - User '.$this->id.' roles: ', $this->roles()->pluck('slug')->toArray()); - \Log::info('Syncing roles from login: ', $rolesSlugs); + \Log::info('Syncing roles from login: ', $externalIds); - // Get IDs of roles that should be detached (only those with assigned_at_login = true) + // Drop every provider-owned role first, so one that is no longer granted + // actually goes away rather than lingering from a previous login. $roleIdsToDetach = $this->roles() - ->where('assigned_at_login', true) + ->loginAssigned() ->pluck('roles.id') ->toArray(); - \Log::info('Roles to detach (IDs): ', $roleIdsToDetach); - - // Detach only those specific roles if (! empty($roleIdsToDetach)) { $this->roles()->detach($roleIdsToDetach); - \Log::info('Detached roles with assigned_at_login=true'); } - // Add new roles from login - $roles = Role::whereIn('slug', $rolesSlugs) - ->where('assigned_at_login', true) + $roles = Role::loginAssigned() + ->whereIn('external_id', $externalIds) ->get(); \Log::info('Adding roles: ', $roles->pluck('slug')->toArray()); @@ -354,7 +353,6 @@ public function syncRolesFromLogin(array $rolesSlugs): void $role->assignTo($this, null); } - // Log final roles after sync \Log::info('After sync - User '.$this->id.' roles: ', $this->roles()->pluck('slug')->toArray()); } @@ -372,14 +370,6 @@ public function hasPermission(string $permission): bool return false; } - /** - * Check if user can access Filament panel. - */ - public function canAccessPanel(\Filament\Panel $panel): bool - { - return $this->hasPermission('filament.access') || $this->isStaff(); - } - /** * Check if user is staff (admin only). */ @@ -450,4 +440,77 @@ public function favoriteEmotes() ->withTimestamps(); } + public function timeouts() + { + return $this->hasMany(Timeout::class); + } + + public function chatBans() + { + return $this->hasMany(ChatBan::class); + } + + /** + * The timeout currently silencing this user, if any. + */ + public function activeTimeout(): ?Timeout + { + return $this->timeouts()->active()->latest('expires_at')->first(); + } + + /** + * The ban currently silencing this user, if any. + */ + public function activeChatBan(): ?ChatBan + { + return $this->chatBans()->active()->latest('id')->first(); + } + + /** + * Can this user delete messages and time other users out? + */ + public function canModerateChat(): bool + { + return $this->isAdmin() || $this->isModerator() || $this->hasPermission('chat.moderate'); + } + + /** + * Can this user ban other users from chat? + */ + public function canBanFromChat(): bool + { + return $this->isAdmin() || $this->hasPermission('chat.ban'); + } + + /** + * Roles rendered as chat badges, highest priority first. + */ + public function chatBadges(): array + { + return $this->activeRoles() + ->visible() + ->ordered() + ->get() + ->map(fn (Role $role) => [ + 'slug' => $role->slug, + 'name' => $role->name, + 'label' => $role->metadata['badge'] ?? static::badgeLabelFor($role), + 'color' => $role->chat_color, + ]) + ->values() + ->all(); + } + + protected static function badgeLabelFor(Role $role): string + { + return match ($role->slug) { + 'admin' => 'ADM', + 'moderator' => 'MOD', + 'staff' => 'STF', + 'sponsor' => 'SPO', + 'supersponsor' => 'SSP', + 'attendee' => 'ATT', + default => mb_strtoupper(mb_substr($role->name, 0, 3)), + }; + } } diff --git a/app/Observers/RecordingObserver.php b/app/Observers/RecordingObserver.php index d65c7b2..b053c02 100644 --- a/app/Observers/RecordingObserver.php +++ b/app/Observers/RecordingObserver.php @@ -16,7 +16,7 @@ public function created(Recording $recording): void { // Dispatch job to process recording if m3u8_url is set // and we don't have duration or thumbnail yet - if ($recording->m3u8_url && (!$recording->duration || !$recording->thumbnail_path)) { + if ($recording->m3u8_url && (! $recording->duration || ! $recording->thumbnail_path)) { Log::info("Dispatching ProcessRecordingJob for newly created recording {$recording->id}"); ProcessRecordingJob::dispatch($recording)->onQueue('recordings'); } @@ -29,17 +29,17 @@ public function updated(Recording $recording): void { // If m3u8_url changed and we don't have duration or thumbnail, process it if ($recording->isDirty('m3u8_url') && $recording->m3u8_url) { - if (!$recording->duration || !$recording->thumbnail_path) { + if (! $recording->duration || ! $recording->thumbnail_path) { Log::info("Dispatching ProcessRecordingJob for updated recording {$recording->id} (m3u8_url changed)"); ProcessRecordingJob::dispatch($recording)->onQueue('recordings'); } } - + // Also dispatch if explicitly requested (e.g., force regenerate thumbnail) if ($recording->isDirty('force_reprocess') && $recording->force_reprocess) { Log::info("Force reprocessing recording {$recording->id}"); ProcessRecordingJob::dispatch($recording)->onQueue('recordings'); - + // Reset the flag $recording->force_reprocess = false; $recording->saveQuietly(); // Save without triggering events @@ -59,10 +59,10 @@ public function deleting(Recording $recording): void Storage::delete($recording->thumbnail_path); Log::info("Deleted thumbnail for recording {$recording->id}: {$recording->thumbnail_path}"); } - + // Clean up any other thumbnails for this recording $thumbnailDir = 'recordings/thumbnails'; - + $files = Storage::files($thumbnailDir); foreach ($files as $file) { if (str_contains($file, "recording_{$recording->id}_")) { @@ -71,7 +71,7 @@ public function deleting(Recording $recording): void } } } catch (\Exception $e) { - Log::error("Failed to delete thumbnails for recording {$recording->id}: " . $e->getMessage()); + Log::error("Failed to delete thumbnails for recording {$recording->id}: ".$e->getMessage()); } } } @@ -82,7 +82,7 @@ public function deleting(Recording $recording): void public function restored(Recording $recording): void { // If a soft-deleted recording is restored and needs processing, dispatch the job - if ($recording->m3u8_url && (!$recording->duration || !$recording->thumbnail_path)) { + if ($recording->m3u8_url && (! $recording->duration || ! $recording->thumbnail_path)) { Log::info("Dispatching ProcessRecordingJob for restored recording {$recording->id}"); ProcessRecordingJob::dispatch($recording)->onQueue('recordings'); } @@ -96,4 +96,4 @@ public function forceDeleted(Recording $recording): void // Same as deleting, ensure thumbnails are removed $this->deleting($recording); } -} \ No newline at end of file +} diff --git a/app/Observers/ShowObserver.php b/app/Observers/ShowObserver.php index c765722..4146793 100644 --- a/app/Observers/ShowObserver.php +++ b/app/Observers/ShowObserver.php @@ -20,21 +20,21 @@ public function updated(Show $show): void if ($show->wasChanged('status')) { $previousStatus = $show->getOriginal('status'); $newStatus = $show->status; - + Log::info('Show status changed via Observer', [ 'show_id' => $show->id, 'show_title' => $show->title, 'previous_status' => $previousStatus, 'new_status' => $newStatus, ]); - + // Fire appropriate event based on new status switch ($newStatus) { case 'live': // Only fire if we're transitioning TO live from another status if ($previousStatus !== 'live') { // Update actual_start if not already set - if (!$show->actual_start) { + if (! $show->actual_start) { $show->actual_start = now(); $show->saveQuietly(); // Save without triggering events again } @@ -42,28 +42,28 @@ public function updated(Show $show): void Log::info('ShowWentLive event fired for show', ['show_id' => $show->id]); } break; - + case 'ended': // Only fire if we're transitioning TO ended from another status if ($previousStatus !== 'ended') { // Update actual_end if not already set - if (!$show->actual_end) { + if (! $show->actual_end) { $show->actual_end = now(); $show->saveQuietly(); // Save without triggering events again } - + // Mark all active viewers as left in the source if ($show->source) { $show->source->activeViewers()->update([ 'left_at' => now(), ]); } - + event(new ShowEnded($show)); Log::info('ShowEnded event fired for show', ['show_id' => $show->id]); } break; - + case 'cancelled': // Only fire if we're transitioning TO cancelled from another status if ($previousStatus !== 'cancelled') { @@ -74,4 +74,4 @@ public function updated(Show $show): void } } } -} \ No newline at end of file +} diff --git a/app/Observers/SourceObserver.php b/app/Observers/SourceObserver.php index 6e98a54..47e3f60 100644 --- a/app/Observers/SourceObserver.php +++ b/app/Observers/SourceObserver.php @@ -22,11 +22,11 @@ public function updating(Source $source): void { // Store the original status value for comparison after update $originalStatus = $source->getOriginal('status'); - + if ($originalStatus instanceof \App\Enum\SourceStatusEnum) { $originalStatus = $originalStatus->value; } - + self::$previousStatuses[$source->id] = $originalStatus; } @@ -40,7 +40,7 @@ public function updated(Source $source): void if ($source->wasChanged('status')) { // Get the previous status from our static storage $previousStatus = self::$previousStatuses[$source->id] ?? null; - + if ($previousStatus === null) { // Fallback to getOriginal if for some reason we don't have it stored $previousStatus = $source->getOriginal('status'); @@ -48,7 +48,7 @@ public function updated(Source $source): void $previousStatus = $previousStatus->value; } } - + Log::info('Source status changed via Observer', [ 'source_id' => $source->id, 'source_name' => $source->name, @@ -56,12 +56,12 @@ public function updated(Source $source): void 'new_status' => $source->status->value, 'changed_via' => 'admin_panel', ]); - + // Broadcast the status change event broadcast(new SourceStatusChangedEvent($source, $previousStatus)); - + // Clean up the static storage unset(self::$previousStatuses[$source->id]); } } -} \ No newline at end of file +} diff --git a/app/Policies/EmotePolicy.php b/app/Policies/EmotePolicy.php new file mode 100644 index 0000000..6ca0712 --- /dev/null +++ b/app/Policies/EmotePolicy.php @@ -0,0 +1,52 @@ +moderates($user); + } + + public function update(User $user, Emote $emote): bool + { + return $this->moderates($user); + } + + public function delete(User $user, Emote $emote): bool + { + return $this->moderates($user); + } + + /** + * Approving is what puts an emote in front of every viewer, so it is the same + * bar as editing one. + */ + public function approve(User $user, Emote $emote): bool + { + return $this->moderates($user); + } + + private function moderates(User $user): bool + { + return $user->hasPermission('chat.moderate') || $user->hasPermission('admin.access'); + } +} diff --git a/app/Policies/RecordingPolicy.php b/app/Policies/RecordingPolicy.php new file mode 100644 index 0000000..61319f8 --- /dev/null +++ b/app/Policies/RecordingPolicy.php @@ -0,0 +1,43 @@ +manages($user); + } + + public function update(User $user, Recording $recording): bool + { + return $this->manages($user); + } + + public function delete(User $user, Recording $recording): bool + { + return $this->manages($user); + } + + private function manages(User $user): bool + { + return $user->hasPermission('stream.manage') || $user->hasPermission('admin.access'); + } +} diff --git a/app/Policies/RolePolicy.php b/app/Policies/RolePolicy.php new file mode 100644 index 0000000..0909551 --- /dev/null +++ b/app/Policies/RolePolicy.php @@ -0,0 +1,44 @@ +manages($user); + } + + public function update(User $user, Role $role): bool + { + return $this->manages($user); + } + + public function delete(User $user, Role $role): bool + { + return $this->manages($user) && ! $role->users()->exists(); + } + + private function manages(User $user): bool + { + return $user->hasPermission('user.manage') || $user->hasPermission('admin.access'); + } +} diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php new file mode 100644 index 0000000..c3992c9 --- /dev/null +++ b/app/Policies/ServerPolicy.php @@ -0,0 +1,70 @@ +manages($user); + } + + public function update(User $user, Server $server): bool + { + return $this->manages($user); + } + + /** + * Only manually managed servers are deleted outright. A Hetzner server has to go + * through deprovisioning so the cloud resource and its DNS record are cleaned up. + */ + public function delete(User $user, Server $server): bool + { + return $this->manages($user) && ! $server->isHetznerServer(); + } + + public function deprovision(User $user, Server $server): bool + { + return $this->manages($user) && $server->isHetznerServer(); + } + + /** + * Provisioning is not tied to one record. + */ + public function provision(User $user): bool + { + return $this->manages($user); + } + + public function viewInstallScript(User $user, Server $server): bool + { + return $this->manages($user); + } + + private function manages(User $user): bool + { + return $user->hasPermission('stream.manage') || $user->hasPermission('admin.access') || $user->isStaff(); + } +} diff --git a/app/Policies/ShowPolicy.php b/app/Policies/ShowPolicy.php new file mode 100644 index 0000000..2b5567b --- /dev/null +++ b/app/Policies/ShowPolicy.php @@ -0,0 +1,58 @@ +manages($user); + } + + public function update(User $user, Show $show): bool + { + return $this->manages($user); + } + + /** + * A live show cannot be deleted; it has to be ended first. Filament enforced this with + * a danger toast from the delete action, which meant the rule lived in the UI. + */ + public function delete(User $user, Show $show): bool + { + return $this->manages($user) && $show->status !== 'live'; + } + + public function goLive(User $user, Show $show): bool + { + return $this->manages($user) && $show->status === 'scheduled'; + } + + public function endStream(User $user, Show $show): bool + { + return $this->manages($user) && $show->status === 'live'; + } + + public function cancel(User $user, Show $show): bool + { + return $this->manages($user) && $show->status === 'scheduled'; + } + + private function manages(User $user): bool + { + return $user->hasPermission('stream.manage') || $user->hasPermission('admin.access') || $user->isStaff(); + } +} diff --git a/app/Policies/SourcePolicy.php b/app/Policies/SourcePolicy.php new file mode 100644 index 0000000..530bf0e --- /dev/null +++ b/app/Policies/SourcePolicy.php @@ -0,0 +1,58 @@ +manages($user); + } + + public function update(User $user, Source $source): bool + { + return $this->manages($user); + } + + /** + * A source with a live show on it cannot be deleted: the stream would keep running + * with nothing describing it. + */ + public function delete(User $user, Source $source): bool + { + return $this->manages($user) && ! $source->liveShows()->exists(); + } + + /** + * Rotating the stream key drops whoever is currently pushing to it. + */ + public function regenerateStreamKey(User $user, Source $source): bool + { + return $this->manages($user); + } + + private function manages(User $user): bool + { + return $user->hasPermission('stream.manage') || $user->hasPermission('admin.access') || $user->isStaff(); + } +} diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 0000000..348413c --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,44 @@ +manages($user); + } + + /** + * Deleting yourself would end the session that is doing the deleting. + */ + public function delete(User $user, User $subject): bool + { + return $this->manages($user) && $user->id !== $subject->id; + } + + private function manages(User $user): bool + { + return $user->hasPermission('user.manage') || $user->hasPermission('admin.access'); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 54756cd..a30eedb 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,8 +2,24 @@ namespace App\Providers; -// use Illuminate\Support\Facades\Gate; +use App\Models\Emote; +use App\Models\Recording; +use App\Models\Role; +use App\Models\Server; +use App\Models\Show; +use App\Models\Source; +use App\Models\User; +use App\Policies\EmotePolicy; +use App\Policies\RecordingPolicy; +use App\Policies\RolePolicy; +use App\Policies\ServerPolicy; +use App\Policies\ShowPolicy; +use App\Policies\SourcePolicy; +use App\Policies\UserPolicy; +use Illuminate\Auth\SessionGuard; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { @@ -13,7 +29,13 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - // + Emote::class => EmotePolicy::class, + Recording::class => RecordingPolicy::class, + Role::class => RolePolicy::class, + Server::class => ServerPolicy::class, + Show::class => ShowPolicy::class, + Source::class => SourcePolicy::class, + User::class => UserPolicy::class, ]; /** @@ -21,6 +43,22 @@ class AuthServiceProvider extends ServiceProvider */ public function boot(): void { - // + // Laravel's own default keeps a remember cookie alive for five years. Cap it at + // auth.remember_lifetime instead. Resolved lazily so booting a guard here does + // not force the session store open on requests that never authenticate. + Auth::resolved(function ($auth) { + $guard = $auth->guard(); + + if ($guard instanceof SessionGuard) { + $guard->setRememberDuration(config('auth.remember_lifetime')); + } + }); + + // Entry gate for the /manage panel. `filament.access` is kept because it is the + // string stored on existing role rows in production; renaming it would need a + // data migration and buys nothing. + Gate::define('access-manage', fn (User $user) => $user->hasPermission('admin.access') + || $user->hasPermission('filament.access') + || $user->isStaff()); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index aea7d1a..74eff60 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,17 +2,9 @@ namespace App\Providers; -use App\Events\Chat\Commands\SlowModeDisabled; -use App\Events\Chat\Commands\SlowModeEnabled; -use App\Events\Chat\DeleteMessagesEvent; use App\Events\SourceStatusChangedEvent; use App\Events\StreamListenerChangeEvent; use App\Events\StreamStatusEvent; -use App\Listeners\Chat\DeleteMessages\DeleteMessagesListener; -use App\Listeners\Chat\SlowMode\AnnounceSlowModeDeactivationListener; -use App\Listeners\Chat\SlowMode\AnnounceSlowModeListener; -use App\Listeners\Chat\SlowMode\SlowModeDisableListener; -use App\Listeners\Chat\SlowMode\SlowModeEnableListener; use App\Listeners\HandleAutoModeShowsListener; use App\Listeners\SaveListenerCountListener; use App\Listeners\SetCacheStatusListener; @@ -39,17 +31,6 @@ class EventServiceProvider extends ServiceProvider StreamListenerChangeEvent::class => [ SaveListenerCountListener::class, ], - SlowModeEnabled::class => [ - SlowModeEnableListener::class, - AnnounceSlowModeListener::class, - ], - SlowModeDisabled::class => [ - SlowModeDisableListener::class, - AnnounceSlowModeDeactivationListener::class, - ], - DeleteMessagesEvent::class => [ - DeleteMessagesListener::class, - ], SourceStatusChangedEvent::class => [ HandleAutoModeShowsListener::class, ], diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php deleted file mode 100644 index bd88b63..0000000 --- a/app/Providers/Filament/AdminPanelProvider.php +++ /dev/null @@ -1,70 +0,0 @@ -default() - ->id('admin') - ->path('admin') - ->login() - ->brandName('EF Streaming Admin') - ->favicon(asset('favicon.ico')) - ->colors([ - 'primary' => Color::Purple, - 'gray' => Color::Slate, - ]) - ->navigationGroups([ - 'Streaming', - 'Infrastructure', - 'User Management', - 'Chat', - ]) - ->collapsibleNavigationGroups(false) - ->sidebarCollapsibleOnDesktop() - ->maxContentWidth('100%') - ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') - ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') - ->pages([ - Pages\Dashboard::class, - ]) - ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') - ->widgets([ - Widgets\AccountWidget::class, - Widgets\FilamentInfoWidget::class, - ]) - ->middleware([ - EncryptCookies::class, - AddQueuedCookiesToResponse::class, - StartSession::class, - AuthenticateSession::class, - ShareErrorsFromSession::class, - VerifyCsrfToken::class, - SubstituteBindings::class, - DisableBladeIconComponents::class, - DispatchServingFilamentEvent::class, - ]) - ->authMiddleware([ - Authenticate::class, - ]); - } -} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index c769c1c..aba9782 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -40,6 +40,18 @@ public function boot(): void Route::middleware('web') ->group(base_path('routes/web.php')); + + Route::middleware(['web', 'auth:web', 'can:access-manage', \App\Http\Middleware\ShareManageProps::class]) + ->prefix('manage') + ->name('manage.') + ->group(base_path('routes/manage.php')); + + // Developer account switcher. Deliberately unauthenticated, so it only + // exists at all when running locally. + if ($this->app->isLocal()) { + Route::middleware(['web', \App\Http\Middleware\LocalOnly::class]) + ->group(base_path('routes/local.php')); + } }); } } diff --git a/app/Services/ArchivePlaylistService.php b/app/Services/ArchivePlaylistService.php new file mode 100644 index 0000000..dba6725 --- /dev/null +++ b/app/Services/ArchivePlaylistService.php @@ -0,0 +1,427 @@ + ['bandwidth' => 1_500_000, 'resolution' => '854x480'], + 'hd' => ['bandwidth' => 3_500_000, 'resolution' => '1280x720'], + 'fhd' => ['bandwidth' => 6_000_000, 'resolution' => '1920x1080'], + ]; + + protected const ARCHIVE_PREFIX = 'archive'; + + protected const RECORDINGS_PREFIX = 'recordings'; + + protected string $disk; + + public function __construct(?string $disk = null) + { + $this->disk = $disk ?? config('stream.archive_disk', 'dvr'); + } + + /** + * Resolve a recording's cut and cache what the listing needs. + * + * Deliberately does not write playlists anywhere. Segments are served through + * presigned URLs, which expire, so a stored playlist would be dead 24 hours after it + * was written. Playlists are rendered per request instead (see renderMaster and + * renderMedia), which also puts the access check on the request that hands out the + * URLs rather than on a static object anyone could fetch. + * + * What is stored is only what a listing needs without touching S3: duration, segment + * count, and whether the range resolves at all. + */ + public function build(Recording $recording): void + { + if (! $recording->starts_at || ! $recording->ends_at) { + throw new \RuntimeException('Recording has no cut range set.'); + } + + if ($recording->ends_at <= $recording->starts_at) { + throw new \RuntimeException('Recording ends before it starts.'); + } + + $source = $recording->archiveSourceSlug(); + if (! $source) { + throw new \RuntimeException('Recording is not attached to a source.'); + } + + // Normalised to UTC before anything else touches them. The archive is bucketed by + // UTC hour, and a marker that arrives as a naive local string (a datetime-local + // input, or a bare string in a seeder) is interpreted in the app timezone, which + // silently shifts it into an hour bucket that holds no segments. The failure then + // reads as "no archived segments cover that range" rather than as a timezone bug. + $segments = $this->segmentsInRange( + $source, + CarbonImmutable::parse($recording->starts_at)->utc(), + CarbonImmutable::parse($recording->ends_at)->utc(), + ); + + if ($segments === []) { + throw new \RuntimeException( + 'No archived segments cover that range. The archive may have expired, ' + .'or the segments may not be uploaded yet.' + ); + } + + $recording->forceFill([ + // The app renders the playlist, so this is a route rather than an S3 object. + 'm3u8_url' => route('recordings.playlist.master', $recording->slug), + 'duration' => (int) round(array_sum(array_column($segments, 'duration'))), + 'segment_count' => count($segments), + 'status' => 'ready', + 'build_error' => null, + 'playlist_built_at' => now(), + ])->save(); + } + + /** + * Master playlist, listing the renditions the archive actually holds for this cut. + */ + public function renderMaster(Recording $recording): string + { + $lines = ['#EXTM3U', '#EXT-X-VERSION:6', '#EXT-X-INDEPENDENT-SEGMENTS']; + + foreach (self::RENDITIONS as $rendition => $meta) { + $lines[] = sprintf( + '#EXT-X-STREAM-INF:BANDWIDTH=%d,RESOLUTION=%s,CODECS="avc1.64001f,mp4a.40.2"', + $meta['bandwidth'], + $meta['resolution'], + ); + $lines[] = route('recordings.playlist.media', [$recording->slug, $rendition]); + } + + return implode("\n", $lines)."\n"; + } + + /** + * Media playlist for one rendition, with a presigned URL per segment. + */ + public function renderMedia(Recording $recording, string $rendition): string + { + if (! array_key_exists($rendition, self::RENDITIONS)) { + throw new \InvalidArgumentException("Unknown rendition [{$rendition}]."); + } + + $source = $recording->archiveSourceSlug(); + + $segments = $this->segmentsInRange( + $source, + CarbonImmutable::parse($recording->starts_at)->utc(), + CarbonImmutable::parse($recording->ends_at)->utc(), + ); + + return $this->renderMediaPlaylist($segments, $source, $rendition); + } + + public function renditions(): array + { + return array_keys(self::RENDITIONS); + } + + /** + * Playlist for an arbitrary window of a source's archive, independent of any cut. + * + * This is what the trim editor previews. Scrubbing only within the current cut would + * be useless for the job it exists to do: an operator sets the in point by looking at + * what happened *before* the current one, so the editor needs to play material outside + * the markers. + */ + public function renderRange( + string $source, + CarbonImmutable $from, + CarbonImmutable $to, + string $rendition, + ): string { + if (! array_key_exists($rendition, self::RENDITIONS)) { + throw new \InvalidArgumentException("Unknown rendition [{$rendition}]."); + } + + $segments = $this->segmentsInRange($source, $from->utc(), $to->utc()); + + return $this->renderMediaPlaylist($segments, $source, $rendition); + } + + /** + * The instant the first segment of a range starts. + * + * The editor maps the video element's currentTime onto wall clock, and a range never + * begins exactly on the requested boundary: selection is by segment start, so the + * first segment can begin slightly before `from`. Without this the markers would be + * off by up to one segment. + */ + public function rangeStart(string $source, CarbonImmutable $from, CarbonImmutable $to): ?CarbonImmutable + { + $segments = $this->segmentsInRange($source, $from->utc(), $to->utc()); + + return $segments === [] ? null : $segments[0]['pdt']; + } + + /** + * Same as build(), but records the failure on the model rather than throwing, so a + * controller can report it without the recording ending up in an unclear state. + */ + public function rebuild(Recording $recording): bool + { + try { + $this->build($recording); + + return true; + } catch (\Throwable $e) { + Log::warning("Playlist build failed for recording {$recording->id}: ".$e->getMessage()); + + $recording->forceFill([ + 'status' => 'failed', + 'build_error' => $e->getMessage(), + ])->save(); + + return false; + } + } + + /** + * Every archived segment whose start falls inside the range. + * + * Selection is on the segment's own start, so a segment straddling either boundary is + * included whole. That is what keeps cuts seamless: the price is that a cut can + * overshoot its end marker by up to one segment, which is far cheaper than the frame + * accuracy it buys back. + */ + public function segmentsInRange(string $source, CarbonImmutable $from, CarbonImmutable $to): array + { + $segments = []; + + foreach ($this->hoursBetween($from, $to) as $hour) { + foreach ($this->readHourIndex($source, $hour) as $segment) { + if ($segment['pdt'] >= $from && $segment['pdt'] < $to) { + $segments[] = $segment; + } + } + } + + // Hour files are appended in observation order, but a range spans several of them + // and the archive sequence is the only ordering that never depends on a clock. + usort($segments, fn ($a, $b) => $a['seq'] <=> $b['seq']); + + return $segments; + } + + /** + * The window an operator can actually cut from: what the archive still holds, and how + * far it has caught up to live. Drives the "archive available from X to Y" hint, so a + * cut is not silently truncated at either end. + */ + public function availableRange(string $source): array + { + $hours = collect(Storage::disk($this->disk)->allFiles(self::ARCHIVE_PREFIX."/{$source}")) + ->filter(fn ($p) => str_ends_with($p, 'index.m3u8')) + ->sort() + ->values(); + + if ($hours->isEmpty()) { + return ['from' => null, 'to' => null]; + } + + $first = $this->parseHourIndex(Storage::disk($this->disk)->get($hours->first())); + $last = $this->parseHourIndex(Storage::disk($this->disk)->get($hours->last())); + + return [ + 'from' => $first === [] ? null : $first[0]['pdt'], + 'to' => $last === [] ? null : end($last)['pdt']->addSeconds((int) end($last)['duration']), + ]; + } + + /** Hour buckets touched by a range, as YYYYMMDD/HH. */ + protected function hoursBetween(CarbonImmutable $from, CarbonImmutable $to): array + { + $hours = []; + $cursor = $from->utc()->startOfHour(); + $end = $to->utc(); + + while ($cursor <= $end) { + $hours[] = $cursor->format('Ymd/H'); + $cursor = $cursor->addHour(); + } + + return $hours; + } + + protected function readHourIndex(string $source, string $hour): array + { + $path = self::ARCHIVE_PREFIX."/{$source}/{$hour}/index.m3u8"; + + if (! Storage::disk($this->disk)->exists($path)) { + return []; + } + + return $this->parseHourIndex(Storage::disk($this->disk)->get($path)); + } + + /** + * Parses an hour index written by archive_uploader.py. + * + * Alongside the standard tags it carries #EXT-X-ARCHIVE-SEQ (monotonic ordering, + * assigned on observation and never clock-derived) and #EXT-X-ARCHIVE-OBSERVED (the + * origin's own wall clock, so drift between the publisher's timeline and real time + * stays measurable after the fact). + */ + public function parseHourIndex(string $contents): array + { + $segments = []; + $duration = null; + $pdt = null; + $observed = null; + $seq = null; + $discontinuity = false; + + foreach (preg_split('/\R/', $contents) as $line) { + $line = trim($line); + + if ($line === '') { + continue; + } + + if (str_starts_with($line, '#EXT-X-ARCHIVE-SEQ:')) { + $seq = (int) substr($line, 19); + } elseif (str_starts_with($line, '#EXT-X-ARCHIVE-OBSERVED:')) { + $observed = $this->parseTimestamp(substr($line, 24)); + } elseif (str_starts_with($line, '#EXTINF:')) { + $duration = (float) strtok(substr($line, 8), ','); + } elseif (str_starts_with($line, '#EXT-X-PROGRAM-DATE-TIME:')) { + $pdt = $this->parseTimestamp(substr($line, 25)); + } elseif ($line === '#EXT-X-DISCONTINUITY') { + $discontinuity = true; + } elseif (! str_starts_with($line, '#')) { + if ($pdt !== null && $duration !== null) { + $segments[] = [ + 'name' => $line, + 'duration' => $duration, + 'pdt' => $pdt, + 'observed' => $observed, + 'seq' => $seq ?? count($segments), + 'discontinuity' => $discontinuity, + ]; + } + $duration = null; + $pdt = null; + $observed = null; + $seq = null; + $discontinuity = false; + } + } + + return $segments; + } + + protected function parseTimestamp(string $value): ?CarbonImmutable + { + try { + return CarbonImmutable::parse(trim($value))->utc(); + } catch (\Throwable) { + return null; + } + } + + protected function renderMediaPlaylist(array $segments, string $source, string $rendition): string + { + // Reachable from the request path, not just from build(): a cut whose hours have + // since expired out of the archive resolves to nothing. Raise something that says + // so, rather than letting max() fail on an empty array. + if ($segments === []) { + throw new \RuntimeException( + 'No archived segments cover this recording any more. The archive it was ' + .'cut from has most likely expired.' + ); + } + + $target = (int) ceil(max(array_column($segments, 'duration'))); + + $lines = [ + '#EXTM3U', + '#EXT-X-VERSION:6', + "#EXT-X-TARGETDURATION:{$target}", + '#EXT-X-MEDIA-SEQUENCE:0', + // Without both of these a player treats the playlist as live and refuses to + // expose a full seek bar. + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-INDEPENDENT-SEGMENTS', + ]; + + foreach ($segments as $i => $segment) { + // The leading discontinuity every session opens with is meaningless once the + // segment is the first thing in the cut. + if ($segment['discontinuity'] && $i > 0) { + $lines[] = '#EXT-X-DISCONTINUITY'; + } + + $lines[] = '#EXT-X-PROGRAM-DATE-TIME:'.$segment['pdt']->format('Y-m-d\TH:i:s.vP'); + $lines[] = sprintf('#EXTINF:%.6f,', $segment['duration']); + $lines[] = $this->segmentUrl($source, $segment, $rendition); + } + + $lines[] = '#EXT-X-ENDLIST'; + + return implode("\n", $lines)."\n"; + } + + /** + * Index entries name segments generically, because all renditions are cut at the same + * instants and one entry therefore describes all three. + * + * Presigned rather than public: the archive holds the raw continuous capture of every + * source, which includes material that was never published and everything an operator + * trimmed off. A signed URL grants access to one object for a bounded time, so the + * bucket itself stays private. + */ + protected function segmentUrl(string $source, array $segment, string $rendition): string + { + $name = str_replace('%v', $rendition, $segment['name']); + $hour = $segment['pdt']->format('Ymd/H'); + $path = self::ARCHIVE_PREFIX."/{$source}/{$hour}/{$name}"; + + // See config/stream.php for why local development cannot use signed URLs. + if (config('stream.archive_url_mode') === 'proxy') { + return route('archive.segment', ['path' => $path]); + } + + return Storage::disk($this->disk)->temporaryUrl( + $path, + now()->addSeconds(self::signedUrlLifetime()), + ); + } + + /** + * How long a segment URL stays valid. + * + * Long enough that a viewer never hits an expiry mid-playback, since a playlist is + * fetched once at the start of a VOD session rather than refreshed like a live one. + * The trade is explicit: a leaked playlist grants access to those segments until the + * signatures lapse. + */ + public static function signedUrlLifetime(): int + { + return (int) config('stream.archive_url_ttl', 86400); + } +} diff --git a/app/Services/AutoscalerService.php b/app/Services/AutoscalerService.php deleted file mode 100644 index 45cc3d2..0000000 --- a/app/Services/AutoscalerService.php +++ /dev/null @@ -1,57 +0,0 @@ -value, ServerStatusEnum::ACTIVE->value]) - ->where('type', ServerTypeEnum::EDGE->value) - ->where('hetzner_id', '!=', 'manual') // Exclude manual servers from autoscaling - ->sum('max_clients'); - } - - public static function determineAction(): AutoscalerAction - { - // Total active users in stream - $serverUserCount = StreamInfoService::getUserCount(); - - // Capacity of servers that are in provisioning and active max_clients - $serverCapacity = self::availableClientSlots(); - - // Is capacity over 80% - if ($serverUserCount > ($serverCapacity * 0.8)) { - return AutoscalerAction::SCALE_UP; - } - // Is under capacity 20% - if ($serverUserCount < ($serverCapacity * 0.2)) { - return AutoscalerAction::SCALE_DOWN; - } - - return AutoscalerAction::NONE; - - } -} diff --git a/app/Services/BrandingService.php b/app/Services/BrandingService.php new file mode 100644 index 0000000..158db4c --- /dev/null +++ b/app/Services/BrandingService.php @@ -0,0 +1,158 @@ + + */ + public const EDITABLE = [ + 'convention_name' => 'Name of the convention, used in page copy.', + 'site_name' => 'Name of this streaming site, used in the header and page titles.', + 'login_eyebrow' => 'Small label above the login headline.', + 'login_headline' => 'Main login headline.', + 'login_tagline' => 'One line under the headline.', + 'login_body' => 'Paragraph explaining what is needed to watch.', + 'login_button_label' => 'Label on the sign-in button.', + 'identity_name' => 'Name of the identity provider people sign in with.', + 'identity_register_url' => 'Where people register a new identity account.', + 'identity_logout_url' => 'Identity provider logout endpoint.', + 'footer_links' => 'Title and address for each footer link, in the order they are shown.', + 'logo_path' => 'Logo image. Leave empty to show the site name as text instead.', + 'login_background_image' => 'Background image for the login screen.', + 'login_background_video' => 'Background video for the login screen. Left empty, the bundled clip is used.', + 'primary_color' => 'Pick a preset or a custom hex. A full 50-950 ramp is derived from it; empty keeps the palette in the stylesheet.', + ]; + + /** + * Every resolved branding value, keyed as in config/branding.php. + * + * @return array + */ + public function all(): array + { + $values = []; + + foreach (array_keys(config('branding')) as $key) { + $values[$key] = BrandingSetting::getValue($key); + } + + return $values; + } + + public function get(string $key, $default = null) + { + return BrandingSetting::getValue($key, $default); + } + + /** + * Shape the branding for the frontend, with URLs already resolved. + * + * @return array + */ + public function forFrontend(): array + { + $values = $this->all(); + + return [ + 'conventionName' => $values['convention_name'], + 'siteName' => $values['site_name'], + 'logoUrl' => $this->assetUrl($values['logo_path']), + 'identity' => [ + 'name' => $values['identity_name'], + 'registerUrl' => $values['identity_register_url'], + 'logoutUrl' => $values['identity_logout_url'], + ], + 'login' => [ + 'eyebrow' => $values['login_eyebrow'], + 'headline' => $values['login_headline'], + 'tagline' => $values['login_tagline'], + 'body' => $values['login_body'], + 'buttonLabel' => $values['login_button_label'], + 'backgroundImage' => $this->assetUrl($values['login_background_image']), + 'backgroundVideo' => $this->assetUrl($values['login_background_video']), + ], + // A list, not a fixed set of slots: an installation names its own + // footer links and has as many as it likes. Empty means the footer + // renders no link row at all. + 'links' => $this->footerLinks(), + ]; + } + + /** + * Footer links as {label, url}, in order, with unusable rows dropped. + * + * @return array + */ + public function footerLinks(): array + { + $links = []; + + foreach (Settings::decodeRows($this->get('footer_links')) as $row) { + $label = is_array($row) ? trim((string) ($row['label'] ?? '')) : ''; + $url = is_array($row) ? trim((string) ($row['url'] ?? '')) : ''; + + if ($label === '' || $url === '') { + continue; + } + + $links[] = ['label' => $label, 'url' => $url]; + } + + return $links; + } + + /** + * CSS custom properties overriding the primary ramp, empty when no accent + * colour is configured so the stylesheet's own palette stays authoritative. + * + * @return array + */ + public function paletteVariables(): array + { + $accent = $this->get('primary_color'); + + $variables = []; + + foreach (ColorRamp::fromHex($accent) as $stop => $value) { + $variables["--color-primary-{$stop}"] = $value; + } + + // The /manage chrome has its own tokens, so it needs re-tinting too or + // the panel keeps the shipped hue while the public site changes. + return $variables + ColorRamp::chromeFromHex($accent); + } + + /** + * Turn a stored path into a usable URL. Absolute URLs pass through, so an + * installation can point at a CDN instead of uploading anything. + */ + protected function assetUrl(?string $path): ?string + { + $path = is_string($path) ? trim($path) : ''; + + if ($path === '') { + return null; + } + + if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://') || str_starts_with($path, '/')) { + return $path; + } + + return Storage::disk('public')->url($path); + } +} diff --git a/app/Services/Chat/ChatModerationService.php b/app/Services/Chat/ChatModerationService.php new file mode 100644 index 0000000..392aca8 --- /dev/null +++ b/app/Services/Chat/ChatModerationService.php @@ -0,0 +1,385 @@ +assertCanModerate($moderator, $target); + + $seconds = max(1, min($seconds, 14 * 24 * 3600)); + $expiresAt = now()->addSeconds($seconds); + + $timeout = Timeout::updateOrCreate( + ['user_id' => $target->id], + [ + 'issued_by_user_id' => $moderator->id, + 'expires_at' => $expiresAt, + 'reason' => $reason, + ], + ); + + $this->log('timeout', $moderator, $target, $sourceId, $reason, ['seconds' => $seconds]); + + Broadcast::send(new ChatUserStateEvent($target, 'timed_out', $reason, $seconds, $sourceId)); + Broadcast::send(new ChatNoticeEvent( + "{$target->name} was timed out for ".$this->humanizeSeconds($seconds).($reason ? " ({$reason})" : ''), + $sourceId, + 'warning', + modsOnly: true, + )); + + return $timeout; + } + + public function removeTimeout(User $moderator, User $target, ?int $sourceId = null): void + { + $this->assertCanModerate($moderator, $target); + + Timeout::where('user_id', $target->id)->delete(); + + $this->log('untimeout', $moderator, $target, $sourceId); + + Broadcast::send(new ChatUserStateEvent($target, 'cleared', null, null, $sourceId)); + Broadcast::send(new ChatNoticeEvent("{$target->name}'s timeout was removed", $sourceId, 'info', modsOnly: true)); + } + + /** + * Ban a user from chat. A null $expiresAt means permanent. + */ + public function ban(User $moderator, User $target, ?string $reason = null, ?Carbon $expiresAt = null, ?int $sourceId = null): ChatBan + { + if (! $moderator->canBanFromChat()) { + throw new AuthorizationException('You do not have permission to ban users.'); + } + + $this->assertCanModerate($moderator, $target); + + $target->chatBans()->active()->update(['lifted_at' => now(), 'lifted_by_user_id' => $moderator->id]); + + $ban = ChatBan::create([ + 'user_id' => $target->id, + 'banned_by_user_id' => $moderator->id, + 'reason' => $reason, + 'expires_at' => $expiresAt, + ]); + + $this->log('ban', $moderator, $target, $sourceId, $reason, ['expires_at' => $expiresAt?->toIso8601String()]); + + Broadcast::send(new ChatUserStateEvent( + $target, + 'banned', + $reason, + $expiresAt ? (int) now()->diffInSeconds($expiresAt) : null, + $sourceId, + )); + Broadcast::send(new ChatNoticeEvent( + "{$target->name} was banned from chat".($reason ? " ({$reason})" : ''), + $sourceId, + 'error', + modsOnly: true, + )); + + return $ban; + } + + public function unban(User $moderator, User $target, ?int $sourceId = null): void + { + if (! $moderator->canBanFromChat()) { + throw new AuthorizationException('You do not have permission to unban users.'); + } + + $target->chatBans()->active()->update(['lifted_at' => now(), 'lifted_by_user_id' => $moderator->id]); + + $this->log('unban', $moderator, $target, $sourceId); + + Broadcast::send(new ChatUserStateEvent($target, 'cleared', null, null, $sourceId)); + Broadcast::send(new ChatNoticeEvent("{$target->name} was unbanned", $sourceId, 'info', modsOnly: true)); + } + + /** + * Delete a single message. + */ + public function deleteMessage(User $moderator, Message $message): void + { + if ($message->user_id !== $moderator->id) { + $this->assertCanModerate($moderator, $message->user); + } + + $message->update(['deleted_by_user_id' => $moderator->id]); + $message->delete(); + + $this->log('delete_message', $moderator, $message->user, $message->source_id, null, [ + 'message_id' => $message->id, + ]); + + Broadcast::send(new ChatMessagesDeletedEvent( + [$message->id], + $message->source_id, + $message->user?->name, + $moderator->name, + )); + } + + /** + * Delete a user's recent messages. $withinSeconds null deletes everything in the source. + * + * @return int number of deleted messages + */ + public function purgeUser(User $moderator, User $target, ?int $sourceId = null, ?int $withinSeconds = null): int + { + $this->assertCanModerate($moderator, $target); + + $query = Message::where('user_id', $target->id)->whereNull('deleted_at'); + + if ($sourceId !== null) { + $query->where('source_id', $sourceId); + } + + if ($withinSeconds !== null) { + $query->where('created_at', '>=', now()->subSeconds($withinSeconds)); + } + + $messages = $query->get(); + + if ($messages->isEmpty()) { + return 0; + } + + Message::whereIn('id', $messages->pluck('id'))->update([ + 'deleted_at' => now(), + 'deleted_by_user_id' => $moderator->id, + ]); + + $this->log('purge', $moderator, $target, $sourceId, null, [ + 'count' => $messages->count(), + 'within_seconds' => $withinSeconds, + ]); + + foreach ($messages->groupBy('source_id') as $groupSourceId => $group) { + Broadcast::send(new ChatMessagesDeletedEvent( + $group->pluck('id')->all(), + $groupSourceId !== null ? (int) $groupSourceId : null, + $target->name, + $moderator->name, + )); + } + + return $messages->count(); + } + + /** + * Wipe the visible chat log for a source. + * + * @return int number of deleted messages + */ + public function clearChat(User $moderator, ?int $sourceId = null): int + { + if (! $moderator->canModerateChat()) { + throw new AuthorizationException('You do not have permission to moderate chat.'); + } + + $query = Message::whereNull('deleted_at'); + + if ($sourceId !== null) { + $query->where('source_id', $sourceId); + } + + $ids = $query->pluck('id'); + + if ($ids->isEmpty()) { + return 0; + } + + Message::whereIn('id', $ids)->update([ + 'deleted_at' => now(), + 'deleted_by_user_id' => $moderator->id, + ]); + + $this->log('clear_chat', $moderator, null, $sourceId, null, ['count' => $ids->count()]); + + Broadcast::send(new ChatMessagesDeletedEvent($ids->all(), $sourceId, null, $moderator->name)); + Broadcast::send(new ChatNoticeEvent('Chat was cleared by a moderator', $sourceId, 'warning')); + + return $ids->count(); + } + + /** + * Post a highlighted announcement into a source's chat. + */ + public function announce(User $moderator, string $text, ?int $sourceId = null): Message + { + if (! $moderator->canModerateChat() && ! $moderator->hasPermission('chat.broadcast')) { + throw new AuthorizationException('You do not have permission to send announcements.'); + } + + $message = Message::create([ + 'message' => $text, + 'user_id' => null, + 'source_id' => $sourceId, + 'is_command' => false, + 'type' => 'announcement', + 'priority' => 'high', + 'metadata' => [ + 'sent_by_user_id' => $moderator->id, + 'sent_by_user_name' => $moderator->name, + ], + ]); + + $this->log('announce', $moderator, null, $sourceId, null, ['message_id' => $message->id]); + + Broadcast::send(new ChatMessageEvent($message)); + + return $message; + } + + /** + * Update chat modes for a source. + * + * @param array $changes + * @return array + */ + public function updateSettings(User $moderator, array $changes, ?int $sourceId = null): array + { + if (! $moderator->canModerateChat()) { + throw new AuthorizationException('You do not have permission to moderate chat.'); + } + + $settings = $this->settings->update($changes, $sourceId); + + $this->log('settings', $moderator, null, $sourceId, null, $changes); + + Broadcast::send(new ChatNoticeEvent($this->describeSettings($settings), $sourceId, 'info')); + + return $settings; + } + + /** + * A moderator may not act on someone whose highest role outranks their own. + */ + public function canActOn(User $moderator, ?User $target): bool + { + if (! $moderator->canModerateChat()) { + return false; + } + + if (! $target) { + return true; + } + + // Nobody moderates themselves; deleting your own message goes a different route. + if ($target->id === $moderator->id) { + return false; + } + + if ($moderator->isAdmin()) { + return true; + } + + // A moderator cannot touch admins or fellow moderators. + return ! $target->isAdmin() && ! $target->canModerateChat(); + } + + protected function assertCanModerate(User $moderator, ?User $target): void + { + if (! $moderator->canModerateChat()) { + throw new AuthorizationException('You do not have permission to moderate chat.'); + } + + if (! $this->canActOn($moderator, $target)) { + throw new AuthorizationException('You cannot moderate this user.'); + } + } + + protected function log(string $action, ?User $moderator, ?User $target, ?int $sourceId, ?string $reason = null, array $metadata = []): void + { + ChatModerationLog::create([ + 'action' => $action, + 'moderator_id' => $moderator?->id, + 'target_user_id' => $target?->id, + 'source_id' => $sourceId, + 'reason' => $reason, + 'metadata' => $metadata ?: null, + ]); + } + + public function humanizeSeconds(int $seconds): string + { + return match (true) { + $seconds < 60 => $seconds.' second'.($seconds === 1 ? '' : 's'), + $seconds < 3600 => intdiv($seconds, 60).' minute'.(intdiv($seconds, 60) === 1 ? '' : 's'), + $seconds < 86400 => intdiv($seconds, 3600).' hour'.(intdiv($seconds, 3600) === 1 ? '' : 's'), + default => intdiv($seconds, 86400).' day'.(intdiv($seconds, 86400) === 1 ? '' : 's'), + }; + } + + /** + * @param array $settings + */ + protected function describeSettings(array $settings): string + { + $active = []; + + if ($settings['slow_mode_seconds'] > 0) { + $active[] = 'slow mode '.$settings['slow_mode_seconds'].'s'; + } + + if ($settings['emote_only']) { + $active[] = 'emote-only'; + } + + if ($settings['sponsors_only']) { + $active[] = 'sponsors-only'; + } + + return $active === [] + ? 'Chat restrictions were turned off' + : 'Chat mode updated: '.implode(', ', $active); + } + + /** + * Parse durations like `10s`, `5m`, `1h`, `2d` or a plain number of seconds. + */ + public static function parseDuration(string $duration): ?int + { + $duration = trim(strtolower($duration)); + + if (is_numeric($duration)) { + return (int) $duration; + } + + if (! preg_match('/^(\d+)\s*(s|m|h|d)$/', $duration, $matches)) { + return null; + } + + return (int) $matches[1] * match ($matches[2]) { + 's' => 1, + 'm' => 60, + 'h' => 3600, + 'd' => 86400, + }; + } +} diff --git a/app/Services/Chat/ChatSettingsService.php b/app/Services/Chat/ChatSettingsService.php new file mode 100644 index 0000000..bb5ed0f --- /dev/null +++ b/app/Services/Chat/ChatSettingsService.php @@ -0,0 +1,78 @@ + 'int', + 'emote_only' => 'bool', + 'sponsors_only' => 'bool', + ]; + + /** + * @return array + */ + public function all(?int $sourceId = null): array + { + return [ + 'slow_mode_seconds' => (int) ChatSetting::getValue('slow_mode_seconds', (string) config('chat.default.slowModeSeconds', 0), $sourceId), + 'emote_only' => filter_var(ChatSetting::getValue('emote_only', '0', $sourceId), FILTER_VALIDATE_BOOL), + 'sponsors_only' => filter_var(ChatSetting::getValue('sponsors_only', '0', $sourceId), FILTER_VALIDATE_BOOL), + 'max_message_length' => (int) config('chat.default.maxMessageLength', 500), + 'max_tries' => (int) config('chat.default.maxTries', 8), + 'rate_decay' => (int) config('chat.default.rateDecay', 30), + ]; + } + + public function slowModeSeconds(?int $sourceId = null): int + { + return (int) ChatSetting::getValue('slow_mode_seconds', (string) config('chat.default.slowModeSeconds', 0), $sourceId); + } + + public function emoteOnly(?int $sourceId = null): bool + { + return filter_var(ChatSetting::getValue('emote_only', '0', $sourceId), FILTER_VALIDATE_BOOL); + } + + public function sponsorsOnly(?int $sourceId = null): bool + { + return filter_var(ChatSetting::getValue('sponsors_only', '0', $sourceId), FILTER_VALIDATE_BOOL); + } + + /** + * Apply a partial settings update and broadcast the result. + * + * @param array $changes + * @return array the full settings after the update + */ + public function update(array $changes, ?int $sourceId = null): array + { + foreach ($changes as $key => $value) { + if (! array_key_exists($key, self::KEYS)) { + continue; + } + + $stored = match (self::KEYS[$key]) { + 'int' => (string) max(0, (int) $value), + 'bool' => filter_var($value, FILTER_VALIDATE_BOOL) ? '1' : '0', + default => (string) $value, + }; + + ChatSetting::setValue($key, $stored, null, $sourceId); + } + + $settings = $this->all($sourceId); + + Broadcast::send(new ChatSettingsUpdatedEvent($settings, $sourceId)); + + return $settings; + } +} diff --git a/app/Services/Chat/MessagePresenter.php b/app/Services/Chat/MessagePresenter.php new file mode 100644 index 0000000..9e835e9 --- /dev/null +++ b/app/Services/Chat/MessagePresenter.php @@ -0,0 +1,116 @@ + */ + protected array $authorMemo = []; + + /** + * @return array + */ + public function present(Message $message): array + { + $author = $message->user_id ? $this->author($message->user ?? User::find($message->user_id)) : null; + + return [ + 'id' => $message->id, + 'type' => $message->type ?: 'user', + 'body' => $message->body, + 'user' => $author, + 'name' => $author['name'] ?? $this->systemName($message), + 'color' => $author['color'] ?? '#f6cb21', + 'badges' => $author['badges'] ?? [], + 'time' => $message->created_at->format('H:i'), + 'timestamp' => $message->created_at->toIso8601String(), + 'is_command' => (bool) $message->is_command, + 'priority' => $message->priority, + 'metadata' => $message->metadata, + 'source_id' => $message->source_id, + 'reply_to' => $this->replyTo($message), + ]; + } + + /** + * @param iterable $messages + * @return array> + */ + public function presentMany(iterable $messages): array + { + $presented = []; + + foreach ($messages as $message) { + $presented[] = $this->present($message); + } + + return $presented; + } + + /** + * @return array|null + */ + public function author(?User $user): ?array + { + if (! $user) { + return null; + } + + if (isset($this->authorMemo[$user->id])) { + return $this->authorMemo[$user->id]; + } + + return $this->authorMemo[$user->id] = Cache::remember( + 'chat_author_'.$user->id, + 300, + fn () => [ + 'id' => $user->id, + 'name' => $user->name, + 'color' => $user->chat_color, + 'badges' => $user->chatBadges(), + ], + ); + } + + public static function forgetAuthor(int $userId): void + { + Cache::forget('chat_author_'.$userId); + } + + protected function systemName(Message $message): string + { + return $message->type === 'announcement' ? 'Announcement' : 'System'; + } + + /** + * @return array|null + */ + protected function replyTo(Message $message): ?array + { + if (! $message->reply_to_id) { + return null; + } + + $parent = $message->relationLoaded('replyTo') ? $message->replyTo : $message->replyTo()->first(); + + if (! $parent) { + return null; + } + + return [ + 'id' => $parent->id, + 'name' => $parent->user?->name, + 'body' => mb_strimwidth($parent->body, 0, 120, '…'), + ]; + } +} diff --git a/app/Services/ChatMessageSanitizer.php b/app/Services/ChatMessageSanitizer.php index ad2dc34..1a2cd15 100644 --- a/app/Services/ChatMessageSanitizer.php +++ b/app/Services/ChatMessageSanitizer.php @@ -2,91 +2,64 @@ namespace App\Services; +use App\Models\User; + +/** + * Cleans up raw chat input. Output stays plain text: escaping and emote/mention + * rendering happen in the client, so no markup is produced or preserved here. + */ class ChatMessageSanitizer { - protected array $allowedDomains = [ - 'eurofurence.org', - ]; - - protected int $maxMessageLength = 500; - - protected int $maxWordLength = 30; - /** - * Sanitize a chat message + * Sanitize a chat message. */ - public function sanitize(string $message, ?\App\Models\User $user = null): string + public function sanitize(string $message, ?User $user = null): string { - // Trim the message + $message = $this->stripControlCharacters($message); + $message = $this->collapseWhitespace($message); + $message = $this->filterUrls($message); $message = trim($message); - // Limit length first - if (mb_strlen($message) > $this->maxMessageLength) { - $message = mb_substr($message, 0, $this->maxMessageLength); + if (mb_strlen($message) > $this->getMaxLength()) { + $message = mb_substr($message, 0, $this->getMaxLength()); } - // Process emotes if user is provided - if ($user) { - $emoteService = app(\App\Services\EmoteService::class); - $parsed = $emoteService->parseMessage($message, $user); - $message = $parsed['message']; - } - - // Process URLs - replace non-whitelisted URLs with [url removed] - $message = $this->filterUrls($message); - - // Escape HTML entities to prevent XSS (but preserve emote tags) - $message = $this->escapeHtmlPreservingEmotes($message); - - // Break long words that could break layout (this adds characters but is for display) - $message = $this->breakLongWords($message); - return $message; } /** - * Escape HTML but preserve emote tags. + * Drop zero-width and control characters used to break layouts or evade filters. */ - protected function escapeHtmlPreservingEmotes(string $message): string + protected function stripControlCharacters(string $message): string { - // Temporarily replace emote tags with placeholders - $emotePattern = '/<\/emote>/'; - $emotePlaceholders = []; - $placeholderIndex = 0; - - $message = preg_replace_callback($emotePattern, function ($matches) use (&$emotePlaceholders, &$placeholderIndex) { - $placeholder = "[[EMOTE_PLACEHOLDER_$placeholderIndex]]"; - $emotePlaceholders[$placeholder] = $matches[0]; - $placeholderIndex++; - - return $placeholder; - }, $message); + // C0/C1 controls (keeping \n and \t), zero-width marks and bidi overrides. + $message = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $message); - // Escape HTML - $message = htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + return preg_replace('/[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{206F}\x{FEFF}]/u', '', $message); + } - // Restore emote tags - foreach ($emotePlaceholders as $placeholder => $emoteTag) { - $message = str_replace($placeholder, $emoteTag, $message); - } + /** + * Cap runaway newlines and spaces so a single message cannot own the viewport. + */ + protected function collapseWhitespace(string $message): string + { + $message = str_replace(["\r\n", "\r", "\t"], ["\n", "\n", ' '], $message); + $message = preg_replace('/\n{3,}/', "\n\n", $message); - return $message; + return preg_replace('/[ ]{4,}/', ' ', $message); } /** - * Filter URLs, keeping only whitelisted domains + * Replace links to non-whitelisted domains with a placeholder. */ protected function filterUrls(string $message): string { - // Match URLs including those without protocol - $urlPattern = '/(?:https?:\/\/|www\.)(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?|(?:[a-zA-Z0-9-]+\.)+(?:com|org|net|edu|gov|io|co|uk|de|fr|jp|cn|in|br|au|ca|ru|ch|se|no|dk|fi|nl|be|at|pl|es|it|pt|gr|tr|ae|sg|hk|my|th|id|ph|vn|kr|tw|mx|ar|cl|pe|co|za)(?:\/[^\s]*)?/i'; + $urlPattern = '/(?:https?:\/\/|www\.)(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?|(?:[a-zA-Z0-9-]+\.)+(?:com|org|net|edu|gov|io|co|uk|de|fr|jp|cn|in|br|au|ca|ru|ch|se|no|dk|fi|nl|be|at|pl|es|it|pt|gr|tr|ae|sg|hk|my|th|id|ph|vn|kr|tw|mx|ar|cl|pe|za)(?:\/[^\s]*)?/i'; return preg_replace_callback($urlPattern, function ($matches) { $url = $matches[0]; - // Check if URL is from allowed domain - foreach ($this->allowedDomains as $domain) { - // Check if the URL contains the allowed domain + foreach ($this->getAllowedDomains() as $domain) { if (stripos($url, $domain) !== false) { return $url; } @@ -97,76 +70,31 @@ protected function filterUrls(string $message): string } /** - * Break long words to prevent layout breaking + * Check if message contains only allowed characters. */ - protected function breakLongWords(string $message): string + public function hasDisallowedCharacters(string $message): bool { - // First, protect emote tags from being broken - $emotePattern = '/]*><\/emote>/'; - $emotePlaceholders = []; - $placeholderIndex = 0; - - $message = preg_replace_callback($emotePattern, function ($matches) use (&$emotePlaceholders, &$placeholderIndex) { - $placeholder = "[[EMOTE_PROTECT_$placeholderIndex]]"; - $emotePlaceholders[$placeholder] = $matches[0]; - $placeholderIndex++; - return $placeholder; - }, $message); - - // Now break long words - $words = explode(' ', $message); - $processedWords = []; - - foreach ($words as $word) { - // Skip breaking if it's an emote placeholder - if (strpos($word, '[[EMOTE_PROTECT_') !== false) { - $processedWords[] = $word; - continue; - } - - // If word is longer than max length, insert zero-width spaces for wrapping - if (mb_strlen($word) > $this->maxWordLength) { - // Insert zero-width space every N characters - $word = implode('​', mb_str_split($word, $this->maxWordLength)); - } - $processedWords[] = $word; - } - - $message = implode(' ', $processedWords); - - // Restore emote tags - foreach ($emotePlaceholders as $placeholder => $emoteTag) { - $message = str_replace($placeholder, $emoteTag, $message); - } - - return $message; + return (bool) preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', $message); } /** - * Check if message contains only allowed characters + * True when a message carries no visible content. */ - public function hasDisallowedCharacters(string $message): bool + public function isEffectivelyEmpty(string $message): bool { - // Allow alphanumeric, common punctuation, spaces, and common Unicode ranges - // This pattern allows most legitimate chat but blocks control characters - $allowedPattern = '/^[\p{L}\p{N}\p{P}\p{S}\p{Zs}\p{Emoji}]+$/u'; - - return ! preg_match($allowedPattern, $message); + return trim($message) === ''; } - /** - * Get the maximum message length - */ public function getMaxLength(): int { - return $this->maxMessageLength; + return (int) config('chat.default.maxMessageLength', 500); } /** - * Get allowed domains for URLs + * @return array */ public function getAllowedDomains(): array { - return $this->allowedDomains; + return config('chat.allowed_domains', []); } } diff --git a/app/Services/CommandRegistry.php b/app/Services/CommandRegistry.php index 089acc3..a876ba5 100644 --- a/app/Services/CommandRegistry.php +++ b/app/Services/CommandRegistry.php @@ -6,7 +6,6 @@ use App\Models\User; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; -use Illuminate\Support\Str; class CommandRegistry { @@ -37,42 +36,45 @@ protected function discoverCommands(): void $this->commands = Cache::remember('chat_commands', 3600, function () { $commands = []; $commandPath = app_path('Console/Commands/Chat'); - - if (!File::exists($commandPath)) { + + if (! File::exists($commandPath)) { return $commands; } $files = File::allFiles($commandPath); - + foreach ($files as $file) { $className = $this->getClassNameFromFile($file); - + if ($className && class_exists($className)) { $reflection = new \ReflectionClass($className); - + // Skip abstract classes and non-command classes - if ($reflection->isAbstract() || !$reflection->implementsInterface(CommandInterface::class)) { + if ($reflection->isAbstract() || ! $reflection->implementsInterface(CommandInterface::class)) { continue; } - - $command = new $className(); + + $command = new $className; $commandName = $command->name(); - + $commands[$commandName] = [ 'class' => $className, 'instance' => null, // Will be instantiated when needed 'metadata' => $command->toArray(), ]; - - // Register aliases - foreach ($command->aliases() as $alias) { - $this->aliases[$alias] = $commandName; - } } } - + return $commands; }); + + // Aliases are rebuilt from the (possibly cached) metadata, otherwise they would + // only exist on the request that warmed the cache. + foreach ($this->commands as $commandName => $data) { + foreach ($data['metadata']['aliases'] ?? [] as $alias) { + $this->aliases[$alias] = $commandName; + } + } } /** @@ -80,11 +82,11 @@ protected function discoverCommands(): void */ protected function getClassNameFromFile($file): ?string { - $relativePath = str_replace(app_path() . '/', '', $file->getPathname()); + $relativePath = str_replace(app_path().'/', '', $file->getPathname()); $relativePath = str_replace('.php', '', $relativePath); $relativePath = str_replace('/', '\\', $relativePath); - - return 'App\\' . $relativePath; + + return 'App\\'.$relativePath; } /** @@ -101,14 +103,14 @@ public function all(): array public function availableFor(User $user): array { $available = []; - + foreach ($this->commands as $name => $data) { $command = $this->get($name); if ($command && $command->authorize($user)) { $available[$name] = $data['metadata']; } } - + return $available; } @@ -119,28 +121,28 @@ public function search(string $query, ?User $user = null): array { $results = []; $query = strtolower($query); - + foreach ($this->commands as $name => $data) { $metadata = $data['metadata']; - + // Check if user can access this command if ($user) { $command = $this->get($name); - if (!$command->authorize($user)) { + if (! $command->authorize($user)) { continue; } } - + // Search in name, aliases, and description - $searchable = strtolower($name . ' ' . - implode(' ', $metadata['aliases'] ?? []) . ' ' . + $searchable = strtolower($name.' '. + implode(' ', $metadata['aliases'] ?? []).' '. ($metadata['description'] ?? '')); - + if (str_contains($searchable, $query)) { $results[$name] = $metadata; } } - + return $results; } @@ -153,17 +155,17 @@ public function get(string $name): ?CommandInterface if (isset($this->aliases[$name])) { $name = $this->aliases[$name]; } - - if (!isset($this->commands[$name])) { + + if (! isset($this->commands[$name])) { return null; } - + // Lazy instantiation if ($this->commands[$name]['instance'] === null) { $className = $this->commands[$name]['class']; - $this->commands[$name]['instance'] = new $className(); + $this->commands[$name]['instance'] = new $className; } - + return $this->commands[$name]['instance']; } @@ -173,16 +175,16 @@ public function get(string $name): ?CommandInterface public function findByInput(string $input): ?CommandInterface { $input = trim($input); - + // Remove command prefix if (str_starts_with($input, '/') || str_starts_with($input, '!')) { $input = substr($input, 1); } - + // Extract command name (first word) $parts = explode(' ', $input); $commandName = $parts[0] ?? ''; - + return $this->get($commandName); } @@ -203,13 +205,13 @@ public function clearCache(): void public function register(CommandInterface $command): void { $name = $command->name(); - + $this->commands[$name] = [ 'class' => get_class($command), 'instance' => $command, 'metadata' => $command->toArray(), ]; - + foreach ($command->aliases() as $alias) { $this->aliases[$alias] = $name; } @@ -230,7 +232,7 @@ public function getSuggestions(string $partial, User $user): array { $suggestions = []; $partial = strtolower(trim($partial, '/!')); - + foreach ($this->availableFor($user) as $name => $metadata) { if (empty($partial) || str_starts_with($name, $partial)) { $suggestions[] = [ @@ -241,14 +243,19 @@ public function getSuggestions(string $partial, User $user): array ]; } } - + // Sort by relevance (exact matches first, then alphabetical) usort($suggestions, function ($a, $b) use ($partial) { - if ($a['name'] === $partial) return -1; - if ($b['name'] === $partial) return 1; + if ($a['name'] === $partial) { + return -1; + } + if ($b['name'] === $partial) { + return 1; + } + return strcmp($a['name'], $b['name']); }); - + return array_slice($suggestions, 0, 10); // Limit to 10 suggestions } -} \ No newline at end of file +} diff --git a/app/Services/DnsKeyService.php b/app/Services/DnsKeyService.php index 6c82180..1452744 100644 --- a/app/Services/DnsKeyService.php +++ b/app/Services/DnsKeyService.php @@ -39,7 +39,7 @@ public function generateKeyFile(): string $disk = Storage::disk('local'); // Ensure temp directory exists - if (!$disk->exists('temp')) { + if (! $disk->exists('temp')) { $disk->makeDirectory('temp'); } @@ -53,9 +53,9 @@ public function generateKeyFile(): string // Write the key file and force local filesystem $written = $disk->put($relativePath, $keyContent); - - if (!$written) { - throw new \Exception("Failed to write DNS key file to local storage"); + + if (! $written) { + throw new \Exception('Failed to write DNS key file to local storage'); } // Get the absolute path @@ -63,13 +63,13 @@ public function generateKeyFile(): string // Double check the file exists and is readable clearstatcache(true, $this->keyFilePath); - - if (!file_exists($this->keyFilePath)) { + + if (! file_exists($this->keyFilePath)) { throw new \Exception("DNS key file does not exist after writing: {$this->keyFilePath}"); } // Set proper permissions (600 - read/write for owner only) - if (!@chmod($this->keyFilePath, 0600)) { + if (! @chmod($this->keyFilePath, 0600)) { throw new \Exception("Failed to set permissions on DNS key file: {$this->keyFilePath}"); } diff --git a/app/Services/DvrExtractorService.php b/app/Services/DvrExtractorService.php index 8365229..e4d718f 100644 --- a/app/Services/DvrExtractorService.php +++ b/app/Services/DvrExtractorService.php @@ -3,14 +3,15 @@ namespace App\Services; use Carbon\Carbon; -use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Process; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class DvrExtractorService { protected string $tempPath; + protected $progressCallback; public function __construct() @@ -25,7 +26,7 @@ public function findSegments(string $stream, Carbon $startTime, Carbon $endTime) { $segments = []; $disk = Storage::disk('dvr'); - + // Convert times to milliseconds // The timestamps in filenames are milliseconds since epoch $startMs = $startTime->timestamp * 1000; @@ -39,23 +40,24 @@ public function findSegments(string $stream, Carbon $startTime, Carbon $endTime) // Date folders are in local time (Europe/Berlin) // But we need to also check dvr/ingress path $datePath = sprintf('dvr/ingress/%s/%s', $stream, $currentDate->format('Y-m-d')); - + try { // List all files for this date using Storage facade - if (!$disk->exists($datePath)) { + if (! $disk->exists($datePath)) { $currentDate->addDay(); + continue; } $files = $disk->files($datePath); - + foreach ($files as $file) { $filename = basename($file); - + // Parse timestamp from filename (format: HH-MM-SS_timestampMs.mp4) if (preg_match('/\d{2}-\d{2}-\d{2}_(\d+)\.mp4$/', $filename, $matches)) { $segmentTimestamp = (int) $matches[1]; - + // Check if segment falls within our time range // Add a small buffer (30 seconds) since segments can be up to ~20 seconds if ($segmentTimestamp >= ($startMs - 30000) && $segmentTimestamp <= ($endMs + 30000)) { @@ -71,7 +73,7 @@ public function findSegments(string $stream, Carbon $startTime, Carbon $endTime) } } catch (\Exception $e) { // Log error but continue with other dates - \Log::warning("Failed to list DVR segments for {$datePath}: " . $e->getMessage()); + \Log::warning("Failed to list DVR segments for {$datePath}: ".$e->getMessage()); } $currentDate->addDay(); @@ -89,55 +91,55 @@ public function findSegments(string $stream, Carbon $startTime, Carbon $endTime) * Extract and combine DVR segments */ public function extract( - string $stream, - Carbon $startTime, - Carbon $endTime, + string $stream, + Carbon $startTime, + Carbon $endTime, string $outputFilename, string $targetStorage = 'public', - callable $progressCallback = null + ?callable $progressCallback = null ): string { $this->progressCallback = $progressCallback; - + // Find segments $this->log('Finding segments...'); $segments = $this->findSegments($stream, $startTime, $endTime); - + if (empty($segments)) { throw new \Exception('No segments found in the specified time range'); } - $this->log("Found " . count($segments) . " segments to process"); + $this->log('Found '.count($segments).' segments to process'); // Create temp directory $sessionId = Str::uuid()->toString(); - $sessionPath = $this->tempPath . '/' . $sessionId; + $sessionPath = $this->tempPath.'/'.$sessionId; File::ensureDirectoryExists($sessionPath); try { // Download segments $this->log('Downloading segments...'); $localFiles = $this->downloadSegments($segments, $sessionPath); - + // Create concat file for ffmpeg $this->log('Creating concatenation list...'); $concatFile = $this->createConcatFile($localFiles, $sessionPath); - + // Combine with ffmpeg $this->log('Combining segments with FFmpeg...'); - $tempOutput = $sessionPath . '/combined.mp4'; + $tempOutput = $sessionPath.'/combined.mp4'; $this->combineSegments($concatFile, $tempOutput); - + // Trim to exact time range if needed $this->log('Trimming to exact time range...'); - $trimmedOutput = $sessionPath . '/output.mp4'; + $trimmedOutput = $sessionPath.'/output.mp4'; $this->trimToExactRange($tempOutput, $trimmedOutput, $segments, $startTime, $endTime); - + // Move to target storage $this->log('Moving to target storage...'); $finalPath = $this->moveToStorage($trimmedOutput, $outputFilename, $targetStorage); - + $this->log('Extraction complete!', 'success'); - + return $finalPath; } finally { // Cleanup temp files @@ -153,27 +155,27 @@ protected function downloadSegments(array $segments, string $localPath): array $disk = Storage::disk('dvr'); $localFiles = []; $totalSegments = count($segments); - + foreach ($segments as $index => $segment) { - $localFile = $localPath . '/' . $segment['filename']; + $localFile = $localPath.'/'.$segment['filename']; $this->log(sprintf( - 'Downloading segment %d/%d: %s', - $index + 1, - $totalSegments, + 'Downloading segment %d/%d: %s', + $index + 1, + $totalSegments, $segment['filename'] )); - + // Download from S3 to local using Storage facade $contents = $disk->get($segment['path']); File::put($localFile, $contents); - + $localFiles[] = [ 'path' => $localFile, 'filename' => $segment['filename'], 'timestamp' => $segment['timestamp'], ]; } - + return $localFiles; } @@ -182,16 +184,16 @@ protected function downloadSegments(array $segments, string $localPath): array */ protected function createConcatFile(array $files, string $sessionPath): string { - $concatFile = $sessionPath . '/concat.txt'; + $concatFile = $sessionPath.'/concat.txt'; $content = ''; - + foreach ($files as $file) { // FFmpeg concat format: file 'path' $content .= sprintf("file '%s'\n", $file['path']); } - + File::put($concatFile, $content); - + return $concatFile; } @@ -213,18 +215,18 @@ protected function combineSegments(string $concatFile, string $outputFile): void '-c', 'copy', '-movflags', '+faststart', // Optimize for streaming '-y', // Overwrite output file - $outputFile + $outputFile, ]; - $this->log('Running FFmpeg: ' . implode(' ', $command)); - + $this->log('Running FFmpeg: '.implode(' ', $command)); + $result = Process::run($command); - - if (!$result->successful()) { - throw new \Exception('FFmpeg failed: ' . $result->errorOutput()); + + if (! $result->successful()) { + throw new \Exception('FFmpeg failed: '.$result->errorOutput()); } - - if (!file_exists($outputFile)) { + + if (! file_exists($outputFile)) { throw new \Exception('FFmpeg did not create output file'); } } @@ -233,8 +235,8 @@ protected function combineSegments(string $concatFile, string $outputFile): void * Trim video to exact time range */ protected function trimToExactRange( - string $inputFile, - string $outputFile, + string $inputFile, + string $outputFile, array $segments, Carbon $startTime, Carbon $endTime @@ -242,15 +244,15 @@ protected function trimToExactRange( // Get the first segment's timestamp to calculate offset $firstSegmentTimestamp = $segments[0]['timestamp']; $lastSegmentTimestamp = end($segments)['timestamp']; - + // Calculate start offset in seconds from the beginning of the first segment $startMs = $startTime->timestamp * 1000; $endMs = $endTime->timestamp * 1000; - + // If we only have one segment or segments are close together, we need to trim $startOffset = max(0, ($startMs - $firstSegmentTimestamp) / 1000); $duration = ($endMs - $startMs) / 1000; - + // Build ffmpeg trim command $command = [ 'ffmpeg', @@ -261,17 +263,17 @@ protected function trimToExactRange( '-avoid_negative_ts', 'make_zero', // Fix timestamp issues '-movflags', '+faststart', // Optimize for streaming '-y', // Overwrite output - $outputFile + $outputFile, ]; - - $this->log('Trimming video with FFmpeg: ' . implode(' ', $command)); - + + $this->log('Trimming video with FFmpeg: '.implode(' ', $command)); + $result = Process::run($command); - - if (!$result->successful()) { + + if (! $result->successful()) { // If copy codec fails (due to keyframe issues), retry with re-encoding $this->log('Copy codec failed, retrying with re-encoding...'); - + $command = [ 'ffmpeg', '-i', $inputFile, @@ -282,17 +284,17 @@ protected function trimToExactRange( '-c:a', 'copy', // Copy audio '-movflags', '+faststart', '-y', - $outputFile + $outputFile, ]; - + $result = Process::run($command); - - if (!$result->successful()) { - throw new \Exception('FFmpeg trim failed: ' . $result->errorOutput()); + + if (! $result->successful()) { + throw new \Exception('FFmpeg trim failed: '.$result->errorOutput()); } } - - if (!file_exists($outputFile)) { + + if (! file_exists($outputFile)) { throw new \Exception('FFmpeg did not create trimmed output file'); } } @@ -303,20 +305,20 @@ protected function trimToExactRange( protected function moveToStorage(string $tempFile, string $filename, string $storageDisk): string { $disk = Storage::disk($storageDisk); - $targetPath = 'dvr-exports/' . $filename; - + $targetPath = 'dvr-exports/'.$filename; + // Ensure directory exists $disk->makeDirectory('dvr-exports'); - + // Read file and store in target disk $contents = File::get($tempFile); $disk->put($targetPath, $contents); - + // Return full path based on disk type if ($storageDisk === 'public') { - return storage_path('app/public/' . $targetPath); + return storage_path('app/public/'.$targetPath); } elseif ($storageDisk === 'local') { - return storage_path('app/' . $targetPath); + return storage_path('app/'.$targetPath); } else { return $targetPath; } @@ -333,7 +335,7 @@ protected function cleanup(string $path): void $this->log('Cleaned up temporary files'); } } catch (\Exception $e) { - $this->log('Warning: Failed to cleanup temp files: ' . $e->getMessage(), 'warn'); + $this->log('Warning: Failed to cleanup temp files: '.$e->getMessage(), 'warn'); } } @@ -346,4 +348,4 @@ protected function log(string $message, string $type = 'info'): void call_user_func($this->progressCallback, $message, $type); } } -} \ No newline at end of file +} diff --git a/app/Services/EmoteService.php b/app/Services/EmoteService.php index 075d61a..bca24e9 100644 --- a/app/Services/EmoteService.php +++ b/app/Services/EmoteService.php @@ -13,22 +13,17 @@ class EmoteService { /** - * Maximum emotes allowed per message before size reduction. - */ - const MAX_EMOTES_PER_MESSAGE = 10; - - /** - * Unlimited emotes but with size reduction. + * Emotes past this count in a single message render at the small size. */ const REDUCED_SIZE_THRESHOLD = 10; /** - * Required emote dimensions. + * Stored emote dimensions. */ const EMOTE_SIZE = 64; /** - * Display size in chat. + * Default display size in chat. */ const DISPLAY_SIZE = 32; @@ -37,105 +32,87 @@ class EmoteService */ public function uploadEmote(UploadedFile $file, string $name, bool $isGlobal, User $user): Emote { - // Validate image dimensions $image = Image::read($file); - // Resize to 64x64 if not already if ($image->width() !== self::EMOTE_SIZE || $image->height() !== self::EMOTE_SIZE) { $image->resize(self::EMOTE_SIZE, self::EMOTE_SIZE); } - // Generate S3 key $extension = $file->getClientOriginalExtension(); $s3Key = 'emotes/'.Str::uuid().'.'.$extension; - // Save to S3 with private visibility Storage::disk('s3')->put($s3Key, (string) $image->encode(), [ 'visibility' => 'private', 'CacheControl' => 'max-age=31536000', - 'ContentType' => 'image/' . $extension, + 'ContentType' => 'image/'.$extension, ]); - // Create emote record (URL will be generated by accessor) $emote = Emote::create([ 'name' => $name, 's3_key' => $s3Key, - 'url' => null, // Will use accessor to generate signed URLs + 'url' => null, // resolved by the model accessor as a signed URL 'uploaded_by_user_id' => $user->id, 'is_global' => $isGlobal, - 'is_approved' => false, // Requires admin approval + 'is_approved' => false, ]); - // Clear emotes cache $this->clearCache(); return $emote; } /** - * Parse emotes in a message. + * Record usage for the emotes referenced in a message. + * + * Messages are stored as raw text: `:name:` codes are rendered client side, so + * nothing is rewritten here. + * + * @return array the emote names that resolved */ - public function parseMessage(string $message, User $user): array + public function recordUsage(string $message, User $user): array { - // Get available emotes for user - $emotes = $this->getAvailableEmotes($user); - - // Find all :emote: patterns - preg_match_all('/:([a-z0-9_]+):/', $message, $matches); - - $usedEmotes = []; - $emoteCount = 0; - $processedMessage = $message; + $available = $this->getAvailableEmotes($user); - foreach ($matches[1] as $index => $emoteName) { - // Check if emote exists and is available - if (isset($emotes[$emoteName])) { - $emote = $emotes[$emoteName]; + preg_match_all('/:([a-z0-9_]+):/i', $message, $matches); - // Determine size class based on total emote count - $sizeClass = $emoteCount >= self::REDUCED_SIZE_THRESHOLD ? 'small' : 'normal'; + $used = []; - $emoteTag = ''; - $processedMessage = str_replace($matches[0][$index], $emoteTag, $processedMessage); + foreach (array_unique($matches[1] ?? []) as $name) { + $name = strtolower($name); - $usedEmotes[] = $emote; - $emoteCount++; - - // Increment usage count (deferred to avoid blocking) - dispatch(function () use ($emote) { - Emote::find($emote['id'])->incrementUsage(); - })->afterResponse(); + if (isset($available[$name])) { + $used[] = $name; } } - // If we have more than threshold, update all emote tags to small size - if ($emoteCount > self::REDUCED_SIZE_THRESHOLD) { - $processedMessage = str_replace('data-size="normal"', 'data-size="small"', $processedMessage); + if ($used !== []) { + $ids = array_map(fn (string $name) => $available[$name]['id'], $used); + + dispatch(function () use ($ids) { + Emote::whereIn('id', $ids)->increment('usage_count'); + })->afterResponse(); } - return [ - 'message' => $processedMessage, - 'emotes' => $usedEmotes, - 'emote_count' => $emoteCount, - ]; + return $used; } /** - * Get all available emotes for a user. + * Get all available emotes for a user, keyed by name. + * + * @return array */ public function getAvailableEmotes(User $user): array { - // Cache for 6 hours (well within the 7-day signed URL expiration) - return Cache::remember('user_emotes_'.$user->id, 21600, function () use ($user) { - // Don't select specific columns to ensure accessors work properly - $emotes = Emote::availableFor($user)->get(); - + // 6 hours stays well inside the 7 day signed URL lifetime. + return Cache::remember($this->userCacheKey('emotes', $user), 21600, function () use ($user) { $indexed = []; - foreach ($emotes as $emote) { + + foreach (Emote::availableFor($user)->get() as $emote) { $indexed[$emote->name] = [ 'id' => $emote->id, 'name' => $emote->name, - 'url' => $emote->url, // This will trigger the URL accessor + 'url' => $emote->url, + 'global' => (bool) $emote->is_global, ]; } @@ -143,36 +120,71 @@ public function getAvailableEmotes(User $user): array }); } + /** + * Payload shared with the frontend: a name => url map for rendering messages, + * and a list with metadata for the picker and autocomplete. + * + * @return array{map: object, list: array>} + */ + public function clientPayload(User $user): array + { + $favoriteIds = array_flip(array_column($this->getUserFavorites($user), 'id')); + + $map = []; + $list = []; + + foreach ($this->getAvailableEmotes($user) as $name => $emote) { + $map[$name] = $emote['url']; + $list[] = [ + 'id' => $emote['id'], + 'name' => $emote['name'], + 'url' => $emote['url'], + 'global' => (bool) ($emote['global'] ?? false), + 'favorite' => isset($favoriteIds[$emote['id']]), + ]; + } + + return [ + // Cast so an empty map serialises as {} rather than []. + 'map' => (object) $map, + 'list' => $list, + ]; + } + /** * Get all approved global emotes. */ public function getGlobalEmotes(): array { - return Cache::remember('global_emotes', 3600, function () { + return Cache::remember('global_emotes_'.$this->version(), 3600, function () { return Emote::approved() ->global() ->orderBy('usage_count', 'desc') ->get() - ->map(function ($emote) { - return [ - 'id' => $emote->id, - 'name' => $emote->name, - 'url' => $emote->url, // This will trigger the URL accessor - ]; - }) + ->map(fn (Emote $emote) => [ + 'id' => $emote->id, + 'name' => $emote->name, + 'url' => $emote->url, + ]) ->toArray(); }); } /** - * Get user's personal emotes. + * Get user's personal emotes, including ones still awaiting approval. */ public function getUserEmotes(User $user): array { return Emote::where('uploaded_by_user_id', $user->id) - ->select('id', 'name', 'url', 'is_approved', 'is_global') ->orderBy('created_at', 'desc') ->get() + ->map(fn (Emote $emote) => [ + 'id' => $emote->id, + 'name' => $emote->name, + 'url' => $emote->url, + 'is_approved' => (bool) $emote->is_approved, + 'is_global' => (bool) $emote->is_global, + ]) ->toArray(); } @@ -181,11 +193,15 @@ public function getUserEmotes(User $user): array */ public function getUserFavorites(User $user): array { - return Cache::remember('user_favorites_'.$user->id, 300, function () use ($user) { + return Cache::remember($this->userCacheKey('favorites', $user), 300, function () use ($user) { return $user->favoriteEmotes() ->approved() - ->select('emotes.id', 'emotes.name', 'emotes.url') ->get() + ->map(fn (Emote $emote) => [ + 'id' => $emote->id, + 'name' => $emote->name, + 'url' => $emote->url, + ]) ->toArray(); }); } @@ -203,8 +219,7 @@ public function toggleFavorite(Emote $emote, User $user): bool $isFavorited = true; } - // Clear user's favorites cache - Cache::forget('user_favorites_'.$user->id); + Cache::forget($this->userCacheKey('favorites', $user)); return $isFavorited; } @@ -214,8 +229,7 @@ public function toggleFavorite(Emote $emote, User $user): bool */ public function validateEmoteName(string $name): bool { - // Must be alphanumeric with underscores, 2-20 characters - return preg_match('/^[a-z0-9_]{2,20}$/', strtolower($name)); + return (bool) preg_match('/^[a-z0-9_]{2,20}$/', strtolower($name)); } /** @@ -227,12 +241,14 @@ public function isNameAvailable(string $name): bool } /** - * Clear all emote caches. + * Invalidate every emote cache by bumping the version segment of their keys. + * + * Flushing the whole cache store would also drop rate limiters and chat settings. */ public function clearCache(): void { - Cache::forget('global_emotes'); - Cache::flush(); // This will clear all user-specific caches + Cache::forever('emote_cache_version', $this->version() + 1); + Cache::forget('emote_stats'); } /** @@ -250,10 +266,24 @@ public function getStatistics(): array 'top_emotes' => Emote::approved() ->orderBy('usage_count', 'desc') ->take(10) - ->select('name', 'url', 'usage_count') ->get() + ->map(fn (Emote $emote) => [ + 'name' => $emote->name, + 'url' => $emote->url, + 'usage_count' => $emote->usage_count, + ]) ->toArray(), ]; }); } + + protected function version(): int + { + return (int) Cache::get('emote_cache_version', 1); + } + + protected function userCacheKey(string $bucket, User $user): string + { + return "user_{$bucket}_{$user->id}_".$this->version(); + } } diff --git a/app/Services/PlaybackTokenService.php b/app/Services/PlaybackTokenService.php new file mode 100644 index 0000000..9230cd3 --- /dev/null +++ b/app/Services/PlaybackTokenService.php @@ -0,0 +1,268 @@ +.`. + * The version prefix is inside the signed body so it cannot be downgraded, and + * verification needs nothing but the shared secret. That is what lets an edge + * check a token locally with njs instead of calling back into Laravel. + * + * See docs/streaming-auth-redesign.md. + */ +class PlaybackTokenService +{ + public const VERSION = 'v1'; + + /** + * Issue a token for an attendee watching a source in the web player. + * + * Callers must have already checked entitlement (Show::canBeAccessedBy) - + * the source binding on the token is what carries that decision forward. + */ + public function issueViewer( + User $user, + Source|string $source, + ?string $edge = null, + ?string $sessionId = null, + ?int $ttl = null, + ): string { + return $this->issue(new PlaybackToken( + type: PlaybackTokenTypeEnum::VIEWER, + source: $this->slug($source), + subject: (string) $user->getKey(), + edge: $edge, + sessionId: $sessionId ?? (string) Str::uuid(), + expiresAt: time() + ($ttl ?? $this->ttl()), + )); + } + + /** + * Issue a token for a signed-out viewer. + * + * Only reachable on an installation with `auth.required` off, and only for + * a source the caller has already established carries no role restriction. + * The token has no subject, which is exactly what "we do not know who this + * is" means; edges never look at `sub`, they only check the source binding. + */ + public function issueGuest( + Source|string $source, + ?string $edge = null, + ?string $sessionId = null, + ?int $ttl = null, + ): string { + return $this->issue(new PlaybackToken( + type: PlaybackTokenTypeEnum::VIEWER, + source: $this->slug($source), + edge: $edge, + sessionId: $sessionId ?? (string) Str::uuid(), + expiresAt: time() + ($ttl ?? $this->ttl()), + )); + } + + /** + * Issue a key for an external embed. + * + * No expiry by default: the URL is baked into a VRChat world and can never + * be rotated, so revocation runs through the key-id allowlist that edges + * refresh instead. Pass $ttl only for a deliberately temporary embed. + */ + public function issueEmbed( + string $keyId, + Source|string $source, + ?string $edge = null, + ?int $ttl = null, + ): string { + return $this->issue(new PlaybackToken( + type: PlaybackTokenTypeEnum::EMBED, + source: $this->slug($source), + keyId: $keyId, + edge: $edge, + expiresAt: $ttl === null ? null : time() + $ttl, + )); + } + + public function issue(PlaybackToken $token): string + { + $body = self::VERSION.'.'.$this->encode($this->json($token->claims())); + + return $body.'.'.$this->encode($this->sign($body, $token->type)); + } + + /** + * Verify a token and return its claims. + * + * @param string|null $expectedSource Slug the request is actually asking for. + * + * @throws InvalidPlaybackTokenException + */ + public function verify(string $encoded, ?string $expectedSource = null): PlaybackToken + { + $parts = explode('.', $encoded); + + if (count($parts) !== 3) { + throw InvalidPlaybackTokenException::malformed('expected three segments'); + } + + [$version, $payload, $signature] = $parts; + + if (! hash_equals(self::VERSION, $version)) { + throw InvalidPlaybackTokenException::unsupportedVersion($version); + } + + $claims = $this->decodeClaims($payload); + + // The type is read from the not-yet-verified payload only to choose which + // secret to check against. Claiming the wrong type simply fails the + // signature check below, so this cannot be used to cross the two secrets. + $type = PlaybackTokenTypeEnum::tryFrom((string) ($claims['typ'] ?? '')); + + if ($type === null) { + throw InvalidPlaybackTokenException::malformed('unknown token type'); + } + + $expected = $this->sign($version.'.'.$payload, $type); + $actual = $this->decode($signature); + + if ($actual === null || ! hash_equals($expected, $actual)) { + throw InvalidPlaybackTokenException::badSignature(); + } + + $token = PlaybackToken::fromClaims($claims); + + if ($token->isExpired($this->leeway())) { + throw InvalidPlaybackTokenException::expired(); + } + + if ($expectedSource !== null && $token->source !== $expectedSource) { + throw InvalidPlaybackTokenException::sourceMismatch($expectedSource, $token->source); + } + + return $token; + } + + /** + * Verify without throwing, for hot paths that only care whether to answer 403. + */ + public function tryVerify(string $encoded, ?string $expectedSource = null): ?PlaybackToken + { + try { + return $this->verify($encoded, $expectedSource); + } catch (InvalidPlaybackTokenException) { + return null; + } + } + + /** + * Whether a secret is configured for this token type. Callers on user-facing + * paths check this first so an environment without secrets keeps working + * instead of erroring; only code that genuinely requires a token lets + * secret() throw. + */ + public function isConfigured(PlaybackTokenTypeEnum $type = PlaybackTokenTypeEnum::VIEWER): bool + { + $secret = config($type->secretConfigKey()); + + return is_string($secret) && $secret !== ''; + } + + public function ttl(): int + { + return (int) config('stream.token.ttl'); + } + + public function leeway(): int + { + return (int) config('stream.token.leeway'); + } + + /** + * Seconds after issue at which the client should ask for a fresh token. The + * gap before expiry is what the 403 recovery path gets to work with if the + * push over the websocket never arrives. + */ + public function refreshAfter(): int + { + return max(60, $this->ttl() - (int) config('stream.token.refresh_margin')); + } + + private function secret(PlaybackTokenTypeEnum $type): string + { + $key = $type->secretConfigKey(); + $secret = config($key); + + if (! is_string($secret) || $secret === '') { + throw new RuntimeException( + "Missing playback token secret [{$key}]. Set the matching HLS_*_SECRET in the environment." + ); + } + + return $secret; + } + + private function sign(string $body, PlaybackTokenTypeEnum $type): string + { + return hash_hmac('sha256', $body, $this->secret($type), true); + } + + /** + * @param array $claims + */ + private function json(array $claims): string + { + return json_encode($claims, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); + } + + /** + * @return array + * + * @throws InvalidPlaybackTokenException + */ + private function decodeClaims(string $payload): array + { + $json = $this->decode($payload); + + if ($json === null) { + throw InvalidPlaybackTokenException::malformed('payload is not valid base64url'); + } + + try { + $claims = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException) { + throw InvalidPlaybackTokenException::malformed('payload is not valid json'); + } + + if (! is_array($claims)) { + throw InvalidPlaybackTokenException::malformed('payload is not a claim set'); + } + + return $claims; + } + + private function encode(string $raw): string + { + return rtrim(strtr(base64_encode($raw), '+/', '-_'), '='); + } + + private function decode(string $encoded): ?string + { + $decoded = base64_decode(strtr($encoded, '-_', '+/'), true); + + return $decoded === false ? null : $decoded; + } + + private function slug(Source|string $source): string + { + return $source instanceof Source ? $source->slug : $source; + } +} diff --git a/app/Services/PretalxImporter.php b/app/Services/PretalxImporter.php new file mode 100644 index 0000000..c2c7e61 --- /dev/null +++ b/app/Services/PretalxImporter.php @@ -0,0 +1,112 @@ + $slotIds + * @return array{imported: int, existing: int, unmapped: int, missing: int} + */ + public function import(array $slotIds, string $eventSlug): array + { + $wanted = array_flip(array_map('strval', $slotIds)); + $rooms = PretalxRoomSource::mapFor($eventSlug); + + $slots = array_filter( + $this->pretalx->slots(), + fn (array $slot) => isset($wanted[$slot['id']]), + ); + + $result = [ + 'imported' => 0, + 'existing' => 0, + 'unmapped' => 0, + // Selected in the browser but gone from pretalx by the time we posted. + 'missing' => count($wanted) - count($slots), + ]; + + $taken = Show::whereIn('pretalx_slot_id', array_keys($wanted)) + ->pluck('pretalx_slot_id') + ->flip(); + + foreach ($slots as $slot) { + if ($taken->has($slot['id'])) { + $result['existing']++; + + continue; + } + + $sourceId = $rooms[$slot['room_id']] ?? null; + + if ($sourceId === null) { + $result['unmapped']++; + + continue; + } + + $this->show($slot, $sourceId); + $result['imported']++; + } + + return $result; + } + + /** + * @param array $slot + */ + private function show(array $slot, int $sourceId): Show + { + return Show::create([ + 'title' => Str::limit($slot['title'], 250, ''), + 'slug' => $this->slug($slot), + 'description' => $slot['description'], + 'source_id' => $sourceId, + 'scheduled_start' => $slot['start'], + 'scheduled_end' => $slot['end'], + 'status' => 'scheduled', + 'auto_mode' => false, + 'required_roles' => [], + 'pretalx_slot_id' => $slot['id'], + ]); + } + + /** + * Same shape the model builds for a hand-made show, but made unique here: a con runs + * the same title in two rooms at once often enough that a clash is normal, and the + * slug column is unique. + * + * @param array $slot + */ + private function slug(array $slot): string + { + $base = Str::limit(Str::slug($slot['title'].'-'.$slot['start']->format('Y-m-d')), 240, ''); + $base = $base !== '' ? $base : 'session-'.$slot['id']; + $slug = $base; + + for ($suffix = 2; Show::where('slug', $slug)->exists(); $suffix++) { + $slug = $base.'-'.$suffix; + } + + return $slug; + } +} diff --git a/app/Services/PretalxService.php b/app/Services/PretalxService.php new file mode 100644 index 0000000..e41da35 --- /dev/null +++ b/app/Services/PretalxService.php @@ -0,0 +1,471 @@ + Settings) and fall + * back to config/pretalx.php. + */ +class PretalxService +{ + /** + * Short enough that a schedule release shows up on the next visit, long enough that + * ticking through the import screen is not one request per click. + */ + private const CACHE_TTL = 300; + + /** + * A published schedule is a few hundred slots; the cap is only there so a runaway + * `next` link cannot walk forever. + */ + private const MAX_PAGES = 25; + + private const PAGE_SIZE = 100; + + /** + * Values that win over the stored settings, for testing a connection that has not + * been saved yet. + * + * @var array + */ + private array $overrides = []; + + /** + * A copy pointed at the given connection details instead of the saved ones. Blank + * values fall through to what is stored, which is how the settings page can test a + * new URL against the token already in the database. + * + * @param array $overrides + */ + public function using(array $overrides): self + { + $clone = clone $this; + $clone->overrides = array_filter( + $overrides, + fn ($value) => is_string($value) && trim($value) !== '', + ); + + return $clone; + } + + public function baseUrl(): ?string + { + $url = trim((string) $this->setting('pretalx_url')); + + return $url === '' ? null : rtrim($url, '/'); + } + + public function event(): ?string + { + $event = trim((string) $this->setting('pretalx_event')); + + return $event === '' ? null : $event; + } + + public function isConfigured(): bool + { + return $this->baseUrl() !== null && $this->event() !== null; + } + + /** + * Every event on the instance the credentials can see, newest first. + * + * Not cached and not tied to the configured event: this is what the settings page + * asks for when it wants a list of slugs to choose from. + * + * @return array + */ + public function events(): array + { + $base = $this->baseUrl(); + + if ($base === null) { + throw new RuntimeException('No pretalx instance URL. Fill it in before testing the connection.'); + } + + $body = $this->get($base.'/api/events/', ['page_size' => self::PAGE_SIZE]); + + // The events endpoint is a plain list on some versions and paginated on others. + $events = array_is_list($body) ? $body : ($body['results'] ?? []); + + $events = array_map(fn (array $event) => [ + 'slug' => (string) ($event['slug'] ?? ''), + 'name' => $this->text($event['name'] ?? null) ?: (string) ($event['slug'] ?? ''), + 'date_from' => $event['date_from'] ?? null, + 'date_to' => $event['date_to'] ?? null, + ], $events); + + $events = array_values(array_filter($events, fn (array $event) => $event['slug'] !== '')); + + usort($events, fn (array $a, array $b) => [$b['date_from'], $a['name']] <=> [$a['date_from'], $b['name']]); + + return $events; + } + + /** + * Keep the last successful event list for an instance, so the settings page can offer + * the slugs as a dropdown without going out to the network on every visit. + * + * @param array> $events + */ + public function rememberEvents(string $instanceUrl, array $events): void + { + Cache::put($this->eventsKey($instanceUrl), $events, now()->addDay()); + } + + /** + * The remembered event list for an instance, empty until a connection test ran. + * + * @return array> + */ + public function rememberedEvents(?string $instanceUrl = null): array + { + $instanceUrl ??= $this->baseUrl(); + + if ($instanceUrl === null) { + return []; + } + + return Cache::get($this->eventsKey($instanceUrl), []); + } + + private function eventsKey(string $instanceUrl): string + { + return 'pretalx.events.'.md5(rtrim($instanceUrl, '/')); + } + + /** + * Check the connection: what the credentials can see, and whether the configured + * event has a schedule worth importing. + * + * Uncached on purpose - the point is to find out what is true right now. + * + * @return array{ + * events: array>, event: ?string, + * eventName: ?string, slots: ?int, warning: ?string + * } + */ + public function probe(): array + { + $events = $this->events(); + $event = $this->event(); + + $result = [ + 'events' => $events, + 'event' => $event, + 'eventName' => null, + 'slots' => null, + 'warning' => null, + ]; + + if ($event === null) { + $result['warning'] = 'No event chosen yet.'; + + return $result; + } + + $known = collect($events)->firstWhere('slug', $event); + + if ($known === null) { + $result['warning'] = "The instance answered, but it has no event '{$event}' these credentials can see."; + + return $result; + } + + $result['eventName'] = $known['name']; + + // One row is enough to learn whether a published schedule exists at all. + $slots = $this->get($this->url('slots'), ['page_size' => 1]); + $result['slots'] = array_key_exists('count', $slots) ? (int) $slots['count'] : null; + + if ($result['slots'] === 0) { + $result['warning'] = 'That event has no sessions in a published schedule yet.'; + } + + return $result; + } + + /** + * Rooms of the configured event, in pretalx's own order. + * + * @return array + */ + public function rooms(): array + { + return $this->cached('rooms', function () { + $rooms = array_map(fn (array $room) => [ + 'id' => (int) $room['id'], + 'name' => $this->text($room['name'] ?? null) ?: 'Room '.$room['id'], + 'position' => $room['position'] ?? PHP_INT_MAX, + ], $this->pages('rooms')); + + usort($rooms, fn (array $a, array $b) => [$a['position'], $a['name']] <=> [$b['position'], $b['name']]); + + return array_map(fn (array $room) => ['id' => $room['id'], 'name' => $room['name']], $rooms); + }); + } + + /** + * Slots of the latest published schedule, normalised and ordered by start time. + * + * Slots that are not actually scheduled (no room, no start) are dropped: there is + * nothing to put on a channel timeline. + * + * @return array, start: Carbon, end: Carbon + * }> + */ + public function slots(): array + { + $slots = $this->cached('slots', function () { + $rows = []; + + foreach ($this->pages('slots', ['expand' => 'submission,submission.speakers']) as $slot) { + $row = $this->slot($slot); + + if ($row !== null) { + $rows[] = $row; + } + } + + return $rows; + }); + + // Carbon does not survive the cache as an object, so the times are rehydrated here + // rather than stored. + $slots = array_map(function (array $slot) { + $slot['start'] = Carbon::parse($slot['start']); + $slot['end'] = Carbon::parse($slot['end']); + + return $slot; + }, $slots); + + usort($slots, fn (array $a, array $b) => $a['start'] <=> $b['start']); + + return $slots; + } + + /** + * Drop the cached schedule, for when a new version was released mid-event. + */ + public function forget(): void + { + Cache::forget($this->cacheKey('rooms')); + Cache::forget($this->cacheKey('slots')); + } + + /** + * @param array $slot + * @return array|null + */ + private function slot(array $slot): ?array + { + $start = $slot['start'] ?? null; + $end = $slot['end'] ?? null; + $room = $slot['room'] ?? null; + + if (! $start || ! $end || $room === null) { + return null; + } + + // `room` is an id unless expanded; `submission` is a code unless expanded. + $submission = is_array($slot['submission'] ?? null) ? $slot['submission'] : []; + + $title = trim((string) ($submission['title'] ?? '')) ?: $this->text($slot['description'] ?? null); + + return [ + 'id' => (string) $slot['id'], + 'code' => is_array($slot['submission'] ?? null) + ? ($submission['code'] ?? null) + : ($slot['submission'] ?? null), + 'title' => $title ?: 'Untitled session', + 'description' => $this->description($submission) ?: $this->text($slot['description'] ?? null), + 'room_id' => (int) (is_array($room) ? ($room['id'] ?? 0) : $room), + 'speakers' => $this->speakers($submission), + 'start' => Carbon::parse($start)->toIso8601String(), + 'end' => Carbon::parse($end)->toIso8601String(), + ]; + } + + /** + * @param array $submission + */ + private function description(array $submission): ?string + { + foreach (['abstract', 'description'] as $key) { + $value = trim((string) ($submission[$key] ?? '')); + + if ($value !== '') { + return $value; + } + } + + return null; + } + + /** + * Speaker names when the submission was expanded, plain codes otherwise. + * + * @param array $submission + * @return array + */ + private function speakers(array $submission): array + { + $speakers = $submission['speakers'] ?? []; + + if (! is_array($speakers)) { + return []; + } + + return array_values(array_filter(array_map( + fn ($speaker) => is_array($speaker) + ? trim((string) ($speaker['name'] ?? '')) + : trim((string) $speaker), + $speakers, + ))); + } + + /** + * Every page of a list endpoint, following `next` rather than counting. + * + * @param array $query + * @return array> + */ + private function pages(string $resource, array $query = []): array + { + $url = $this->url($resource); + $query = $query + ['page_size' => self::PAGE_SIZE]; + $results = []; + $seen = []; + + for ($page = 0; $page < self::MAX_PAGES && $url !== null; $page++) { + // A `next` link already carries its own query string, including the expands. + $body = $this->get($url, $page === 0 ? $query : []); + + foreach ($body['results'] ?? [] as $item) { + $results[] = $item; + } + + $seen[$url] = true; + $url = $body['next'] ?? null; + + // A pagination link that points at a page already read would loop forever and + // pile up duplicates; stopping is better than reading the same page 25 times. + if ($url !== null && isset($seen[$url])) { + break; + } + } + + return $results; + } + + /** + * @param array $query + * @return array + */ + private function get(string $url, array $query = []): array + { + $token = trim((string) $this->setting('pretalx_token')); + + $request = Http::timeout(15) + ->acceptJson() + ->when($token !== '', fn ($request) => $request->withHeaders([ + 'Authorization' => 'Token '.$token, + ])); + + try { + // Passing an empty array as the second argument replaces the URL's own query + // string with nothing, which would strip the `page` and `expand` of a `next` + // link and walk page one forever. + $response = $query === [] ? $request->get($url) : $request->get($url, $query); + } catch (\Throwable $e) { + throw new RuntimeException('Could not reach pretalx: '.$e->getMessage(), previous: $e); + } + + if ($response->status() === 401 || $response->status() === 403) { + throw new RuntimeException( + 'pretalx refused the request. Check the API token, and that the schedule is published.' + ); + } + + if ($response->status() === 404) { + throw new RuntimeException('pretalx does not know this event slug, or it has no published schedule.'); + } + + if ($response->failed()) { + throw new RuntimeException('pretalx answered with HTTP '.$response->status().'.'); + } + + $body = $response->json(); + + if (! is_array($body)) { + throw new RuntimeException('pretalx answered with something that is not JSON.'); + } + + return $body; + } + + private function url(string $resource): string + { + if (! $this->isConfigured()) { + throw new RuntimeException('pretalx is not configured. Set the instance URL and event slug in Settings.'); + } + + return $this->baseUrl().'/api/events/'.rawurlencode($this->event()).'/'.$resource.'/'; + } + + /** + * @template T + * + * @param callable(): T $callback + * @return T + */ + private function cached(string $resource, callable $callback): mixed + { + return Cache::remember($this->cacheKey($resource), self::CACHE_TTL, $callback); + } + + private function cacheKey(string $resource): string + { + return 'pretalx.'.$resource.'.'.md5((string) $this->baseUrl().'|'.(string) $this->event()); + } + + /** + * pretalx returns internationalised strings as {locale: text}; English first, then + * whatever else is there, so a single-language instance still reads. + */ + private function text(mixed $value): ?string + { + if (is_string($value)) { + return trim($value) ?: null; + } + + if (! is_array($value) || $value === []) { + return null; + } + + $preferred = $value['en'] ?? null; + $text = trim((string) ($preferred ?? reset($value))); + + return $text ?: null; + } + + private function setting(string $key): mixed + { + return $this->overrides[$key] ?? BrandingSetting::getValue($key, config('pretalx.'.$key)); + } +} diff --git a/app/Services/RecordingService.php b/app/Services/RecordingService.php index e1eea44..40eef9a 100644 --- a/app/Services/RecordingService.php +++ b/app/Services/RecordingService.php @@ -66,7 +66,7 @@ public function extractDuration(string $url): ?int } Log::warning('Failed to extract duration from m3u8, falling back to ffprobe'); } - + // Fallback to ffprobe for non-m3u8 files or if m3u8 parsing failed try { $command = [ @@ -81,6 +81,7 @@ public function extractDuration(string $url): ?int if (! $result->successful()) { Log::error('FFprobe error extracting duration: '.$result->errorOutput()); + return null; } @@ -92,10 +93,11 @@ public function extractDuration(string $url): ?int return null; } catch (\Exception $e) { Log::error('Failed to extract duration with ffprobe: '.$e->getMessage()); + return null; } } - + /** * Extract duration by parsing m3u8 playlist */ @@ -104,22 +106,23 @@ protected function extractDurationFromM3u8(string $url): ?int try { // Download the m3u8 playlist $response = Http::timeout(10)->get($url); - - if (!$response->successful()) { - Log::error('Failed to fetch m3u8 playlist: ' . $response->status()); + + if (! $response->successful()) { + Log::error('Failed to fetch m3u8 playlist: '.$response->status()); + return null; } - + $content = $response->body(); $lines = explode("\n", $content); $totalDuration = 0.0; $segmentCount = 0; - + // Check if it's a master playlist if (str_contains($content, '#EXT-X-STREAM-INF')) { // This is a master playlist, we need to fetch a variant - Log::info('Detected master playlist, fetching first variant from: ' . $url); - + Log::info('Detected master playlist, fetching first variant from: '.$url); + // Find first variant URL $variantUrl = null; $variantCount = 0; @@ -131,7 +134,7 @@ protected function extractDurationFromM3u8(string $url): ?int // Next non-empty, non-comment line should be the variant URL for ($j = $i + 1; $j < count($lines); $j++) { $nextLine = trim($lines[$j]); - if ($nextLine && !str_starts_with($nextLine, '#')) { + if ($nextLine && ! str_starts_with($nextLine, '#')) { $variantUrl = $nextLine; break 2; // Break out of both loops } @@ -139,26 +142,27 @@ protected function extractDurationFromM3u8(string $url): ?int } } } - + Log::info("Master playlist has {$variantCount} variants"); - - if (!$variantUrl) { + + if (! $variantUrl) { Log::error('No variant URL found in master playlist'); + return null; } - + // Make variant URL absolute if it's relative - if (!filter_var($variantUrl, FILTER_VALIDATE_URL)) { + if (! filter_var($variantUrl, FILTER_VALIDATE_URL)) { $baseUrl = dirname($url); - $variantUrl = $baseUrl . '/' . $variantUrl; + $variantUrl = $baseUrl.'/'.$variantUrl; } - - Log::info('Fetching first variant playlist: ' . $variantUrl); - + + Log::info('Fetching first variant playlist: '.$variantUrl); + // Recursively fetch the variant playlist return $this->extractDurationFromM3u8($variantUrl); } - + // Parse segment durations from media playlist foreach ($lines as $line) { // Look for EXTINF tags which contain segment duration @@ -172,16 +176,19 @@ protected function extractDurationFromM3u8(string $url): ?int } } } - + if ($totalDuration > 0) { Log::info("Extracted duration from m3u8: {$totalDuration} seconds from {$segmentCount} segments"); + return (int) round($totalDuration); } - - Log::warning("No duration extracted from m3u8 (no EXTINF tags found)"); + + Log::warning('No duration extracted from m3u8 (no EXTINF tags found)'); + return null; } catch (\Exception $e) { - Log::error('Failed to parse m3u8 for duration: ' . $e->getMessage()); + Log::error('Failed to parse m3u8 for duration: '.$e->getMessage()); + return null; } } @@ -211,7 +218,7 @@ public function generateThumbnail(Recording $recording): ?string // For longer videos, capture from the middle $captureTime = min($recording->duration / 2, 300); // Max 5 minutes in } - + $result = $this->captureFrameAtTime($recording->m3u8_url, $tempPath, $captureTime); if (! $result || ! file_exists($tempPath)) { @@ -268,7 +275,7 @@ protected function captureFrameAtTime(string $videoUrl, string $outputPath, floa { // Use the URL directly $inputUrl = $videoUrl; - + // Build ffmpeg command // -ss: Seek to specific time // -i: input stream @@ -278,7 +285,7 @@ protected function captureFrameAtTime(string $videoUrl, string $outputPath, floa $command = [ 'ffmpeg', '-y', // Overwrite output - '-ss', (string)$timeInSeconds, // Seek to specific time + '-ss', (string) $timeInSeconds, // Seek to specific time '-i', $inputUrl, '-frames:v', '1', // Capture 1 frame '-vf', "scale={$this->thumbnailWidth}:{$this->thumbnailHeight}:force_original_aspect_ratio=decrease,pad={$this->thumbnailWidth}:{$this->thumbnailHeight}:(ow-iw)/2:(oh-ih)/2", @@ -292,12 +299,13 @@ protected function captureFrameAtTime(string $videoUrl, string $outputPath, floa if (! $result->successful()) { Log::error('FFmpeg error capturing thumbnail: '.$result->errorOutput()); + return false; } return true; } - + /** * Get the URL of the first video segment from an m3u8 playlist */ @@ -305,14 +313,14 @@ protected function getFirstSegmentUrl(string $m3u8Url): ?string { try { $response = Http::timeout(10)->get($m3u8Url); - - if (!$response->successful()) { + + if (! $response->successful()) { return null; } - + $content = $response->body(); $lines = explode("\n", $content); - + // Check if it's a master playlist if (str_contains($content, '#EXT-X-STREAM-INF')) { // Get the first variant playlist @@ -320,12 +328,13 @@ protected function getFirstSegmentUrl(string $m3u8Url): ?string if (str_starts_with($line, '#EXT-X-STREAM-INF')) { for ($j = $i + 1; $j < count($lines); $j++) { $nextLine = trim($lines[$j]); - if ($nextLine && !str_starts_with($nextLine, '#')) { + if ($nextLine && ! str_starts_with($nextLine, '#')) { // Make URL absolute if relative - if (!filter_var($nextLine, FILTER_VALIDATE_URL)) { + if (! filter_var($nextLine, FILTER_VALIDATE_URL)) { $baseUrl = dirname($m3u8Url); - $nextLine = $baseUrl . '/' . $nextLine; + $nextLine = $baseUrl.'/'.$nextLine; } + // Recursively get first segment from variant playlist return $this->getFirstSegmentUrl($nextLine); } @@ -333,26 +342,28 @@ protected function getFirstSegmentUrl(string $m3u8Url): ?string } } } - + // Find the first .ts segment foreach ($lines as $line) { $line = trim($line); - if ($line && !str_starts_with($line, '#')) { + if ($line && ! str_starts_with($line, '#')) { // This should be a segment URL if (str_contains($line, '.ts') || str_contains($line, '.m4s')) { // Make URL absolute if relative - if (!filter_var($line, FILTER_VALIDATE_URL)) { + if (! filter_var($line, FILTER_VALIDATE_URL)) { $baseUrl = dirname($m3u8Url); - $line = $baseUrl . '/' . $line; + $line = $baseUrl.'/'.$line; } + return $line; } } } - + return null; } catch (\Exception $e) { - Log::error('Failed to get first segment from m3u8: ' . $e->getMessage()); + Log::error('Failed to get first segment from m3u8: '.$e->getMessage()); + return null; } } @@ -438,4 +449,3 @@ public function processUnprocessedRecordings(): void Log::info("Processed {$recordings->count()} recordings"); } } - diff --git a/app/Services/ServerProvisioningService.php b/app/Services/ServerProvisioningService.php index 15a4fbc..9f2101b 100644 --- a/app/Services/ServerProvisioningService.php +++ b/app/Services/ServerProvisioningService.php @@ -17,7 +17,7 @@ public function generateInstallScript(Server $server): string $sharedSecret = $server->shared_secret ?: Str::random(32); // Update server with shared secret if not set - if (!$server->shared_secret) { + if (! $server->shared_secret) { $server->update(['shared_secret' => $sharedSecret]); } @@ -35,12 +35,12 @@ public function generateCloudInit(Server $server): string { $serverUrl = config('app.url'); $sharedSecret = $server->shared_secret ?: Str::random(32); - + // Update server with shared secret if not set - if (!$server->shared_secret) { + if (! $server->shared_secret) { $server->update(['shared_secret' => $sharedSecret]); } - + // Simple cloud-init that just downloads and runs the install script $cloudInit = << /var/log/ef-streaming-install.log 2>&1 + - /opt/install.sh > /var/log/streaming-install.log 2>&1 -final_message: "EF Streaming server setup completed after \$UPTIME seconds" +final_message: "Streaming server setup completed after \$UPTIME seconds" YAML; return $cloudInit; @@ -70,15 +70,26 @@ public function generateConfig(Server $server, string $type): string $serverUrl = config('app.url'); $sharedSecret = $server->shared_secret ?: Str::random(32); - $viewName = match($type) { + // The edge token verifier and its Dockerfile are shipped verbatim from + // docker/edge-nginx so there is a single source of truth: the file the + // edges run is the same file that is tested here. + if ($type === 'hls-auth-js') { + return $this->edgeFile('hls-auth.js'); + } + + if ($type === 'edge-dockerfile') { + return $this->edgeFile('Dockerfile'); + } + + $viewName = match ($type) { 'docker-compose' => "server-provisioning.{$server->type->value}.docker-compose", 'nginx' => "server-provisioning.{$server->type->value}.nginx-config", 'caddy' => "server-provisioning.{$server->type->value}.caddyfile", - 'srs' => "server-provisioning.origin.srs-config", + 'srs' => 'server-provisioning.origin.srs-config', default => null, }; - if (!$viewName || !View::exists($viewName)) { + if (! $viewName || ! View::exists($viewName)) { return ''; } @@ -94,31 +105,33 @@ public function generateConfig(Server $server, string $type): string $parsedUrl = parse_url($serverUrl); $nginxUpstreamHost = $parsedUrl['host'] ?? 'localhost'; $nginxUpstreamScheme = $parsedUrl['scheme'] ?? 'http'; - + // For HTTPS, use the URL directly without port. For HTTP, use host:port if ($nginxUpstreamScheme === 'https') { $nginxUpstream = $serverUrl; // Use full HTTPS URL } else { $nginxUpstreamPort = $parsedUrl['port'] ?? 80; - $nginxUpstream = 'http://' . $nginxUpstreamHost . ':' . $nginxUpstreamPort; + $nginxUpstream = 'http://'.$nginxUpstreamHost.':'.$nginxUpstreamPort; } - // For edge server, we need to connect to origin - // Use a sensible default if no origin server is found - $originHost = $originServer ? $originServer->hostname : 'origin.stream.eurofurence.org'; + // For edge server, we need to connect to origin. With no origin on + // record, fall back to origin. so the config is still valid. + $originHost = $originServer + ? $originServer->hostname + : trim('origin.'.config('dns.zone'), '.'); // For nginx upstream block - just hostname:port, no protocol - $originUpstream = $originHost . ':443'; - + $originUpstream = $originHost.':443'; + // Determine if we can use internal networking $useInternalNetwork = false; $originInternalUpstream = null; - + if ($server->type->value === 'edge' && $originServer) { // Check if both servers are Hetzner servers with internal IPs if ($server->canUseInternalNetworkWith($originServer)) { $useInternalNetwork = true; // Internal network uses HTTPS on port 443 to Caddy (using internal IP) - $originInternalUpstream = $originServer->internal_ip . ':443'; + $originInternalUpstream = $originServer->internal_ip.':443'; } } @@ -131,9 +144,25 @@ public function generateConfig(Server $server, string $type): string 'originServer' => $originServer, 'useInternalNetwork' => $useInternalNetwork, 'originInternalUpstream' => $originInternalUpstream, + // Edges verify playback tokens locally, so they need the same + // secrets and the same expiry leeway as the app. + 'hlsViewerSecret' => config('stream.token.viewer_secret') ?? '', + 'hlsEmbedSecret' => config('stream.token.embed_secret') ?? '', + 'hlsTokenLeeway' => (int) config('stream.token.leeway'), + 'systemStreamkey' => config('stream.system_streamkey') ?? '', ])->render(); } + /** + * Read a file that edges run unmodified out of docker/edge-nginx. + */ + private function edgeFile(string $name): string + { + $path = base_path("docker/edge-nginx/{$name}"); + + return is_readable($path) ? (string) file_get_contents($path) : ''; + } + // Legacy methods for backward compatibility public function generateDockerCompose(Server $server): string { @@ -164,4 +193,4 @@ public function generateSrsConfig(Server $server): string { return $this->generateConfig($server, 'srs'); } -} \ No newline at end of file +} diff --git a/app/Services/ShowStatisticsService.php b/app/Services/ShowStatisticsService.php index 38d4b63..6e58b9e 100644 --- a/app/Services/ShowStatisticsService.php +++ b/app/Services/ShowStatisticsService.php @@ -2,10 +2,9 @@ namespace App\Services; -use App\Models\Server; use App\Models\Show; -use App\Models\Source; use App\Models\ShowStatistic; +use App\Models\Source; use Carbon\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; @@ -15,30 +14,30 @@ class ShowStatisticsService { public function recordStatistics(Show $show): void { - if (!$show->actual_start || $show->status !== 'live') { + if (! $show->actual_start || $show->status !== 'live') { return; } // Get the source for this show $source = $show->source; - + $currentViewerCount = 0; $uniqueViewers = 0; - + if ($source) { // Get active viewer count from source_users table $currentViewerCount = $source->activeViewers()->count(); - + // For unique viewers, count distinct users who have watched this source today $uniqueViewers = DB::table('source_users') ->where('source_id', $source->id) ->where('joined_at', '>=', now()->startOfDay()) ->distinct('user_id') ->count('user_id'); - + // Also check cache as fallback (in case edge servers are reporting) $cachedCount = Cache::get("stream_total_viewers:{$source->slug}", 0); - + // Use the higher of the two counts (in case edge servers are reporting higher numbers) if ($cachedCount > $currentViewerCount) { $currentViewerCount = $cachedCount; @@ -103,17 +102,30 @@ public function getShowStatistics(Show $show): array ]; } + /** + * Average, peak and unique viewers per hour of the broadcast. + * + * Bucketed in PHP rather than with DATE_FORMAT: that function is MySQL-only, so this + * query threw "column %Y-%m-%d %H:00:00 does not exist" on Postgres and SQLite, taking + * the whole statistics page down everywhere except production. + * + * The row count is bounded by the length of a show at one sample per minute, so there + * is nothing to gain from pushing the grouping into the database. + */ private function getHourlyStats(Show $show, Carbon $startTime, Carbon $endTime): Collection { return ShowStatistic::where('show_id', $show->id) ->whereBetween('recorded_at', [$startTime, $endTime]) - ->selectRaw('DATE_FORMAT(recorded_at, "%Y-%m-%d %H:00:00") as hour') - ->selectRaw('AVG(viewer_count) as avg_viewers') - ->selectRaw('MAX(viewer_count) as peak_viewers') - ->selectRaw('MAX(unique_viewers) as unique_viewers') - ->groupBy('hour') - ->orderBy('hour') - ->get(); + ->orderBy('recorded_at') + ->get(['recorded_at', 'viewer_count', 'unique_viewers']) + ->groupBy(fn (ShowStatistic $stat) => $stat->recorded_at->format('Y-m-d H:00:00')) + ->map(fn (Collection $hour, string $key) => [ + 'hour' => $key, + 'avg_viewers' => round($hour->avg('viewer_count'), 1), + 'peak_viewers' => (int) $hour->max('viewer_count'), + 'unique_viewers' => (int) $hour->max('unique_viewers'), + ]) + ->values(); } public function getRealtimeStats(Show $show): array @@ -122,7 +134,7 @@ public function getRealtimeStats(Show $show): array $source = Source::where('slug', $show->slug) ->orWhere('id', $show->source_id) ->first(); - + $currentViewers = 0; if ($source) { $currentViewers = Cache::get("stream_total_viewers:{$source->slug}", 0); @@ -138,10 +150,10 @@ public function getRealtimeStats(Show $show): array return [ 'current' => $currentViewers, - 'trend' => $last5Minutes->map(fn($stat) => [ + 'trend' => $last5Minutes->map(fn ($stat) => [ 'time' => $stat->recorded_at->format('H:i:s'), 'count' => $stat->viewer_count, ]), ]; } -} \ No newline at end of file +} diff --git a/app/Services/ThumbnailService.php b/app/Services/ThumbnailService.php index 3c452c3..b8c3581 100644 --- a/app/Services/ThumbnailService.php +++ b/app/Services/ThumbnailService.php @@ -40,7 +40,7 @@ public function captureFromHls(Show $show): ?string $systemStreamkey = config('stream.system_streamkey') ?: env('STREAM_KEY'); if ($systemStreamkey) { $separator = str_contains($streamUrl, '?') ? '&' : '?'; - $streamUrl .= $separator . 'streamkey=' . $systemStreamkey; + $streamUrl .= $separator.'streamkey='.$systemStreamkey; } Log::info("Capturing thumbnail for show {$show->id} from URL: {$streamUrl}"); @@ -71,7 +71,7 @@ public function captureFromHls(Show $show): ?string ['visibility' => 'private'] ); - if (!$uploaded) { + if (! $uploaded) { throw new \Exception('Failed to upload thumbnail to S3'); } @@ -202,8 +202,8 @@ public function deleteShowThumbnails(Show $show): void public function uploadThumbnail(Show $show, string $localPath): ?string { try { - if (!file_exists($localPath)) { - throw new \Exception('File does not exist: ' . $localPath); + if (! file_exists($localPath)) { + throw new \Exception('File does not exist: '.$localPath); } // Generate filename @@ -218,7 +218,7 @@ public function uploadThumbnail(Show $show, string $localPath): ?string ['visibility' => 'private'] ); - if (!$uploaded) { + if (! $uploaded) { throw new \Exception('Failed to upload to S3'); } @@ -237,7 +237,7 @@ public function uploadThumbnail(Show $show, string $localPath): ?string return $s3Path; } catch (\Exception $e) { - Log::error("Failed to upload thumbnail for show {$show->id}: " . $e->getMessage()); + Log::error("Failed to upload thumbnail for show {$show->id}: ".$e->getMessage()); $show->update([ 'thumbnail_capture_error' => $e->getMessage(), diff --git a/app/Support/Chat/Broadcast.php b/app/Support/Chat/Broadcast.php new file mode 100644 index 0000000..4d1411c --- /dev/null +++ b/app/Support/Chat/Broadcast.php @@ -0,0 +1,29 @@ + $event::class, + 'error' => $e->getMessage(), + ]); + } + } +} diff --git a/app/Support/ColorPresets.php b/app/Support/ColorPresets.php new file mode 100644 index 0000000..f3cc8ba --- /dev/null +++ b/app/Support/ColorPresets.php @@ -0,0 +1,74 @@ + + */ + /** + * The neutral ramp in resources/css/app.css, which is what renders when no + * accent is saved. + * + * Selecting it stores nothing: an empty `primary_color` means the stylesheet + * stays authoritative, and its hand-tuned ramp is closer than anything + * ColorRamp would derive from the 500 stop alone. The hex here is only what + * the swatch paints itself. + */ + public const BUILT_IN = ['value' => '', 'hex' => '#6a7282', 'label' => 'Neutral gray (built-in)']; + + public const PRESETS = [ + '#048072' => 'Deep Teal', + + '#ef4444' => 'Red', + '#f97316' => 'Orange', + '#f59e0b' => 'Amber', + '#eab308' => 'Yellow', + '#84cc16' => 'Lime', + '#22c55e' => 'Green', + '#10b981' => 'Emerald', + '#14b8a6' => 'Teal', + '#06b6d4' => 'Cyan', + '#0ea5e9' => 'Sky', + '#3b82f6' => 'Blue', + '#6366f1' => 'Indigo', + '#8b5cf6' => 'Violet', + '#a855f7' => 'Purple', + '#d946ef' => 'Fuchsia', + '#ec4899' => 'Pink', + '#f43f5e' => 'Rose', + '#64748b' => 'Slate', + ]; + + /** + * Presets as a list the frontend can iterate over, the built-in first so + * getting back to the neutral default is one click rather than clearing a + * field by hand. + * + * `value` is what gets stored, `hex` is what the swatch shows. They differ + * only for the built-in, which stores nothing. + * + * @return array + */ + public static function forFrontend(): array + { + $presets = [self::BUILT_IN]; + + foreach (self::PRESETS as $hex => $label) { + $presets[] = ['value' => $hex, 'hex' => $hex, 'label' => $label]; + } + + return $presets; + } +} diff --git a/app/Support/ColorRamp.php b/app/Support/ColorRamp.php new file mode 100644 index 0000000..2ce5182 --- /dev/null +++ b/app/Support/ColorRamp.php @@ -0,0 +1,193 @@ + [94.77, 0.78], + '100' => [89.50, 1.66], + '200' => [80.03, 1.48], + '300' => [71.68, 1.32], + '400' => [62.48, 1.16], + '500' => [53.86, 1.00], + '600' => [45.51, 0.84], + '700' => [38.07, 0.71], + '800' => [29.53, 0.55], + '900' => [21.87, 0.41], + '950' => [17.17, 0.32], + ]; + + /** + * Ceiling on the base chroma before it is scaled across the stops. A very + * saturated accent multiplied up for the light stops lands outside sRGB, + * where the browser gamut-maps it and the ramp stops being evenly spaced. + * Clamping the base keeps the shape of the ramp intact instead. + */ + private const MAX_BASE_CHROMA = 0.13; + + /** + * @return array stop => oklch() string, empty when unparseable + */ + public static function fromHex(?string $hex): array + { + $rgb = self::hexToRgb($hex); + + if ($rgb === null) { + return []; + } + + [, $chroma, $hue] = self::rgbToOklch($rgb); + + $chroma = min($chroma, self::MAX_BASE_CHROMA); + + // A greyscale accent has no meaningful hue; keep it grey rather than + // inventing one from floating point noise. + if ($chroma < 0.002) { + $chroma = 0.0; + $hue = 0.0; + } + + $ramp = []; + + foreach (self::STOPS as $stop => [$lightness, $chromaScale]) { + $stopChroma = round($chroma * $chromaScale, 4); + + $ramp[$stop] = sprintf('oklch(%s%% %s %s)', round($lightness, 2), $stopChroma, round($hue, 2)); + } + + return $ramp; + } + + /** + * The /manage chrome, re-tinted to the accent hue. + * + * The panel does not use the primary ramp: it has its own surface, text and + * hairline tokens, tuned for a dark control room and hardcoded to one hue. So + * setting an accent repainted the public site and left the panel looking like + * it belonged to a different installation. + * + * Only the hue moves. The lightness steps are what make a card read against + * the page, and the chroma is deliberately tiny — enough that the greys do + * not look dead, not enough to be a colour. `--state-live` is the exception: + * it is the active/focus accent, so it keeps its designed strength and takes + * the hue with it. + * + * Semantic states are left alone on purpose. Ok, warn, danger and info mean + * something, and a red brand must not repaint "healthy" red. + * + * @return array empty for a greyscale accent, which has no + * hue worth spreading and would flatten the + * live indicator into the background + */ + public static function chromeFromHex(?string $hex): array + { + $rgb = self::hexToRgb($hex); + + if ($rgb === null) { + return []; + } + + [, $chroma, $hue] = self::rgbToOklch($rgb); + + if ($chroma < 0.002) { + return []; + } + + $hue = round($hue, 2); + + return [ + '--surface-0' => "oklch(0.16 0.008 {$hue})", + '--surface-1' => "oklch(0.19 0.009 {$hue})", + '--surface-2' => "oklch(0.22 0.01 {$hue})", + '--surface-3' => "oklch(0.26 0.012 {$hue})", + '--fg-1' => "oklch(0.97 0.005 {$hue})", + '--fg-2' => "oklch(0.75 0.01 {$hue})", + '--fg-3' => "oklch(0.58 0.012 {$hue})", + '--hairline' => "oklch(0.28 0.012 {$hue})", + '--state-live' => "oklch(0.75 0.12 {$hue})", + '--state-idle' => "oklch(0.58 0.012 {$hue})", + ]; + } + + /** + * @return array{0: float, 1: float, 2: float}|null 0-1 sRGB components + */ + private static function hexToRgb(?string $hex): ?array + { + if (! is_string($hex)) { + return null; + } + + $hex = ltrim(trim($hex), '#'); + + if (strlen($hex) === 3) { + $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2]; + } + + if (! preg_match('/^[0-9a-fA-F]{6}$/', $hex)) { + return null; + } + + return [ + hexdec(substr($hex, 0, 2)) / 255, + hexdec(substr($hex, 2, 2)) / 255, + hexdec(substr($hex, 4, 2)) / 255, + ]; + } + + /** + * @param array{0: float, 1: float, 2: float} $rgb + * @return array{0: float, 1: float, 2: float} lightness %, chroma, hue deg + */ + private static function rgbToOklch(array $rgb): array + { + [$r, $g, $b] = array_map([self::class, 'toLinear'], $rgb); + + // sRGB -> LMS (Björn Ottosson's Oklab matrices) + $l = 0.4122214708 * $r + 0.5363325363 * $g + 0.0514459929 * $b; + $m = 0.2119034982 * $r + 0.6806995451 * $g + 0.1073969566 * $b; + $s = 0.0883024619 * $r + 0.2817188376 * $g + 0.6299787005 * $b; + + $l = self::cbrt($l); + $m = self::cbrt($m); + $s = self::cbrt($s); + + $labL = 0.2104542553 * $l + 0.7936177850 * $m - 0.0040720468 * $s; + $labA = 1.9779984951 * $l - 2.4285922050 * $m + 0.4505937099 * $s; + $labB = 0.0259040371 * $l + 0.7827717662 * $m - 0.8086757660 * $s; + + $chroma = sqrt($labA ** 2 + $labB ** 2); + $hue = atan2($labB, $labA) * 180 / M_PI; + + if ($hue < 0) { + $hue += 360; + } + + return [$labL * 100, $chroma, $hue]; + } + + private static function toLinear(float $channel): float + { + return $channel <= 0.04045 + ? $channel / 12.92 + : (($channel + 0.055) / 1.055) ** 2.4; + } + + private static function cbrt(float $value): float + { + return $value < 0 ? -((-$value) ** (1 / 3)) : $value ** (1 / 3); + } +} diff --git a/app/Support/Manage/Action.php b/app/Support/Manage/Action.php new file mode 100644 index 0000000..c788bab --- /dev/null +++ b/app/Support/Manage/Action.php @@ -0,0 +1,132 @@ + + */ +final class Action implements Arrayable +{ + private ?string $icon = null; + + private string $tone = Status::INFO; + + /** @var array{heading: string, description: ?string, submit: string}|null */ + private ?array $confirm = null; + + /** @var array> */ + private array $fields = []; + + private ?string $disabledReason = null; + + private bool $newTab = false; + + private function __construct( + public readonly string $name, + public readonly string $label, + public readonly string $url, + public readonly string $method, + ) {} + + public static function link(string $name, string $label, string $url): self + { + return new self($name, $label, $url, 'get'); + } + + public static function post(string $name, string $label, string $url): self + { + return new self($name, $label, $url, 'post'); + } + + public static function put(string $name, string $label, string $url): self + { + return new self($name, $label, $url, 'put'); + } + + public static function delete(string $name, string $label, string $url): self + { + return new self($name, $label, $url, 'delete'); + } + + public function icon(string $icon): self + { + $this->icon = $icon; + + return $this; + } + + public function tone(string $tone): self + { + $this->tone = $tone; + + return $this; + } + + public function confirm(string $heading, ?string $description = null, string $submit = 'Confirm'): self + { + $this->confirm = [ + 'heading' => $heading, + 'description' => $description, + 'submit' => $submit, + ]; + + return $this; + } + + /** + * Fields to collect in a modal before submitting, e.g. the status select on + * "Update Status" or the type select on "Provision Cloud Server". + * + * Shape per field: ['key', 'label', 'type', 'options'?, 'default'?, 'required'?, 'helper'?] + * + * @param array> $fields + */ + public function fields(array $fields): self + { + $this->fields = $fields; + + return $this; + } + + public function disabled(?string $reason): self + { + $this->disabledReason = $reason; + + return $this; + } + + public function newTab(bool $newTab = true): self + { + $this->newTab = $newTab; + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'label' => $this->label, + 'url' => $this->url, + 'method' => $this->method, + 'icon' => $this->icon, + 'tone' => $this->tone, + 'confirm' => $this->confirm, + 'fields' => $this->fields === [] ? null : $this->fields, + 'disabledReason' => $this->disabledReason, + 'newTab' => $this->newTab, + ]; + } +} diff --git a/app/Support/Manage/Column.php b/app/Support/Manage/Column.php new file mode 100644 index 0000000..eb20f97 --- /dev/null +++ b/app/Support/Manage/Column.php @@ -0,0 +1,231 @@ + string, 'description' => ?string] + * badge Status::make() triple + * image url string|null + * bool boolean + * datetime string, or ['display' => string, 'title' => ?string] + * duration preformatted string + * color hex string + * copyable string + * toggle ['value' => bool, 'url' => string] (writes immediately) + * icon ['icon' => string, 'tone' => string, 'title' => ?string] + * + * @implements Arrayable + */ +final class Column implements Arrayable +{ + private bool $sortable = false; + + private ?string $sortKey = null; + + private ?Closure $sortUsing = null; + + private bool $searchable = false; + + private ?string $searchKey = null; + + private bool $toggleable = false; + + private bool $hiddenByDefault = false; + + private string $align = 'left'; + + private ?string $fallback = null; + + private ?string $width = null; + + private function __construct( + public readonly string $key, + public readonly string $label, + public readonly string $type, + ) {} + + public static function make(string $key, ?string $label = null, string $type = 'text'): self + { + return new self($key, $label ?? str($key)->headline()->toString(), $type); + } + + public static function text(string $key, ?string $label = null): self + { + return self::make($key, $label, 'text'); + } + + public static function number(string $key, ?string $label = null): self + { + return self::make($key, $label, 'number')->align('right'); + } + + public static function badge(string $key, ?string $label = null): self + { + return self::make($key, $label, 'badge'); + } + + public static function image(string $key, ?string $label = null): self + { + return self::make($key, $label, 'image'); + } + + public static function bool(string $key, ?string $label = null): self + { + return self::make($key, $label, 'bool')->align('center'); + } + + public static function datetime(string $key, ?string $label = null): self + { + return self::make($key, $label, 'datetime'); + } + + public static function duration(string $key, ?string $label = null): self + { + return self::make($key, $label, 'duration')->align('right'); + } + + public static function color(string $key, ?string $label = null): self + { + return self::make($key, $label, 'color'); + } + + public static function copyable(string $key, ?string $label = null): self + { + return self::make($key, $label, 'copyable'); + } + + public static function toggle(string $key, ?string $label = null): self + { + return self::make($key, $label, 'toggle')->align('center'); + } + + public static function icon(string $key, ?string $label = null): self + { + return self::make($key, $label, 'icon')->align('center'); + } + + /** + * @param string|null $sortKey Database column to sort by, when it differs from the cell key. + */ + public function sortable(?string $sortKey = null): self + { + $this->sortable = true; + $this->sortKey = $sortKey; + + return $this; + } + + /** + * Custom sort, required for columns that sort across a relation. + * + * @param Closure(\Illuminate\Database\Eloquent\Builder, string): void $callback + */ + public function sortUsing(Closure $callback): self + { + $this->sortable = true; + $this->sortUsing = $callback; + + return $this; + } + + /** + * @param string|null $searchKey Column, or `relation.column` to search through a relation. + */ + public function searchable(?string $searchKey = null): self + { + $this->searchable = true; + $this->searchKey = $searchKey; + + return $this; + } + + public function toggleable(bool $hiddenByDefault = false): self + { + $this->toggleable = true; + $this->hiddenByDefault = $hiddenByDefault; + + return $this; + } + + public function align(string $align): self + { + $this->align = $align; + + return $this; + } + + /** + * Rendered instead of an empty cell. + */ + public function fallback(string $fallback): self + { + $this->fallback = $fallback; + + return $this; + } + + public function width(string $width): self + { + $this->width = $width; + + return $this; + } + + public function isSortable(): bool + { + return $this->sortable; + } + + public function isSearchable(): bool + { + return $this->searchable; + } + + public function isHiddenByDefault(): bool + { + return $this->hiddenByDefault; + } + + public function resolvedSortKey(): ?string + { + return $this->sortKey ?? $this->key; + } + + public function resolvedSearchKey(): string + { + return $this->searchKey ?? $this->key; + } + + public function sortCallback(): ?Closure + { + return $this->sortUsing; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'key' => $this->key, + 'label' => $this->label, + 'type' => $this->type, + 'align' => $this->align, + 'sortable' => $this->sortable, + 'sortKey' => $this->sortUsing ? $this->key : $this->resolvedSortKey(), + 'toggleable' => $this->toggleable, + 'hiddenByDefault' => $this->hiddenByDefault, + 'fallback' => $this->fallback, + 'width' => $this->width, + ]; + } +} diff --git a/app/Support/Manage/Filter.php b/app/Support/Manage/Filter.php new file mode 100644 index 0000000..52f9c2a --- /dev/null +++ b/app/Support/Manage/Filter.php @@ -0,0 +1,192 @@ + + */ +final class Filter implements Arrayable +{ + /** @var array */ + private array $options = []; + + private bool $multiple = false; + + private ?string $placeholder = null; + + private ?string $trueLabel = null; + + private ?string $falseLabel = null; + + private mixed $default = null; + + private ?Closure $apply = null; + + private function __construct( + public readonly string $key, + public readonly string $label, + public readonly string $type, + ) {} + + public static function select(string $key, ?string $label = null): self + { + return new self($key, $label ?? str($key)->headline()->toString(), 'select'); + } + + public static function ternary(string $key, ?string $label = null): self + { + return new self($key, $label ?? str($key)->headline()->toString(), 'ternary'); + } + + public static function boolean(string $key, ?string $label = null): self + { + return new self($key, $label ?? str($key)->headline()->toString(), 'boolean'); + } + + /** + * @param array $options value => label + */ + public function options(array $options): self + { + $this->options = $options; + + return $this; + } + + public function multiple(bool $multiple = true): self + { + $this->multiple = $multiple; + + return $this; + } + + public function placeholder(string $placeholder): self + { + $this->placeholder = $placeholder; + + return $this; + } + + public function trueLabel(string $label): self + { + $this->trueLabel = $label; + + return $this; + } + + public function falseLabel(string $label): self + { + $this->falseLabel = $label; + + return $this; + } + + public function default(mixed $default): self + { + $this->default = $default; + + return $this; + } + + /** + * @param Closure(Builder, mixed): void $callback + */ + public function apply(Closure $callback): self + { + $this->apply = $callback; + + return $this; + } + + public function defaultValue(): mixed + { + return $this->default ?? match ($this->type) { + 'boolean' => false, + 'select' => $this->multiple ? [] : '', + default => '', + }; + } + + /** + * Whether the given request value should narrow the query at all. + */ + public function isActive(mixed $value): bool + { + return match ($this->type) { + 'boolean' => (bool) $value, + 'select' => $this->multiple ? ! empty($value) : $value !== '' && $value !== null, + 'ternary' => $value === '1' || $value === '0' || $value === true || $value === false, + default => $value !== '' && $value !== null, + }; + } + + /** + * Coerce a raw request value into the shape the filter works with. + */ + public function normalize(mixed $value): mixed + { + return match ($this->type) { + 'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN), + 'ternary' => match (true) { + $value === true, $value === '1', $value === 1 => '1', + $value === false, $value === '0', $value === 0 => '0', + default => '', + }, + 'select' => $this->multiple ? array_values(array_filter((array) $value, fn ($v) => $v !== '' && $v !== null)) : (string) $value, + default => $value, + }; + } + + public function applyTo(Builder $query, mixed $value): void + { + if ($this->apply) { + ($this->apply)($query, $value); + + return; + } + + match ($this->type) { + 'select' => $this->multiple + ? $query->whereIn($this->key, $value) + : $query->where($this->key, $value), + 'ternary' => $query->where($this->key, $value === '1'), + default => null, + }; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'key' => $this->key, + 'label' => $this->label, + 'type' => $this->type, + 'options' => $this->options === [] ? null : collect($this->options) + ->map(fn (string $label, string $value) => ['value' => $value, 'label' => $label]) + ->values() + ->all(), + 'multiple' => $this->multiple, + 'placeholder' => $this->placeholder, + 'trueLabel' => $this->trueLabel, + 'falseLabel' => $this->falseLabel, + 'default' => $this->defaultValue(), + ]; + } +} diff --git a/app/Support/Manage/Navigation.php b/app/Support/Manage/Navigation.php new file mode 100644 index 0000000..f51fae7 --- /dev/null +++ b/app/Support/Manage/Navigation.php @@ -0,0 +1,131 @@ +>}> + */ + public function groups(): array + { + $badges = $this->badges(); + + /* + * Four headings, deliberately. Seven groups meant most of them held one + * item, so the rail was mostly separators and the eye had to read every + * heading to find anything. Programme work is one block, the machines are + * another, and everything that is configuration rather than daily + * operation sits under Administration. + */ + $groups = [ + ['label' => 'Overview', 'items' => [ + $this->item('Dashboard', 'layout-dashboard', 'manage.home', $badges['alerts'] ?? null), + ]], + ['label' => 'Streaming', 'items' => [ + $this->item('Sources', 'video', 'manage.sources.index', $badges['sources'] ?? null), + $this->item('Shows', 'play-circle', 'manage.shows.index', $badges['shows'] ?? null), + $this->item('Planner', 'calendar', 'manage.shows.planner'), + // Import has no rail entry on purpose: it is reached from the Shows + // table, next to the programme it adds to. + $this->item('Recordings', 'film', 'manage.recordings.index'), + ]], + ['label' => 'Infrastructure', 'items' => [ + $this->item('Servers', 'server', 'manage.servers.index'), + ]], + ['label' => 'Administration', 'items' => [ + $this->item('Users', 'users', 'manage.users.index'), + $this->item('Roles', 'shield-check', 'manage.roles.index', $badges['roles'] ?? null), + $this->item('Emotes', 'smile', 'manage.emotes.index', $badges['emotes'] ?? null), + $this->item('Settings', 'paintbrush', 'manage.settings'), + ]], + ]; + + return collect($groups) + ->map(fn (array $group) => [ + 'label' => $group['label'], + 'items' => array_values(array_filter($group['items'])), + ]) + ->filter(fn (array $group) => $group['items'] !== []) + ->values() + ->all(); + } + + /** + * @param array{label: string, tone: string}|null $badge + * @return array|null + */ + private function item(string $label, string $icon, string $route, ?array $badge = null): ?array + { + if (! Route::has($route)) { + return null; + } + + return [ + 'label' => $label, + 'icon' => $icon, + 'route' => $route, + 'url' => route($route), + 'badge' => $badge, + ]; + } + + /** + * Mirrors the Filament navigation badges: live/upcoming shows, online sources, + * pending emotes, total roles. + * + * @return array + */ + private function badges(): array + { + return Cache::remember('manage.nav.badges', self::BADGE_TTL, function () { + $live = Show::live()->count(); + $upcoming = Show::upcoming()->count(); + $online = Source::where('status', SourceStatusEnum::ONLINE)->count(); + $pending = Emote::pending()->count(); + $roles = Role::count(); + + /* + * A cheap count of the hard failures the dashboard lists, so the rail can + * flag them without running the full alert set on every request. + */ + $broken = Source::where('status', SourceStatusEnum::ERROR)->count() + + Server::whereNot('status', ServerStatusEnum::DELETED) + ->where(fn ($query) => $query + ->where('status', ServerStatusEnum::ERROR) + ->orWhere('health_status', 'unhealthy')) + ->count(); + + return [ + 'alerts' => $broken > 0 ? ['label' => (string) $broken, 'tone' => Status::DANGER] : null, + 'shows' => match (true) { + $live > 0 => ['label' => $live.' live', 'tone' => Status::LIVE], + $upcoming > 0 => ['label' => (string) $upcoming, 'tone' => Status::WARN], + default => null, + }, + 'sources' => $online > 0 ? ['label' => (string) $online, 'tone' => Status::OK] : null, + 'emotes' => $pending > 0 ? ['label' => (string) $pending, 'tone' => Status::WARN] : null, + 'roles' => $roles > 0 ? ['label' => (string) $roles, 'tone' => Status::IDLE] : null, + ]; + }); + } +} diff --git a/app/Support/Manage/Overview.php b/app/Support/Manage/Overview.php new file mode 100644 index 0000000..920d9a4 --- /dev/null +++ b/app/Support/Manage/Overview.php @@ -0,0 +1,342 @@ + + */ + public function statusStrip(): array + { + $edge = Server::query()->where('type', ServerTypeEnum::EDGE); + + $active = (clone $edge)->where('status', ServerStatusEnum::ACTIVE)->count(); + $total = (clone $edge)->whereNot('status', ServerStatusEnum::DELETED)->count(); + + return [ + 'stream' => Status::stream($this->streamStatus()), + 'liveShows' => Show::live()->count(), + 'edge' => ['active' => $active, 'total' => $total], + 'viewers' => (int) (clone $edge) + ->where('status', ServerStatusEnum::ACTIVE) + ->sum('viewer_count'), + ]; + } + + /** + * Edge servers grouped by status, one card each (the ServerActive widget). + * + * @return array + */ + public function edgeServerCards(): array + { + return Server::query() + ->where('type', ServerTypeEnum::EDGE) + ->whereNot('status', ServerStatusEnum::DELETED) + ->selectRaw('status, count(*) as aggregate') + ->groupBy('status') + ->get() + ->map(function (Server $row) { + $status = Status::server($row->status); + + return [ + 'label' => 'Edge '.strtolower($status['label']), + 'value' => (int) $row->aggregate, + 'tone' => $status['tone'], + ]; + }) + ->all(); + } + + /** + * The Capacity widget. + * + * @return array + */ + public function capacityCards(): array + { + $edge = Server::query()->where('type', ServerTypeEnum::EDGE); + + $maxClients = (int) (clone $edge)->where('status', ServerStatusEnum::ACTIVE)->sum('max_clients'); + $booting = (int) (clone $edge)->where('status', ServerStatusEnum::PROVISIONING)->sum('max_clients'); + $waiting = User::whereNull('server_id')->count(); + $viewers = (int) (clone $edge)->where('status', ServerStatusEnum::ACTIVE)->sum('viewer_count'); + + return [ + [ + 'label' => 'Max clients', + 'value' => $maxClients, + 'tone' => Status::INFO, + 'hint' => $maxClients > 0 ? round($viewers / $maxClients * 100).'% in use' : null, + ], + [ + 'label' => 'Booting capacity', + 'value' => $booting, + 'tone' => $booting > 0 ? Status::WARN : Status::IDLE, + 'hint' => null, + ], + [ + 'label' => 'Waiting users', + 'value' => $waiting, + 'tone' => $waiting > 0 ? Status::DANGER : Status::OK, + 'hint' => 'No server assigned yet', + ], + ]; + } + + /** + * Viewer numbers, live now and where they are sitting. + * + * @return array{total: int, peak: int, perSource: array} + */ + public function viewers(): array + { + $live = Show::live()->get(); + + return [ + 'total' => (int) Server::query() + ->where('type', ServerTypeEnum::EDGE) + ->where('status', ServerStatusEnum::ACTIVE) + ->sum('viewer_count'), + 'peak' => (int) $live->max('peak_viewer_count'), + 'perSource' => Source::query() + ->orderByDesc('priority') + ->orderBy('name') + ->get() + ->map(fn (Source $source) => [ + 'name' => $source->name, + 'viewers' => (int) $source->shows()->where('status', 'live')->sum('viewer_count'), + 'status' => Status::source($source->status), + ]) + ->all(), + ]; + } + + /** + * One row per server, edge and origin, for the health table. + * + * @return array> + */ + public function servers(): array + { + return Server::query() + ->whereNot('status', ServerStatusEnum::DELETED) + ->orderBy('type') + ->orderBy('hostname') + ->get() + ->map(function (Server $server) { + $isEdge = $server->type === ServerTypeEnum::EDGE; + $max = (int) $server->max_clients; + $viewers = (int) $server->viewer_count; + + return [ + 'id' => $server->id, + 'hostname' => $server->hostname ?? '(unnamed)', + 'ip' => $server->ip, + 'type' => $server->type?->value, + 'status' => Status::server($server->status), + 'health' => $isEdge ? Status::health($server->health_status) : null, + 'healthMessage' => $server->health_check_message, + 'viewers' => $viewers, + 'maxClients' => $max, + 'load' => $isEdge && $max > 0 ? (int) round($viewers / $max * 100) : null, + 'heartbeat' => $server->last_heartbeat?->diffForHumans(), + 'heartbeatStale' => $this->isStale($server), + 'url' => Route::has('manage.servers.edit') + ? route('manage.servers.edit', $server) + : null, + ]; + }) + ->all(); + } + + /** + * Everything currently wrong, worst first, so the maintainer reads one list + * instead of inferring problems from four tables. + * + * @return array + */ + public function alerts(): array + { + $alerts = []; + + foreach (Source::where('status', SourceStatusEnum::ERROR)->get() as $source) { + $alerts[] = [ + 'tone' => Status::DANGER, + 'title' => "Source '{$source->name}' is in error", + 'detail' => 'The encoder connection failed or was rejected.', + 'url' => Route::has('manage.sources.edit') ? route('manage.sources.edit', $source) : null, + ]; + } + + $servers = Server::query()->whereNot('status', ServerStatusEnum::DELETED)->get(); + + foreach ($servers as $server) { + $name = $server->hostname ?? "server #{$server->id}"; + + if ($server->status === ServerStatusEnum::ERROR) { + $alerts[] = [ + 'tone' => Status::DANGER, + 'title' => "Server {$name} is in error", + 'detail' => $server->health_check_message, + 'url' => Route::has('manage.servers.edit') ? route('manage.servers.edit', $server) : null, + ]; + + continue; + } + + if ($server->health_status === 'unhealthy') { + $alerts[] = [ + 'tone' => Status::DANGER, + 'title' => "Server {$name} is failing its health check", + 'detail' => $server->health_check_message, + 'url' => Route::has('manage.servers.edit') ? route('manage.servers.edit', $server) : null, + ]; + + continue; + } + + if ($this->isStale($server)) { + $alerts[] = [ + 'tone' => Status::WARN, + 'title' => "Server {$name} has not checked in", + 'detail' => 'Last heartbeat '.($server->last_heartbeat?->diffForHumans() ?? 'never'), + 'url' => Route::has('manage.servers.edit') ? route('manage.servers.edit', $server) : null, + ]; + } + } + + // A live show pushing nothing is the failure an operator most wants to catch early. + foreach (Show::live()->with('source')->get() as $show) { + if ($show->source && $show->source->status !== SourceStatusEnum::ONLINE) { + $alerts[] = [ + 'tone' => Status::DANGER, + 'title' => "'{$show->title}' is live but its source is not online", + 'detail' => "Source '{$show->source->name}' is ".($show->source->status?->value ?? 'unknown').'.', + 'url' => Route::has('manage.shows.edit') ? route('manage.shows.edit', $show) : null, + ]; + } + } + + $edge = Server::query()->where('type', ServerTypeEnum::EDGE)->where('status', ServerStatusEnum::ACTIVE); + $capacity = (int) (clone $edge)->sum('max_clients'); + $viewers = (int) (clone $edge)->sum('viewer_count'); + + if ($capacity > 0 && $viewers / $capacity >= 0.9) { + $alerts[] = [ + 'tone' => Status::WARN, + 'title' => 'Edge capacity is nearly full', + 'detail' => round($viewers / $capacity * 100)."% of {$capacity} slots in use.", + 'url' => Route::has('manage.servers.index') ? route('manage.servers.index') : null, + ]; + } + + $waiting = User::whereNull('server_id')->count(); + + if ($waiting > 0 && $viewers > 0) { + $alerts[] = [ + 'tone' => Status::WARN, + 'title' => "{$waiting} viewers have no server assigned", + 'detail' => 'They are waiting for an edge server to come up.', + 'url' => Route::has('manage.servers.index') ? route('manage.servers.index') : null, + ]; + } + + // Danger before warning; the order within a tone is the order found above. + usort($alerts, fn (array $a, array $b) => $this->severity($a['tone']) <=> $this->severity($b['tone'])); + + return $alerts; + } + + /** + * What is on air now and what is coming up, so the producer can see the next handover. + * + * @return array> + */ + public function schedule(int $hours = 6): array + { + $until = now()->addHours($hours); + + return Show::query() + ->with('source') + ->where(function ($query) use ($until) { + $query->where('status', 'live') + ->orWhere(fn ($q) => $q + ->where('status', 'scheduled') + ->whereBetween('scheduled_start', [now()->subMinutes(15), $until])); + }) + ->orderByRaw("case when status = 'live' then 0 else 1 end") + ->orderBy('scheduled_start') + ->get() + ->map(fn (Show $show) => [ + 'id' => $show->id, + 'title' => $show->title, + 'source' => $show->source?->name, + 'sourceStatus' => $show->source ? Status::source($show->source->status) : null, + 'status' => Status::show($show->status), + 'start' => $show->scheduled_start?->format('H:i'), + 'end' => $show->scheduled_end?->format('H:i'), + 'startsIn' => $show->status === 'scheduled' && $show->scheduled_start + ? $show->scheduled_start->diffForHumans(['short' => true]) + : null, + 'viewers' => (int) $show->viewer_count, + 'autoMode' => (bool) $show->auto_mode, + 'url' => Route::has('manage.shows.edit') ? route('manage.shows.edit', $show) : null, + ]) + ->all(); + } + + /** + * A server that has not reported in for three heartbeat intervals is treated as + * missing, matching the window `activeViewers()` uses for viewer sessions. + */ + private function isStale(Server $server): bool + { + if ($server->status !== ServerStatusEnum::ACTIVE) { + return false; + } + + return $server->last_heartbeat === null + || $server->last_heartbeat->lt(now()->subMinutes(3)); + } + + private function severity(string $tone): int + { + return match ($tone) { + Status::DANGER => 0, + Status::WARN => 1, + default => 2, + }; + } + + private function streamStatus(): StreamStatusEnum + { + return StreamStatusEnum::tryFrom( + Cache::get('stream.status', static fn () => StreamStatusEnum::OFFLINE->value) + ) ?? StreamStatusEnum::OFFLINE; + } +} diff --git a/app/Support/Manage/Settings.php b/app/Support/Manage/Settings.php new file mode 100644 index 0000000..d1ea3f5 --- /dev/null +++ b/app/Support/Manage/Settings.php @@ -0,0 +1,344 @@ +> + */ + public function groups(): array + { + return array_map(function (array $group) { + $group['fields'] = array_map(fn (array $field) => $this->field($field), $group['fields']); + + return $group; + }, config('settings.groups', [])); + } + + /** + * Validation rules for an update, keyed as the form posts them. + * + * @return array> + */ + public function rules(): array + { + $rules = []; + + foreach ($this->fields() as $field) { + $rules['values.'.$field['key']] = $field['rules'] ?? ['nullable', 'string']; + + // A repeater validates its rows too, one rule set per column. + foreach ($field['itemRules'] ?? [] as $column => $columnRules) { + $rules["values.{$field['key']}.*.{$column}"] = $columnRules; + } + } + + return $rules; + } + + /** + * Field labels, so a validation message names the control rather than the key. + * + * @return array + */ + public function attributes(): array + { + $attributes = []; + + foreach ($this->fields() as $field) { + $attributes['values.'.$field['key']] = strtolower($field['label']); + + foreach (array_keys($field['itemRules'] ?? []) as $column) { + $attributes["values.{$field['key']}.*.{$column}"] = $column; + } + } + + return $attributes; + } + + /** + * Save the posted values, ignoring anything not declared in the registry. + * + * A value equal to the shipped default deletes its row rather than writing + * one, so "use the default" really does hand the key back to + * config/branding.php instead of pinning today's default into the database. + * + * @param array $values + */ + public function save(array $values): void + { + foreach ($this->fields() as $field) { + if (! array_key_exists($field['key'], $values)) { + continue; + } + + $value = $values[$field['key']]; + $value = is_string($value) ? trim($value) : $value; + + if (($field['type'] ?? null) === 'password') { + $this->saveSecret($field, $value); + + continue; + } + + if (($field['type'] ?? null) === 'links') { + $value = $this->cleanRows($value, array_keys($field['itemRules'] ?? [])); + } + + $store = $field['store'] ?? config('settings.store', 'branding'); + + if ($this->matchesDefault($value, config("{$store}.{$field['key']}"))) { + BrandingSetting::where('key', $field['key'])->get()->each->delete(); + + continue; + } + + BrandingSetting::setValue( + $field['key'], + is_array($value) ? json_encode($value) : $value, + $field['helper'] ?? null, + ); + } + } + + /** + * A secret is write-only: blank keeps the stored value, the clear sentinel deletes + * it, and anything else replaces it. + * + * @param array $field + */ + private function saveSecret(array $field, mixed $value): void + { + // Blank, or the mask the page was given, both mean "leave the stored one alone". + if ($value === null || $value === '' || $value === self::MASK_SECRET) { + return; + } + + if ($value === self::CLEAR_SECRET) { + BrandingSetting::where('key', $field['key'])->get()->each->delete(); + + return; + } + + BrandingSetting::setValue($field['key'], $value, $field['helper'] ?? null); + } + + /** + * A cleared field counts as "back to the default", not as a stored blank. + * + * ConvertEmptyStringsToNull rewrites an emptied input to null before it ever + * reaches here, and BrandingSetting::getValue reads a null row as unset, so + * a blank row could never win over a non-empty default anyway. Deleting says + * the same thing without leaving a row that claims otherwise. + */ + private function matchesDefault(mixed $value, mixed $default): bool + { + if ($value === null || $value === '' || $value === []) { + return true; + } + + return $value === $default; + } + + /** + * Drop repeater rows that are blank or missing a column, and keep only the + * declared columns, so a hand-crafted post cannot smuggle extra keys into + * the stored JSON. Order is preserved; that is what the footer renders by. + * + * @param array $columns + * @return array> + */ + private function cleanRows(mixed $rows, array $columns): array + { + if (! is_array($rows) || $columns === []) { + return []; + } + + $clean = []; + + foreach ($rows as $row) { + if (! is_array($row)) { + continue; + } + + $values = []; + + foreach ($columns as $column) { + $value = $row[$column] ?? null; + $values[$column] = is_string($value) ? trim($value) : ''; + } + + // All or nothing: a row missing either half is an unfinished edit, + // not a link. + if (in_array('', $values, true)) { + continue; + } + + $clean[] = $values; + } + + return $clean; + } + + /** + * Drop every saved value so the config defaults apply again. Uploaded files are + * left on the disk: another installation setting may still point at them. + */ + public function reset(): void + { + // One delete per row so the model's cache-clearing hook fires for each key. + BrandingSetting::query()->get()->each->delete(); + } + + /** + * @return array> + */ + private function fields(): array + { + return collect(config('settings.groups', [])) + ->flatMap(fn (array $group) => $group['fields']) + ->all(); + } + + /** + * @param array $field + * @return array + */ + private function field(array $field): array + { + $store = $field['store'] ?? config('settings.store', 'branding'); + $default = config("{$store}.{$field['key']}"); + $value = BrandingSetting::getValue($field['key'], $default); + + // Repeaters are stored as JSON but edited as rows. + if ($field['type'] === 'links') { + $value = self::decodeRows($value); + $default = self::decodeRows($default); + } + + // A secret is never sent to the browser: a stored one is represented by the mask, + // which the save side reads back as "unchanged". + if ($field['type'] === 'password') { + $stored = is_string($value) && trim($value) !== ''; + + return [ + 'key' => $field['key'], + 'label' => $field['label'], + 'type' => 'password', + 'helper' => $field['helper'] ?? null, + 'purpose' => null, + 'full' => $field['full'] ?? false, + 'presets' => null, + 'required' => in_array('required', $field['rules'] ?? [], true), + 'value' => $stored ? self::MASK_SECRET : '', + 'default' => '', + 'hasValue' => $stored, + 'overridden' => $stored, + 'previewUrl' => null, + ]; + } + + return [ + 'key' => $field['key'], + 'label' => $field['label'], + 'type' => $field['type'], + 'helper' => $field['helper'] ?? null, + 'purpose' => $field['purpose'] ?? null, + 'full' => $field['full'] ?? false, + // Colour fields may offer a swatch row; hex => label, in order. + 'presets' => $this->presets($field), + 'required' => in_array('required', $field['rules'] ?? [], true), + 'value' => $value, + 'default' => $default, + // Whether this key is currently overriding the shipped default. + 'overridden' => $value !== $default, + 'previewUrl' => in_array($field['type'], ['image', 'video'], true) + ? $this->assetUrl($value) + : null, + ]; + } + + /** + * A stored repeater value as rows. Accepts the JSON string the table holds + * and an already-decoded array (the config default), and answers with an + * empty list for anything unparseable, so one bad row can never break the + * settings page or the footer. + * + * @return array> + */ + public static function decodeRows(mixed $value): array + { + if (is_array($value)) { + return array_values($value); + } + + if (! is_string($value) || trim($value) === '') { + return []; + } + + $decoded = json_decode($value, true); + + return is_array($decoded) ? array_values($decoded) : []; + } + + /** + * Preset swatches as a list, so the order survives the trip to the frontend. + * + * @param array $field + * @return array|null + */ + private function presets(array $field): ?array + { + if (empty($field['presets'])) { + return null; + } + + return \App\Support\ColorPresets::forFrontend(); + } + + /** + * Same resolution BrandingService uses: absolute URLs and rooted paths pass + * through, anything else is a path on the public disk. + */ + private function assetUrl(mixed $path): ?string + { + $path = is_string($path) ? trim($path) : ''; + + if ($path === '') { + return null; + } + + if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://') || str_starts_with($path, '/')) { + return $path; + } + + return Storage::disk('public')->url($path); + } +} diff --git a/app/Support/Manage/Status.php b/app/Support/Manage/Status.php new file mode 100644 index 0000000..e6f4c09 --- /dev/null +++ b/app/Support/Manage/Status.php @@ -0,0 +1,148 @@ + $label, 'tone' => $tone, 'icon' => $icon]; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function show(?string $status): array + { + return match ($status) { + 'live' => self::make('Live', self::LIVE, 'signal'), + 'scheduled' => self::make('Scheduled', self::WARN, 'clock'), + 'ended' => self::make('Ended', self::IDLE, 'circle-check'), + 'cancelled' => self::make('Cancelled', self::DANGER, 'circle-x'), + default => self::make((string) $status, self::IDLE, null), + }; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function source(SourceStatusEnum|string|null $status): array + { + $value = $status instanceof SourceStatusEnum ? $status->value : $status; + + return match ($value) { + SourceStatusEnum::ONLINE->value => self::make('Online', self::LIVE, 'signal'), + SourceStatusEnum::OFFLINE->value => self::make('Offline', self::IDLE, 'signal-zero'), + SourceStatusEnum::ERROR->value => self::make('Error', self::DANGER, 'triangle-alert'), + default => self::make((string) $value, self::IDLE, null), + }; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function server(ServerStatusEnum|string|null $status): array + { + $value = $status instanceof ServerStatusEnum ? $status->value : $status; + + return match ($value) { + ServerStatusEnum::ACTIVE->value => self::make('Active', self::OK, 'circle-check'), + ServerStatusEnum::PROVISIONING->value => self::make('Provisioning', self::WARN, 'loader'), + ServerStatusEnum::DEPROVISIONING->value => self::make('Deprovisioning', self::DANGER, 'loader'), + ServerStatusEnum::DELETED->value => self::make('Deleted', self::IDLE, 'circle-x'), + ServerStatusEnum::ERROR->value => self::make('Error', self::DANGER, 'triangle-alert'), + default => self::make((string) $value, self::IDLE, null), + }; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function serverType(ServerTypeEnum|string|null $type): array + { + $value = $type instanceof ServerTypeEnum ? $type->value : $type; + + return match ($value) { + ServerTypeEnum::ORIGIN->value => self::make('Origin', self::WARN, 'radio-tower'), + ServerTypeEnum::EDGE->value => self::make('Edge', self::OK, 'server'), + default => self::make((string) $value, self::IDLE, null), + }; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function health(?string $health): array + { + return match ($health) { + 'healthy' => self::make('Healthy', self::OK, 'heart-pulse'), + 'unhealthy' => self::make('Unhealthy', self::DANGER, 'heart-crack'), + default => self::make('Unknown', self::IDLE, 'circle-help'), + }; + } + + /** + * @return array{label: string, tone: string, icon: string|null} + */ + public static function stream(StreamStatusEnum|string|null $status): array + { + $value = $status instanceof StreamStatusEnum ? $status->value : $status; + + return match ($value) { + StreamStatusEnum::ONLINE->value => self::make('Online', self::LIVE, 'signal'), + StreamStatusEnum::STARTING_SOON->value => self::make('Starting soon', self::WARN, 'clock'), + StreamStatusEnum::PROVISIONING->value => self::make('Provisioning', self::WARN, 'loader'), + StreamStatusEnum::TECHNICAL_ISSUE->value => self::make('Technical issue', self::DANGER, 'triangle-alert'), + StreamStatusEnum::OFFLINE->value => self::make('Offline', self::IDLE, 'signal-zero'), + default => self::make((string) $value, self::IDLE, null), + }; + } + + /** + * Two-state badge, e.g. Auto/Manual or Restricted/Public. + * + * @return array{label: string, tone: string, icon: string|null} + */ + public static function toggle( + bool $state, + string $trueLabel, + string $falseLabel, + string $trueTone = self::OK, + string $falseTone = self::IDLE, + ?string $trueIcon = null, + ?string $falseIcon = null, + ): array { + return $state + ? self::make($trueLabel, $trueTone, $trueIcon) + : self::make($falseLabel, $falseTone, $falseIcon); + } +} diff --git a/app/Support/Manage/Table.php b/app/Support/Manage/Table.php new file mode 100644 index 0000000..1a5509e --- /dev/null +++ b/app/Support/Manage/Table.php @@ -0,0 +1,324 @@ + */ + private array $columns = []; + + /** @var array */ + private array $filters = []; + + private ?string $defaultSortKey = null; + + private string $defaultSortDir = 'asc'; + + private ?Closure $rowsUsing = null; + + private ?Closure $recordUrlUsing = null; + + private ?Closure $rowActionsUsing = null; + + /** @var array */ + private array $bulkActions = []; + + /** @var array */ + private array $pageActions = []; + + private int $perPage = 25; + + /** @var array */ + private array $perPageOptions = [10, 25, 50, 100]; + + private function __construct(private readonly Builder $query) {} + + public static function make(Builder $query): self + { + return new self($query); + } + + /** + * Identifies the table for per-user column-visibility persistence. + */ + public function name(string $name): self + { + $this->name = $name; + + return $this; + } + + /** + * @param array $columns + */ + public function columns(array $columns): self + { + $this->columns = $columns; + + return $this; + } + + /** + * @param array $filters + */ + public function filters(array $filters): self + { + $this->filters = $filters; + + return $this; + } + + public function defaultSort(string $key, string $dir = 'asc'): self + { + $this->defaultSortKey = $key; + $this->defaultSortDir = $dir; + + return $this; + } + + /** + * Maps a record to its cell values, keyed by column key. + * + * @param Closure(Model): array $callback + */ + public function rows(Closure $callback): self + { + $this->rowsUsing = $callback; + + return $this; + } + + /** + * Where clicking the row navigates. + * + * @param Closure(Model): ?string $callback + */ + public function recordUrl(Closure $callback): self + { + $this->recordUrlUsing = $callback; + + return $this; + } + + /** + * @param Closure(Model): array $callback + */ + public function rowActions(Closure $callback): self + { + $this->rowActionsUsing = $callback; + + return $this; + } + + /** + * @param array $actions + */ + public function bulkActions(array $actions): self + { + $this->bulkActions = $actions; + + return $this; + } + + /** + * @param array $actions + */ + public function pageActions(array $actions): self + { + $this->pageActions = $actions; + + return $this; + } + + public function perPage(int $perPage): self + { + $this->perPage = $perPage; + + return $this; + } + + /** + * @return array + */ + public function toArray(Request $request): array + { + $search = trim((string) $request->input('search', '')); + $filterValues = $this->resolveFilterValues($request); + + $this->applyFilters($filterValues); + $this->applySearch($search); + $sort = $this->applySort($request); + + $perPage = $this->resolvePerPage($request); + $paginator = $this->query->paginate($perPage)->withQueryString(); + + return [ + 'name' => $this->name, + 'rows' => collect($paginator->items())->map(fn (Model $record) => [ + 'id' => $record->getKey(), + 'url' => $this->recordUrlUsing ? ($this->recordUrlUsing)($record) : null, + 'cells' => $this->rowsUsing ? ($this->rowsUsing)($record) : $record->attributesToArray(), + 'actions' => $this->rowActionsUsing + ? array_map(fn (Action $action) => $action->toArray(), array_values(($this->rowActionsUsing)($record))) + : [], + ])->all(), + 'columns' => array_map(fn (Column $column) => $column->toArray(), $this->columns), + 'hiddenColumns' => $this->hiddenColumns(), + 'filters' => array_map( + fn (Filter $filter) => $filter->toArray() + ['value' => $filterValues[$filter->key]], + $this->filters, + ), + 'sort' => $sort, + 'search' => $search, + 'meta' => [ + 'page' => $paginator->currentPage(), + 'perPage' => $paginator->perPage(), + 'perPageOptions' => $this->perPageOptions, + 'total' => $paginator->total(), + 'lastPage' => $paginator->lastPage(), + 'from' => $paginator->firstItem(), + 'to' => $paginator->lastItem(), + ], + 'bulkActions' => array_map(fn (Action $action) => $action->toArray(), $this->bulkActions), + 'pageActions' => array_map(fn (Action $action) => $action->toArray(), $this->pageActions), + ]; + } + + /** + * Column keys the current user has hidden, defaulting to the declared hidden set. + * + * @return array + */ + private function hiddenColumns(): array + { + $stored = session("manage.table.{$this->name}.hidden"); + + if (is_array($stored)) { + return array_values($stored); + } + + return collect($this->columns) + ->filter(fn (Column $column) => $column->isHiddenByDefault()) + ->map(fn (Column $column) => $column->key) + ->values() + ->all(); + } + + /** + * A filter absent from the request falls back to its declared default, which is how + * "hide ended shows" stays on until the operator explicitly turns it off. + * + * @return array + */ + private function resolveFilterValues(Request $request): array + { + $values = []; + + foreach ($this->filters as $filter) { + $raw = $request->input("filter.{$filter->key}"); + + $values[$filter->key] = $raw === null + ? $filter->defaultValue() + : $filter->normalize($raw); + } + + return $values; + } + + /** + * @param array $values + */ + private function applyFilters(array $values): void + { + foreach ($this->filters as $filter) { + $value = $values[$filter->key]; + + if ($filter->isActive($value)) { + $filter->applyTo($this->query, $value); + } + } + } + + private function applySearch(string $search): void + { + if ($search === '') { + return; + } + + $searchable = array_filter($this->columns, fn (Column $column) => $column->isSearchable()); + + if ($searchable === []) { + return; + } + + $operator = $this->query->getConnection()->getDriverName() === 'pgsql' ? 'ilike' : 'like'; + $term = '%'.$search.'%'; + + $this->query->where(function (Builder $query) use ($searchable, $operator, $term) { + foreach ($searchable as $column) { + $key = $column->resolvedSearchKey(); + + if (str_contains($key, '.')) { + [$relation, $attribute] = explode('.', $key, 2); + $query->orWhereHas($relation, fn (Builder $q) => $q->where($attribute, $operator, $term)); + + continue; + } + + $query->orWhere($query->qualifyColumn($key), $operator, $term); + } + }); + } + + /** + * @return array{key: string|null, dir: string} + */ + private function applySort(Request $request): array + { + $requestedKey = $request->input('sort'); + $dir = strtolower((string) $request->input('dir', $this->defaultSortDir)) === 'desc' ? 'desc' : 'asc'; + + $column = collect($this->columns)->first( + fn (Column $column) => $column->isSortable() && $column->key === $requestedKey + ); + + if (! $column) { + if ($this->defaultSortKey) { + $this->query->orderBy($this->defaultSortKey, $this->defaultSortDir); + } + + return ['key' => $this->defaultSortKey, 'dir' => $this->defaultSortDir]; + } + + if ($callback = $column->sortCallback()) { + $callback($this->query, $dir); + } else { + $this->query->orderBy($column->resolvedSortKey(), $dir); + } + + return ['key' => $column->key, 'dir' => $dir]; + } + + private function resolvePerPage(Request $request): int + { + $requested = (int) $request->input('per_page', $this->perPage); + + return in_array($requested, $this->perPageOptions, true) ? $requested : $this->perPage; + } +} diff --git a/app/Support/Manage/Toast.php b/app/Support/Manage/Toast.php new file mode 100644 index 0000000..d5573a5 --- /dev/null +++ b/app/Support/Manage/Toast.php @@ -0,0 +1,95 @@ + $tone, 'title' => $title, 'body' => $body]; + } + + /** + * @return array{tone: string, title: string, body: string|null} + */ + public static function success(string $title, ?string $body = null): array + { + return self::make('success', $title, $body); + } + + /** + * @return array{tone: string, title: string, body: string|null} + */ + public static function warning(string $title, ?string $body = null): array + { + return self::make('warning', $title, $body); + } + + /** + * @return array{tone: string, title: string, body: string|null} + */ + public static function danger(string $title, ?string $body = null): array + { + return self::make('danger', $title, $body); + } + + /** + * @param array{tone: string, title: string, body: string|null} $toast + */ + public static function flash(array $toast): void + { + self::put('toast', $toast); + } + + /** + * Writes into Inertia's own flash bag, so the payload arrives as the top-level `flash` + * prop and stays out of the browser's history state (a back navigation must not replay + * a toast). + * + * It is written for the *next* request rather than through Inertia::flash(), which uses + * session()->now(). On inertia-laravel 2.0.19 a now() value cannot survive the redirect + * an action performs: the key sits in `_flash.old`, and ageFlashData() forgets old keys + * on save before the middleware's re-flash can take effect. Since every manage action + * redirects, flashing forward is both correct and simpler. + * + * Revisit when the package flashes forward itself. + */ + public static function put(string $key, mixed $value): void + { + session()->flash(SessionKey::FlashData->value, [ + ...Inertia::getFlashed(), + $key => $value, + ]); + } + + public static function flashSuccess(string $title, ?string $body = null): void + { + self::flash(self::success($title, $body)); + } + + public static function flashWarning(string $title, ?string $body = null): void + { + self::flash(self::warning($title, $body)); + } + + public static function flashDanger(string $title, ?string $body = null): void + { + self::flash(self::danger($title, $body)); + } +} diff --git a/app/Support/Markdown.php b/app/Support/Markdown.php new file mode 100644 index 0000000..1563bcd --- /dev/null +++ b/app/Support/Markdown.php @@ -0,0 +1,42 @@ + + */ + private const OPTIONS = [ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + // A con abstract is written as prose with single newlines; without this every + // line break would collapse into one wall of text. + 'renderer' => ['soft_break' => "
\n"], + // Deeply nested quoting is not something an abstract needs, and it is the usual + // way to make the parser do too much work. + 'max_nesting_level' => 10, + ]; + + public static function render(?string $text): ?string + { + $text = is_string($text) ? trim($text) : ''; + + if ($text === '') { + return null; + } + + return trim(Str::markdown($text, self::OPTIONS)); + } +} diff --git a/app/Support/PlaybackToken.php b/app/Support/PlaybackToken.php new file mode 100644 index 0000000..4bb0fdc --- /dev/null +++ b/app/Support/PlaybackToken.php @@ -0,0 +1,144 @@ +expiresAt === null) { + return null; + } + + return $this->expiresAt - ($now ?? time()); + } + + /** + * Leeway absorbs clock drift between the app and an edge, and a refresh that + * lands a little late. Edges apply the same window. + */ + public function isExpired(int $leeway = 0, ?int $now = null): bool + { + if ($this->expiresAt === null) { + return false; + } + + return ($now ?? time()) > $this->expiresAt + $leeway; + } + + public function isViewer(): bool + { + return $this->type === PlaybackTokenTypeEnum::VIEWER; + } + + public function isEmbed(): bool + { + return $this->type === PlaybackTokenTypeEnum::EMBED; + } + + /** + * Wire format. Null claims are dropped so the encoded token stays short + * enough to sit comfortably in a query string. + * + * @return array + */ + public function claims(): array + { + return array_filter([ + 'typ' => $this->type->value, + 'src' => $this->source, + 'sub' => $this->subject, + 'kid' => $this->keyId, + 'edge' => $this->edge, + 'sid' => $this->sessionId, + 'exp' => $this->expiresAt, + ], static fn ($value) => $value !== null); + } + + /** + * @param array $claims + * + * @throws InvalidPlaybackTokenException + */ + public static function fromClaims(array $claims): self + { + $type = PlaybackTokenTypeEnum::tryFrom((string) ($claims['typ'] ?? '')); + + if ($type === null) { + throw InvalidPlaybackTokenException::malformed('unknown token type'); + } + + $source = $claims['src'] ?? null; + + if (! is_string($source) || $source === '') { + throw InvalidPlaybackTokenException::malformed('missing source binding'); + } + + $expiresAt = $claims['exp'] ?? null; + + if ($expiresAt !== null && ! is_int($expiresAt)) { + throw InvalidPlaybackTokenException::malformed('expiry is not an integer'); + } + + if ($expiresAt === null && $type->requiresExpiry()) { + throw InvalidPlaybackTokenException::missingExpiry(); + } + + return new self( + type: $type, + source: $source, + subject: self::optionalString($claims, 'sub'), + keyId: self::optionalString($claims, 'kid'), + edge: self::optionalString($claims, 'edge'), + sessionId: self::optionalString($claims, 'sid'), + expiresAt: $expiresAt, + ); + } + + /** + * @param array $claims + */ + private static function optionalString(array $claims, string $key): ?string + { + $value = $claims[$key] ?? null; + + if ($value === null) { + return null; + } + + if (! is_string($value) && ! is_int($value)) { + throw InvalidPlaybackTokenException::malformed("claim [{$key}] is not a scalar"); + } + + return (string) $value; + } +} diff --git a/composer.json b/composer.json index 7c0d393..d26e79b 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,6 @@ "require": { "php": "^8.2", "doctrine/dbal": "^4.3", - "filament/filament": "^3.3", "flowframe/laravel-trend": "^0.4.0", "guzzlehttp/guzzle": "^7.9", "inertiajs/inertia-laravel": "^2.0", @@ -26,10 +25,8 @@ }, "require-dev": { "fakerphp/faker": "^1.24", - "filament/upgrade": "^3.3", "laravel/breeze": "^2.3", "laravel/pint": "^1.21", - "laravel/sail": "^1.43", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.6", "phpunit/phpunit": "^11.5", @@ -53,8 +50,7 @@ "@php artisan package:discover --ansi" ], "post-update-cmd": [ - "@php artisan vendor:publish --tag=laravel-assets --ansi --force", - "@php artisan filament:upgrade" + "@php artisan vendor:publish --tag=laravel-assets --ansi --force" ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" diff --git a/composer.lock b/composer.lock index 1a1093b..49d762d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,74 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9bbc40509a97d767fe99241d6fbb15e4", + "content-hash": "94773b25986d18e8cf049476b92c4303", "packages": [ - { - "name": "anourvalar/eloquent-serialize", - "version": "1.3.5", - "source": { - "type": "git", - "url": "https://github.com/AnourValar/eloquent-serialize.git", - "reference": "1a7dead8d532657e5358f8f27c0349373517681e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/1a7dead8d532657e5358f8f27c0349373517681e", - "reference": "1a7dead8d532657e5358f8f27c0349373517681e", - "shasum": "" - }, - "require": { - "laravel/framework": "^8.0|^9.0|^10.0|^11.0|^12.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.26", - "laravel/legacy-factories": "^1.1", - "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^9.5|^10.5|^11.0", - "psalm/plugin-laravel": "^2.8|^3.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "EloquentSerialize": "AnourValar\\EloquentSerialize\\Facades\\EloquentSerializeFacade" - } - } - }, - "autoload": { - "psr-4": { - "AnourValar\\EloquentSerialize\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Laravel Query Builder (Eloquent) serialization", - "homepage": "https://github.com/AnourValar/eloquent-serialize", - "keywords": [ - "anourvalar", - "builder", - "copy", - "eloquent", - "job", - "laravel", - "query", - "querybuilder", - "queue", - "serializable", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/AnourValar/eloquent-serialize/issues", - "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.3.5" - }, - "time": "2025-12-04T13:38:21+00:00" - }, { "name": "aws/aws-crt-php", "version": "v1.2.7", @@ -223,156 +157,6 @@ }, "time": "2026-01-23T19:05:51+00:00" }, - { - "name": "blade-ui-kit/blade-heroicons", - "version": "2.6.0", - "source": { - "type": "git", - "url": "https://github.com/driesvints/blade-heroicons.git", - "reference": "4553b2a1f6c76f0ac7f3bc0de4c0cfa06a097d19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/driesvints/blade-heroicons/zipball/4553b2a1f6c76f0ac7f3bc0de4c0cfa06a097d19", - "reference": "4553b2a1f6c76f0ac7f3bc0de4c0cfa06a097d19", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-icons": "^1.6", - "illuminate/support": "^9.0|^10.0|^11.0|^12.0", - "php": "^8.0" - }, - "require-dev": { - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpunit/phpunit": "^9.0|^10.5|^11.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BladeUI\\Heroicons\\BladeHeroiconsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "BladeUI\\Heroicons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "homepage": "https://driesvints.com" - } - ], - "description": "A package to easily make use of Heroicons in your Laravel Blade views.", - "homepage": "https://github.com/blade-ui-kit/blade-heroicons", - "keywords": [ - "Heroicons", - "blade", - "laravel" - ], - "support": { - "issues": "https://github.com/driesvints/blade-heroicons/issues", - "source": "https://github.com/driesvints/blade-heroicons/tree/2.6.0" - }, - "funding": [ - { - "url": "https://github.com/sponsors/driesvints", - "type": "github" - }, - { - "url": "https://www.paypal.com/paypalme/driesvints", - "type": "paypal" - } - ], - "time": "2025-02-13T20:53:33+00:00" - }, - { - "name": "blade-ui-kit/blade-icons", - "version": "1.8.1", - "source": { - "type": "git", - "url": "https://github.com/driesvints/blade-icons.git", - "reference": "47e7b6f43250e6404e4224db8229219cd42b543c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/driesvints/blade-icons/zipball/47e7b6f43250e6404e4224db8229219cd42b543c", - "reference": "47e7b6f43250e6404e4224db8229219cd42b543c", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/view": "^8.0|^9.0|^10.0|^11.0|^12.0", - "php": "^7.4|^8.0", - "symfony/console": "^5.3|^6.0|^7.0", - "symfony/finder": "^5.3|^6.0|^7.0" - }, - "require-dev": { - "mockery/mockery": "^1.5.1", - "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", - "phpunit/phpunit": "^9.0|^10.5|^11.0" - }, - "bin": [ - "bin/blade-icons-generate" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BladeUI\\Icons\\BladeIconsServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "BladeUI\\Icons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "homepage": "https://driesvints.com" - } - ], - "description": "A package to easily make use of icons in your Laravel Blade views.", - "homepage": "https://github.com/driesvints/blade-icons", - "keywords": [ - "blade", - "icons", - "laravel", - "svg" - ], - "support": { - "issues": "https://github.com/driesvints/blade-icons/issues", - "source": "https://github.com/driesvints/blade-icons" - }, - "funding": [ - { - "url": "https://github.com/sponsors/driesvints", - "type": "github" - }, - { - "url": "https://www.paypal.com/paypalme/driesvints", - "type": "paypal" - } - ], - "time": "2026-01-20T09:46:32+00:00" - }, { "name": "brick/math", "version": "0.14.1", @@ -632,111 +416,6 @@ ], "time": "2025-01-03T16:18:33+00:00" }, - { - "name": "danharrin/date-format-converter", - "version": "v0.3.1", - "source": { - "type": "git", - "url": "https://github.com/danharrin/date-format-converter.git", - "reference": "7c31171bc981e48726729a5f3a05a2d2b63f0b1e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/danharrin/date-format-converter/zipball/7c31171bc981e48726729a5f3a05a2d2b63f0b1e", - "reference": "7c31171bc981e48726729a5f3a05a2d2b63f0b1e", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/helpers.php", - "src/standards.php" - ], - "psr-4": { - "DanHarrin\\DateFormatConverter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dan Harrin", - "email": "dan@danharrin.com" - } - ], - "description": "Convert token-based date formats between standards.", - "homepage": "https://github.com/danharrin/date-format-converter", - "support": { - "issues": "https://github.com/danharrin/date-format-converter/issues", - "source": "https://github.com/danharrin/date-format-converter" - }, - "funding": [ - { - "url": "https://github.com/danharrin", - "type": "github" - } - ], - "time": "2024-06-13T09:38:44+00:00" - }, - { - "name": "danharrin/livewire-rate-limiting", - "version": "v2.1.0", - "source": { - "type": "git", - "url": "https://github.com/danharrin/livewire-rate-limiting.git", - "reference": "14dde653a9ae8f38af07a0ba4921dc046235e1a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/14dde653a9ae8f38af07a0ba4921dc046235e1a0", - "reference": "14dde653a9ae8f38af07a0ba4921dc046235e1a0", - "shasum": "" - }, - "require": { - "illuminate/support": "^9.0|^10.0|^11.0|^12.0", - "php": "^8.0" - }, - "require-dev": { - "livewire/livewire": "^3.0", - "livewire/volt": "^1.3", - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpunit/phpunit": "^9.0|^10.0|^11.5.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "DanHarrin\\LivewireRateLimiting\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dan Harrin", - "email": "dan@danharrin.com" - } - ], - "description": "Apply rate limiters to Laravel Livewire actions.", - "homepage": "https://github.com/danharrin/livewire-rate-limiting", - "support": { - "issues": "https://github.com/danharrin/livewire-rate-limiting/issues", - "source": "https://github.com/danharrin/livewire-rate-limiting" - }, - "funding": [ - { - "url": "https://github.com/danharrin", - "type": "github" - } - ], - "time": "2025-02-21T08:52:11+00:00" - }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -1216,532 +895,100 @@ "php": ">=8.1", "symfony/polyfill-intl-idn": "^1.26" }, - "require-dev": { - "phpunit/phpunit": "^10.2", - "vimeo/psalm": "^5.12" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2025-03-06T22:45:56+00:00" - }, - { - "name": "evenement/evenement", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Evenement\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" - }, - "time": "2023-08-08T05:53:35+00:00" - }, - { - "name": "filament/actions", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/actions.git", - "reference": "f8ea2b015b12c00522f1d6a7bcb9453b5f08beb1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/f8ea2b015b12c00522f1d6a7bcb9453b5f08beb1", - "reference": "f8ea2b015b12c00522f1d6a7bcb9453b5f08beb1", - "shasum": "" - }, - "require": { - "anourvalar/eloquent-serialize": "^1.2", - "filament/forms": "self.version", - "filament/infolists": "self.version", - "filament/notifications": "self.version", - "filament/support": "self.version", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/database": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "league/csv": "^9.16", - "openspout/openspout": "^4.23", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Actions\\ActionsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Filament\\Actions\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Easily add beautiful action modals to any Livewire component.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-01T16:29:27+00:00" - }, - { - "name": "filament/filament", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/panels.git", - "reference": "790e3c163e93f5746beea88b93d38673424984b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/790e3c163e93f5746beea88b93d38673424984b6", - "reference": "790e3c163e93f5746beea88b93d38673424984b6", - "shasum": "" - }, - "require": { - "danharrin/livewire-rate-limiting": "^0.3|^1.0|^2.0", - "filament/actions": "self.version", - "filament/forms": "self.version", - "filament/infolists": "self.version", - "filament/notifications": "self.version", - "filament/support": "self.version", - "filament/tables": "self.version", - "filament/widgets": "self.version", - "illuminate/auth": "^10.45|^11.0|^12.0", - "illuminate/console": "^10.45|^11.0|^12.0", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/cookie": "^10.45|^11.0|^12.0", - "illuminate/database": "^10.45|^11.0|^12.0", - "illuminate/http": "^10.45|^11.0|^12.0", - "illuminate/routing": "^10.45|^11.0|^12.0", - "illuminate/session": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "illuminate/view": "^10.45|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\FilamentServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/global_helpers.php", - "src/helpers.php" - ], - "psr-4": { - "Filament\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A collection of full-stack components for accelerated Laravel app development.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-01T16:29:34+00:00" - }, - { - "name": "filament/forms", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/forms.git", - "reference": "f708ce490cff3770071d18e9ea678eb4b7c65c58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/f708ce490cff3770071d18e9ea678eb4b7c65c58", - "reference": "f708ce490cff3770071d18e9ea678eb4b7c65c58", - "shasum": "" - }, - "require": { - "danharrin/date-format-converter": "^0.3", - "filament/actions": "self.version", - "filament/support": "self.version", - "illuminate/console": "^10.45|^11.0|^12.0", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/database": "^10.45|^11.0|^12.0", - "illuminate/filesystem": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "illuminate/validation": "^10.45|^11.0|^12.0", - "illuminate/view": "^10.45|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Forms\\FormsServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Filament\\Forms\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Easily add beautiful forms to any Livewire component.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-01T16:29:33+00:00" - }, - { - "name": "filament/infolists", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/infolists.git", - "reference": "ac7fc1c8acc651c6c793696f0772747791c91155" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/infolists/zipball/ac7fc1c8acc651c6c793696f0772747791c91155", - "reference": "ac7fc1c8acc651c6c793696f0772747791c91155", - "shasum": "" - }, - "require": { - "filament/actions": "self.version", - "filament/support": "self.version", - "illuminate/console": "^10.45|^11.0|^12.0", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/database": "^10.45|^11.0|^12.0", - "illuminate/filesystem": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "illuminate/view": "^10.45|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Infolists\\InfolistsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Filament\\Infolists\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Easily add beautiful read-only infolists to any Livewire component.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-01T16:28:31+00:00" - }, - { - "name": "filament/notifications", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/notifications.git", - "reference": "3a6ef54b6a8cefc79858e7033e4d6b65fb2d859b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/notifications/zipball/3a6ef54b6a8cefc79858e7033e4d6b65fb2d859b", - "reference": "3a6ef54b6a8cefc79858e7033e4d6b65fb2d859b", - "shasum": "" - }, - "require": { - "filament/actions": "self.version", - "filament/support": "self.version", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/filesystem": "^10.45|^11.0|^12.0", - "illuminate/notifications": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Notifications\\NotificationsServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/Testing/Autoload.php" - ], - "psr-4": { - "Filament\\Notifications\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Easily add beautiful notifications to any Livewire app.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-01T16:29:16+00:00" - }, - { - "name": "filament/support", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/support.git", - "reference": "c37f4b9045a7c514974e12562b5a41813860b505" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/c37f4b9045a7c514974e12562b5a41813860b505", - "reference": "c37f4b9045a7c514974e12562b5a41813860b505", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-heroicons": "^2.5", - "doctrine/dbal": "^3.2|^4.0", - "ext-intl": "*", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "illuminate/view": "^10.45|^11.0|^12.0", - "kirschbaum-development/eloquent-power-joins": "^3.0|^4.0", - "livewire/livewire": "^3.5", - "php": "^8.1", - "ryangjchandler/blade-capture-directive": "^0.2|^0.3|^1.0", - "spatie/color": "^1.5", - "spatie/invade": "^1.0|^2.0", - "spatie/laravel-package-tools": "^1.9", - "symfony/console": "^6.0|^7.0", - "symfony/html-sanitizer": "^6.1|^7.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Support\\SupportServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Filament\\Support\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Core helper methods and foundation code for all Filament packages.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2026-01-09T09:01:14+00:00" - }, - { - "name": "filament/tables", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/tables.git", - "reference": "c88d17248827b3fbca09db53d563498d29c6b180" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/c88d17248827b3fbca09db53d563498d29c6b180", - "reference": "c88d17248827b3fbca09db53d563498d29c6b180", - "shasum": "" - }, - "require": { - "filament/actions": "self.version", - "filament/forms": "self.version", - "filament/support": "self.version", - "illuminate/console": "^10.45|^11.0|^12.0", - "illuminate/contracts": "^10.45|^11.0|^12.0", - "illuminate/database": "^10.45|^11.0|^12.0", - "illuminate/filesystem": "^10.45|^11.0|^12.0", - "illuminate/support": "^10.45|^11.0|^12.0", - "illuminate/view": "^10.45|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" }, "type": "library", "extra": { - "laravel": { - "providers": [ - "Filament\\Tables\\TablesServiceProvider" - ] + "branch-alias": { + "dev-master": "4.0.x-dev" } }, "autoload": { "psr-4": { - "Filament\\Tables\\": "src" + "Egulias\\EmailValidator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Easily add beautiful tables to any Livewire component.", - "homepage": "https://github.com/filamentphp/filament", + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, - "time": "2026-01-01T16:29:37+00:00" + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" }, { - "name": "filament/widgets", - "version": "v3.3.47", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/filamentphp/widgets.git", - "reference": "2bf59fd94007b69c22c161f7a4749ea19560e03e" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/widgets/zipball/2bf59fd94007b69c22c161f7a4749ea19560e03e", - "reference": "2bf59fd94007b69c22c161f7a4749ea19560e03e", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "filament/support": "self.version", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9" + "php": ">=7.0" }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Widgets\\WidgetsServiceProvider" - ] - } + "require-dev": { + "phpunit/phpunit": "^9 || ^6" }, + "type": "library", "autoload": { "psr-4": { - "Filament\\Widgets\\": "src" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Easily add beautiful dashboard widgets to any Livewire component.", - "homepage": "https://github.com/filamentphp/filament", + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2026-01-01T16:29:32+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "flowframe/laravel-trend", @@ -2491,69 +1738,6 @@ }, "time": "2025-03-19T14:43:43+00:00" }, - { - "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.2.11", - "source": { - "type": "git", - "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "0e3e3372992e4bf82391b3c7b84b435c3db73588" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/0e3e3372992e4bf82391b3c7b84b435c3db73588", - "reference": "0e3e3372992e4bf82391b3c7b84b435c3db73588", - "shasum": "" - }, - "require": { - "illuminate/database": "^11.42|^12.0", - "illuminate/support": "^11.42|^12.0", - "php": "^8.2" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "dev-master", - "laravel/legacy-factories": "^1.0@dev", - "orchestra/testbench": "^9.0|^10.0", - "phpunit/phpunit": "^10.0|^11.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Kirschbaum\\PowerJoins\\PowerJoinsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Kirschbaum\\PowerJoins\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Luis Dalmolin", - "email": "luis.nh@gmail.com", - "role": "Developer" - } - ], - "description": "The Laravel magic applied to joins.", - "homepage": "https://github.com/kirschbaum-development/eloquent-power-joins", - "keywords": [ - "eloquent", - "join", - "laravel", - "mysql" - ], - "support": { - "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.11" - }, - "time": "2025-12-17T00:37:48+00:00" - }, { "name": "laminas/laminas-diactoros", "version": "3.8.0", @@ -3553,97 +2737,6 @@ ], "time": "2022-12-11T20:36:23+00:00" }, - { - "name": "league/csv", - "version": "9.28.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/csv.git", - "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/6582ace29ae09ba5b07049d40ea13eb19c8b5073", - "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^8.1.2" - }, - "require-dev": { - "ext-dom": "*", - "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.92.3", - "phpbench/phpbench": "^1.4.3", - "phpstan/phpstan": "^1.12.32", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.2", - "phpstan/phpstan-strict-rules": "^1.6.2", - "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.5.4", - "symfony/var-dumper": "^6.4.8 || ^7.4.0 || ^8.0" - }, - "suggest": { - "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", - "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters", - "ext-mbstring": "Needed to ease transcoding CSV using mb stream filters", - "ext-mysqli": "Requiered to use the package with the MySQLi extension", - "ext-pdo": "Required to use the package with the PDO extension", - "ext-pgsql": "Requiered to use the package with the PgSQL extension", - "ext-sqlite3": "Required to use the package with the SQLite3 extension" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "League\\Csv\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ignace Nyamagana Butera", - "email": "nyamsprod@gmail.com", - "homepage": "https://github.com/nyamsprod/", - "role": "Developer" - } - ], - "description": "CSV data manipulation made easy in PHP", - "homepage": "https://csv.thephpleague.com", - "keywords": [ - "convert", - "csv", - "export", - "filter", - "import", - "read", - "transform", - "write" - ], - "support": { - "docs": "https://csv.thephpleague.com", - "issues": "https://github.com/thephpleague/csv/issues", - "rss": "https://github.com/thephpleague/csv/releases.atom", - "source": "https://github.com/thephpleague/csv" - }, - "funding": [ - { - "url": "https://github.com/sponsors/nyamsprod", - "type": "github" - } - ], - "time": "2025-12-27T15:18:42+00:00" - }, { "name": "league/flysystem", "version": "3.31.0", @@ -4134,82 +3227,6 @@ ], "time": "2026-01-15T06:54:53+00:00" }, - { - "name": "livewire/livewire", - "version": "v3.7.6", - "source": { - "type": "git", - "url": "https://github.com/livewire/livewire.git", - "reference": "276ac156f6ae414990784854a2673e3d23c68b24" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/276ac156f6ae414990784854a2673e3d23c68b24", - "reference": "276ac156f6ae414990784854a2673e3d23c68b24", - "shasum": "" - }, - "require": { - "illuminate/database": "^10.0|^11.0|^12.0", - "illuminate/routing": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "illuminate/validation": "^10.0|^11.0|^12.0", - "laravel/prompts": "^0.1.24|^0.2|^0.3", - "league/mime-type-detection": "^1.9", - "php": "^8.1", - "symfony/console": "^6.0|^7.0", - "symfony/http-kernel": "^6.2|^7.0" - }, - "require-dev": { - "calebporzio/sushi": "^2.1", - "laravel/framework": "^10.15.0|^11.0|^12.0", - "mockery/mockery": "^1.3.1", - "orchestra/testbench": "^8.21.0|^9.0|^10.0", - "orchestra/testbench-dusk": "^8.24|^9.1|^10.0", - "phpunit/phpunit": "^10.4|^11.5", - "psy/psysh": "^0.11.22|^0.12" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Livewire": "Livewire\\Livewire" - }, - "providers": [ - "Livewire\\LivewireServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Livewire\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Caleb Porzio", - "email": "calebporzio@gmail.com" - } - ], - "description": "A front-end framework for Laravel.", - "support": { - "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.7.6" - }, - "funding": [ - { - "url": "https://github.com/livewire", - "type": "github" - } - ], - "time": "2026-01-23T05:41:38+00:00" - }, { "name": "lkdevelopment/hetzner-cloud-php-sdk", "version": "v2.9.1", @@ -4258,77 +3275,10 @@ "hetzner cloud" ], "support": { - "issues": "https://github.com/LKDevelopment/hetzner-cloud-php-sdk/issues", - "source": "https://github.com/LKDevelopment/hetzner-cloud-php-sdk/tree/v2.9.1" - }, - "time": "2025-10-17T10:12:11+00:00" - }, - { - "name": "masterminds/html5", - "version": "2.10.0", - "source": { - "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fcf91eb64359852f00d921887b219479b4f21251" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", - "reference": "fcf91eb64359852f00d921887b219479b4f21251", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Masterminds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" - }, - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - } - ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", - "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" - ], - "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + "issues": "https://github.com/LKDevelopment/hetzner-cloud-php-sdk/issues", + "source": "https://github.com/LKDevelopment/hetzner-cloud-php-sdk/tree/v2.9.1" }, - "time": "2025-07-25T09:04:22+00:00" + "time": "2025-10-17T10:12:11+00:00" }, { "name": "monolog/monolog", @@ -4981,99 +3931,6 @@ ], "time": "2024-09-09T07:06:30+00:00" }, - { - "name": "openspout/openspout", - "version": "v4.32.0", - "source": { - "type": "git", - "url": "https://github.com/openspout/openspout.git", - "reference": "41f045c1f632e1474e15d4c7bc3abcb4a153563d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/openspout/openspout/zipball/41f045c1f632e1474e15d4c7bc3abcb4a153563d", - "reference": "41f045c1f632e1474e15d4c7bc3abcb4a153563d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-fileinfo": "*", - "ext-filter": "*", - "ext-libxml": "*", - "ext-xmlreader": "*", - "ext-zip": "*", - "php": "~8.3.0 || ~8.4.0 || ~8.5.0" - }, - "require-dev": { - "ext-zlib": "*", - "friendsofphp/php-cs-fixer": "^3.86.0", - "infection/infection": "^0.31.2", - "phpbench/phpbench": "^1.4.1", - "phpstan/phpstan": "^2.1.22", - "phpstan/phpstan-phpunit": "^2.0.7", - "phpstan/phpstan-strict-rules": "^2.0.6", - "phpunit/phpunit": "^12.3.7" - }, - "suggest": { - "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", - "ext-mbstring": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "OpenSpout\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Adrien Loison", - "email": "adrien@box.com" - } - ], - "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way", - "homepage": "https://github.com/openspout/openspout", - "keywords": [ - "OOXML", - "csv", - "excel", - "memory", - "odf", - "ods", - "office", - "open", - "php", - "read", - "scale", - "spreadsheet", - "stream", - "write", - "xlsx" - ], - "support": { - "issues": "https://github.com/openspout/openspout/issues", - "source": "https://github.com/openspout/openspout/tree/v4.32.0" - }, - "funding": [ - { - "url": "https://paypal.me/filippotessarotto", - "type": "custom" - }, - { - "url": "https://github.com/Slamdunk", - "type": "github" - } - ], - "time": "2025-09-03T16:03:54+00:00" - }, { "name": "paragonie/sodium_compat", "version": "v2.5.0", @@ -6633,84 +5490,6 @@ ], "time": "2024-06-11T12:45:25+00:00" }, - { - "name": "ryangjchandler/blade-capture-directive", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/ryangjchandler/blade-capture-directive.git", - "reference": "bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ryangjchandler/blade-capture-directive/zipball/bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d", - "reference": "bbb1513dfd89eaec87a47fe0c449a7e3d4a1976d", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^10.0|^11.0|^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.9.2" - }, - "require-dev": { - "nunomaduro/collision": "^7.0|^8.0", - "nunomaduro/larastan": "^2.0|^3.0", - "orchestra/testbench": "^8.0|^9.0|^10.0", - "pestphp/pest": "^2.0|^3.7", - "pestphp/pest-plugin-laravel": "^2.0|^3.1", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", - "phpstan/phpstan-phpunit": "^1.0|^2.0", - "phpunit/phpunit": "^10.0|^11.5.3", - "spatie/laravel-ray": "^1.26" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "BladeCaptureDirective": "RyanChandler\\BladeCaptureDirective\\Facades\\BladeCaptureDirective" - }, - "providers": [ - "RyanChandler\\BladeCaptureDirective\\BladeCaptureDirectiveServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "RyanChandler\\BladeCaptureDirective\\": "src", - "RyanChandler\\BladeCaptureDirective\\Database\\Factories\\": "database/factories" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ryan Chandler", - "email": "support@ryangjchandler.co.uk", - "role": "Developer" - } - ], - "description": "Create inline partials in your Blade templates with ease.", - "homepage": "https://github.com/ryangjchandler/blade-capture-directive", - "keywords": [ - "blade-capture-directive", - "laravel", - "ryangjchandler" - ], - "support": { - "issues": "https://github.com/ryangjchandler/blade-capture-directive/issues", - "source": "https://github.com/ryangjchandler/blade-capture-directive/tree/v1.1.0" - }, - "funding": [ - { - "url": "https://github.com/ryangjchandler", - "type": "github" - } - ], - "time": "2025-02-25T09:09:36+00:00" - }, { "name": "sentry/sentry", "version": "4.19.1", @@ -6889,124 +5668,6 @@ ], "time": "2026-01-07T08:53:19+00:00" }, - { - "name": "spatie/color", - "version": "1.8.0", - "source": { - "type": "git", - "url": "https://github.com/spatie/color.git", - "reference": "142af7fec069a420babea80a5412eb2f646dcd8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/color/zipball/142af7fec069a420babea80a5412eb2f646dcd8c", - "reference": "142af7fec069a420babea80a5412eb2f646dcd8c", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.22", - "phpunit/phpunit": "^6.5||^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Spatie\\Color\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sebastian De Deyne", - "email": "sebastian@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "A little library to handle color conversions", - "homepage": "https://github.com/spatie/color", - "keywords": [ - "color", - "conversion", - "rgb", - "spatie" - ], - "support": { - "issues": "https://github.com/spatie/color/issues", - "source": "https://github.com/spatie/color/tree/1.8.0" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2025-02-10T09:22:41+00:00" - }, - { - "name": "spatie/invade", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/spatie/invade.git", - "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/invade/zipball/b920f6411d21df4e8610a138e2e87ae4957d7f63", - "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "require-dev": { - "pestphp/pest": "^1.20", - "phpstan/phpstan": "^1.4", - "spatie/ray": "^1.28" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Spatie\\Invade\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "role": "Developer" - } - ], - "description": "A PHP function to work with private properties and methods", - "homepage": "https://github.com/spatie/invade", - "keywords": [ - "invade", - "spatie" - ], - "support": { - "source": "https://github.com/spatie/invade/tree/2.1.0" - }, - "funding": [ - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2024-05-17T09:06:10+00:00" - }, { "name": "spatie/laravel-package-tools", "version": "1.92.7", @@ -7623,99 +6284,31 @@ "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/filesystem", - "version": "v8.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-12-01T09:13:36+00:00" - }, - { - "name": "symfony/finder", - "version": "v7.4.3", + "name": "symfony/filesystem", + "version": "v8.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06" + "url": "https://github.com/symfony/filesystem.git", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7735,10 +6328,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.3" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -7758,33 +6351,32 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { - "name": "symfony/html-sanitizer", - "version": "v7.4.0", + "name": "symfony/finder", + "version": "v7.4.3", "source": { "type": "git", - "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "5b0bbcc3600030b535dd0b17a0e8c56243f96d7f" + "url": "https://github.com/symfony/finder.git", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/5b0bbcc3600030b535dd0b17a0e8c56243f96d7f", - "reference": "5b0bbcc3600030b535dd0b17a0e8c56243f96d7f", + "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06", "shasum": "" }, "require": { - "ext-dom": "*", - "league/uri": "^6.5|^7.0", - "masterminds/html5": "^2.7.2", - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HtmlSanitizer\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7796,23 +6388,18 @@ ], "authors": [ { - "name": "Titouan Galopin", - "email": "galopintitouan@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a document's DOM.", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "keywords": [ - "Purifier", - "html", - "sanitizer" - ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.3" }, "funding": [ { @@ -7832,7 +6419,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-foundation", @@ -10211,46 +8798,6 @@ }, "time": "2024-11-21T13:46:39+00:00" }, - { - "name": "filament/upgrade", - "version": "v3.3.47", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/upgrade.git", - "reference": "af5727d5b639e85aae0fb47b1970246907fe8e93" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/upgrade/zipball/af5727d5b639e85aae0fb47b1970246907fe8e93", - "reference": "af5727d5b639e85aae0fb47b1970246907fe8e93", - "shasum": "" - }, - "require": { - "nunomaduro/termwind": "^1.0|^2.0", - "php": "^8.1", - "rector/rector": "^1.0" - }, - "bin": [ - "bin/filament-v3" - ], - "type": "library", - "autoload": { - "psr-4": { - "Filament\\Upgrade\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Upgrade Filament v2 code to Filament v3.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2025-04-23T06:39:44+00:00" - }, { "name": "filp/whoops", "version": "2.18.4", @@ -10501,69 +9048,6 @@ }, "time": "2026-01-05T16:49:17+00:00" }, - { - "name": "laravel/sail", - "version": "v1.52.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/sail.git", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3", - "shasum": "" - }, - "require": { - "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", - "php": "^8.0", - "symfony/console": "^6.0|^7.0", - "symfony/yaml": "^6.0|^7.0" - }, - "require-dev": { - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^2.0" - }, - "bin": [ - "bin/sail" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Sail\\SailServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Sail\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Docker files for running a basic Laravel application.", - "keywords": [ - "docker", - "laravel" - ], - "support": { - "issues": "https://github.com/laravel/sail/issues", - "source": "https://github.com/laravel/sail" - }, - "time": "2026-01-01T02:46:03+00:00" - }, { "name": "mockery/mockery", "version": "1.6.12", @@ -10924,59 +9408,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "phpstan/phpstan", - "version": "1.12.32", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", - "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2025-09-30T10:16:31+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "11.0.12", @@ -11421,65 +9852,6 @@ ], "time": "2026-01-16T16:26:27+00:00" }, - { - "name": "rector/rector", - "version": "1.2.10", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.12.5" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" - }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" - ], - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.10" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2024-11-08T13:59:10+00:00" - }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -12900,82 +11272,6 @@ ], "time": "2024-10-20T05:08:20+00:00" }, - { - "name": "symfony/yaml", - "version": "v7.4.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-12-04T18:11:45+00:00" - }, { "name": "theseer/tokenizer", "version": "1.3.1", diff --git a/config/app.php b/config/app.php index b863d5e..1e0dead 100644 --- a/config/app.php +++ b/config/app.php @@ -168,7 +168,6 @@ App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\HorizonServiceProvider::class, - App\Providers\Filament\AdminPanelProvider::class, App\Providers\RouteServiceProvider::class, ])->toArray(), diff --git a/config/auth.php b/config/auth.php index 9548c15..8d8bf31 100644 --- a/config/auth.php +++ b/config/auth.php @@ -18,6 +18,23 @@ 'passwords' => 'users', ], + /* + |-------------------------------------------------------------------------- + | Mandatory login + |-------------------------------------------------------------------------- + | + | When true (the default) every page except the login screen is behind the + | identity provider. When false the browse, schedule, archive and player + | pages are open to guests and signing in is only required for chat, which + | needs an identity to attribute, rate limit and moderate. + | + | Restricted shows and recordings stay hidden from guests either way; the + | role check in `accessibleBy()` fails closed without a user. + | + */ + + 'required' => (bool) env('AUTH_REQUIRED', true), + /* |-------------------------------------------------------------------------- | Authentication Guards @@ -112,4 +129,19 @@ 'password_timeout' => 10800, + /* + |-------------------------------------------------------------------------- + | Remember Me Duration + |-------------------------------------------------------------------------- + | + | How long, in minutes, the "remember me" cookie issued at sign-in stays + | valid. Attendees sign in once and are expected to stay signed in for the + | run-up to the convention and the event itself, so this defaults to four + | weeks rather than Laravel's five years. Applied to the session guard in + | AuthServiceProvider. + | + */ + + 'remember_lifetime' => (int) env('AUTH_REMEMBER_LIFETIME', 40320), + ]; diff --git a/config/branding.php b/config/branding.php new file mode 100644 index 0000000..70d8065 --- /dev/null +++ b/config/branding.php @@ -0,0 +1,87 @@ + Settings), which stores what + | you save in the branding_settings table; a key with no row falls back to + | the literal below, so a fresh install boots with neutral copy. + | + | Deliberately no env() here. Branding is edited by organisers, not by ops, + | and a second source would only be able to disagree: once a value is saved + | in the panel it wins, so an env var that looks authoritative would quietly + | stop applying. Scripted setup goes through `php artisan branding:set`. + | + | Keys are flat and dot-free on purpose: they map 1:1 onto the setting keys + | in the database and onto the Inertia "branding" prop. + | + */ + + 'convention_name' => env('APP_NAME', 'Streaming'), + + 'site_name' => env('APP_NAME', 'Streaming'), + + // Shown above the login headline. Keep it short. Empty by default: an + // installation that has not set one gets no placeholder convention name. + 'login_eyebrow' => null, + + 'login_headline' => 'Livestream', + + 'login_tagline' => 'Open to everyone', + + 'login_body' => 'Sign in to watch the live streams and recordings.', + + 'login_button_label' => 'Sign in', + + // Name of the OIDC provider, used in the sign-in and register wording. + 'identity_name' => 'identity', + + /* + | Identity provider endpoints. Both are installation specific, so they stay + | empty here: the login screen hides the register link when there is no + | URL, and logout falls back to the local session teardown. + */ + 'identity_register_url' => null, + + 'identity_logout_url' => null, + + /* + | Footer links, as a list of {label, url} in the order they are shown. Any + | number of them, titled whatever the installation wants; empty means the + | footer link row is not rendered at all. Stored as JSON in the settings + | table, so this stays a plain PHP array here. + */ + 'footer_links' => [], + + /* + | Path on the public disk to a logo image. When empty nothing is rendered + | in its place and callers fall back to the site name in text. + */ + 'logo_path' => null, + + /* + | Background media for the login screen. The image is used as the video + | poster, so it is what visitors see before the clip has buffered. + */ + 'login_background_image' => null, + + /* + | Left empty, the login screen falls back to the background clip bundled + | with the frontend assets. + */ + 'login_background_video' => null, + + /* + | Base accent colour as a hex string. When set, a 50-950 ramp is derived + | from it at runtime and injected as CSS custom properties, overriding the + | --color-primary-* defaults in resources/css/app.css. No rebuild involved. + | Leave empty to use the ramp shipped in the stylesheet untouched. + */ + 'primary_color' => null, + +]; diff --git a/config/chat.php b/config/chat.php index 2f2696c..6faf10f 100644 --- a/config/chat.php +++ b/config/chat.php @@ -1,15 +1,46 @@ (bool) env('CHAT_ENABLED', true), + 'default' => [ /* - * Max Tries is the amount of tries a user can send a message before they are rate limited. - * This is only used for users that are not a moderator or higher. - * Rate Decay is the amount of time in seconds before the rate limit resets. - * In Slow Mode Rate Decay is the amount of seconds between each message. + * Max Tries is the amount of messages a user can send within Rate Decay seconds + * before they are rate limited. Only applies to users without + * the `chat.ignore.ratelimit` permission. + * + * Slow Mode Seconds is the enforced gap between messages when slow mode is on + * (0 disables it). It can be overridden per source from the mod tools. */ - 'maxTries' => 8, - 'rateDecay' => 30, - 'slowMode' => false, + 'maxTries' => (int) env('CHAT_MAX_TRIES', 8), + 'rateDecay' => (int) env('CHAT_RATE_DECAY', 30), + 'slowModeSeconds' => (int) env('CHAT_SLOW_MODE_SECONDS', 0), + 'maxMessageLength' => (int) env('CHAT_MAX_MESSAGE_LENGTH', 500), + ], + + /* + * Links to these domains stay clickable in chat, everything else is + * stripped. Comma separated in CHAT_ALLOWED_DOMAINS; empty by default, so a + * fresh install strips every link until an operator opts domains in. + */ + 'allowed_domains' => array_values(array_filter(array_map( + 'trim', + explode(',', (string) env('CHAT_ALLOWED_DOMAINS', '')) + ))), + + /* + * How many messages the client keeps in memory / the backlog endpoints return. + */ + 'history' => [ + 'initial' => 60, + 'page' => 50, + 'buffer' => 300, + // Lines sent to the browse page chat excerpt; it shows as many as fit. + 'excerpt' => 40, ], ]; diff --git a/config/database.php b/config/database.php index 137ad18..9843dba 100644 --- a/config/database.php +++ b/config/database.php @@ -59,7 +59,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (class_exists(\Pdo\Mysql::class) ? \Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], diff --git a/config/dns.php b/config/dns.php index bb9b249..cc680b1 100644 --- a/config/dns.php +++ b/config/dns.php @@ -12,9 +12,9 @@ | */ - 'server' => env('DNS_SERVER', '85.199.154.53'), + 'server' => env('DNS_SERVER'), - 'zone' => env('DNS_ZONE', 'stream.eurofurence.org'), + 'zone' => env('DNS_ZONE'), 'key_name' => env('DNS_KEY_NAME', 'stream-ddns'), diff --git a/config/filament.php b/config/filament.php deleted file mode 100644 index b687973..0000000 --- a/config/filament.php +++ /dev/null @@ -1,40 +0,0 @@ - [ - - // 'echo' => [ - // 'broadcaster' => 'pusher', - // 'key' => env('VITE_PUSHER_APP_KEY'), - // 'cluster' => env('VITE_PUSHER_APP_CLUSTER'), - // 'forceTLS' => true, - // ], - - ], - - /* - |-------------------------------------------------------------------------- - | Default Filesystem Disk - |-------------------------------------------------------------------------- - | - | This is the storage disk Filament will use to put media. You may use any - | of the disks defined in the `config/filesystems.php`. - | - */ - - 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), - -]; diff --git a/config/horizon.php b/config/horizon.php index 583b1b9..eda2512 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -182,7 +182,7 @@ 'defaults' => [ 'supervisor-1' => [ 'connection' => 'redis', - 'queue' => ['default','recordings'], + 'queue' => ['default', 'recordings'], 'balance' => 'auto', 'autoScalingStrategy' => 'time', 'maxProcesses' => 1, diff --git a/config/livewire.php b/config/livewire.php deleted file mode 100644 index 0d2ba89..0000000 --- a/config/livewire.php +++ /dev/null @@ -1,160 +0,0 @@ - 'App\\Livewire', - - /* - |--------------------------------------------------------------------------- - | View Path - |--------------------------------------------------------------------------- - | - | This value is used to specify where Livewire component Blade templates are - | stored when running file creation commands like `artisan make:livewire`. - | It is also used if you choose to omit a component's render() method. - | - */ - - 'view_path' => resource_path('views/livewire'), - - /* - |--------------------------------------------------------------------------- - | Layout - |--------------------------------------------------------------------------- - | The view that will be used as the layout when rendering a single component - | as an entire page via `Route::get('/post/create', CreatePost::class);`. - | In this case, the view returned by CreatePost will render into $slot. - | - */ - - 'layout' => 'components.layouts.app', - - /* - |--------------------------------------------------------------------------- - | Lazy Loading Placeholder - |--------------------------------------------------------------------------- - | Livewire allows you to lazy load components that would otherwise slow down - | the initial page load. Every component can have a custom placeholder or - | you can define the default placeholder view for all components below. - | - */ - - 'lazy_placeholder' => null, - - /* - |--------------------------------------------------------------------------- - | Temporary File Uploads - |--------------------------------------------------------------------------- - | - | Livewire handles file uploads by storing uploads in a temporary directory - | before the file is stored permanently. All file uploads are directed to - | a global endpoint for temporary storage. You may configure this below: - | - */ - - 'temporary_file_upload' => [ - 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) - 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' - 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' - 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... - 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', - 'mov', 'avi', 'wmv', 'mp3', 'm4a', - 'jpg', 'jpeg', 'mpga', 'webp', 'wma', - ], - 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... - 'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs... - ], - - /* - |--------------------------------------------------------------------------- - | Render On Redirect - |--------------------------------------------------------------------------- - | - | This value determines if Livewire will run a component's `render()` method - | after a redirect has been triggered using something like `redirect(...)` - | Setting this to true will render the view once more before redirecting - | - */ - - 'render_on_redirect' => false, - - /* - |--------------------------------------------------------------------------- - | Eloquent Model Binding - |--------------------------------------------------------------------------- - | - | Previous versions of Livewire supported binding directly to eloquent model - | properties using wire:model by default. However, this behavior has been - | deemed too "magical" and has therefore been put under a feature flag. - | - */ - - 'legacy_model_binding' => false, - - /* - |--------------------------------------------------------------------------- - | Auto-inject Frontend Assets - |--------------------------------------------------------------------------- - | - | By default, Livewire automatically injects its JavaScript and CSS into the - | and of pages containing Livewire components. By disabling - | this behavior, you need to use @livewireStyles and @livewireScripts. - | - */ - - 'inject_assets' => true, - - /* - |--------------------------------------------------------------------------- - | Navigate (SPA mode) - |--------------------------------------------------------------------------- - | - | By adding `wire:navigate` to links in your Livewire application, Livewire - | will prevent the default link handling and instead request those pages - | via AJAX, creating an SPA-like effect. Configure this behavior here. - | - */ - - 'navigate' => [ - 'show_progress_bar' => true, - 'progress_bar_color' => '#2299dd', - ], - - /* - |--------------------------------------------------------------------------- - | HTML Morph Markers - |--------------------------------------------------------------------------- - | - | Livewire intelligently "morphs" existing HTML into the newly rendered HTML - | after each update. To make this process more reliable, Livewire injects - | "markers" into the rendered Blade surrounding @if, @class & @foreach. - | - */ - - 'inject_morph_markers' => true, - - /* - |--------------------------------------------------------------------------- - | Pagination Theme - |--------------------------------------------------------------------------- - | - | When enabling Livewire's pagination feature by using the `WithPagination` - | trait, Livewire will use Tailwind templates to render pagination views - | on the page. If you want Bootstrap CSS, you can specify: "bootstrap" - | - */ - - 'pagination_theme' => 'tailwind', -]; diff --git a/config/manage.php b/config/manage.php new file mode 100644 index 0000000..5046123 --- /dev/null +++ b/config/manage.php @@ -0,0 +1,84 @@ + [ + + 'show_thumbnail' => [ + 'disk' => 's3', + 'directory' => 'shows/thumbnails', + 'visibility' => 'private', + 'mimes' => ['jpeg', 'jpg', 'png', 'webp'], + 'max' => 5120, + 'preserve_filename' => true, + 'resize' => null, + ], + + 'recording_thumbnail' => [ + 'disk' => 's3', + 'directory' => 'recordings/thumbnails', + 'visibility' => 'private', + 'mimes' => ['jpeg', 'jpg', 'png', 'webp'], + 'max' => 5120, + 'preserve_filename' => false, + 'resize' => ['width' => 1280, 'height' => 720, 'mode' => 'cover'], + ], + + 'emote' => [ + 'disk' => 's3', + 'directory' => 'emotes', + 'visibility' => 'private', + 'mimes' => ['jpeg', 'jpg', 'png', 'webp', 'gif'], + 'max' => 2048, + 'preserve_filename' => true, + 'resize' => ['width' => 64, 'height' => 64, 'mode' => 'cover', 'aspect' => '1:1'], + ], + + 'branding_logo' => [ + 'disk' => 'public', + 'directory' => 'branding', + 'visibility' => 'public', + 'mimes' => ['jpeg', 'jpg', 'png', 'webp', 'svg'], + 'max' => 2048, + 'preserve_filename' => true, + 'resize' => null, + ], + + 'branding_login_image' => [ + 'disk' => 'public', + 'directory' => 'branding', + 'visibility' => 'public', + 'mimes' => ['jpeg', 'jpg', 'png', 'webp'], + 'max' => 8192, + 'preserve_filename' => true, + 'resize' => null, + ], + + 'branding_login_video' => [ + 'disk' => 'public', + 'directory' => 'branding', + 'visibility' => 'public', + 'mimes' => ['mp4', 'webm'], + 'max' => 51200, + 'preserve_filename' => true, + 'resize' => null, + ], + + ], + +]; diff --git a/config/pretalx.php b/config/pretalx.php new file mode 100644 index 0000000..5163f76 --- /dev/null +++ b/config/pretalx.php @@ -0,0 +1,24 @@ + Settings and stored in the settings table, exactly like + | branding, so an event slug can change without a deploy. + | + | Keys are prefixed because the settings table is one flat namespace. + | + */ + + 'pretalx_url' => null, + + 'pretalx_event' => null, + + 'pretalx_token' => null, + +]; diff --git a/config/services.php b/config/services.php index 3bc7cf6..5ba5a6c 100644 --- a/config/services.php +++ b/config/services.php @@ -35,6 +35,12 @@ 'url' => env('OIDC_URL'), 'client_id' => env('OIDC_CLIENT_ID'), 'secret' => env('OIDC_SECRET'), + + /* + | Which provider group maps to which role is not configured here: a role + | claims a group ID (or a registration package) through its + | `external_id`, editable under Administration > Roles. + */ ], 'stream' => [ diff --git a/config/settings.php b/config/settings.php new file mode 100644 index 0000000..6a7867a --- /dev/null +++ b/config/settings.php @@ -0,0 +1,221 @@ + [ + + [ + 'key' => 'identity', + 'label' => 'Identity', + 'description' => 'Who this installation belongs to, and where people get an account.', + 'columns' => 2, + 'fields' => [ + [ + 'key' => 'convention_name', + 'label' => 'Convention name', + 'type' => 'text', + 'helper' => 'Name of the convention, used in page copy.', + 'rules' => ['required', 'string', 'max:255'], + ], + [ + 'key' => 'site_name', + 'label' => 'Site name', + 'type' => 'text', + 'helper' => 'Name of this streaming site, used in the header and page titles.', + 'rules' => ['required', 'string', 'max:255'], + ], + [ + 'key' => 'identity_name', + 'label' => 'Identity provider name', + 'type' => 'text', + 'helper' => 'Name of the identity provider people sign in with.', + 'rules' => ['required', 'string', 'max:255'], + ], + [ + 'key' => 'identity_register_url', + 'label' => 'Register URL', + 'type' => 'url', + 'helper' => 'Where people register a new identity account.', + 'rules' => ['nullable', 'url', 'max:2048'], + ], + [ + 'key' => 'identity_logout_url', + 'label' => 'Logout URL', + 'type' => 'url', + 'helper' => 'Identity provider logout endpoint.', + 'rules' => ['nullable', 'url', 'max:2048'], + ], + ], + ], + + [ + 'key' => 'login', + 'label' => 'Login screen', + 'description' => 'Everything shown to visitors before they sign in.', + 'columns' => 2, + 'fields' => [ + [ + 'key' => 'login_eyebrow', + 'label' => 'Eyebrow', + 'type' => 'text', + 'helper' => 'Small label above the login headline.', + 'rules' => ['nullable', 'string', 'max:255'], + ], + [ + 'key' => 'login_headline', + 'label' => 'Headline', + 'type' => 'text', + 'helper' => 'Main login headline.', + 'rules' => ['required', 'string', 'max:255'], + ], + [ + 'key' => 'login_tagline', + 'label' => 'Tagline', + 'type' => 'text', + 'helper' => 'One line under the headline.', + 'rules' => ['nullable', 'string', 'max:255'], + ], + [ + 'key' => 'login_button_label', + 'label' => 'Button label', + 'type' => 'text', + 'helper' => 'Label on the sign-in button.', + 'rules' => ['required', 'string', 'max:255'], + ], + [ + 'key' => 'login_body', + 'label' => 'Intro paragraph', + 'type' => 'textarea', + 'helper' => 'Paragraph explaining what is needed to watch.', + 'rules' => ['nullable', 'string', 'max:2000'], + 'full' => true, + ], + ], + ], + + [ + 'key' => 'look', + 'label' => 'Look', + 'description' => 'Logo, accent colour and the login background. Uploads land on the public disk.', + 'columns' => 2, + 'fields' => [ + [ + 'key' => 'primary_color', + 'label' => 'Accent colour', + 'type' => 'color', + 'helper' => 'Pick a preset or set any hex. A full 50-950 ramp is derived from it; empty keeps the built-in palette.', + 'rules' => ['nullable', 'string', 'regex:/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/'], + 'presets' => \App\Support\ColorPresets::PRESETS, + ], + [ + 'key' => 'logo_path', + 'label' => 'Logo', + 'type' => 'image', + 'purpose' => 'branding_logo', + 'helper' => 'Leave empty to show the site name as text instead.', + 'rules' => ['nullable', 'string', 'max:2048'], + ], + [ + 'key' => 'login_background_image', + 'label' => 'Login background image', + 'type' => 'image', + 'purpose' => 'branding_login_image', + 'helper' => 'Also used as the poster for the background video.', + 'rules' => ['nullable', 'string', 'max:2048'], + ], + [ + 'key' => 'login_background_video', + 'label' => 'Login background video', + 'type' => 'video', + 'purpose' => 'branding_login_video', + 'helper' => 'Left empty, the bundled clip is used.', + 'rules' => ['nullable', 'string', 'max:2048'], + ], + ], + ], + + [ + 'key' => 'links', + 'label' => 'Footer links', + 'description' => 'Shown in the footer of the public site, in this order. Add as many as you need; with none, the footer link row is hidden.', + 'columns' => 1, + 'fields' => [ + [ + 'key' => 'footer_links', + 'label' => 'Links', + // A repeater of {label, url} rows, stored as JSON. `itemRules` + // are expanded onto values.footer_links.* by Settings::rules(). + 'type' => 'links', + 'full' => true, + 'helper' => 'Title and address for each link. Empty rows are dropped on save.', + 'rules' => ['nullable', 'array', 'max:12'], + 'itemRules' => [ + 'label' => ['required', 'string', 'max:40'], + 'url' => ['required', 'url', 'max:2048'], + ], + ], + ], + ], + + [ + 'key' => 'pretalx', + 'label' => 'Pretalx', + 'description' => 'Where the programme comes from. With these set, /manage > Shows can import sessions from the published schedule.', + 'columns' => 2, + 'fields' => [ + [ + 'key' => 'pretalx_url', + 'label' => 'Instance URL', + 'type' => 'url', + 'store' => 'pretalx', + 'helper' => 'Root of the pretalx instance, for example https://cfp.example.org.', + 'rules' => ['nullable', 'url', 'max:2048'], + ], + [ + 'key' => 'pretalx_event', + 'label' => 'Event slug', + 'type' => 'text', + 'store' => 'pretalx', + 'helper' => 'The slug in the pretalx URL, for example my-con-2026.', + 'rules' => ['nullable', 'string', 'max:255'], + ], + [ + 'key' => 'pretalx_token', + 'label' => 'API token', + 'type' => 'password', + 'store' => 'pretalx', + 'full' => true, + 'helper' => 'From the pretalx user account, under API tokens. Only needed while the schedule is unpublished or the event is private.', + 'rules' => ['nullable', 'string', 'max:255'], + ], + ], + ], + + ], + + /* + | Config namespace every group falls back to unless a field overrides it with + | its own `store`. Branding is the bulk of it; the pretalx group names its own. + */ + 'store' => 'branding', + +]; diff --git a/config/stream.php b/config/stream.php index 4c8b488..c58eee2 100644 --- a/config/stream.php +++ b/config/stream.php @@ -34,16 +34,6 @@ ], ], - // Auto-scaling thresholds - 'autoscale' => [ - 'enabled' => env('STREAM_AUTOSCALE_ENABLED', false), - 'min_servers' => env('STREAM_AUTOSCALE_MIN_SERVERS', 1), - 'max_servers' => env('STREAM_AUTOSCALE_MAX_SERVERS', 10), - 'scale_up_threshold' => env('STREAM_AUTOSCALE_UP_THRESHOLD', 80), // % capacity - 'scale_down_threshold' => env('STREAM_AUTOSCALE_DOWN_THRESHOLD', 20), // % capacity - 'cooldown_minutes' => env('STREAM_AUTOSCALE_COOLDOWN', 5), - ], - // Stream quality settings (bitrates in kbps) 'qualities' => [ 'fhd' => [ @@ -72,9 +62,71 @@ 'hls_port' => env('DOCKER_HLS_PORT', 80), ], + // Container images baked into the generated provisioning scripts. Built + // from docker/ in this repo; set the full reference including the registry + // namespace an operator publishes them under. + 'images' => [ + 'ffmpeg_hls' => env('STREAM_IMAGE_FFMPEG_HLS', 'ffmpeg-hls:latest'), + 'dvr_uploader' => env('STREAM_IMAGE_DVR_UPLOADER', 'dvr-uploader:latest'), + ], + + // Filesystem disk holding the segment archive and the generated recording + // playlists. Must be the same bucket archive_uploader.py writes to on the origin. + // + // Production uses the dedicated `dvr` disk. Locally there is one versitygw bucket + // for everything, so point this at `s3` rather than configuring DVR_AWS_* twice. + 'archive_disk' => env('ARCHIVE_DISK', 'dvr'), + + // How segment URLs inside a recording playlist are produced. + // + // 'signed' Presigned S3 URLs, straight from the bucket to the player. Production. + // The bucket MUST send CORS headers or hls.js cannot fetch the segments: + // it reads them over XHR, so a missing Access-Control-Allow-Origin fails + // playback even though the URL itself is valid. + // + // 'proxy' Streamed through the app on its own origin. Local only. The dev S3 + // (versitygw) sends no CORS headers and speaks plain HTTP, which a page + // served over TLS blocks as mixed content; a presigned URL cannot be put + // behind a proxy to fix either, because the signature covers the Host. + // Never use this in production: it puts PHP in the media path for every + // two second segment. + 'archive_url_mode' => env('ARCHIVE_URL_MODE', 'signed'), + + // Lifetime of a presigned segment URL. A VOD playlist is fetched once at the start + // of a session rather than refreshed, so this only has to outlast a viewing; the + // trade is that a leaked playlist stays usable until the signatures lapse. + 'archive_url_ttl' => (int) env('ARCHIVE_URL_TTL', 86400), + // System streamkey for internal operations (thumbnails, monitoring, etc.) 'system_streamkey' => env('STREAM_SYSTEM_STREAMKEY', ''), + // Playback tokens. Short-lived HMAC-signed capabilities that replace the + // permanent per-user streamkey, verified locally on the edges so PHP stays + // out of the media path. See docs/streaming-auth-redesign.md. + 'token' => [ + // Separate secrets per token type, so leaking the viewer secret cannot + // be used to mint long-lived embed keys. Both are shared with the edges. + 'viewer_secret' => env('HLS_VIEWER_SECRET'), + 'embed_secret' => env('HLS_EMBED_SECRET'), + + // Viewer token lifetime. Expiry is the revocation mechanism, so a ban + // takes effect within this window at worst. + 'ttl' => (int) env('HLS_TOKEN_TTL', 900), + + // Seconds a token is still accepted past its expiry, to absorb clock + // drift between app and edges and a refresh that lands late. + 'leeway' => (int) env('HLS_TOKEN_LEEWAY', 60), + + // How long before expiry the server pushes a fresh token to the player. + // The remainder is the budget for the 403 recovery path. + 'refresh_margin' => (int) env('HLS_TOKEN_REFRESH_MARGIN', 180), + ], + + // Local dev loops: with DEV_STREAMS=true, sources play the HLS that + // scripts/dev-streams.sh writes into public/dev-streams/ instead of + // being proxied to an edge server that does not exist on a laptop. + 'dev_streams' => env('DEV_STREAMS', false), + // Local streaming server override configuration // When client IPs match these subnets, force use of the specified hostname 'local_streaming_ipv4_subnet' => env('LOCAL_STREAMING_IPV4_SUBNET', ''), diff --git a/database/factories/ServerFactory.php b/database/factories/ServerFactory.php new file mode 100644 index 0000000..aa50afc --- /dev/null +++ b/database/factories/ServerFactory.php @@ -0,0 +1,81 @@ + + */ +class ServerFactory extends Factory +{ + protected $model = Server::class; + + /** + * Default is a manually managed edge server: no hetzner_id, which is the + * distinction the panel keys "Delete" versus "Deprovision" off. + * + * @return array + */ + public function definition(): array + { + return [ + 'hetzner_id' => null, + 'hostname' => $this->faker->unique()->domainWord().'.edge.test', + 'ip' => $this->faker->ipv4(), + 'port' => 8080, + 'type' => ServerTypeEnum::EDGE, + 'status' => ServerStatusEnum::ACTIVE, + 'shared_secret' => Str::random(40), + 'max_clients' => 100, + 'viewer_count' => 0, + 'immutable' => true, + ]; + } + + public function edge(): self + { + return $this->state(['type' => ServerTypeEnum::EDGE]); + } + + public function origin(): self + { + return $this->state([ + 'type' => ServerTypeEnum::ORIGIN, + 'hostname' => 'origin.'.$this->faker->unique()->domainWord().'.test', + 'port' => 443, + 'max_clients' => 1000, + ]); + } + + /** + * A Hetzner-managed server, which is what makes "Deprovision" the offered action. + */ + public function cloud(): self + { + return $this->state(['hetzner_id' => (string) $this->faker->unique()->numberBetween(1000000, 9999999)]); + } + + public function status(ServerStatusEnum $status): self + { + return $this->state(['status' => $status]); + } + + public function withHeartbeat(): self + { + return $this->state(['last_heartbeat' => now()]); + } + + public function healthy(): self + { + return $this->state([ + 'health_status' => 'healthy', + 'last_health_check' => now(), + 'health_check_message' => 'Health check passed', + ]); + } +} diff --git a/database/factories/ShowFactory.php b/database/factories/ShowFactory.php index 232ffed..cfe4ddc 100644 --- a/database/factories/ShowFactory.php +++ b/database/factories/ShowFactory.php @@ -68,4 +68,4 @@ public function ended(): static 'actual_end' => now(), ]); } -} \ No newline at end of file +} diff --git a/database/factories/SourceFactory.php b/database/factories/SourceFactory.php index 2f905b4..f96738f 100644 --- a/database/factories/SourceFactory.php +++ b/database/factories/SourceFactory.php @@ -22,6 +22,7 @@ class SourceFactory extends Factory public function definition(): array { $name = $this->faker->unique()->words(3, true); + return [ 'name' => $name, 'slug' => Str::slug($name), @@ -60,4 +61,4 @@ public function error(): static 'status' => SourceStatusEnum::ERROR, ]); } -} \ No newline at end of file +} diff --git a/database/migrations/2023_08_31_052337_clients_remove_fk_to_server_user_and_change_it_to_servers_on_clients_table.php b/database/migrations/2023_08_31_052337_clients_remove_fk_to_server_user_and_change_it_to_servers_on_clients_table.php index 47c3d12..6d15aff 100644 --- a/database/migrations/2023_08_31_052337_clients_remove_fk_to_server_user_and_change_it_to_servers_on_clients_table.php +++ b/database/migrations/2023_08_31_052337_clients_remove_fk_to_server_user_and_change_it_to_servers_on_clients_table.php @@ -10,10 +10,10 @@ public function up(): void { // Check if clients table exists before trying to modify it - if (!Schema::hasTable('clients')) { + if (! Schema::hasTable('clients')) { return; } - + // Truncate clients table directly using DB facade DB::table('clients')->truncate(); @@ -58,10 +58,10 @@ public function up(): void public function down(): void { // Check if clients table exists before trying to modify it - if (!Schema::hasTable('clients')) { + if (! Schema::hasTable('clients')) { return; } - + Schema::table('clients', function (Blueprint $table) { $table->dropConstrainedForeignIdFor(\App\Models\Server::class); $table->foreignId('server_user_id')->nullable()->after('id')->constrained('server_user', 'id', 'server_user_id_fk')->cascadeOnDelete(); diff --git a/database/migrations/2023_08_31_055110_add_user_id_to_clients_table.php b/database/migrations/2023_08_31_055110_add_user_id_to_clients_table.php index dae7767..53183ac 100644 --- a/database/migrations/2023_08_31_055110_add_user_id_to_clients_table.php +++ b/database/migrations/2023_08_31_055110_add_user_id_to_clients_table.php @@ -9,10 +9,10 @@ public function up(): void { // Check if clients table exists before trying to modify it - if (!Schema::hasTable('clients')) { + if (! Schema::hasTable('clients')) { return; } - + Schema::table('clients', function (Blueprint $table) { $table->foreignIdFor(\App\Models\User::class)->after('id')->constrained()->cascadeOnDelete(); }); @@ -21,10 +21,10 @@ public function up(): void public function down(): void { // Check if clients table exists before trying to modify it - if (!Schema::hasTable('clients')) { + if (! Schema::hasTable('clients')) { return; } - + Schema::table('clients', function (Blueprint $table) { $table->dropConstrainedForeignIdFor(\App\Models\User::class); }); diff --git a/database/migrations/2025_08_29_170705_simplify_role_user_table.php b/database/migrations/2025_08_29_170705_simplify_role_user_table.php index fdadf7e..f1d4c71 100644 --- a/database/migrations/2025_08_29_170705_simplify_role_user_table.php +++ b/database/migrations/2025_08_29_170705_simplify_role_user_table.php @@ -2,8 +2,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; return new class extends Migration { @@ -18,7 +18,7 @@ public function up(): void $table->dropIndex('role_user_expires_at_index'); }); } - + Schema::table('role_user', function (Blueprint $table) { // Drop the columns we don't need if they exist $columnsToDelete = []; @@ -31,13 +31,13 @@ public function up(): void if (Schema::hasColumn('role_user', 'assigned_by')) { $columnsToDelete[] = 'assigned_by'; } - - if (!empty($columnsToDelete)) { + + if (! empty($columnsToDelete)) { $table->dropColumn($columnsToDelete); } }); - if (!Schema::hasColumn('role_user', 'assigned_by_user_id')) { + if (! Schema::hasColumn('role_user', 'assigned_by_user_id')) { Schema::table('role_user', function (Blueprint $table) { // Add assigned_by_user_id as a foreign key $table->foreignId('assigned_by_user_id')->nullable()->after('user_id')->constrained('users')->nullOnDelete(); diff --git a/database/migrations/2025_08_29_171750_remove_is_staff_from_roles_table.php b/database/migrations/2025_08_29_171750_remove_is_staff_from_roles_table.php index b06301d..78538b4 100644 --- a/database/migrations/2025_08_29_171750_remove_is_staff_from_roles_table.php +++ b/database/migrations/2025_08_29_171750_remove_is_staff_from_roles_table.php @@ -20,7 +20,7 @@ public function up(): void // Index might not exist } }); - + Schema::table('roles', function (Blueprint $table) { $table->dropColumn('is_staff'); }); diff --git a/database/migrations/2025_08_30_031133_remove_priority_from_sources_table.php b/database/migrations/2025_08_30_031133_remove_priority_from_sources_table.php index dc7ada5..3982c44 100644 --- a/database/migrations/2025_08_30_031133_remove_priority_from_sources_table.php +++ b/database/migrations/2025_08_30_031133_remove_priority_from_sources_table.php @@ -20,7 +20,7 @@ public function up(): void // Index might not exist } }); - + Schema::table('sources', function (Blueprint $table) { $table->dropColumn('priority'); }); diff --git a/database/migrations/2025_08_30_145103_rename_thumbnail_url_to_thumbnail_path_in_shows_table.php b/database/migrations/2025_08_30_145103_rename_thumbnail_url_to_thumbnail_path_in_shows_table.php index 6494431..bb983d0 100644 --- a/database/migrations/2025_08_30_145103_rename_thumbnail_url_to_thumbnail_path_in_shows_table.php +++ b/database/migrations/2025_08_30_145103_rename_thumbnail_url_to_thumbnail_path_in_shows_table.php @@ -25,4 +25,4 @@ public function down(): void $table->renameColumn('thumbnail_path', 'thumbnail_url'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_153138_create_timeouts_table.php b/database/migrations/2025_08_30_153138_create_timeouts_table.php index 90e2ed1..55faf5a 100644 --- a/database/migrations/2025_08_30_153138_create_timeouts_table.php +++ b/database/migrations/2025_08_30_153138_create_timeouts_table.php @@ -18,7 +18,7 @@ public function up(): void $table->datetime('expires_at'); $table->text('reason')->nullable(); $table->timestamps(); - + $table->index(['user_id', 'expires_at']); }); } @@ -30,4 +30,4 @@ public function down(): void { Schema::dropIfExists('timeouts'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_153154_create_chat_settings_table.php b/database/migrations/2025_08_30_153154_create_chat_settings_table.php index a798d6f..87130e9 100644 --- a/database/migrations/2025_08_30_153154_create_chat_settings_table.php +++ b/database/migrations/2025_08_30_153154_create_chat_settings_table.php @@ -17,7 +17,7 @@ public function up(): void $table->text('value'); $table->text('description')->nullable(); $table->timestamps(); - + $table->index('key'); }); } @@ -29,4 +29,4 @@ public function down(): void { Schema::dropIfExists('chat_settings'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_153213_create_system_messages_table.php b/database/migrations/2025_08_30_153213_create_system_messages_table.php index 7e92162..cfd6749 100644 --- a/database/migrations/2025_08_30_153213_create_system_messages_table.php +++ b/database/migrations/2025_08_30_153213_create_system_messages_table.php @@ -19,7 +19,7 @@ public function up(): void $table->string('priority')->default('normal'); $table->json('metadata')->nullable(); $table->timestamps(); - + $table->index(['type', 'created_at']); $table->index('priority'); }); @@ -32,4 +32,4 @@ public function down(): void { Schema::dropIfExists('system_messages'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_155138_remove_unused_columns_from_users_table.php b/database/migrations/2025_08_30_155138_remove_unused_columns_from_users_table.php index a8efbe2..49e602d 100644 --- a/database/migrations/2025_08_30_155138_remove_unused_columns_from_users_table.php +++ b/database/migrations/2025_08_30_155138_remove_unused_columns_from_users_table.php @@ -13,7 +13,7 @@ public function up(): void { Schema::table('users', function (Blueprint $table) { $columnsToRemove = []; - + if (Schema::hasColumn('users', 'level')) { $columnsToRemove[] = 'level'; } @@ -26,8 +26,8 @@ public function up(): void if (Schema::hasColumn('users', 'badge_type')) { $columnsToRemove[] = 'badge_type'; } - - if (!empty($columnsToRemove)) { + + if (! empty($columnsToRemove)) { $table->dropColumn($columnsToRemove); } }); diff --git a/database/migrations/2025_08_30_163337_remove_unnecessary_fields_from_sources_table.php b/database/migrations/2025_08_30_163337_remove_unnecessary_fields_from_sources_table.php index 898b0d9..80dd2b2 100644 --- a/database/migrations/2025_08_30_163337_remove_unnecessary_fields_from_sources_table.php +++ b/database/migrations/2025_08_30_163337_remove_unnecessary_fields_from_sources_table.php @@ -19,7 +19,7 @@ public function up(): void } catch (\Exception $e) { // Index doesn't exist, continue } - + Schema::table('sources', function (Blueprint $table) { // Drop columns if they exist $columnsToDrop = []; @@ -28,8 +28,8 @@ public function up(): void $columnsToDrop[] = $column; } } - - if (!empty($columnsToDrop)) { + + if (! empty($columnsToDrop)) { $table->dropColumn($columnsToDrop); } }); @@ -48,4 +48,4 @@ public function down(): void $table->json('metadata')->nullable(); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_164024_update_servers_for_origin_edge_architecture.php b/database/migrations/2025_08_30_164024_update_servers_for_origin_edge_architecture.php index b6e826f..00a1003 100644 --- a/database/migrations/2025_08_30_164024_update_servers_for_origin_edge_architecture.php +++ b/database/migrations/2025_08_30_164024_update_servers_for_origin_edge_architecture.php @@ -15,11 +15,11 @@ public function up(): void // Add fields for origin server $table->string('hls_path')->nullable()->after('shared_secret'); $table->string('origin_url')->nullable()->after('hls_path'); - + // Add fields for edge server monitoring $table->integer('viewer_count')->default(0)->after('max_clients'); $table->timestamp('last_heartbeat')->nullable()->after('viewer_count'); - + // Add index for finding origin server quickly $table->index(['type', 'status']); }); @@ -35,4 +35,4 @@ public function down(): void $table->dropIndex(['type', 'status']); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_164306_create_viewer_statistics_table.php b/database/migrations/2025_08_30_164306_create_viewer_statistics_table.php index 3654ea0..9191627 100644 --- a/database/migrations/2025_08_30_164306_create_viewer_statistics_table.php +++ b/database/migrations/2025_08_30_164306_create_viewer_statistics_table.php @@ -18,7 +18,7 @@ public function up(): void $table->integer('unique_viewers')->default(0); $table->timestamp('recorded_at'); $table->timestamps(); - + $table->index(['show_id', 'recorded_at']); }); } diff --git a/database/migrations/2025_08_30_172431_drop_clients_table.php b/database/migrations/2025_08_30_172431_drop_clients_table.php index 462e763..488b235 100644 --- a/database/migrations/2025_08_30_172431_drop_clients_table.php +++ b/database/migrations/2025_08_30_172431_drop_clients_table.php @@ -31,4 +31,4 @@ public function down(): void $table->timestamps(); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_30_175447_drop_user_badges_table.php b/database/migrations/2025_08_30_175447_drop_user_badges_table.php index 9d99b71..156e3d4 100644 --- a/database/migrations/2025_08_30_175447_drop_user_badges_table.php +++ b/database/migrations/2025_08_30_175447_drop_user_badges_table.php @@ -28,7 +28,7 @@ public function down(): void $table->foreignId('revoked_by_user_id')->nullable()->constrained('users')->onDelete('set null'); $table->timestamp('revoked_at')->nullable(); $table->timestamps(); - + $table->index(['user_id', 'badge_type']); }); } diff --git a/database/migrations/2025_08_30_180818_update_servers_table_nullable_fields.php b/database/migrations/2025_08_30_180818_update_servers_table_nullable_fields.php index d80675c..ee173d9 100644 --- a/database/migrations/2025_08_30_180818_update_servers_table_nullable_fields.php +++ b/database/migrations/2025_08_30_180818_update_servers_table_nullable_fields.php @@ -14,18 +14,18 @@ public function up(): void Schema::table('servers', function (Blueprint $table) { // Make hetzner_id nullable $table->string('hetzner_id')->nullable()->change(); - + // Make ip nullable $table->string('ip')->nullable()->change(); - + // Add health check fields if they don't exist - if (!Schema::hasColumn('servers', 'health_status')) { + if (! Schema::hasColumn('servers', 'health_status')) { $table->enum('health_status', ['healthy', 'unhealthy', 'unknown'])->default('unknown')->after('status'); } - if (!Schema::hasColumn('servers', 'last_health_check')) { + if (! Schema::hasColumn('servers', 'last_health_check')) { $table->timestamp('last_health_check')->nullable()->after('last_heartbeat'); } - if (!Schema::hasColumn('servers', 'health_check_message')) { + if (! Schema::hasColumn('servers', 'health_check_message')) { $table->string('health_check_message')->nullable()->after('last_health_check'); } }); @@ -40,7 +40,7 @@ public function down(): void // Revert nullable changes $table->string('hetzner_id')->nullable(false)->change(); $table->string('ip')->nullable(false)->change(); - + // Drop health check columns $table->dropColumn(['health_status', 'last_health_check', 'health_check_message']); }); diff --git a/database/migrations/2025_08_31_055126_drop_model_has_permissions_table.php b/database/migrations/2025_08_31_055126_drop_model_has_permissions_table.php index 8ee667f..48323e6 100644 --- a/database/migrations/2025_08_31_055126_drop_model_has_permissions_table.php +++ b/database/migrations/2025_08_31_055126_drop_model_has_permissions_table.php @@ -23,16 +23,16 @@ public function down(): void $table->unsignedBigInteger('permission_id'); $table->string('model_type'); $table->unsignedBigInteger('model_id'); - + $table->index(['model_id', 'model_type'], 'model_has_permissions_model_id_model_type_index'); - + $table->foreign('permission_id') - ->references('id') - ->on('permissions') - ->onDelete('cascade'); - + ->references('id') + ->on('permissions') + ->onDelete('cascade'); + $table->primary(['permission_id', 'model_id', 'model_type'], 'model_has_permissions_permission_model_type_primary'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_31_055157_drop_personal_access_tokens_table.php b/database/migrations/2025_08_31_055157_drop_personal_access_tokens_table.php index 567958e..ac3bf35 100644 --- a/database/migrations/2025_08_31_055157_drop_personal_access_tokens_table.php +++ b/database/migrations/2025_08_31_055157_drop_personal_access_tokens_table.php @@ -30,4 +30,4 @@ public function down(): void $table->timestamps(); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_31_055229_drop_permissions_table.php b/database/migrations/2025_08_31_055229_drop_permissions_table.php index a5815f0..88c6e2e 100644 --- a/database/migrations/2025_08_31_055229_drop_permissions_table.php +++ b/database/migrations/2025_08_31_055229_drop_permissions_table.php @@ -24,8 +24,8 @@ public function down(): void $table->string('name'); $table->string('guard_name'); $table->timestamps(); - + $table->unique(['name', 'guard_name']); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_31_055543_rename_viewer_statistics_to_show_statistics.php b/database/migrations/2025_08_31_055543_rename_viewer_statistics_to_show_statistics.php index 0d23bc1..e4b0816 100644 --- a/database/migrations/2025_08_31_055543_rename_viewer_statistics_to_show_statistics.php +++ b/database/migrations/2025_08_31_055543_rename_viewer_statistics_to_show_statistics.php @@ -1,7 +1,6 @@ string('type')->default('user')->after('message'); $table->string('priority')->nullable()->after('type'); $table->json('metadata')->nullable()->after('priority'); - + $table->index('type'); }); } @@ -29,4 +29,4 @@ public function down(): void $table->dropColumn(['type', 'priority', 'metadata']); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_08_31_add_error_status_to_sources_table.php b/database/migrations/2025_08_31_add_error_status_to_sources_table.php index 9e1bdbb..8e8220e 100644 --- a/database/migrations/2025_08_31_add_error_status_to_sources_table.php +++ b/database/migrations/2025_08_31_add_error_status_to_sources_table.php @@ -2,8 +2,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; return new class extends Migration { @@ -14,11 +14,15 @@ public function up(): void { // Update the status column to include 'error' as a valid value // For SQLite (testing), we need to recreate the column - if (config('database.default') === 'sqlite') { + $driver = DB::connection()->getDriverName(); + if ($driver === 'sqlite') { Schema::table('sources', function (Blueprint $table) { // SQLite doesn't support modifying columns directly // We'll handle this differently for testing }); + } elseif ($driver === 'pgsql') { + DB::statement("ALTER TABLE sources ALTER COLUMN status SET DEFAULT 'offline'"); + DB::statement("ALTER TABLE sources ADD CONSTRAINT sources_status_check CHECK (status IN ('online', 'offline', 'error'))"); } else { // For MySQL, we can modify the column directly DB::statement("ALTER TABLE sources MODIFY COLUMN status VARCHAR(255) DEFAULT 'offline' CHECK (status IN ('online', 'offline', 'error'))"); @@ -32,12 +36,16 @@ public function down(): void { // Revert any sources with 'error' status to 'offline' DB::table('sources')->where('status', 'error')->update(['status' => 'offline']); - - if (config('database.default') === 'sqlite') { + + $driver = DB::connection()->getDriverName(); + if ($driver === 'sqlite') { // SQLite doesn't support modifying columns directly + } elseif ($driver === 'pgsql') { + DB::statement('ALTER TABLE sources DROP CONSTRAINT IF EXISTS sources_status_check'); + DB::statement("ALTER TABLE sources ADD CONSTRAINT sources_status_check CHECK (status IN ('online', 'offline'))"); } else { // For MySQL, revert the column constraint DB::statement("ALTER TABLE sources MODIFY COLUMN status VARCHAR(255) DEFAULT 'offline' CHECK (status IN ('online', 'offline'))"); } } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_01_085827_add_auto_mode_to_shows_table.php b/database/migrations/2025_09_01_085827_add_auto_mode_to_shows_table.php index 3314621..ed51276 100644 --- a/database/migrations/2025_09_01_085827_add_auto_mode_to_shows_table.php +++ b/database/migrations/2025_09_01_085827_add_auto_mode_to_shows_table.php @@ -26,4 +26,4 @@ public function down(): void $table->dropColumn('auto_mode'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_01_100251_remove_is_featured_from_shows_table.php b/database/migrations/2025_09_01_100251_remove_is_featured_from_shows_table.php index c9cf770..fb00434 100644 --- a/database/migrations/2025_09_01_100251_remove_is_featured_from_shows_table.php +++ b/database/migrations/2025_09_01_100251_remove_is_featured_from_shows_table.php @@ -27,4 +27,4 @@ public function down(): void $table->index('is_featured'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_01_114326_add_priority_to_sources_table.php b/database/migrations/2025_09_01_114326_add_priority_to_sources_table.php index 7a0087e..7dfd72b 100644 --- a/database/migrations/2025_09_01_114326_add_priority_to_sources_table.php +++ b/database/migrations/2025_09_01_114326_add_priority_to_sources_table.php @@ -27,4 +27,4 @@ public function down(): void $table->dropColumn('priority'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_03_184000_create_recordings_table.php b/database/migrations/2025_09_03_184000_create_recordings_table.php index 50a2d4b..89d5e3e 100644 --- a/database/migrations/2025_09_03_184000_create_recordings_table.php +++ b/database/migrations/2025_09_03_184000_create_recordings_table.php @@ -22,7 +22,7 @@ public function up(): void $table->integer('views')->default(0); $table->boolean('is_published')->default(true); $table->timestamps(); - + $table->index(['is_published', 'date']); }); } @@ -34,4 +34,4 @@ public function down(): void { Schema::dropIfExists('recordings'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_04_121126_add_show_id_and_slug_to_recordings_table.php b/database/migrations/2025_09_04_121126_add_show_id_and_slug_to_recordings_table.php index 61c2c4a..7f09e5d 100644 --- a/database/migrations/2025_09_04_121126_add_show_id_and_slug_to_recordings_table.php +++ b/database/migrations/2025_09_04_121126_add_show_id_and_slug_to_recordings_table.php @@ -13,33 +13,33 @@ public function up(): void { // First add columns without unique constraint Schema::table('recordings', function (Blueprint $table) { - if (!Schema::hasColumn('recordings', 'show_id')) { + if (! Schema::hasColumn('recordings', 'show_id')) { $table->unsignedBigInteger('show_id')->nullable()->after('id'); $table->foreign('show_id')->references('id')->on('shows')->onDelete('set null'); $table->index('show_id'); } - - if (!Schema::hasColumn('recordings', 'slug')) { + + if (! Schema::hasColumn('recordings', 'slug')) { $table->string('slug')->nullable()->after('title'); } }); - + // Generate slugs for existing records $recordings = \App\Models\Recording::all(); foreach ($recordings as $recording) { $baseSlug = \Illuminate\Support\Str::slug($recording->title); $slug = $baseSlug; $count = 1; - + while (\App\Models\Recording::where('slug', $slug)->where('id', '!=', $recording->id)->exists()) { - $slug = $baseSlug . '-' . $count; + $slug = $baseSlug.'-'.$count; $count++; } - + $recording->slug = $slug; $recording->save(); } - + // Now make slug not nullable and add unique constraint if (Schema::hasColumn('recordings', 'slug')) { Schema::table('recordings', function (Blueprint $table) { @@ -60,4 +60,4 @@ public function down(): void $table->dropColumn(['show_id', 'slug']); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_09_04_121200_add_recordable_to_shows_table.php b/database/migrations/2025_09_04_121200_add_recordable_to_shows_table.php index 555cd5a..3e11633 100644 --- a/database/migrations/2025_09_04_121200_add_recordable_to_shows_table.php +++ b/database/migrations/2025_09_04_121200_add_recordable_to_shows_table.php @@ -25,4 +25,4 @@ public function down(): void $table->dropColumn('recordable'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_07_29_233211_create_jobs_table.php b/database/migrations/2026_07_29_233211_create_jobs_table.php new file mode 100644 index 0000000..6098d9b --- /dev/null +++ b/database/migrations/2026_07_29_233211_create_jobs_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + } +}; diff --git a/database/migrations/2026_07_29_235000_create_branding_settings_table.php b/database/migrations/2026_07_29_235000_create_branding_settings_table.php new file mode 100644 index 0000000..3c6bff7 --- /dev/null +++ b/database/migrations/2026_07_29_235000_create_branding_settings_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('key')->unique(); + $table->text('value')->nullable(); + $table->text('description')->nullable(); + $table->timestamps(); + + $table->index('key'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('branding_settings'); + } +}; diff --git a/database/migrations/2026_07_30_120000_add_auto_stop_at_to_shows_table.php b/database/migrations/2026_07_30_120000_add_auto_stop_at_to_shows_table.php new file mode 100644 index 0000000..952ef4e --- /dev/null +++ b/database/migrations/2026_07_30_120000_add_auto_stop_at_to_shows_table.php @@ -0,0 +1,33 @@ +timestamp('auto_stop_at')->nullable()->after('auto_mode'); + }); + } + + public function down(): void + { + Schema::table('shows', function (Blueprint $table) { + $table->dropColumn('auto_stop_at'); + }); + } +}; diff --git a/database/migrations/2026_07_30_150000_revamp_chat_system.php b/database/migrations/2026_07_30_150000_revamp_chat_system.php new file mode 100644 index 0000000..b48f8c2 --- /dev/null +++ b/database/migrations/2026_07_30_150000_revamp_chat_system.php @@ -0,0 +1,76 @@ +text('message')->change(); + $table->foreignId('reply_to_id')->nullable()->after('source_id') + ->constrained('messages')->nullOnDelete(); + $table->index(['source_id', 'created_at']); + }); + + Schema::create('chat_bans', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('banned_by_user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->text('reason')->nullable(); + $table->timestamp('expires_at')->nullable(); // null = permanent + $table->timestamp('lifted_at')->nullable(); + $table->foreignId('lifted_by_user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->timestamps(); + + $table->index(['user_id', 'lifted_at']); + }); + + Schema::create('chat_moderation_logs', function (Blueprint $table) { + $table->id(); + $table->string('action'); + $table->foreignId('moderator_id')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('target_user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('source_id')->nullable()->constrained('sources')->nullOnDelete(); + $table->text('reason')->nullable(); + $table->json('metadata')->nullable(); + $table->timestamps(); + + $table->index(['source_id', 'created_at']); + $table->index(['target_user_id', 'created_at']); + }); + + // Chat settings become per-source, with source_id = null acting as the global default. + Schema::table('chat_settings', function (Blueprint $table) { + $table->foreignId('source_id')->nullable()->after('id') + ->constrained('sources')->cascadeOnDelete(); + }); + + Schema::table('chat_settings', function (Blueprint $table) { + $table->dropUnique('chat_settings_key_unique'); + $table->unique(['key', 'source_id']); + }); + } + + public function down(): void + { + Schema::table('chat_settings', function (Blueprint $table) { + $table->dropUnique(['key', 'source_id']); + $table->dropConstrainedForeignId('source_id'); + $table->unique('key'); + }); + + Schema::dropIfExists('chat_moderation_logs'); + Schema::dropIfExists('chat_bans'); + + Schema::table('messages', function (Blueprint $table) { + $table->dropIndex(['source_id', 'created_at']); + $table->dropConstrainedForeignId('reply_to_id'); + $table->string('message', 500)->change(); + }); + } +}; diff --git a/database/migrations/2026_08_03_010000_make_recordings_cuts_over_the_archive.php b/database/migrations/2026_08_03_010000_make_recordings_cuts_over_the_archive.php new file mode 100644 index 0000000..6e3c452 --- /dev/null +++ b/database/migrations/2026_08_03_010000_make_recordings_cuts_over_the_archive.php @@ -0,0 +1,86 @@ +foreignId('source_id')->nullable()->after('show_id') + ->constrained()->nullOnDelete(); + + // The cut. Distinct from shows.actual_start/actual_end on purpose: those + // record when the show aired, these record what the viewer sees. They start + // equal and diverge as an operator trims. + $table->timestamp('starts_at')->nullable()->after('date'); + $table->timestamp('ends_at')->nullable()->after('starts_at'); + + // Where the segments live, e.g. archive/prime. Stored rather than derived so + // a recording still resolves if a source is later renamed. + $table->string('archive_prefix')->nullable()->after('ends_at'); + + // draft - cut exists, playlist not built yet + // ready - playlist built, not visible to viewers + // failed - playlist build failed; message in build_error + // Publication stays on is_published; this is about the artefact, not access. + $table->string('status')->default('draft')->after('archive_prefix'); + $table->text('build_error')->nullable()->after('status'); + $table->timestamp('playlist_built_at')->nullable()->after('build_error'); + + $table->unsignedInteger('segment_count')->nullable()->after('playlist_built_at'); + + $table->index(['status', 'is_published']); + $table->index(['source_id', 'starts_at']); + }); + + // m3u8_url is now generated rather than supplied, so it cannot be required at + // insert time: a draft exists before its playlist is built. + Schema::table('recordings', function (Blueprint $table) { + $table->string('m3u8_url')->nullable()->change(); + }); + + Schema::table('shows', function (Blueprint $table) { + $table->dropColumn('recordable'); + }); + } + + public function down(): void + { + Schema::table('shows', function (Blueprint $table) { + $table->boolean('recordable')->default(false)->after('auto_mode'); + }); + + Schema::table('recordings', function (Blueprint $table) { + $table->dropIndex(['status', 'is_published']); + $table->dropIndex(['source_id', 'starts_at']); + $table->dropConstrainedForeignId('source_id'); + $table->dropColumn([ + 'starts_at', 'ends_at', 'archive_prefix', 'status', + 'build_error', 'playlist_built_at', 'segment_count', + ]); + }); + } +}; diff --git a/database/migrations/2026_08_03_010000_replace_assigned_at_login_with_external_id_on_roles.php b/database/migrations/2026_08_03_010000_replace_assigned_at_login_with_external_id_on_roles.php new file mode 100644 index 0000000..ac98f98 --- /dev/null +++ b/database/migrations/2026_08_03_010000_replace_assigned_at_login_with_external_id_on_roles.php @@ -0,0 +1,96 @@ +string('external_id')->nullable()->unique()->after('slug'); + }); + + $this->backfill(); + + Schema::table('roles', function (Blueprint $table) { + if (Schema::hasColumn('roles', 'assigned_at_login')) { + // The index has to go first: dropping a column that one covers + // fails on MySQL. + try { + $table->dropIndex('roles_assigned_at_login_index'); + } catch (\Throwable) { + // Older installs never had the index. + } + + $table->dropColumn('assigned_at_login'); + } + }); + } + + public function down(): void + { + Schema::table('roles', function (Blueprint $table) { + $table->boolean('assigned_at_login')->default(true)->after('slug'); + $table->index('assigned_at_login'); + }); + + // Whatever carried an identifier was the set being synced. + DB::table('roles')->update(['assigned_at_login' => false]); + DB::table('roles')->whereNotNull('external_id')->update(['assigned_at_login' => true]); + + Schema::table('roles', function (Blueprint $table) { + $table->dropUnique(['external_id']); + $table->dropColumn('external_id'); + }); + } + + /** + * Carry the old mapping over so an existing install keeps syncing without + * anyone retyping group IDs. + * + * Two sources: the OIDC_GROUP_ROLE_MAP pairs, which held the provider group + * IDs, and the sponsor tiers, which were matched by slug against the + * registration packages. + */ + private function backfill(): void + { + foreach (explode(',', (string) env('OIDC_GROUP_ROLE_MAP', '')) as $pair) { + $parts = array_map('trim', explode('=', $pair, 2)); + + if (count($parts) !== 2 || $parts[0] === '' || $parts[1] === '') { + continue; + } + + [$groupId, $slug] = $parts; + + DB::table('roles') + ->where('slug', $slug) + ->whereNull('external_id') + ->update(['external_id' => $groupId]); + } + + if (! Schema::hasColumn('roles', 'assigned_at_login')) { + return; + } + + // Package-derived tiers matched on their own slug, so that is their identifier. + DB::table('roles') + ->whereIn('slug', ['sponsor', 'super-sponsor', 'supersponsor']) + ->where('assigned_at_login', true) + ->whereNull('external_id') + ->update(['external_id' => DB::raw('slug')]); + } +}; diff --git a/database/migrations/2026_08_03_010500_add_announce_recording_to_shows_table.php b/database/migrations/2026_08_03_010500_add_announce_recording_to_shows_table.php new file mode 100644 index 0000000..ff79faf --- /dev/null +++ b/database/migrations/2026_08_03_010500_add_announce_recording_to_shows_table.php @@ -0,0 +1,34 @@ +boolean('announce_recording')->default(false)->after('auto_mode'); + }); + } + + public function down(): void + { + Schema::table('shows', function (Blueprint $table) { + $table->dropColumn('announce_recording'); + }); + } +}; diff --git a/database/migrations/2026_08_03_020000_make_recording_cut_markers_naive_timestamps.php b/database/migrations/2026_08_03_020000_make_recording_cut_markers_naive_timestamps.php new file mode 100644 index 0000000..951ccde --- /dev/null +++ b/database/migrations/2026_08_03_020000_make_recording_cut_markers_naive_timestamps.php @@ -0,0 +1,81 @@ +getDriverName() !== 'pgsql') { + return; + } + + foreach (['starts_at', 'ends_at', 'playlist_built_at'] as $column) { + if (! $this->isTimestampTz($column)) { + continue; + } + + // USING keeps the instant Postgres currently holds; dropping the offset then + // leaves the digits reading as the local time they were always meant to be. + DB::statement( + "ALTER TABLE recordings ALTER COLUMN {$column} TYPE timestamp without time zone " + ."USING {$column} AT TIME ZONE 'UTC'" + ); + } + } + + public function down(): void + { + if (DB::connection()->getDriverName() !== 'pgsql') { + return; + } + + foreach (['starts_at', 'ends_at', 'playlist_built_at'] as $column) { + DB::statement( + "ALTER TABLE recordings ALTER COLUMN {$column} TYPE timestamp with time zone " + ."USING {$column} AT TIME ZONE 'UTC'" + ); + } + } + + protected function isTimestampTz(string $column): bool + { + $type = DB::selectOne( + 'SELECT data_type FROM information_schema.columns ' + .'WHERE table_name = ? AND column_name = ?', + ['recordings', $column], + ); + + return $type && str_contains($type->data_type, 'with time zone'); + } +}; diff --git a/database/migrations/2026_08_03_100000_add_pretalx_slot_id_to_shows_table.php b/database/migrations/2026_08_03_100000_add_pretalx_slot_id_to_shows_table.php new file mode 100644 index 0000000..91773c8 --- /dev/null +++ b/database/migrations/2026_08_03_100000_add_pretalx_slot_id_to_shows_table.php @@ -0,0 +1,27 @@ +string('pretalx_slot_id')->nullable()->unique()->after('metadata'); + }); + } + + public function down(): void + { + Schema::table('shows', function (Blueprint $table) { + $table->dropUnique(['pretalx_slot_id']); + $table->dropColumn('pretalx_slot_id'); + }); + } +}; diff --git a/database/migrations/2026_08_03_100100_create_pretalx_room_sources_table.php b/database/migrations/2026_08_03_100100_create_pretalx_room_sources_table.php new file mode 100644 index 0000000..d6c08be --- /dev/null +++ b/database/migrations/2026_08_03_100100_create_pretalx_room_sources_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('event_slug'); + $table->unsignedBigInteger('room_id'); + $table->string('room_name')->nullable(); + $table->foreignId('source_id')->nullable()->constrained()->nullOnDelete(); + $table->timestamps(); + + $table->unique(['event_slug', 'room_id']); + }); + } + + public function down(): void + { + Schema::dropIfExists('pretalx_room_sources'); + } +}; diff --git a/database/migrations/2026_08_03_120000_fold_footer_urls_into_footer_links.php b/database/migrations/2026_08_03_120000_fold_footer_urls_into_footer_links.php new file mode 100644 index 0000000..259a7ec --- /dev/null +++ b/database/migrations/2026_08_03_120000_fold_footer_urls_into_footer_links.php @@ -0,0 +1,82 @@ + + */ + private const LEGACY = [ + 'support_url' => 'Support', + 'imprint_url' => 'Legal Notice', + 'privacy_url' => 'Privacy', + ]; + + public function up(): void + { + $rows = DB::table('branding_settings') + ->whereIn('key', array_keys(self::LEGACY)) + ->pluck('value', 'key'); + + $links = []; + + foreach (self::LEGACY as $key => $label) { + $url = trim((string) ($rows[$key] ?? '')); + + if ($url !== '') { + $links[] = ['label' => $label, 'url' => $url]; + } + } + + if ($links !== []) { + DB::table('branding_settings')->updateOrInsert( + ['key' => 'footer_links'], + [ + 'value' => json_encode($links), + 'description' => 'Title and address for each footer link, in the order they are shown.', + 'updated_at' => now(), + 'created_at' => now(), + ], + ); + } + + DB::table('branding_settings')->whereIn('key', array_keys(self::LEGACY))->delete(); + } + + public function down(): void + { + $stored = DB::table('branding_settings')->where('key', 'footer_links')->value('value'); + $links = json_decode((string) $stored, true) ?: []; + + // Only the three known titles can go back into named slots; anything an + // installation added beyond them has nowhere to live in the old shape. + foreach (self::LEGACY as $key => $label) { + $match = collect($links)->firstWhere('label', $label); + + if ($match === null) { + continue; + } + + DB::table('branding_settings')->updateOrInsert( + ['key' => $key], + [ + 'value' => $match['url'], + 'updated_at' => now(), + 'created_at' => now(), + ], + ); + } + + DB::table('branding_settings')->where('key', 'footer_links')->delete(); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 9a47593..10adf22 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -22,7 +22,7 @@ public function run(): void $this->call([ RoleSeeder::class, ]); - + // Create local development servers and test source for testing if (App::isLocal()) { $this->call([ diff --git a/database/seeders/DevStreamChannelsSeeder.php b/database/seeders/DevStreamChannelsSeeder.php new file mode 100644 index 0000000..887831b --- /dev/null +++ b/database/seeders/DevStreamChannelsSeeder.php @@ -0,0 +1,100 @@ + 'prime', 'name' => 'Prime', 'priority' => 100, 'description' => 'The main channel: ceremonies, the parade and the big stage shows.'], + ['slug' => 'dance-stage', 'name' => 'Dance Stage', 'priority' => 60, 'description' => 'Dance competition, DJ sets and everything after dark.'], + ['slug' => 'panel-room', 'name' => 'Panel Room', 'priority' => 40, 'description' => 'Talks, workshops and Q&A sessions.'], + ['slug' => 'art-track', 'name' => 'Art Track', 'priority' => 20, 'description' => 'Art show walkthroughs, live drawing and the auction.'], + ]; + + public function run(): void + { + if (! app()->isLocal()) { + $this->command->warn('DevStreamChannelsSeeder only runs in local.'); + + return; + } + + $now = Carbon::now(); + + foreach (self::CHANNELS as $channel) { + $source = Source::updateOrCreate( + ['slug' => $channel['slug']], + [ + 'name' => $channel['name'], + 'description' => $channel['description'], + 'priority' => $channel['priority'], + 'status' => SourceStatusEnum::OFFLINE, + 'stream_key' => 'dev_'.$channel['slug'].'_'.Str::random(12), + ] + ); + + $this->command->info("Channel: {$source->name} (priority {$source->priority})"); + } + + $prime = Source::where('slug', 'prime')->first(); + $dance = Source::where('slug', 'dance-stage')->first(); + $panels = Source::where('slug', 'panel-room')->first(); + $art = Source::where('slug', 'art-track')->first(); + + // Live now: the primary channel plus two others, so the hero has a + // featured show and the grid has company. + $this->show($prime, 'Prime', 'Your 24/7 channel showing the prime shows and fun content from other conventions.', $now->copy()->subDay(), $now->copy()->addDays(7), 'live', 2148); + $this->show($prime, 'Opening Ceremony', 'Guest of honour introductions, the charity reveal and the first look at this year\'s theme.', $now->copy()->addMinutes(36), $now->copy()->addMinutes(96), 'scheduled'); + $this->show($panels, 'Fursuit Care Panel', 'Washing, drying, repairs, and how to survive a hot con day in full suit.', $now->copy()->subMinutes(26), $now->copy()->addMinutes(34), 'live', 312); + $this->show($art, 'Art Show Walkthrough', 'A slow walk past every piece in the art show, with commentary from the artists.', $now->copy()->subMinutes(41), $now->copy()->addMinutes(19), 'live', 96); + + // Starting soon and later today. + $this->show($prime, 'Fursuit Parade', 'Every suiter in the building, one lap of the hall.', $now->copy()->addMinutes(120), $now->copy()->addMinutes(180), 'scheduled'); + $this->show($dance, 'Dance Competition Prelims', 'First round of the dance competition.', $now->copy()->addMinutes(75), $now->copy()->addMinutes(255), 'scheduled'); + $this->show($prime, 'Charity Auction', 'Bid on the good stuff. All proceeds to this year\'s charity.', $now->copy()->addHours(3), $now->copy()->addHours(5), 'scheduled'); + $this->show($panels, 'Writing Workshop', 'Bring a draft, leave with edits.', $now->copy()->addHours(4), $now->copy()->addHours(5)->addMinutes(30), 'scheduled'); + $this->show($dance, 'Closing Dance', 'The last set of the con.', $now->copy()->addHours(6), $now->copy()->addHours(9), 'scheduled'); + + // Tomorrow, so the guide has a second day tab. + $tomorrow = $now->copy()->addDay()->setTime(11, 0); + $this->show($prime, 'Closing Ceremony', 'Thank yous, numbers, and next year\'s theme.', $tomorrow->copy(), $tomorrow->copy()->addHours(2), 'scheduled'); + $this->show($art, 'Art Auction', 'The pieces that went to auction, under the hammer.', $tomorrow->copy()->addHours(3), $tomorrow->copy()->addHours(5), 'scheduled'); + + $this->command->info(''); + $this->command->info('Channels seeded. Start the video with: ./scripts/dev-streams.sh'); + } + + private function show(?Source $source, string $title, string $description, Carbon $start, Carbon $end, string $status, int $viewers = 0): void + { + if (! $source) { + return; + } + + Show::updateOrCreate( + ['slug' => Str::slug($source->slug.'-'.$title)], + [ + 'title' => $title, + 'description' => $description, + 'source_id' => $source->id, + 'scheduled_start' => $start, + 'scheduled_end' => $end, + 'actual_start' => $status === 'live' ? $start : null, + 'status' => $status, + 'viewer_count' => $viewers, + 'announce_recording' => true, + ] + ); + } +} diff --git a/database/seeders/LocalDevelopmentServersSeeder.php b/database/seeders/LocalDevelopmentServersSeeder.php index f0c2ae7..f24f30c 100644 --- a/database/seeders/LocalDevelopmentServersSeeder.php +++ b/database/seeders/LocalDevelopmentServersSeeder.php @@ -17,8 +17,9 @@ class LocalDevelopmentServersSeeder extends Seeder public function run(): void { // Only run in local environment - if (!app()->isLocal()) { + if (! app()->isLocal()) { $this->command->info('Skipping local development servers seeder (not in local environment)'); + return; } @@ -80,12 +81,12 @@ public function run(): void ['Edge', "{$localEdge->hostname}:{$localEdge->port}", $localEdge->port, $localEdge->status->value, 'Browser access point / CDN'], ] ); - + $this->command->info(''); $this->command->info('To start streaming:'); - $this->command->info('1. Run: docker-compose up'); + $this->command->info('1. Run the SRS origin/edge stack separately (no longer bundled via docker-compose locally)'); $this->command->info('2. Configure OBS with RTMP URL: rtmp://localhost:1935/live'); $this->command->info('3. Use stream key from your Source model'); $this->command->info('4. Access HLS stream at: http://localhost:8085/live/{source_slug}_fhd/index.m3u8'); } -} \ No newline at end of file +} diff --git a/database/seeders/LocalDevelopmentSourceSeeder.php b/database/seeders/LocalDevelopmentSourceSeeder.php index 71465bd..7fe1e32 100644 --- a/database/seeders/LocalDevelopmentSourceSeeder.php +++ b/database/seeders/LocalDevelopmentSourceSeeder.php @@ -16,22 +16,24 @@ class LocalDevelopmentSourceSeeder extends Seeder public function run(): void { // Only run in local environment - if (!app()->isLocal()) { + if (! app()->isLocal()) { $this->command->info('Skipping local development source seeder (not in local environment)'); + return; } - $this->command->info('Creating local development test source...'); + $this->command->info('Creating local development source...'); - // Create a test source + // The primary channel, same name as production so local screenshots match $source = Source::updateOrCreate( [ - 'slug' => 'test-stream', + 'slug' => 'prime', ], [ - 'name' => 'Test Stream', - 'description' => 'Local development test stream for testing RTMP ingress and HLS distribution', - 'stream_key' => 'test_secret_key_' . Str::random(16), + 'name' => 'Prime', + 'description' => 'The main channel: ceremonies, the parade and the big stage shows.', + 'priority' => 100, + 'stream_key' => 'dev_prime_'.Str::random(16), 'status' => SourceStatusEnum::OFFLINE, ] ); @@ -42,20 +44,20 @@ public function run(): void $this->command->info('║ OBS CONFIGURATION SETTINGS ║'); $this->command->info('╠══════════════════════════════════════════════════════════════════════════╣'); $this->command->info('║ Server URL: rtmp://localhost:1935/live ║'); - $this->command->info('║ Stream Key: ' . str_pad($source->getObsStreamKey(), 60) . ' ║'); + $this->command->info('║ Stream Key: '.str_pad($source->getObsStreamKey(), 60).' ║'); $this->command->info('╚══════════════════════════════════════════════════════════════════════════╝'); $this->command->info(''); $this->command->info('HLS Playback URLs:'); - $this->command->info(' Master Playlist: http://localhost:8085/live/test-stream/index.m3u8'); - $this->command->info(' FHD Quality: http://localhost:8085/live/test-stream_fhd/index.m3u8'); - $this->command->info(' HD Quality: http://localhost:8085/live/test-stream_hd/index.m3u8'); - $this->command->info(' SD Quality: http://localhost:8085/live/test-stream_sd/index.m3u8'); + $this->command->info(' Master Playlist: http://localhost:8085/live/prime/index.m3u8'); + $this->command->info(' FHD Quality: http://localhost:8085/live/prime_fhd/index.m3u8'); + $this->command->info(' HD Quality: http://localhost:8085/live/prime_hd/index.m3u8'); + $this->command->info(' SD Quality: http://localhost:8085/live/prime_sd/index.m3u8'); $this->command->info(''); $this->command->info('Testing with VLC:'); - $this->command->info(' vlc http://localhost:8085/live/test-stream_fhd/index.m3u8'); + $this->command->info(' vlc http://localhost:8085/live/prime_fhd/index.m3u8'); $this->command->info(''); $this->command->info('Testing with ffplay:'); - $this->command->info(' ffplay http://localhost:8085/live/test-stream_fhd/index.m3u8'); + $this->command->info(' ffplay http://localhost:8085/live/prime_fhd/index.m3u8'); $this->command->info(''); } -} \ No newline at end of file +} diff --git a/database/seeders/RecordingSeeder.php b/database/seeders/RecordingSeeder.php index 0f9116c..7e6cd06 100644 --- a/database/seeders/RecordingSeeder.php +++ b/database/seeders/RecordingSeeder.php @@ -17,7 +17,7 @@ public function run(): void [ 'title' => 'Opening Ceremony 2024', 'slug' => 'opening-ceremony-2024', - 'description' => 'The grand opening ceremony of Eurofurence 2024, featuring special guests, announcements, and a spectacular light show.', + 'description' => 'The grand opening ceremony of the 2024 convention, featuring special guests, announcements, and a spectacular light show.', 'date' => Carbon::now()->subDays(7)->setHour(19)->setMinute(0), 'duration' => 5400, // 1.5 hours 'm3u8_url' => 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', @@ -67,7 +67,7 @@ public function run(): void [ 'title' => 'Closing Ceremony 2024', 'slug' => 'closing-ceremony-2024', - 'description' => 'The emotional closing ceremony of Eurofurence 2024. See you next year!', + 'description' => 'The emotional closing ceremony of the 2024 convention. See you next year!', 'date' => Carbon::now()->subDays(1)->setHour(18)->setMinute(0), 'duration' => 3600, // 1 hour 'm3u8_url' => 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', @@ -77,7 +77,7 @@ public function run(): void [ 'title' => 'Behind the Scenes Documentary', 'slug' => 'behind-the-scenes-2024', - 'description' => 'A special documentary showing the preparation and hard work that goes into making Eurofurence happen.', + 'description' => 'A special documentary showing the preparation and hard work that goes into making the convention happen.', 'date' => Carbon::now()->subHours(12), 'duration' => 2700, // 45 minutes 'm3u8_url' => 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index 170fdb3..8650802 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -20,7 +20,7 @@ public function run(): void 'description' => 'Full system administrator with all permissions', 'chat_color' => '#FF0000', // Red color for admins 'priority' => 100, // Highest priority - 'assigned_at_login' => true, // Synced from identity provider groups + 'external_id' => null, // Set the provider's group ID to sync this role at login 'is_visible' => true, 'permissions' => [ 'admin.access', @@ -59,7 +59,7 @@ public function run(): void 'description' => 'Chat and user moderator with limited permissions', 'chat_color' => '#00FF00', // Green color for moderators 'priority' => 50, // High priority but less than admin - 'assigned_at_login' => true, // Synced from identity provider groups + 'external_id' => null, // Set the provider's group ID to sync this role at login 'is_visible' => true, 'permissions' => [ 'filament.access', @@ -79,15 +79,15 @@ public function run(): void ] ); - // Staff role (general EF staff) + // Staff role (general convention staff) Role::updateOrCreate( ['slug' => 'staff'], [ 'name' => 'Staff', - 'description' => 'General Eurofurence staff member', + 'description' => 'General convention staff member', 'chat_color' => '#3B82F6', // Blue color for staff 'priority' => 30, - 'assigned_at_login' => true, // Synced from identity provider groups + 'external_id' => null, // Set the provider's group ID to sync this role at login 'is_visible' => true, 'permissions' => [ 'chat.send', @@ -108,7 +108,7 @@ public function run(): void 'description' => 'Event super sponsor', 'chat_color' => '#83559e', // Purple color for super sponsors 'priority' => 28, - 'assigned_at_login' => true, // Can be synced from registration system + 'external_id' => null, // Set the registration package name to sync this role at login 'is_visible' => true, 'permissions' => [ 'chat.bypass_slow_mode', @@ -129,7 +129,7 @@ public function run(): void 'description' => 'Event sponsor', 'chat_color' => '#f6cb21', // Yellow/Gold color for sponsors 'priority' => 25, - 'assigned_at_login' => true, // Can be synced from registration system + 'external_id' => null, // Set the registration package name to sync this role at login 'is_visible' => true, 'permissions' => [ 'chat.bypass_slow_mode', @@ -149,7 +149,7 @@ public function run(): void 'description' => 'Registered attendee', 'chat_color' => null, // Use default chat color 'priority' => 10, - 'assigned_at_login' => true, // Can be synced from registration system + 'external_id' => null, // Set the registration package name to sync this role at login 'is_visible' => false, // Don't show badge for regular attendees 'permissions' => [ 'chat.send', @@ -167,7 +167,6 @@ public function run(): void 'description' => 'Authenticated user without ticket', 'chat_color' => null, // Use default chat color 'priority' => 5, // Lower priority than attendee - 'assigned_at_login' => false, 'is_visible' => false, // Don't show badge 'permissions' => [ 'chat.send', diff --git a/database/seeders/ShowSeeder.php b/database/seeders/ShowSeeder.php index 1780313..cb86ae4 100644 --- a/database/seeders/ShowSeeder.php +++ b/database/seeders/ShowSeeder.php @@ -2,115 +2,148 @@ namespace Database\Seeders; +use App\Enum\SourceStatusEnum; use App\Models\Show; use App\Models\Source; use Carbon\Carbon; use Illuminate\Database\Seeder; +use Illuminate\Support\Str; +/** + * A running order worth planning against. + * + * Two rules the old seed broke, and the planner made obvious: + * + * 1. No two shows on the same source may overlap. A 24/7 "Prime" block spanning eight + * days sat under every other show on the same lane, so every block was a clash and + * dragging was meaningless. + * 2. More than one lane. Multi-track planning needs multiple sources to plan across. + * + * Times are laid out as a real convention day: a morning slot, an afternoon block, evening + * shows, and a dance that crosses midnight on the party channel. + */ class ShowSeeder extends Seeder { + /** + * Extra dev channels, so the planner has tracks to lay shows across. The primary + * channel comes from LocalDevelopmentSourceSeeder. + */ + private const EXTRA_SOURCES = [ + ['slug' => 'stage-b', 'name' => 'Stage B', 'priority' => 50, 'description' => 'Panels, workshops and the smaller rooms.'], + ['slug' => 'dance', 'name' => 'Dance', 'priority' => 20, 'description' => 'The dance stage. Runs late.'], + ]; + public function run(): void { - $source = Source::first(); + if (Show::count() > 0) { + $this->command->info('Shows already exist, skipping seeding.'); - if (!$source) { - $this->command->error('No source found. Please run LocalDevelopmentSourceSeeder first.'); return; } - // if shows already exist, skip seeding - if (Show::count() > 0) { - $this->command->info('Shows already exist, skipping seeding.'); + $prime = Source::where('slug', 'prime')->first(); + + if (! $prime) { + $this->command->error('No source found. Please run LocalDevelopmentSourceSeeder first.'); + return; } - $now = Carbon::now(); - - // Main show - started yesterday, ends in 7 days (LIVE) - Show::create([ - 'title' => 'Main Stage', - 'slug' => 'main-stage', - 'description' => 'Live coverage of the main convention stage featuring panels, performances, and special events throughout the day.', - 'scheduled_start' => $now->copy()->subDay(), - 'scheduled_end' => $now->copy()->addDays(7), - 'actual_start' => $now->copy()->subDay(), - 'source_id' => $source->id, - 'thumbnail_path' => null, - 'status' => 'live', - ]); - - // Main show subtitles stream (LIVE) - Show::create([ - 'title' => 'Main Stage (Subtitled)', - 'slug' => 'main-stage-subtitled', - 'description' => 'Main convention stage with live subtitles for accessibility. Same content as the main stream with real-time captions.', - 'scheduled_start' => $now->copy()->subDay(), - 'scheduled_end' => $now->copy()->addDays(7), - 'actual_start' => $now->copy()->subDay(), - 'source_id' => $source->id, - 'thumbnail_path' => null, - 'status' => 'live', - ]); - - // Pop quiz - currently live, ends in an hour (LIVE) - Show::create([ - 'title' => 'Pop Quiz Hour', - 'slug' => 'pop-quiz-hour', - 'description' => 'Join us for the interactive pop quiz! Test your knowledge about the convention, fandom, and special guests.', - 'scheduled_start' => $now->copy()->subHours(2), - 'scheduled_end' => $now->copy()->addHour(), - 'actual_start' => $now->copy()->subHours(2), - 'source_id' => $source->id, - 'thumbnail_path' => null, - 'status' => 'live', - ]); - - // Schedule 5 sequential events, each running an hour - $eventStartTime = $now->copy()->addHours(2); - - $events = [ - [ - 'title' => 'Artist Alley Showcase', - 'slug' => 'artist-alley-showcase', - 'description' => 'Meet the talented artists and see live demonstrations of various art techniques and styles.', - ], - [ - 'title' => 'Fursuit Parade', - 'slug' => 'fursuit-parade', - 'description' => 'The annual fursuit parade featuring hundreds of amazing costumes from attendees around the world.', - ], - [ - 'title' => 'Voice Acting Workshop', - 'slug' => 'voice-acting-workshop', - 'description' => 'Learn the basics of voice acting from industry professionals in this interactive workshop.', - ], - [ - 'title' => 'Game Show Hour', - 'slug' => 'game-show-hour', - 'description' => 'Contestants compete in various challenges and trivia games with prizes from convention sponsors.', - ], - [ - 'title' => 'Closing Ceremony', - 'slug' => 'closing-ceremony', - 'description' => 'Join us for the official closing ceremony, awards presentation, and announcement of next year\'s convention.', - ], + $stageB = $this->source(self::EXTRA_SOURCES[0]); + $dance = $this->source(self::EXTRA_SOURCES[1]); + + $today = Carbon::today(); + + // Lane, title, day offset, start clock, length in minutes, status. + $plan = [ + // --- Primary channel: the big room, one show at a time. + [$prime, 'Opening Ceremony', 0, '10:00', 90, 'ended'], + [$prime, 'Artist Alley Showcase', 0, '12:00', 60, 'ended'], + [$prime, 'Fursuit Parade', 0, '14:00', 120, 'live'], + [$prime, 'Game Show Hour', 0, '17:00', 60, 'scheduled'], + [$prime, 'Evening Feature', 0, '20:00', 150, 'scheduled'], + [$prime, 'Morning Warm-up', 1, '09:30', 45, 'scheduled'], + [$prime, 'Charity Auction', 1, '13:00', 120, 'scheduled'], + [$prime, 'Closing Ceremony', 2, '15:00', 90, 'scheduled'], + + // --- Second stage: runs alongside, never against itself. + [$stageB, 'Voice Acting Workshop', 0, '11:00', 75, 'ended'], + [$stageB, 'Dealers Den Tour', 0, '13:00', 45, 'ended'], + [$stageB, 'Panel: Art of Fursuiting', 0, '15:30', 60, 'scheduled'], + [$stageB, 'Writers Round Table', 0, '18:00', 90, 'scheduled'], + [$stageB, 'Fandom History Talk', 1, '11:00', 60, 'scheduled'], + [$stageB, 'Photography Workshop', 1, '16:00', 90, 'scheduled'], + + // --- Dance: the late one, crossing midnight. This is the hard-stop case. + [$dance, 'Warm-up Set', 0, '21:00', 60, 'scheduled'], + [$dance, 'Headline Dance', 0, '22:30', 210, 'scheduled'], + [$dance, 'Afterhours', 1, '22:00', 180, 'scheduled'], ]; - foreach ($events as $event) { + foreach ($plan as [$source, $title, $dayOffset, $clock, $minutes, $status]) { + $start = $today->clone()->addDays($dayOffset)->setTimeFromTimeString($clock); + $end = $start->clone()->addMinutes($minutes); + Show::create([ - 'title' => $event['title'], - 'slug' => $event['slug'], - 'description' => $event['description'], - 'scheduled_start' => $eventStartTime->copy(), - 'scheduled_end' => $eventStartTime->copy()->addHour(), + 'title' => $title, + 'slug' => Str::slug($title).'-'.$start->format('Y-m-d'), + 'description' => $title.' on '.$source->name.'.', 'source_id' => $source->id, + 'scheduled_start' => $start, + 'scheduled_end' => $end, + // Only what actually ran has real timestamps; a scheduled show has none. + 'actual_start' => in_array($status, ['ended', 'live'], true) ? $start : null, + 'actual_end' => $status === 'ended' ? $end : null, + 'status' => $status, + // The dance is the case auto mode exists for: nobody is awake to end it. + 'auto_mode' => $source->is($dance), + 'auto_stop_at' => $source->is($dance) ? $end : null, + 'announce_recording' => true, + 'required_roles' => [], 'thumbnail_path' => null, - 'status' => 'scheduled', ]); - - $eventStartTime->addHour(); } - $this->command->info('Show seeder completed successfully!'); + $this->assertNoOverlaps(); + + $this->command->info('Seeded '.count($plan).' shows across 3 channels, no overlaps.'); + } + + /** + * @param array $attributes + */ + private function source(array $attributes): Source + { + return Source::updateOrCreate( + ['slug' => $attributes['slug']], + [ + 'name' => $attributes['name'], + 'description' => $attributes['description'], + 'priority' => $attributes['priority'], + 'stream_key' => 'dev_'.$attributes['slug'].'_'.Str::random(16), + 'status' => SourceStatusEnum::OFFLINE, + ], + ); + } + + /** + * The seed's own guard: a clash here would show up as a red block in the planner and + * send someone hunting for a bug that is really just bad test data. + */ + private function assertNoOverlaps(): void + { + Show::with('source')->get()->groupBy('source_id')->each(function ($shows) { + $ordered = $shows->sortBy('scheduled_start')->values(); + + $ordered->each(function (Show $show, int $index) use ($ordered) { + $next = $ordered->get($index + 1); + + if ($next && $show->scheduled_end->gt($next->scheduled_start)) { + $this->command->warn( + "Overlap on {$show->source?->name}: '{$show->title}' ends after '{$next->title}' starts." + ); + } + }); + }); } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..634e11d --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,227 @@ +# Local mirror of the production streaming path. +# +# publisher -> SRS (ingress) -> ffmpeg ABR -> origin nginx -> origin caddy +# -> edge nginx -> edge caddy -> browser +# SRS DVR -> dvr-uploader -> S3 (versitygw) -> recordings + thumbnails +# +# The Laravel app itself stays native under Yerd; containers reach it through the +# app-bridge service, and SRS webhooks hit it exactly as they do in production. +# +# Start with: ./scripts/dev-stack.sh up + +name: streaming-dev + +services: + # ------------------------------------------------------------- app bridge + # Plain HTTP entry point to the host-served Laravel app; see + # docker/dev/app-bridge.conf for why this exists. + app-bridge: + image: nginx:1.27-alpine + volumes: + - ./docker/dev/app-bridge.conf:/etc/nginx/conf.d/default.conf:ro + extra_hosts: + - "streaming.test:host-gateway" + restart: unless-stopped + + # ---------------------------------------------------------------- ingress + origin-srs: + image: ossrs/srs:5 + command: ./objs/srs -c /usr/local/srs/conf/origin.conf + volumes: + - ./docker/dev/origin-srs.conf:/usr/local/srs/conf/origin.conf:ro + - dvr:/dvr/recordings + ports: + - "1935:1935" # RTMP ingress (point OBS here) + - "1985:1985" # SRS HTTP API + depends_on: + - app-bridge + restart: unless-stopped + + # ------------------------------------------------------- ABR transcoding + hls-transcoder: + build: ./docker/ffmpeg-hls + environment: + SRS_API_URL: http://origin-srs:1985/api/v1 + SRS_RTMP_URL: rtmp://origin-srs:1935 + OUTPUT_BASE_DIR: /var/www/hls/live + CHECK_INTERVAL: "5" + # copy is the local default: the ladder is remuxed rather than encoded, so + # a laptop can hold several channels at once. Set DEV_ABR_MODE=transcode + # when you are actually working on the ladder itself. + ABR_MODE: ${DEV_ABR_MODE:-copy} + # Production runs a 60 minute rewind window (1800 segments). That is ~5GB of + # disk per source, which is not something a laptop wants, so dev defaults to + # 5 minutes. Raise it when you are actually working on the DVR window itself. + DVR_WINDOW_SEGMENTS: ${DEV_DVR_WINDOW_SEGMENTS:-150} + volumes: + - hls:/var/www/hls + depends_on: + - origin-srs + restart: unless-stopped + + # ----------------------------------------------------------------- origin + origin-nginx: + image: nginx:1.27-alpine + volumes: + - ./docker/dev/origin-nginx.conf:/etc/nginx/nginx.conf:ro + - hls:/var/www/hls + depends_on: + - hls-transcoder + - app-bridge + restart: unless-stopped + + origin-caddy: + image: caddy:2-alpine + volumes: + - ./docker/dev/origin-Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-logs:/var/log/caddy + ports: + - "8070:8070" # origin, mostly for debugging + depends_on: + - origin-nginx + restart: unless-stopped + + # ------------------------------------------------------------------- edge + # Built rather than pulled: the image adds the njs module and hls-auth.js, so + # playback tokens are verified locally here exactly as they are in production. + edge-nginx: + build: ./docker/edge-nginx + environment: + HLS_VIEWER_SECRET: ${HLS_VIEWER_SECRET:-} + HLS_EMBED_SECRET: ${HLS_EMBED_SECRET:-} + HLS_TOKEN_LEEWAY: ${HLS_TOKEN_LEEWAY:-60} + STREAM_SYSTEM_STREAMKEY: ${STREAM_SYSTEM_STREAMKEY:-} + volumes: + - ./docker/dev/edge-nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - origin-caddy + - app-bridge + restart: unless-stopped + + edge-caddy: + image: caddy:2-alpine + volumes: + - ./docker/dev/edge-Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-logs:/var/log/caddy + ports: + # 8085 matches the edge server row that LocalDevelopmentServersSeeder creates. + # 8080 is Yerd's and 8081 is Reverb's, so the edge stays off both. + - "8085:8080" + depends_on: + - edge-nginx + restart: unless-stopped + + # --------------------------------------------------------------- storage + # versitygw exposes a plain directory over the S3 API: recordings, DVR + # uploads and thumbnails all behave like they do against real object storage. + s3: + image: ghcr.io/versity/versitygw:latest + command: > + --access ${DEV_S3_KEY:-devkey} + --secret ${DEV_S3_SECRET:-devsecret123} + --region ${AWS_DEFAULT_REGION:-eu-central-1} + --port :7070 + posix /buckets + volumes: + - s3-data:/buckets + ports: + # Host side only. 7070 is AnyDesk's default listener, which silently wins the + # bind and takes the whole stack down with it, so the host port is both moved + # and overridable. Inside the compose network this is still :7070, which is + # why S3_ENDPOINT below does not change. + - "${DEV_S3_PORT:-7075}:7070" + restart: unless-stopped + + s3-init: + image: alpine:3 + # versitygw's posix backend treats top-level directories as buckets. + command: sh -c "mkdir -p /buckets/${AWS_BUCKET:-streaming} && echo 'bucket ready'" + volumes: + - s3-data:/buckets + depends_on: + - s3 + restart: "no" + + # ------------------------------------------------------------ DVR upload + dvr-uploader: + build: ./docker/dvr-uploader + environment: + S3_BUCKET: ${AWS_BUCKET:-streaming} + S3_REGION: ${AWS_DEFAULT_REGION:-eu-central-1} + S3_ACCESS_KEY: ${DEV_S3_KEY:-devkey} + S3_SECRET_KEY: ${DEV_S3_SECRET:-devsecret123} + S3_ENDPOINT: http://s3:7070 + RECORDINGS_PATH: /dvr/recordings + DELETE_AFTER_UPLOAD: "false" + FILE_AGE_SECONDS: "15" + WEBHOOK_URL: http://app-bridge/api/srs/dvr + volumes: + - dvr:/dvr/recordings + depends_on: + - s3 + - app-bridge + restart: unless-stopped + + # --------------------------------------------------------- segment archive + # Mirrors the transcoder's HLS segments to S3 and maintains the per-hour index + # playlists that recordings are later cut from. Separate container from + # dvr-uploader on purpose: that one still handles the SRS MP4 DVR as a cold + # backup, and the two watch different volumes. + archive-uploader: + build: ./docker/dvr-uploader + command: ["python", "-u", "archive_uploader.py"] + environment: + S3_BUCKET: ${AWS_BUCKET:-streaming} + S3_REGION: ${AWS_DEFAULT_REGION:-eu-central-1} + S3_ACCESS_KEY: ${DEV_S3_KEY:-devkey} + S3_SECRET_KEY: ${DEV_S3_SECRET:-devsecret123} + S3_ENDPOINT: http://s3:7070 + HLS_PATH: /var/www/hls/live + # Matches DVR_WINDOW_SEGMENTS on the transcoder (150 segments x 2s = 5 min). + # The reaper must never delete inside the window the player can still seek to. + DVR_WINDOW_SECONDS: ${DEV_DVR_WINDOW_SECONDS:-300} + REAP_INTERVAL: "30" + INDEX_UPLOAD_INTERVAL: "20" + # Unlimited locally; S3 is a container on the same host. Set this low + # (e.g. 10, well under the ~11.5 Mbps per source the ladder produces) to + # watch the backlog guard fire. + MAX_UPLOAD_RATE_MBPS: ${DEV_MAX_UPLOAD_RATE_MBPS:-0} + volumes: + - hls:/var/www/hls + - archive-state:/var/lib/dvr-archive + depends_on: + - s3 + - hls-transcoder + restart: unless-stopped + + # -------------------------------------------------------- fake broadcasters + # Stands in for OBS: one ffmpeg per channel, pushing a distinct animated + # pattern into the ingress app with the channel's stream key. + publisher: + image: linuxserver/ffmpeg:latest + entrypoint: ["/bin/bash", "/publish.sh"] + environment: + RTMP_URL: rtmp://origin-srs:1935/ingress + CHANNELS: ${DEV_PUBLISH_CHANNELS:-} + SIZE: ${DEV_PUBLISH_SIZE:-1280x720} + FPS: ${DEV_PUBLISH_FPS:-30} + # loop encodes one short clip per channel and then pushes it with -c copy, + # so the publishers stop costing CPU once the clips are cached in the + # volume below. Set DEV_PUBLISH_MODE=live for a moving on-screen clock. + MODE: ${DEV_PUBLISH_MODE:-loop} + CLIP_SECONDS: ${DEV_PUBLISH_CLIP_SECONDS:-20} + CLIP_DIR: /clips + volumes: + - ./docker/dev/publish.sh:/publish.sh:ro + - publisher-clips:/clips + depends_on: + - origin-srs + restart: unless-stopped + +volumes: + hls: + dvr: + archive-state: + s3-data: + caddy-logs: + publisher-clips: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index c9ce47f..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,237 +0,0 @@ -services: - laravel.test: - build: - context: ./docker/8.4 - dockerfile: Dockerfile - args: - WWWGROUP: '${WWWGROUP}' - image: sail-8.4/app - extra_hosts: - - 'host.docker.internal:host-gateway' - ports: - - '${APP_PORT:-80}:80' - - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' - environment: - WWWUSER: '${WWWUSER}' - LARAVEL_SAIL: 1 - XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' - XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' - IGNITION_LOCAL_SITES_PATH: '${PWD}' - # SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" - volumes: - - '.:/var/www/html' - networks: - - sail - depends_on: - - mysql - - redis - mysql: - image: 'mysql/mysql-server:8.0' - ports: - - '${FORWARD_DB_PORT:-3306}:3306' - environment: - MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' - MYSQL_ROOT_HOST: '%' - MYSQL_DATABASE: '${DB_DATABASE}' - MYSQL_USER: '${DB_USERNAME}' - MYSQL_PASSWORD: '${DB_PASSWORD}' - MYSQL_ALLOW_EMPTY_PASSWORD: 1 - volumes: - - 'sail-mysql:/var/lib/mysql' - - './docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' - networks: - - sail - healthcheck: - test: - - CMD - - mysqladmin - - ping - - '-p${DB_PASSWORD}' - retries: 3 - timeout: 5s - redis: - image: 'redis:alpine' - ports: - - '${FORWARD_REDIS_PORT:-6379}:6379' - volumes: - - 'sail-redis:/data' - networks: - - sail - healthcheck: - test: - - CMD - - redis-cli - - ping - retries: 3 - timeout: 5s - # ORIGIN SERVICES - origin-srs: - image: ossrs/srs:6 - ports: - - '${FORWARD_SRS_RTMP_PORT:-1935}:1935' - - '${FORWARD_SRS_HTTP_PORT:-8082}:8082' - - '${FORWARD_SRS_API_PORT:-1985}:1985' - environment: - SRS_HTTP_PORT: 8082 - networks: - - sail - volumes: - - ./docker/origin-srs/origin.conf:/usr/local/srs/conf/custom.conf:ro - - dvr-recordings:/dvr/recordings - command: ./objs/srs -c /usr/local/srs/conf/custom.conf - depends_on: - - laravel.test - restart: unless-stopped - - # Origin FFmpeg HLS Transcoder - Creates multi-bitrate HLS from SRS -# origin-ffmpeg-hls: - # image: eurofurence/ffmpeg-hls:latest - # environment: - # SRS_API_URL: http://localhost:1985/api/v1 - # SRS_RTMP_URL: rtmp://localhost:1935 - # OUTPUT_BASE_DIR: /var/www/hls/live - # CHECK_INTERVAL: 5 - # volumes: - # # HLS output directory shared with origin-nginx - # - hls-content:/var/www/hls - # network_mode: host - # restart: unless-stopped - # depends_on: - # - origin-srs - - # Origin Nginx - Serves HLS with auth_request authentication - origin-nginx: - image: nginx:alpine - ports: - - '${FORWARD_ORIGIN_NGINX_PORT:-8083}:8083' - volumes: - - ./docker/origin-nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - hls-content:/var/www/hls:ro - - origin-nginx-logs:/var/log/nginx - networks: - - sail - depends_on: - - laravel.test - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8083/health"] - interval: 10s - timeout: 5s - retries: 3 - restart: unless-stopped - - # Origin Caddy - SSL termination for origin (port 8070 for local, 8080 in production) - origin-caddy: - image: caddy:alpine - ports: - - '${FORWARD_ORIGIN_CADDY_PORT:-8070}:8070' - environment: - DOMAIN: '${APP_URL:-localhost}' - CADDY_PORT: 8070 - NGINX_ORIGIN_PORT: 8083 - volumes: - - ./docker/origin-caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - origin-caddy-data:/data - - origin-caddy-config:/config - - origin-caddy-logs:/var/log/caddy - networks: - - sail - depends_on: - - origin-nginx - restart: unless-stopped - - # DVR S3 Uploader Service - dvr-uploader: - image: eurofurence/dvr-uploader:latest - environment: - S3_BUCKET: '${DVR_AWS_BUCKET:-ef-streaming-recordings}' - S3_REGION: '${DVRnp_AWS_DEFAULT_REGION:-eu-central-1}' - S3_ACCESS_KEY: '${DVR_AWS_ACCESS_KEY_ID}' - S3_SECRET_KEY: '${DVR_AWS_SECRET_ACCESS_KEY}' - S3_ENDPOINT: '${DVR_AWS_ENDPOINT}' - RECORDINGS_PATH: /dvr/recordings - DELETE_AFTER_UPLOAD: 'true' - WEBHOOK_URL: 'http://laravel.test/api/dvr/upload-webhook' - FILE_AGE_SECONDS: '30' - UPLOAD_DELAY_SECONDS: '5' - MAX_UPLOAD_RATE_MBPS: '3' - volumes: - - dvr-recordings:/dvr/recordings - networks: - - sail - depends_on: - - origin-srs - - laravel.test - restart: unless-stopped - - # EDGE SERVICES - # Edge Nginx - Proxies and caches content from origin Caddy - edge-nginx: - image: nginx:alpine - ports: - - '${FORWARD_EDGE_NGINX_PORT:-8081}:8081' - volumes: - - ./docker/edge-nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - edge-nginx-logs:/var/log/nginx - - edge-nginx-cache:/var/cache/nginx - networks: - - sail - depends_on: - - laravel.test - - origin-caddy - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/health"] - interval: 10s - timeout: 5s - retries: 3 - restart: unless-stopped - - # Edge Caddy - SSL termination for edge server - edge-caddy: - image: caddy:alpine - ports: - - '${FORWARD_EDGE_CADDY_PORT:-8080}:8080' - environment: - DOMAIN: '${APP_URL:-localhost}' - CADDY_PORT: 8080 - NGINX_EDGE_PORT: 8081 - volumes: - - ./docker/edge-caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - caddy-data:/data - - caddy-config:/config - - caddy-logs:/var/log/caddy - networks: - - sail - restart: unless-stopped -volumes: - sail-mysql: - driver: local - sail-redis: - driver: local - # Origin volumes - hls-content: - driver: local - origin-nginx-logs: - driver: local - origin-caddy-data: - driver: local - origin-caddy-config: - driver: local - origin-caddy-logs: - driver: local - # DVR volumes - dvr-recordings: - driver: local - # Edge volumes - edge-nginx-logs: - driver: local - edge-nginx-cache: - driver: local - caddy-data: - driver: local - caddy-config: - driver: local - caddy-logs: - driver: local -networks: - sail: - driver: bridge diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile deleted file mode 100644 index cb0fbdc..0000000 --- a/docker/8.4/Dockerfile +++ /dev/null @@ -1,71 +0,0 @@ -FROM ubuntu:24.04 - -LABEL maintainer="Taylor Otwell" - -ARG WWWGROUP -ARG NODE_VERSION=22 -ARG MYSQL_CLIENT="mysql-client" -ARG POSTGRES_VERSION=17 - -WORKDIR /var/www/html - -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=UTC -ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" -ENV SUPERVISOR_PHP_USER="sail" - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom - -RUN apt-get update && apt-get upgrade -y \ - && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ - && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ - && apt-get update \ - && apt-get install -y php8.4-cli php8.4-dev \ - php8.4-pgsql php8.4-sqlite3 php8.4-gd \ - php8.4-curl php8.4-mongodb \ - php8.4-imap php8.4-mysql php8.4-mbstring \ - php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \ - php8.4-intl php8.4-readline \ - php8.4-ldap \ - php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \ - php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \ - && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ - && apt-get update \ - && apt-get install -y nodejs \ - && npm install -g npm \ - && npm install -g pnpm \ - && npm install -g bun \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y yarn \ - && apt-get install -y $MYSQL_CLIENT \ - && apt-get install -y postgresql-client-$POSTGRES_VERSION \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4 - -RUN userdel -r ubuntu -RUN groupadd --force -g $WWWGROUP sail -RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail - -COPY start-container /usr/local/bin/start-container -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini -RUN chmod +x /usr/local/bin/start-container - -EXPOSE 80/tcp - -ENTRYPOINT ["start-container"] diff --git a/docker/8.4/php.ini b/docker/8.4/php.ini deleted file mode 100644 index 0d8ce9e..0000000 --- a/docker/8.4/php.ini +++ /dev/null @@ -1,5 +0,0 @@ -[PHP] -post_max_size = 100M -upload_max_filesize = 100M -variables_order = EGPCS -pcov.directory = . diff --git a/docker/8.4/start-container b/docker/8.4/start-container deleted file mode 100644 index 40c55df..0000000 --- a/docker/8.4/start-container +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then - echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." - exit 1 -fi - -if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER sail -fi - -if [ ! -d /.composer ]; then - mkdir /.composer -fi - -chmod -R ugo+rw /.composer - -if [ $# -gt 0 ]; then - if [ "$SUPERVISOR_PHP_USER" = "root" ]; then - exec "$@" - else - exec gosu $WWWUSER "$@" - fi -else - exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -fi diff --git a/docker/8.4/supervisord.conf b/docker/8.4/supervisord.conf deleted file mode 100644 index 9dcdd15..0000000 --- a/docker/8.4/supervisord.conf +++ /dev/null @@ -1,42 +0,0 @@ -[supervisord] -nodaemon=true -user=root -logfile=/var/log/supervisor/supervisord.log -pidfile=/var/run/supervisord.pid - -[program:php] -command=%(ENV_SUPERVISOR_PHP_COMMAND)s -user=%(ENV_SUPERVISOR_PHP_USER)s -environment=LARAVEL_SAIL="1" -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:horizon] -command=/usr/bin/php /var/www/html/artisan horizon -user=sail -environment=LARAVEL_SAIL="1" -autostart=true -autorestart=true -redirect_stderr=true -stdout_logfile=/var/www/html/storage/logs/horizon.log -stopwaitsecs=3600 - -[program:schedule] -command=/bin/bash -c "while true; do /usr/bin/php /var/www/html/artisan schedule:run --verbose --no-interaction & sleep 60; done" -user=sail -environment=LARAVEL_SAIL="1" -autostart=true -autorestart=true -redirect_stderr=true -stdout_logfile=/var/www/html/storage/logs/schedule.log - -[program:reverb] -command=/usr/bin/php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8090 -user=sail -autostart=true -autorestart=true -redirect_stderr=true -stdout_logfile=/var/www/html/storage/logs/reverb.log -stopwaitsecs=10 diff --git a/docker/dev/app-bridge.conf b/docker/dev/app-bridge.conf new file mode 100644 index 0000000..05fd140 --- /dev/null +++ b/docker/dev/app-bridge.conf @@ -0,0 +1,34 @@ +# Bridge from the container network to the host-served Laravel app. +# +# Yerd matches sites by hostname and redirects plain HTTP to HTTPS, so a +# container asking for http://host.docker.internal/... gets "No site matches +# this Host" or a 301 that SRS treats as a failed webhook. This container +# accepts plain HTTP, then forwards to https://streaming.test with the Host +# header Yerd expects and without verifying its self-signed certificate. +# +# In production none of this exists: SRS and nginx reach the app directly. + +server { + listen 80; + server_name _; + + # streaming.test resolves to the host gateway via extra_hosts in the compose file. + resolver 127.0.0.11 ipv6=off; + + location / { + proxy_pass https://streaming.test; + + proxy_ssl_verify off; + proxy_ssl_server_name on; + + proxy_set_header Host streaming.test; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + # Webhooks are small and should fail fast rather than hold a stream open. + proxy_connect_timeout 5s; + proxy_read_timeout 15s; + proxy_redirect off; + } +} diff --git a/docker/dev/edge-Caddyfile b/docker/dev/edge-Caddyfile new file mode 100644 index 0000000..7d22b2c --- /dev/null +++ b/docker/dev/edge-Caddyfile @@ -0,0 +1,17 @@ +# Generated for local docker-compose (scripts/dev-stack.sh). +# Same as the production config, with upstreams pointed at compose services. +:8080 { + # Set up logging + log { + output file /var/log/caddy/access.log + format json + } + + # Forward ALL traffic to nginx edge server + reverse_proxy edge-nginx:8081 { + # Pass through real IP + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + } +} diff --git a/install.sh b/docker/dev/edge-nginx.conf similarity index 51% rename from install.sh rename to docker/dev/edge-nginx.conf index 42339b6..581673b 100644 --- a/install.sh +++ b/docker/dev/edge-nginx.conf @@ -1,109 +1,22 @@ -# EF Streaming Edge Server Installation Script -# Generated: 2025-08-31 18:55:53 -# Server ID: 10 -# Hostname: edge-10-Ur16YGLgHK2J.stream.eurofurence.org - -set -e - -echo "================================================" -echo "EF Streaming Server Installation" -echo "Server Type: edge" -echo "Generated: 2025-08-31 18:55:53" -echo "================================================" - -# Update system -apt-get update -apt-get upgrade -y - -# Install Docker -if ! command -v docker &> /dev/null; then - echo "Installing Docker..." - curl -fsSL https://get.docker.com -o get-docker.sh - sh get-docker.sh - rm get-docker.sh -else - echo "Docker already installed" -fi - -# Install Docker Compose -if ! command -v docker-compose &> /dev/null; then - echo "Installing Docker Compose..." - curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose -else - echo "Docker Compose already installed" -fi - -# Create working directory -mkdir -p /opt/ef-streaming -cd /opt/ef-streaming - -# Create environment file -cat > .env < docker-compose.yml <<'DOCKERCOMPOSE' -version: '3.8' - -services: - # Edge Nginx - Caching proxy for HLS content - edge-nginx: - image: nginx:alpine - container_name: edge-nginx - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf:ro - tmpfs: - - /var/cache/nginx:rw,noexec,nosuid,size=512m - restart: unless-stopped - networks: - - streaming - - # Edge Caddy - SSL termination for edge - edge-caddy: - image: caddy:alpine - container_name: edge-caddy - ports: - - "80:80" - - "443:443" - environment: - DOMAIN: edge-10-Ur16YGLgHK2J.stream.eurofurence.org - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - caddy-data:/data - - caddy-config:/config - restart: unless-stopped - depends_on: - - edge-nginx - networks: - - streaming - -networks: - streaming: - driver: bridge - -volumes: - caddy-data: - caddy-config:DOCKERCOMPOSE - -# Create Edge Nginx configuration -cat > nginx.conf <<'NGINXCONF' +# Generated for local docker-compose (scripts/dev-stack.sh). +# Same as the production config, with upstreams pointed at compose services. +# +# njs verifies playback tokens locally with an HMAC, so a request carrying ?t= +# never reaches Laravel. See docs/streaming-auth-redesign.md. +load_module modules/ngx_http_js_module.so; + user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; +# Passed through to njs as process.env. Keeping the secrets in the environment +# rather than in this file means they are not written to disk here. +env HLS_VIEWER_SECRET; +env HLS_EMBED_SECRET; +env HLS_TOKEN_LEEWAY; +env STREAM_SYSTEM_STREAMKEY; + events { worker_connections 4096; use epoll; @@ -111,6 +24,8 @@ events { } http { + js_import hlsAuth from /etc/nginx/njs/hls-auth.js; + include /etc/nginx/mime.types; default_type application/octet-stream; @@ -128,9 +43,12 @@ http { gzip_vary on; gzip_proxied any; gzip_comp_level 6; + # video/mp2t is deliberately absent: MPEG-TS is already compressed, so gzipping + # segments burns CPU for no size gain. Playlists do compress, and at a DVR + # window they are large enough for it to matter. gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss - application/vnd.apple.mpegurl video/mp2t; + application/vnd.apple.mpegurl; # Rate limiting limit_req_zone $binary_remote_addr zone=viewer_limit:10m rate=30r/s; @@ -139,23 +57,24 @@ http { # Cache paths for different content types - optimized for quality switching proxy_cache_path /var/cache/nginx/auth levels=1:2 keys_zone=auth_cache:10m max_size=100m inactive=10s use_temp_path=off; - + proxy_cache_path /var/cache/nginx/hls levels=1:2 keys_zone=hls_cache:10m max_size=100m inactive=2s use_temp_path=off; - + proxy_cache_path /var/cache/nginx/segments levels=1:2 keys_zone=segment_cache:100m - max_size=2g inactive=1h use_temp_path=off + max_size=4g inactive=2m use_temp_path=off loader_files=200 loader_sleep=50ms loader_threshold=300ms; + # Upstream for origin Caddy server upstream origin_caddy { - server origin-8-1Fn5B9fkKSmO.stream.eurofurence.org:8070; + server origin-caddy:8070; keepalive 32; } server { - listen 80; - listen [::]:80; + listen 8081; + listen [::]:8081; server_name _; # Rate limiting @@ -169,10 +88,22 @@ http { add_header Content-Type text/plain; } - # Authentication subrequest endpoint + # Playback token verification, entirely local: no network call, no PHP. + # Falls back to /auth-legacy when the request carries a streamkey instead. location = /auth { internal; - proxy_pass http://well-oarfish-oddly.ngrok-free.app:443/api/hls/auth; + js_content hlsAuth.verify; + } + + # Legacy fallback, reached only for a per-user streamkey, which can only + # be resolved in the database. Goes away with the streamkey itself. + # + # The cache key is now effectively per streamkey rather than per segment + # URI, because $uri here is the constant /auth-legacy, so repeat segment + # requests from the same viewer stop hitting PHP. + location = /auth-legacy { + internal; + proxy_pass http://app-bridge:80/api/hls/auth; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; @@ -180,19 +111,30 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Edge-Server "edge-nginx"; - + # Pass streamkey as header for authentication proxy_set_header X-Stream-Key $arg_streamkey; - - # Cache auth responses for performance + + # Cache auth responses for performance. + # + # Laravel answers with 'Cache-Control: no-cache, private', which nginx + # obeys by default - so without this the cache never stored anything and + # every single segment and playlist request went through to PHP. + # Ignoring those headers is what makes proxy_cache_valid below real. + # Cost: a revoked streamkey stays usable for up to the cache lifetime. + proxy_ignore_headers Cache-Control Expires Set-Cookie; proxy_cache auth_cache; proxy_cache_key "$remote_addr:$arg_streamkey:$uri"; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 1m; proxy_cache_valid 401 403 1s; } # HLS m3u8 playlist files - proxy and cache from origin location ~ ^/live/(.+\.m3u8)$ { + # Playlists are authenticated too now; previously only segments were. + auth_request /auth; + auth_request_set $auth_status $upstream_status; + # Proxy to origin Caddy server proxy_pass http://origin_caddy$request_uri; proxy_http_version 1.1; @@ -200,7 +142,7 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection ""; - + # Cache configuration for m3u8 playlists proxy_cache hls_cache; # Cache key uses URI without query parameters @@ -210,16 +152,16 @@ http { proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; proxy_cache_lock_timeout 5s; - + # CORS headers add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range' always; - + # Add cache status header for debugging add_header X-Cache-Status $upstream_cache_status; - + # HLS headers add_header Content-Type "application/vnd.apple.mpegurl"; add_header Cache-Control "no-cache, no-store, must-revalidate"; @@ -231,7 +173,7 @@ http { # Perform authentication check auth_request /auth; auth_request_set $auth_status $upstream_status; - + # Proxy to origin Caddy server proxy_pass http://origin_caddy$request_uri; proxy_http_version 1.1; @@ -239,29 +181,29 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection ""; - + # Cache configuration for TS segments proxy_cache segment_cache; # Cache key uses URI without query parameters proxy_cache_key "$scheme$proxy_host$uri"; - proxy_cache_valid 200 5m; + proxy_cache_valid 200 2m; proxy_cache_valid 404 10s; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; proxy_cache_lock_timeout 5s; - + # CORS headers add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range' always; - + # Add cache status header for debugging add_header X-Cache-Status $upstream_cache_status; - + # Cache headers for CDN and browsers - expires 5m; - add_header Cache-Control "public, max-age=300, immutable"; + expires 2m; + add_header Cache-Control "public, max-age=120, immutable"; add_header Content-Type "video/mp2t"; add_header X-Content-Type-Options "nosniff"; } @@ -271,77 +213,4 @@ http { return 404; } } -}NGINXCONF - -# Create Edge Caddy configuration -cat > Caddyfile <<'CADDYFILE' -edge-10-Ur16YGLgHK2J.stream.eurofurence.org { - reverse_proxy edge-nginx:80 -}CADDYFILE -# Start services -echo "Starting Docker services..." -docker compose up -d - -# Wait for services to be ready -echo "Waiting for services to start..." -WAITED=0 -MAX_WAIT=60 -while [ $WAITED -lt $MAX_WAIT ]; do - if [ "edge" = "origin" ]; then - # For origin, check if SRS is responding - if curl -s http://localhost:1985/api/v1/versions > /dev/null 2>&1; then - echo "Origin services are ready!" - break - fi - else - # For edge, check if nginx is responding - if curl -s http://localhost:8081/health > /dev/null 2>&1; then - echo "Edge services are ready!" - break - fi - fi - echo "Waiting for services... ($WAITED/$MAX_WAIT seconds)" - sleep 5 - WAITED=$((WAITED + 5)) -done - -# Show service status -docker compose ps - -# Get server information -# Force IPv4 for PUBLIC_IP and use the configured hostname -PUBLIC_IP=$(curl -4 -s ifconfig.me) -HOSTNAME="edge-10-Ur16YGLgHK2J.stream.eurofurence.org" - -echo "================================================" -echo "Server Information:" -echo " Public IP: $PUBLIC_IP" -echo " Hostname: $HOSTNAME" -echo " Server Type: edge" -echo " Server ID: 10" -echo "================================================" - -# Register server with main app (optional - may fail if network not ready) -echo "Attempting to register server with main application..." -curl -L -X POST "https://well-oarfish-oddly.ngrok-free.app/api/server/register" \ - -H "X-Shared-Secret: 2rf93eFTm1dmlxRwDVyfGgkk5QYxVixG7TUW3JLK" \ - -H "Content-Type: application/json" \ - -d "{ - \"server_id\": \"10\", - \"hostname\": \"$HOSTNAME\", - \"ip\": \"$PUBLIC_IP\", - \"status\": \"active\" - }" || echo "Registration failed - server will register on first heartbeat" - -echo "================================================" -echo "Installation complete!" -echo "Server is ready at: $PUBLIC_IP" -echo "================================================" - -# Setup auto-restart on boot -systemctl enable docker - -# Setup heartbeat cron job (every minute) -(crontab -l 2>/dev/null; echo "* * * * * /opt/ef-streaming/heartbeat.sh") | crontab - - -exit 0 +} diff --git a/docker/dev/origin-Caddyfile b/docker/dev/origin-Caddyfile new file mode 100644 index 0000000..27e3912 --- /dev/null +++ b/docker/dev/origin-Caddyfile @@ -0,0 +1,32 @@ +# Generated for local docker-compose (scripts/dev-stack.sh). +# Same as the production config, with upstreams pointed at compose services. +# Origin Caddy - SSL termination for origin server +# In production this would be on port 8080, using 8070 for local development +:8070 { + # Enable automatic HTTPS with Let's Encrypt in production + # For localhost, it will use a self-signed certificate + + # Set up logging + log { + output file /var/log/caddy/access.log + format json + } + + # Forward all traffic to origin nginx server + # Nginx handles authentication via auth_request + reverse_proxy origin-nginx:8083 { + # Pass through real IP + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + + # Pass through query parameters including streamkey + header_up X-Original-URI {uri} + + # Connection pooling for better performance + transport http { + keepalive 32s + keepalive_idle_conns 10 + } + } +} \ No newline at end of file diff --git a/docker/dev/origin-nginx.conf b/docker/dev/origin-nginx.conf new file mode 100644 index 0000000..48392be --- /dev/null +++ b/docker/dev/origin-nginx.conf @@ -0,0 +1,105 @@ +# Generated for local docker-compose (scripts/dev-stack.sh). +# Same as the production config, with upstreams pointed at compose services. +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + access_log /var/log/nginx/access.log; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression for HLS content + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + # video/mp2t is deliberately absent: MPEG-TS is already compressed, so gzipping + # segments burns CPU for no size gain. Playlists do compress, and at a DVR + # window they are large enough for it to matter. + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/vnd.apple.mpegurl; + + # Upstream for Laravel authentication service + upstream laravel_auth { + server app-bridge:80; + keepalive 32; + } + + server { + listen 8083; + listen [::]:8083; + server_name _; + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Authentication subrequest endpoint + location = /auth { + internal; + proxy_pass http://laravel_auth/api/hls/auth; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Pass streamkey as header for authentication + proxy_set_header X-Stream-Key $arg_streamkey; + } + + # HLS m3u8 playlist files - serve from shared volume (no auth needed) + location ~ ^/live/(.+\.m3u8)$ { + # No authentication for m3u8 playlists + + # Serve files from the HLS output directory + root /var/www/hls; + try_files $uri =404; + + # Content type headers only + add_header Content-Type "application/vnd.apple.mpegurl"; + add_header X-Content-Type-Options "nosniff"; + } + + # TS segment files - serve from shared volume + location ~ ^/live/(.+\.ts)$ { + # Perform authentication check + auth_request /auth; + auth_request_set $auth_status $upstream_status; + + # Serve files from the HLS output directory + root /var/www/hls; + try_files $uri =404; + + # Content type headers only + add_header Content-Type "video/mp2t"; + add_header X-Content-Type-Options "nosniff"; + } + + # Default location + location / { + return 404; + } + } +} \ No newline at end of file diff --git a/docker/dev/origin-srs.conf b/docker/dev/origin-srs.conf new file mode 100644 index 0000000..448ebdf --- /dev/null +++ b/docker/dev/origin-srs.conf @@ -0,0 +1,81 @@ +# Generated for local docker-compose (scripts/dev-stack.sh). +# Same as production, with webhooks pointed at the host-served Laravel app. +# SRS Origin Server Configuration +# Simple passthrough - no transcoding, no HLS +# FFmpeg container handles HLS ABR generation with perfect GOP alignment + +listen 1935; +max_connections 300; +server_id 71; + +srs_log_tank console; +daemon off; + +# HTTP API for stream monitoring +http_api { + enabled on; + listen 1985; +} + +# HTTP server for stats/debugging (not for HLS) +http_server { + enabled on; + listen 8082; + dir ./objs/nginx/html; +} + +# Disable RTC +rtc_server { + enabled off; +} + +# Main vhost - simple passthrough +vhost __defaultVhost__ { + # Webhook authentication for publishing + http_hooks { + enabled on; + on_publish http://app-bridge/api/srs/auth; + on_unpublish http://app-bridge/api/srs/unpublish; + on_dvr http://app-bridge/api/srs/dvr; + } + + # DVR configuration for recording streams + dvr { + enabled on; + # Apply to all streams + dvr_apply all; + # Use segment plan to split files + dvr_plan segment; + # Path with stream-based organization and timestamp + # Creates: /dvr/recordings/[app]/[stream]/[2006]-[01]-[02]/[15]-[04]-[05]_[timestamp].mp4 + dvr_path /dvr/recordings/[app]/[stream]/[2006]-[01]-[02]/[15]-[04]-[05]_[timestamp].mp4; + # 60s per segment locally (production uses 600) so a DVR file lands + # while you are still looking at the screen. + dvr_duration 60; + # Wait for keyframe before splitting + dvr_wait_keyframe on; + # Full time jitter handling for proper timestamps + time_jitter full; + } + + # No HLS - FFmpeg handles this + hls { + enabled off; + } + + # No transcoding - FFmpeg handles this + transcode { + enabled off; + } + + # Keep GOP cache for lower latency on playback start + play { + gop_cache on; + mw_latency 1800; + } + + # No HTTP remux + http_remux { + enabled off; + } +} diff --git a/docker/dev/publish.sh b/docker/dev/publish.sh new file mode 100755 index 0000000..942eb24 --- /dev/null +++ b/docker/dev/publish.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# +# Fake broadcasters. One ffmpeg per channel, each pushing a distinct animated +# pattern into the SRS ingress app with that channel's stream key, exactly the +# way OBS would. +# +# CHANNELS is a space-separated list of slug:streamkey pairs, produced by +# `php artisan dev:stream-keys` and passed in by scripts/dev-stack.sh. +# +# MODE picks how the video is produced: +# +# loop (default) Encode a short clip per pattern once, then push it on an +# endless loop with -c copy. After the first start there is +# no encoding at all, so ten channels cost about as much CPU +# as none. The wall clock in the overlay is frozen at capture +# time, which is the one thing you give up. +# live Encode continuously, one x264 per channel. Use it when you +# need a moving clock or genuinely fresh frames. +# +set -uo pipefail + +RTMP_URL="${RTMP_URL:-rtmp://origin-srs:1935/ingress}" +SIZE="${SIZE:-1280x720}" +FPS="${FPS:-30}" +CHANNELS="${CHANNELS:-}" +MODE="${MODE:-loop}" +CLIP_SECONDS="${CLIP_SECONDS:-20}" +CLIP_DIR="${CLIP_DIR:-/clips}" + +# GOP length in seconds. Must match hls_time in the transcoder, otherwise a +# copy-mode ladder cannot cut segments on keyframes. +GOP_SECONDS="${GOP_SECONDS:-2}" + +if [[ -z "$CHANNELS" ]]; then + echo "No CHANNELS set. Run ./scripts/dev-stack.sh publish to start broadcasters." + # Idle instead of crash-looping: the stack is still useful without publishers. + tail -f /dev/null +fi + +# Each channel gets its own look so you can tell the streams apart on the grid. +patterns=( + "testsrc2=size=${SIZE}:rate=${FPS}" + "life=size=${SIZE}:rate=${FPS}:mold=10:ratio=0.1:death_color=#39ff14:life_color=#00b3a4" + "smptehdbars=size=${SIZE}:rate=${FPS}" + "mandelbrot=size=${SIZE}:rate=${FPS}:maxiter=180" + "rgbtestsrc=size=${SIZE}:rate=${FPS}" +) + +pids=() + +cleanup() { + for pid in "${pids[@]:-}"; do + kill "$pid" 2>/dev/null || true + done + wait 2>/dev/null || true +} +trap cleanup EXIT INT TERM + +overlay() { + local label="$1" + + echo "drawtext=text='${label}':fontsize=48:fontcolor=white:borderw=3:bordercolor=black@0.6:x=40:y=40,drawtext=text='%{localtime\\:%H\\\\\\:%M\\\\\\:%S}':fontsize=32:fontcolor=white:borderw=2:bordercolor=black@0.6:x=40:y=110" +} + +# Encode one loopable clip per pattern. Cached in CLIP_DIR, so this only costs +# something the first time the stack comes up with a given size and pattern set. +build_clip() { + local index="$1" pattern="$2" label="$3" + local clip="${CLIP_DIR}/${label}-${SIZE}-${FPS}.mp4" + + if [[ -s "$clip" ]]; then + echo "$clip" + return 0 + fi + + echo "Building loop clip ${index} (${CLIP_SECONDS}s, ${SIZE}@${FPS})..." >&2 + + # No -re here: this renders as fast as the CPU allows and then never runs + # again. Closed 2s GOPs keep the clip usable by the copy-mode ladder. + ffmpeg -hide_banner -loglevel error -y \ + -f lavfi -i "$pattern" \ + -f lavfi -i "sine=frequency=$((200 + index * 60)):sample_rate=48000" \ + -t "$CLIP_SECONDS" \ + -vf "$(overlay "$label")" \ + -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p \ + -g $((FPS * GOP_SECONDS)) -keyint_min $((FPS * GOP_SECONDS)) -sc_threshold 0 \ + -b:v 4000k -maxrate 4000k -bufsize 8000k \ + -c:a aac -b:a 128k -ar 48000 -ac 2 \ + -movflags +faststart \ + "$clip" >&2 || return 1 + + echo "$clip" +} + +publish_loop() { + local clip="$1" slug="$2" key="$3" + + # -re paces the loop at wall-clock speed; -c copy means no encoder runs at + # all. genpts rewrites the timestamps that -stream_loop resets, which SRS + # would otherwise reject as non-monotonic at every wrap. + ffmpeg -hide_banner -loglevel warning \ + -fflags +genpts -re -stream_loop -1 -i "$clip" \ + -c copy \ + -f flv "${RTMP_URL}/${slug}?secret=${key}" & +} + +publish_live() { + local pattern="$1" slug="$2" key="$3" index="$4" + + # -re keeps generation at wall-clock speed; without it ffmpeg races ahead and + # SRS drops the connection. + ffmpeg -hide_banner -loglevel warning \ + -re -f lavfi -i "$pattern" \ + -f lavfi -i "sine=frequency=$((200 + index * 60)):sample_rate=48000" \ + -vf "$(overlay "$slug")" \ + -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p \ + -g $((FPS * GOP_SECONDS)) -keyint_min $((FPS * GOP_SECONDS)) -sc_threshold 0 \ + -b:v 4000k -maxrate 4000k -bufsize 8000k \ + -c:a aac -b:a 128k -ar 48000 -ac 2 \ + -f flv "${RTMP_URL}/${slug}?secret=${key}" & +} + +mkdir -p "$CLIP_DIR" + +index=0 +for entry in $CHANNELS; do + slug="${entry%%:*}" + key="${entry#*:}" + pattern_index=$((index % ${#patterns[@]})) + pattern="${patterns[$pattern_index]}" + index=$((index + 1)) + + echo "Publishing ${slug} -> ${RTMP_URL}/${slug} (mode: ${MODE})" + + if [[ "$MODE" == "loop" ]]; then + # One clip per channel, so the slug stays burned into the picture even when + # more channels than patterns are running and two of them share a look. + if clip="$(build_clip "$pattern_index" "$pattern" "$slug")"; then + publish_loop "$clip" "$slug" "$key" + else + echo "Clip build failed for ${slug}, falling back to live encoding." >&2 + publish_live "$pattern" "$slug" "$key" "$index" + fi + else + publish_live "$pattern" "$slug" "$key" "$index" + fi + + pids+=($!) +done + +echo "Started ${#pids[@]} publisher(s)." +wait diff --git a/docker/dvr-uploader/Dockerfile b/docker/dvr-uploader/Dockerfile index 8f28a0e..44d387a 100644 --- a/docker/dvr-uploader/Dockerfile +++ b/docker/dvr-uploader/Dockerfile @@ -8,10 +8,12 @@ RUN pip install --no-cache-dir \ watchdog \ requests -# Copy the uploader script -COPY uploader.py /app/ +# Two entrypoints share this image. uploader.py handles the SRS MP4 DVR; the +# archive uploader mirrors HLS segments and maintains the hour indexes. They watch +# different volumes and are deployed as separate containers, so the MP4 path can +# keep running as a cold backup while the segment archive proves itself. +COPY uploader.py archive_uploader.py /app/ -# Create recordings directory -RUN mkdir -p /dvr/recordings +RUN mkdir -p /dvr/recordings /var/lib/dvr-archive CMD ["python", "-u", "uploader.py"] \ No newline at end of file diff --git a/docker/dvr-uploader/archive_uploader.py b/docker/dvr-uploader/archive_uploader.py new file mode 100644 index 0000000..537f174 --- /dev/null +++ b/docker/dvr-uploader/archive_uploader.py @@ -0,0 +1,735 @@ +#!/usr/bin/env python3 +""" +HLS segment archive uploader. + +Mirrors the transcoder's HLS output to S3 and maintains a per-hour index, so that a +recording can later be cut by selecting a range of segments and writing a playlist, +rather than concatenating and re-encoding MP4s. See docs/dvr-archive-plan.md. + +This runs alongside uploader.py, which keeps handling the SRS MP4 DVR as a cold +backup. The two watch different volumes and share nothing but the image. + +Three jobs, in one process because they all need the same view of the segment +directory and the same parse of the playlists: + + index read each rendition playlist, record what it says about segments that are + now complete, before the sliding window forgets them + upload copy those segments to S3 and verify the copy landed + reap delete local segments that S3 has confirmed and that have aged out of the + live rewind window + +Correctness rests on a reconciling sweep rather than inotify. Segments arrive every +two seconds, so a short polling interval is both simpler and sufficient, and a sweep +recovers from a crash without needing to have observed the events it missed. +""" + +import logging +import os +import sqlite3 +import threading +import time +from datetime import datetime, timedelta, timezone +from pathlib import Path + +import boto3 +from botocore.config import Config +from botocore.exceptions import ClientError + +logging.basicConfig( + level=os.environ.get('LOG_LEVEL', 'INFO').upper(), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', +) +logger = logging.getLogger('archive-uploader') + +# ----------------------------------------------------------------- configuration + +S3_BUCKET = os.environ.get('S3_BUCKET', 'streaming-recordings') +S3_REGION = os.environ.get('S3_REGION', 'eu-central-1') +S3_ACCESS_KEY = os.environ.get('S3_ACCESS_KEY') +S3_SECRET_KEY = os.environ.get('S3_SECRET_KEY') +S3_ENDPOINT = os.environ.get('S3_ENDPOINT') + +# Where the transcoder writes. This is the hls-content volume, not the SRS DVR one. +HLS_PATH = os.environ.get('HLS_PATH', '/var/www/hls/live') + +# Prefix for raw segments and hour indexes. +ARCHIVE_PREFIX = os.environ.get('ARCHIVE_PREFIX', 'archive') + +# Local staging for hour index playlists. Kept on disk so a restart does not have to +# rebuild an hour it already wrote, and so the index survives an S3 outage. +INDEX_PATH = os.environ.get('INDEX_PATH', '/var/lib/dvr-archive/index') +MANIFEST_DB = os.environ.get('MANIFEST_DB', '/var/lib/dvr-archive/manifest.sqlite') + +SWEEP_INTERVAL = int(os.environ.get('SWEEP_INTERVAL', '5')) +INDEX_UPLOAD_INTERVAL = int(os.environ.get('INDEX_UPLOAD_INTERVAL', '60')) +REAP_INTERVAL = int(os.environ.get('REAP_INTERVAL', '120')) + +# A segment is never deleted locally while it is still inside the live rewind window, +# regardless of whether S3 has it. Must be >= the transcoder's DVR window. +DVR_WINDOW_SECONDS = int(os.environ.get('DVR_WINDOW_SECONDS', '3600')) + +MAX_CONCURRENT_UPLOADS = int(os.environ.get('MAX_CONCURRENT_UPLOADS', '5')) + +# Ceiling on upload bandwidth, so continuous archive traffic cannot starve the edge +# of the origin's uplink. 0 disables the limit. Replaces MAX_UPLOAD_RATE_MBPS, which +# was configured on the origin for a long time but never actually read. +MAX_UPLOAD_RATE_MBPS = float(os.environ.get('MAX_UPLOAD_RATE_MBPS', '0')) + +RENDITIONS = [r for r in os.environ.get('ARCHIVE_RENDITIONS', 'sd,hd,fhd').split(',') if r] + +# Which rendition's playlist is treated as authoritative for timing. The others are +# checked against it rather than parsed independently. +CANONICAL_RENDITION = os.environ.get('CANONICAL_RENDITION', 'hd') + +boto_config = Config( + max_pool_connections=100, + retries={'max_attempts': 5, 'mode': 'adaptive'}, + read_timeout=120, + connect_timeout=30, +) + +_s3_kwargs = {'region_name': S3_REGION, 'config': boto_config} +if S3_ACCESS_KEY and S3_SECRET_KEY: + _s3_kwargs['aws_access_key_id'] = S3_ACCESS_KEY + _s3_kwargs['aws_secret_access_key'] = S3_SECRET_KEY +if S3_ENDPOINT: + _s3_kwargs['endpoint_url'] = S3_ENDPOINT + +s3 = boto3.client('s3', **_s3_kwargs) + +upload_semaphore = threading.Semaphore(MAX_CONCURRENT_UPLOADS) +rate_limiter_lock = threading.Lock() +_rate_tokens = {'bytes': 0.0, 'at': time.monotonic()} + +metrics = {'indexed': 0, 'uploaded': 0, 'verified': 0, 'reaped': 0, 'failed': 0} +metrics_lock = threading.Lock() + + +# ---------------------------------------------------------------------- manifest + +class Manifest: + """ + Durable record of what has been indexed, uploaded and verified. + + Everything here is keyed so that repeating work is harmless: the sweep re-reads + the same playlist entries every few seconds and must converge rather than + duplicate. A crash costs at most the in-flight uploads, which the next sweep + picks up again. + """ + + def __init__(self, path): + Path(path).parent.mkdir(parents=True, exist_ok=True) + self._local = threading.local() + self._path = path + with self._conn() as c: + c.executescript(""" + CREATE TABLE IF NOT EXISTS segments ( + path TEXT PRIMARY KEY, + key TEXT NOT NULL, + source TEXT NOT NULL, + size INTEGER, + etag TEXT, + uploaded_at REAL, + verified_at REAL + ); + CREATE INDEX IF NOT EXISTS segments_verified + ON segments (verified_at); + + -- One row per logical segment (all renditions share it), carrying the + -- monotonic ordering key. Assigned on first observation, never reused. + CREATE TABLE IF NOT EXISTS indexed ( + source TEXT NOT NULL, + session TEXT NOT NULL, + n INTEGER NOT NULL, + seq INTEGER NOT NULL, + hour TEXT NOT NULL, + PRIMARY KEY (source, session, n) + ); + + CREATE TABLE IF NOT EXISTS counters ( + source TEXT PRIMARY KEY, + next_seq INTEGER NOT NULL + ); + """) + + def _conn(self): + # sqlite connections are not shareable across threads. + if not hasattr(self._local, 'conn'): + self._local.conn = sqlite3.connect(self._path, timeout=30) + self._local.conn.execute('PRAGMA journal_mode=WAL') + return self._local.conn + + def next_seq(self, source, count=1): + """Reserve `count` ordering keys for a source.""" + with self._conn() as c: + row = c.execute( + 'SELECT next_seq FROM counters WHERE source = ?', (source,) + ).fetchone() + start = row[0] if row else 0 + c.execute( + 'INSERT INTO counters (source, next_seq) VALUES (?, ?) ' + 'ON CONFLICT(source) DO UPDATE SET next_seq = ?', + (source, start + count, start + count), + ) + return start + + def already_indexed(self, source, session, n): + with self._conn() as c: + return c.execute( + 'SELECT 1 FROM indexed WHERE source = ? AND session = ? AND n = ?', + (source, session, n), + ).fetchone() is not None + + def record_indexed(self, source, session, n, seq, hour): + with self._conn() as c: + c.execute( + 'INSERT OR IGNORE INTO indexed (source, session, n, seq, hour) ' + 'VALUES (?, ?, ?, ?, ?)', + (source, session, n, seq, hour), + ) + + def known(self, path): + with self._conn() as c: + return c.execute( + 'SELECT 1 FROM segments WHERE path = ?', (str(path),) + ).fetchone() is not None + + def record_pending(self, path, key, source): + with self._conn() as c: + c.execute( + 'INSERT OR IGNORE INTO segments (path, key, source) VALUES (?, ?, ?)', + (str(path), key, source), + ) + + def record_verified(self, path, size, etag): + with self._conn() as c: + c.execute( + 'UPDATE segments SET size = ?, etag = ?, uploaded_at = ?, ' + 'verified_at = ? WHERE path = ?', + (size, etag, time.time(), time.time(), str(path)), + ) + + def pending_uploads(self, limit=500): + with self._conn() as c: + return c.execute( + 'SELECT path, key, source FROM segments WHERE verified_at IS NULL ' + 'LIMIT ?', (limit,) + ).fetchall() + + def pending_count(self): + with self._conn() as c: + return c.execute( + 'SELECT COUNT(*) FROM segments WHERE verified_at IS NULL' + ).fetchone()[0] + + def reapable(self, limit=2000): + with self._conn() as c: + return c.execute( + 'SELECT path, key, size FROM segments WHERE verified_at IS NOT NULL ' + 'LIMIT ?', (limit,) + ).fetchall() + + def forget(self, path): + with self._conn() as c: + c.execute('DELETE FROM segments WHERE path = ?', (str(path),)) + + +# ------------------------------------------------------------- playlist parsing + +class Entry: + """One segment as the playlist describes it.""" + + __slots__ = ('name', 'duration', 'pdt', 'discontinuity', 'source', 'rendition', + 'session', 'n') + + def __init__(self, name, duration, pdt, discontinuity): + self.name = name + self.duration = duration + self.pdt = pdt + self.discontinuity = discontinuity + # prime_hd_1785710235_000042.ts -> (prime, hd, 1785710235, 42) + stem = name[:-3] if name.endswith('.ts') else name + parts = stem.rsplit('_', 3) + self.source, self.rendition, self.session, n = parts + self.n = int(n) + + @property + def hour(self): + """Bucket by the segment's start, so a segment straddling the boundary + belongs to the earlier hour.""" + return self.pdt.strftime('%Y%m%d/%H') + + def generic_name(self): + """Name with the rendition replaced by %v, so one index entry covers all.""" + return f'{self.source}_%v_{self.session}_{self.n:06d}.ts' + + +def parse_playlist(path): + """ + Returns (entries, complete_count). + + The last entry is excluded from `complete_count` because FFmpeg appends a segment + to the playlist as it finishes writing it; only once a *further* segment appears + is the previous one certainly closed. That is exact, unlike watching the file size + settle, which is what the MP4 uploader has to do. + """ + try: + text = Path(path).read_text() + except (OSError, UnicodeDecodeError): + return [], 0 + + entries = [] + duration = None + pdt = None + discontinuity = False + + for line in text.splitlines(): + line = line.strip() + if not line: + continue + if line.startswith('#EXTINF:'): + try: + duration = float(line[8:].split(',')[0]) + except ValueError: + duration = None + elif line.startswith('#EXT-X-PROGRAM-DATE-TIME:'): + pdt = _parse_pdt(line.split(':', 1)[1]) + elif line == '#EXT-X-DISCONTINUITY': + discontinuity = True + elif not line.startswith('#'): + if duration is not None and pdt is not None: + try: + entries.append(Entry(line, duration, pdt, discontinuity)) + except (ValueError, IndexError): + logger.debug('Unparseable segment name, skipping: %s', line) + duration = None + pdt = None + discontinuity = False + + return entries, max(0, len(entries) - 1) + + +def _parse_pdt(value): + """FFmpeg writes 2026-08-02T22:37:17.805+0000.""" + value = value.strip() + try: + # Python needs +00:00 rather than +0000 before 3.11. + if len(value) >= 5 and value[-5] in '+-' and ':' not in value[-5:]: + value = value[:-2] + ':' + value[-2:] + return datetime.fromisoformat(value) + except ValueError: + return None + + +def discover_sources(): + """Streams currently being transcoded, from the master playlists on disk.""" + base = Path(HLS_PATH) + if not base.is_dir(): + return [] + return sorted(p.name[:-len('_master.m3u8')] for p in base.glob('*_master.m3u8')) + + +# ---------------------------------------------------------------------- indexing + +class Indexer: + """ + Writes one HLS playlist per source per hour, which is the durable record of what + the sliding live playlist used to say. + + The stored form is a playlist rather than JSON on purpose: cutting a recording + then means concatenating hour files, dropping the entries outside the range and + appending ENDLIST, with no format conversion anywhere. + """ + + HEADER = ( + '#EXTM3U\n' + '#EXT-X-VERSION:6\n' + '#EXT-X-TARGETDURATION:2\n' + '#EXT-X-INDEPENDENT-SEGMENTS\n' + ) + + def __init__(self, manifest): + self.manifest = manifest + self.dirty = set() + self.lock = threading.Lock() + + def local_path(self, source, hour): + return Path(INDEX_PATH) / source / hour / 'index.m3u8' + + def s3_key(self, source, hour): + return f'{ARCHIVE_PREFIX}/{source}/{hour}/index.m3u8' + + def add(self, entries): + """Append entries not seen before. Ordering keys are assigned here, on first + observation, which is the only monotonic signal that involves no clock.""" + if not entries: + return 0 + + fresh = [ + e for e in entries + if not self.manifest.already_indexed(e.source, e.session, e.n) + ] + if not fresh: + return 0 + + source = fresh[0].source + seq = self.manifest.next_seq(source, len(fresh)) + observed = datetime.now(timezone.utc) + + written = 0 + for entry in fresh: + path = self.local_path(source, entry.hour) + path.parent.mkdir(parents=True, exist_ok=True) + if not path.exists(): + path.write_text(self.HEADER) + + with path.open('a') as fh: + if entry.discontinuity: + fh.write('#EXT-X-DISCONTINUITY\n') + fh.write(f'#EXT-X-ARCHIVE-SESSION:{entry.session}\n') + fh.write(f'#EXT-X-ARCHIVE-SEQ:{seq}\n') + # Our own wall clock at the moment this segment was first seen + # complete, independent of the PDT FFmpeg derived from the + # publisher's timeline. + # + # PDT is anchored once at session start and then advances with the + # incoming stream, so it tracks the publisher's crystal rather than + # real time. Two independent clocks at typical +/-50ppm tolerance can + # separate by several seconds a day, and a con-long session never + # reconnects to re-anchor. Recording observed time costs ~45 bytes an + # entry in a file that is read once per cut, and it means the drift + # can be measured and corrected after the fact instead of having to + # be known in advance. + fh.write( + '#EXT-X-ARCHIVE-OBSERVED:' + + observed.strftime('%Y-%m-%dT%H:%M:%S.') + + f'{observed.microsecond // 1000:03d}+0000\n' + ) + fh.write(f'#EXTINF:{entry.duration:.6f},\n') + fh.write( + '#EXT-X-PROGRAM-DATE-TIME:' + + entry.pdt.strftime('%Y-%m-%dT%H:%M:%S.') + + f'{entry.pdt.microsecond // 1000:03d}+0000\n' + ) + fh.write(entry.generic_name() + '\n') + + self.manifest.record_indexed(source, entry.session, entry.n, seq, entry.hour) + with self.lock: + self.dirty.add((source, entry.hour)) + seq += 1 + written += 1 + + return written + + def flush(self): + """Push changed hour indexes to S3. The in-progress hour is re-uploaded + whole; it is a few tens of KB, so a delta protocol would not pay for itself.""" + with self.lock: + pending = list(self.dirty) + self.dirty.clear() + + for source, hour in pending: + path = self.local_path(source, hour) + if not path.exists(): + continue + try: + s3.put_object( + Bucket=S3_BUCKET, + Key=self.s3_key(source, hour), + Body=path.read_bytes(), + ContentType='application/vnd.apple.mpegurl', + ) + except ClientError as exc: + logger.error('Index upload failed for %s/%s: %s', source, hour, exc) + # Put it back so the next flush retries. + with self.lock: + self.dirty.add((source, hour)) + + +def assert_renditions_aligned(source, canonical_entries, playlists): + """ + One index entry describes all three renditions, which only holds while FFmpeg + cuts them at identical instants. That is what -force_key_frames buys, but if it + ever stops holding the index would mislabel two renditions out of three and + nothing downstream would notice. So check rather than assume. + """ + expected = {(e.session, e.n) for e in canonical_entries} + for rendition, entries in playlists.items(): + if rendition == CANONICAL_RENDITION: + continue + got = {(e.session, e.n) for e in entries} + missing = expected - got + if len(missing) > 2: # 1-2 in flight is normal skew between renditions + logger.error( + 'Rendition %s of %s is out of step with %s: %d segments differ. ' + 'Index entries assume identical boundaries across renditions.', + rendition, source, CANONICAL_RENDITION, len(missing), + ) + + +# --------------------------------------------------------------------- uploading + +class RateLimitedReader: + """ + Token bucket over the read side of an upload. + + The origin uploads roughly 1.4 MB/s per source continuously while also feeding + the edge, so unbounded archive traffic competes with viewers for the same uplink. + boto3 takes any file-like object, so throttling reads throttles the transfer. + """ + + def __init__(self, fh, rate_bytes_per_sec): + self.fh = fh + self.rate = rate_bytes_per_sec + + def read(self, size=-1): + chunk = self.fh.read(size) + if chunk and self.rate > 0: + self._consume(len(chunk)) + return chunk + + def _consume(self, count): + with rate_limiter_lock: + now = time.monotonic() + elapsed = now - _rate_tokens['at'] + _rate_tokens['at'] = now + _rate_tokens['bytes'] = max(0.0, _rate_tokens['bytes'] - elapsed * self.rate) + _rate_tokens['bytes'] += count + over = _rate_tokens['bytes'] - self.rate # allow one second of burst + delay = over / self.rate if over > 0 else 0 + if delay > 0: + time.sleep(min(delay, 5)) + + def __getattr__(self, name): + return getattr(self.fh, name) + + +def upload_segment(manifest, path, key): + """Upload then confirm. Only a confirmed copy makes a segment reapable.""" + with upload_semaphore: + try: + local = Path(path) + if not local.exists(): + # Reaped or removed underneath us; nothing to do. + manifest.forget(path) + return + + size = local.stat().st_size + if size == 0: + return + + rate = MAX_UPLOAD_RATE_MBPS * 1_000_000 / 8 + with local.open('rb') as fh: + body = RateLimitedReader(fh, rate) if rate > 0 else fh + s3.put_object( + Bucket=S3_BUCKET, + Key=key, + Body=body, + ContentType='video/mp2t', + ) + + head = s3.head_object(Bucket=S3_BUCKET, Key=key) + if head['ContentLength'] != size: + logger.error( + 'Size mismatch for %s: local %d, remote %d. Not marking verified.', + key, size, head['ContentLength'], + ) + with metrics_lock: + metrics['failed'] += 1 + return + + manifest.record_verified(path, size, head.get('ETag', '').strip('"')) + with metrics_lock: + metrics['uploaded'] += 1 + metrics['verified'] += 1 + + except ClientError as exc: + logger.error('Upload failed for %s: %s', key, exc) + with metrics_lock: + metrics['failed'] += 1 + except OSError as exc: + logger.error('Read failed for %s: %s', path, exc) + with metrics_lock: + metrics['failed'] += 1 + + +# ------------------------------------------------------------------------ reaper + +def reap(manifest): + """ + Delete local segments that S3 has confirmed and that have left the rewind window. + + Both conditions are required. Verification alone is not enough: the segments + inside the window are what makes live rewind work, and they are also what the + transcoder's session-collision check reads. Age alone is obviously not enough. + """ + now = time.time() + freed = 0 + + for path, key, size in manifest.reapable(): + local = Path(path) + if not local.exists(): + manifest.forget(path) + continue + + try: + age = now - local.stat().st_mtime + except OSError: + continue + + if age < DVR_WINDOW_SECONDS: + continue + + # Re-confirm against S3 rather than trusting the manifest alone; a bucket + # lifecycle rule or an out-of-band delete would otherwise go unnoticed. + try: + head = s3.head_object(Bucket=S3_BUCKET, Key=key) + except ClientError: + logger.warning('Not in S3 at reap time, keeping local copy: %s', key) + continue + + if size is not None and head['ContentLength'] != size: + logger.warning('Size drift at reap time, keeping local copy: %s', key) + continue + + try: + local.unlink() + manifest.forget(path) + freed += 1 + except OSError as exc: + logger.error('Could not delete %s: %s', path, exc) + + if freed: + with metrics_lock: + metrics['reaped'] += freed + logger.info('Reaped %d verified segment(s) past the %ds window', + freed, DVR_WINDOW_SECONDS) + + +# -------------------------------------------------------------------- main sweep + +def sweep(manifest, indexer): + """One reconciling pass: parse playlists, index and enqueue what is complete.""" + for source in discover_sources(): + playlists = {} + for rendition in RENDITIONS: + path = Path(HLS_PATH) / f'{source}_{rendition}.m3u8' + if path.exists(): + entries, complete = parse_playlist(path) + playlists[rendition] = entries[:complete] + + canonical = playlists.get(CANONICAL_RENDITION) + if not canonical: + continue + + assert_renditions_aligned(source, canonical, playlists) + + written = indexer.add(canonical) + if written: + with metrics_lock: + metrics['indexed'] += written + + # Every rendition's bytes still have to be uploaded individually, even though + # one index entry covers all of them. + for rendition, entries in playlists.items(): + for entry in entries: + path = Path(HLS_PATH) / entry.name + if manifest.known(path): + continue + key = f'{ARCHIVE_PREFIX}/{source}/{entry.hour}/{entry.name}' + manifest.record_pending(path, key, source) + + for path, key, _ in manifest.pending_uploads(): + threading.Thread( + target=upload_segment, args=(manifest, path, key), daemon=True + ).start() + + +def periodic(interval, fn, *args): + while True: + time.sleep(interval) + try: + fn(*args) + except Exception: + logger.exception('%s failed', getattr(fn, '__name__', fn)) + + +def report(manifest): + """ + Metrics, plus a standing check that the upload cap is above the ingest rate. + + Setting MAX_UPLOAD_RATE_MBPS below what the transcoder produces does not degrade + gracefully: uploads simply fall behind for hours and then the origin disk fills. + The symptom appears a long way from the cause, so watch the backlog trend and say + so early. A rising backlog across several minutes means the cap (or the link) is + under the ingest rate, not that a single upload was slow. + """ + history = [] + + while True: + time.sleep(60) + + pending = manifest.pending_count() + history.append(pending) + history = history[-5:] + + with metrics_lock: + logger.info( + 'indexed=%d uploaded=%d verified=%d reaped=%d failed=%d pending=%d', + metrics['indexed'], metrics['uploaded'], metrics['verified'], + metrics['reaped'], metrics['failed'], pending, + ) + + if len(history) == 5 and all(b < a for b, a in zip(history, history[1:])): + sources = len(discover_sources()) or 1 + needed = sources * 11.5 # measured ladder total, Mbps per source + logger.error( + 'Upload backlog has grown for %d minutes straight (%s). The archive ' + 'is not keeping up with ingest and the origin disk will fill. %d ' + 'source(s) need ~%.0f Mbps sustained; cap is %s.', + len(history), ' -> '.join(str(h) for h in history), sources, needed, + f'{MAX_UPLOAD_RATE_MBPS} Mbps' if MAX_UPLOAD_RATE_MBPS > 0 + else 'unlimited (so the link itself is the limit)', + ) + + +def main(): + logger.info('HLS archive uploader starting') + logger.info('Watching %s, archiving to s3://%s/%s', HLS_PATH, S3_BUCKET, ARCHIVE_PREFIX) + logger.info('Renditions: %s (canonical %s)', ','.join(RENDITIONS), CANONICAL_RENDITION) + logger.info('Rewind window held locally: %ds', DVR_WINDOW_SECONDS) + logger.info( + 'Upload cap: %s', + f'{MAX_UPLOAD_RATE_MBPS} Mbps' if MAX_UPLOAD_RATE_MBPS > 0 else 'unlimited', + ) + + Path(INDEX_PATH).mkdir(parents=True, exist_ok=True) + manifest = Manifest(MANIFEST_DB) + indexer = Indexer(manifest) + + try: + s3.head_bucket(Bucket=S3_BUCKET) + logger.info('Connected to bucket %s', S3_BUCKET) + except ClientError as exc: + # Not fatal: the bucket may appear later, and segments accumulate on disk + # meanwhile rather than being lost. + logger.error('Bucket %s not reachable yet: %s', S3_BUCKET, exc) + + threading.Thread(target=report, args=(manifest,), daemon=True).start() + threading.Thread( + target=periodic, args=(INDEX_UPLOAD_INTERVAL, indexer.flush), daemon=True + ).start() + threading.Thread( + target=periodic, args=(REAP_INTERVAL, reap, manifest), daemon=True + ).start() + + while True: + try: + sweep(manifest, indexer) + except Exception: + logger.exception('Sweep failed') + time.sleep(SWEEP_INTERVAL) + + +if __name__ == '__main__': + main() diff --git a/docker/dvr-uploader/uploader.py b/docker/dvr-uploader/uploader.py index 8d79f96..2b1c4f3 100644 --- a/docker/dvr-uploader/uploader.py +++ b/docker/dvr-uploader/uploader.py @@ -27,7 +27,7 @@ logger = logging.getLogger('dvr-uploader') # Configuration from environment variables -S3_BUCKET = os.environ.get('S3_BUCKET', 'ef-streaming-recordings') +S3_BUCKET = os.environ.get('S3_BUCKET', 'streaming-recordings') S3_REGION = os.environ.get('S3_REGION', 'eu-central-1') S3_ACCESS_KEY = os.environ.get('S3_ACCESS_KEY') S3_SECRET_KEY = os.environ.get('S3_SECRET_KEY') diff --git a/docker/edge-nginx/Dockerfile b/docker/edge-nginx/Dockerfile new file mode 100644 index 0000000..a354383 --- /dev/null +++ b/docker/edge-nginx/Dockerfile @@ -0,0 +1,18 @@ +# Edge nginx with njs. +# +# njs is what lets a playback token be verified on the edge with a local HMAC +# instead of an auth subrequest into Laravel, which is the whole point of the +# redesign: PHP stays out of the media hot path. See +# docs/streaming-auth-redesign.md. +# +# nginx publishes nginx-module-njs for the exact nginx version in each base +# image, so the dynamic module always matches the running binary. Pinning the +# base image tag therefore pins the module too. +FROM nginx:alpine + +RUN apk add --no-cache nginx-module-njs + +COPY hls-auth.js /etc/nginx/njs/hls-auth.js + +# nginx.conf is mounted at runtime so it can be regenerated per edge without a +# rebuild. diff --git a/docker/edge-nginx/hls-auth.js b/docker/edge-nginx/hls-auth.js new file mode 100644 index 0000000..ea1ae5e --- /dev/null +++ b/docker/edge-nginx/hls-auth.js @@ -0,0 +1,301 @@ +/* + * Edge-side playback token verification. + * + * This runs inside nginx via njs and is the fast path that keeps PHP out of the + * media hot path: a request carrying `?t=` is verified here with a local + * HMAC and never touches Laravel. + * + * A request carrying the legacy `?streamkey=` falls back to the old + * /api/hls/auth subrequest, so nothing breaks while both credentials are in + * circulation. That fallback goes away when streamkeys do. + * + * Keep in sync with app/Services/PlaybackTokenService.php - the two must agree + * on the wire format exactly. See docs/streaming-auth-redesign.md. + */ + +const crypto = require('crypto'); + +const TOKEN_VERSION = 'v1'; + +/* Internal location that proxies to Laravel, used only for legacy streamkeys. */ +const LEGACY_AUTH_LOCATION = '/auth-legacy'; + +/* + * Mirrors the slug pattern in HlsSessionController. Source slugs are kebab-case + * so the underscore is safe as the quality separator: + * /live/main-stage_master.m3u8 + * /live/main-stage_fhd.m3u8 + * /live/main-stage_fhd_00042.ts + */ +const SLUG_PATTERN = /^\/live\/([^\/_]+?)(?:_(?:master|fhd|hd|sd|ld))?(?:\.|_)/; + +function env(name, fallback) { + const value = process.env[name]; + + return value === undefined || value === '' ? fallback : value; +} + +/* Matches stream.token.leeway so both ends allow the same grace past expiry. */ +function leewaySeconds() { + const parsed = Number(env('HLS_TOKEN_LEEWAY', '60')); + + return isFinite(parsed) ? parsed : 60; +} + +function secretFor(type) { + if (type === 'viewer') { + return env('HLS_VIEWER_SECRET', ''); + } + + if (type === 'embed') { + return env('HLS_EMBED_SECRET', ''); + } + + return ''; +} + +function nowSeconds() { + return Math.floor(Date.now() / 1000); +} + +function splitUri(requestUri) { + const separator = requestUri.indexOf('?'); + + if (separator === -1) { + return { path: requestUri, query: '' }; + } + + return { + path: requestUri.substring(0, separator), + query: requestUri.substring(separator + 1), + }; +} + +function queryParam(query, name) { + const parts = query.split('&'); + + for (let i = 0; i < parts.length; i++) { + const separator = parts[i].indexOf('='); + + if (separator === -1) { + continue; + } + + if (parts[i].substring(0, separator) === name) { + try { + return decodeURIComponent(parts[i].substring(separator + 1)); + } catch (e) { + return null; + } + } + } + + return null; +} + +function sourceSlug(path) { + const matches = path.match(SLUG_PATTERN); + + return matches === null ? null : matches[1]; +} + +function base64UrlDecode(value, encoding) { + try { + return Buffer.from(value.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString(encoding); + } catch (e) { + return null; + } +} + +/* + * Compared over equal-length hex digests, so a mismatch never leaks where in + * the digest it happened. Unequal lengths only occur on malformed input. + */ +function constantTimeEqual(a, b) { + if (a.length !== b.length) { + return false; + } + + let difference = 0; + + for (let i = 0; i < a.length; i++) { + difference |= a.charCodeAt(i) ^ b.charCodeAt(i); + } + + return difference === 0; +} + +function reject(reason) { + return { ok: false, reason: reason }; +} + +/* + * Returns { ok, reason } or { ok: true, claims }. Reasons are for the error log + * only; every failure answers 403 so a caller learns nothing from the response. + */ +function verifyToken(token, expectedSlug) { + const parts = token.split('.'); + + if (parts.length !== 3) { + return reject('malformed'); + } + + if (parts[0] !== TOKEN_VERSION) { + return reject('unsupported_version'); + } + + const payload = base64UrlDecode(parts[1], 'utf8'); + + if (payload === null) { + return reject('malformed'); + } + + let claims; + + try { + claims = JSON.parse(payload); + } catch (e) { + return reject('malformed'); + } + + if (claims === null || typeof claims !== 'object') { + return reject('malformed'); + } + + // The type picks which secret to check. Claiming the wrong one just fails + // the signature below, so this cannot be used to cross the two secrets. + if (claims.typ !== 'viewer' && claims.typ !== 'embed') { + return reject('malformed'); + } + + const secret = secretFor(claims.typ); + + if (secret === '') { + return reject('secret_missing'); + } + + const expected = crypto + .createHmac('sha256', secret) + .update(parts[0] + '.' + parts[1]) + .digest('hex'); + const actual = base64UrlDecode(parts[2], 'hex'); + + if (actual === null || !constantTimeEqual(expected, actual)) { + return reject('bad_signature'); + } + + // Viewer tokens must expire; embed keys are stable for a baked-in URL. + if (claims.typ === 'viewer' && typeof claims.exp !== 'number') { + return reject('missing_expiry'); + } + + if (typeof claims.exp === 'number' && nowSeconds() > claims.exp + leewaySeconds()) { + return reject('expired'); + } + + if (typeof claims.src !== 'string' || claims.src === '') { + return reject('malformed'); + } + + // The source binding is the entitlement: it was checked once at mint time. + if (expectedSlug !== null && claims.src !== expectedSlug) { + return reject('source_mismatch'); + } + + return { ok: true, claims: claims }; +} + +/* + * auth_request handler. 204 allows the request, 403 denies it. + * + * $request_uri is the original client request line and is preserved across the + * auth subrequest, so the query string is parsed from it rather than from + * $arg_* to avoid any ambiguity about whose args those are. + */ +async function verify(r) { + const target = splitUri(r.variables.request_uri || ''); + const slug = sourceSlug(target.path); + + if (slug === null) { + r.error('hls-auth: unrecognised URI ' + target.path); + r.return(403); + + return; + } + + const token = queryParam(target.query, 't'); + + if (token !== null) { + const result = verifyToken(token, slug); + + if (result.ok) { + r.return(204); + + return; + } + + r.error('hls-auth: rejected token for ' + slug + ': ' + result.reason); + r.return(403); + + return; + } + + // Internal callers send the system key. Laravel's playlist proxy uses the + // header so the URL stays identical for every viewer and the key never + // reaches the access log; ffmpeg (thumbnail capture) can only put it in the + // query string, so both are accepted. + const systemKey = env('STREAM_SYSTEM_STREAMKEY', ''); + const streamkey = queryParam(target.query, 'streamkey'); + const headerKey = r.headersIn['X-Stream-Key'] || null; + + if (systemKey !== '') { + if (headerKey !== null && constantTimeEqual(headerKey, systemKey)) { + r.return(204); + + return; + } + + if (streamkey !== null && constantTimeEqual(streamkey, systemKey)) { + r.return(204); + + return; + } + } + + if (streamkey === null) { + r.return(403); + + return; + } + + // A per-user streamkey can only be resolved in the database, so this one + // still costs a round trip to Laravel. Removed with the streamkey itself. + try { + const reply = await r.subrequest(LEGACY_AUTH_LOCATION, { args: target.query }); + + if (reply.status >= 200 && reply.status < 300) { + r.return(204); + + return; + } + + // The app answers 404 when it thinks the stream is unknown or offline. + // That is not an authorisation decision, so let the origin answer it - + // it will 404 too if the segment really is gone. Reporting 403 here made + // a stream restart look like an auth failure, and players treat a 403 as + // fatal where they will retry around a 404. + if (reply.status === 404) { + r.return(204); + + return; + } + + r.error('hls-auth: legacy auth returned ' + reply.status + ' for ' + slug); + r.return(403); + } catch (e) { + r.error('hls-auth: legacy auth subrequest failed: ' + e.message); + r.return(403); + } +} + +export default { verify }; diff --git a/docker/edge-nginx/nginx.conf b/docker/edge-nginx/nginx.conf index bee0b56..d6761c4 100644 --- a/docker/edge-nginx/nginx.conf +++ b/docker/edge-nginx/nginx.conf @@ -1,8 +1,19 @@ +# njs verifies playback tokens locally with an HMAC, so a request carrying ?t= +# never reaches Laravel. See docs/streaming-auth-redesign.md. +load_module modules/ngx_http_js_module.so; + user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; +# Passed through to njs as process.env. Keeping the secrets in the environment +# rather than in this file means they are not written to disk here. +env HLS_VIEWER_SECRET; +env HLS_EMBED_SECRET; +env HLS_TOKEN_LEEWAY; +env STREAM_SYSTEM_STREAMKEY; + events { worker_connections 4096; use epoll; @@ -10,6 +21,8 @@ events { } http { + js_import hlsAuth from /etc/nginx/njs/hls-auth.js; + include /etc/nginx/mime.types; default_type application/octet-stream; @@ -27,9 +40,13 @@ http { gzip_vary on; gzip_proxied any; gzip_comp_level 6; + # An 1800-entry DVR playlist is ~179KB raw and ~10KB gzipped, so compressing + # m3u8 is worth real bandwidth. video/mp2t is deliberately absent: MPEG-TS is + # already compressed, so gzipping segments burns CPU on every request for no + # size gain. gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss - application/vnd.apple.mpegurl video/mp2t; + application/vnd.apple.mpegurl; # Rate limiting limit_req_zone $binary_remote_addr zone=viewer_limit:10m rate=30r/s; @@ -69,8 +86,20 @@ http { add_header Content-Type text/plain; } - # Authentication subrequest endpoint + # Playback token verification, entirely local: no network call, no PHP. + # Falls back to /auth-legacy when the request carries a streamkey instead. location = /auth { + internal; + js_content hlsAuth.verify; + } + + # Legacy fallback, reached only for a per-user streamkey, which can only + # be resolved in the database. Goes away with the streamkey itself. + # + # The cache key is now effectively per streamkey rather than per segment + # URI, because $uri here is the constant /auth-legacy, so repeat segment + # requests from the same viewer stop hitting PHP. + location = /auth-legacy { internal; proxy_pass http://localhost:80/api/hls/auth; proxy_pass_request_body off; @@ -84,7 +113,14 @@ http { # Pass streamkey as header for authentication proxy_set_header X-Stream-Key $arg_streamkey; - # Cache auth responses for performance + # Cache auth responses for performance. + # + # Laravel answers with 'Cache-Control: no-cache, private', which nginx + # obeys by default - so without this the cache never stored anything and + # every single segment and playlist request went through to PHP. + # Ignoring those headers is what makes proxy_cache_valid below real. + # Cost: a revoked streamkey stays usable for up to the cache lifetime. + proxy_ignore_headers Cache-Control Expires Set-Cookie; proxy_cache auth_cache; proxy_cache_key "$remote_addr:$arg_streamkey:$uri"; proxy_cache_valid 200 1m; @@ -93,6 +129,10 @@ http { # HLS m3u8 playlist files - proxy and cache from origin location ~ ^/live/(.+\.m3u8)$ { + # Playlists are authenticated too now; previously only segments were. + auth_request /auth; + auth_request_set $auth_status $upstream_status; + # Proxy to origin Caddy server proxy_pass http://origin_caddy$request_uri; proxy_http_version 1.1; diff --git a/docker/ffmpeg-hls/stream-manager.sh b/docker/ffmpeg-hls/stream-manager.sh index 5ad8be5..3216808 100644 --- a/docker/ffmpeg-hls/stream-manager.sh +++ b/docker/ffmpeg-hls/stream-manager.sh @@ -8,14 +8,114 @@ SRS_RTMP_URL="${SRS_RTMP_URL:-rtmp://localhost:1935}" OUTPUT_BASE_DIR="${OUTPUT_BASE_DIR:-/var/www/html/hls/live}" CHECK_INTERVAL="${CHECK_INTERVAL:-5}" +# The live rewind window. hls_time is 2s, so 1800 segments is 60 minutes of seekable +# playlist. hls_delete_threshold keeps a further margin of segments on disk after they +# fall out of the playlist; that margin is the grace the S3 uploader gets to copy a +# segment before it disappears. See docs/dvr-archive-plan.md. +DVR_WINDOW_SEGMENTS="${DVR_WINDOW_SEGMENTS:-1800}" +HLS_DELETE_THRESHOLD="${HLS_DELETE_THRESHOLD:-60}" + +# A publisher reconnect starts a new FFmpeg session under a new timestamp prefix, and +# FFmpeg only ever deletes segments it wrote itself, so the previous session's files +# are left behind. This reaper clears them. Retention is derived from the window plus +# a margin, so it can never reach a segment the current playlist still references. +# Set to 0 to disable, which is what the S3 uploader wants once it owns deletion. +ORPHAN_RETENTION_MINUTES="${ORPHAN_RETENTION_MINUTES:-$(( (DVR_WINDOW_SEGMENTS + HLS_DELETE_THRESHOLD) * 2 / 60 + 5 ))}" +REAP_INTERVAL_SECONDS="${REAP_INTERVAL_SECONDS:-300}" + +# FFmpeg can sit alive while producing nothing, which the PID check in check_streams +# cannot see. Playlists are rewritten on every completed segment, so their mtime is +# the liveness signal. Zero disables the watchdog. +SEGMENT_STALL_SECONDS="${SEGMENT_STALL_SECONDS:-15}" +STARTUP_GRACE_SECONDS="${STARTUP_GRACE_SECONDS:-30}" + +# transcode: the real ABR ladder (480p/720p/1080p), three x264 encodes per stream. +# copy: remux only. The publisher's bitstream is written to all three +# renditions unchanged, so the master playlist, the variant names and +# the segment layout are identical to production while the CPU cost +# is roughly zero. Quality switching in the player is a no-op picture +# wise; everything around it still behaves the same. +ABR_MODE="${ABR_MODE:-transcode}" + # Associative array to track running FFmpeg processes declare -A FFMPEG_PIDS declare -A STREAM_APPS +declare -A FFMPEG_STARTED + +# Rewrite the master playlist so the three copy-mode renditions look distinct. +# +# In copy mode every rendition carries the same bitstream, so FFmpeg writes the +# same BANDWIDTH and RESOLUTION for all three. Players treat indistinguishable +# levels as a single quality: hls.js collapses them and the quality menu +# disappears, which makes the picker impossible to work on locally. +# +# Substituting the production ladder's numbers restores a three-rung menu that +# behaves like the real thing. The picture genuinely does not change when you +# switch - only the advertised metadata differs. CODECS is left as FFmpeg wrote +# it, since that part is accurate. +# +# Only ever called for ABR_MODE=copy. +rewrite_copy_mode_master() { + local master=$1 + + [[ -f "$master" ]] || return 1 + + awk ' + /^#EXT-X-STREAM-INF:/ { stream_inf = $0; next } + stream_inf != "" && /^[^#]/ { + bandwidth = "4500000"; resolution = "1280x720" + if ($0 ~ /_sd\.m3u8/) { bandwidth = "1500000"; resolution = "854x480" } + if ($0 ~ /_hd\.m3u8/) { bandwidth = "3500000"; resolution = "1280x720" } + if ($0 ~ /_fhd\.m3u8/) { bandwidth = "6000000"; resolution = "1920x1080" } + + sub(/BANDWIDTH=[0-9]+/, "BANDWIDTH=" bandwidth, stream_inf) + sub(/RESOLUTION=[0-9]+x[0-9]+/, "RESOLUTION=" resolution, stream_inf) + + print stream_inf + stream_inf = "" + } + { if (stream_inf == "") print } + ' "$master" > "${master}.tmp" || return 1 + + # Rename so a player never reads a half-written master. + mv "${master}.tmp" "$master" +} + +# FFmpeg writes the master once at startup, so wait for it to appear and then +# patch it. Runs in the background so it never delays stream startup. +watch_copy_mode_master() { + local master=$1 + local pid=$2 + local waited=0 + + while [[ ! -f "$master" ]] && kill -0 "$pid" 2>/dev/null && [ $waited -lt 30 ]; do + sleep 1 + waited=$((waited + 1)) + done + + kill -0 "$pid" 2>/dev/null || return 0 + + if rewrite_copy_mode_master "$master"; then + echo "[$(date)] Advertised a distinct ladder in $(basename "$master") (copy mode)" + fi + + # Cheap guard in case FFmpeg rewrites the master mid-session, e.g. on a + # discontinuity. Costs one grep per interval for as long as the stream runs. + while kill -0 "$pid" 2>/dev/null; do + sleep "$CHECK_INTERVAL" + if [[ -f "$master" ]] && ! grep -q 'BANDWIDTH=1500000' "$master" 2>/dev/null; then + rewrite_copy_mode_master "$master" + fi + done +} echo "Starting Dynamic FFmpeg HLS Manager" echo "SRS API: $SRS_API_URL" echo "Output directory: $OUTPUT_BASE_DIR" echo "Check interval: ${CHECK_INTERVAL}s" +echo "DVR window: ${DVR_WINDOW_SEGMENTS} segments (+${HLS_DELETE_THRESHOLD} retained)" +echo "Orphan retention: ${ORPHAN_RETENTION_MINUTES}m" +echo "Stall watchdog: ${SEGMENT_STALL_SECONDS}s (grace ${STARTUP_GRACE_SECONDS}s)" # Function to start FFmpeg for a stream start_ffmpeg() { @@ -53,53 +153,103 @@ start_ffmpeg() { # Create output directory local output_dir="$OUTPUT_BASE_DIR" mkdir -p "$output_dir" - - # Clean up old segments and playlists for this stream - echo "[$(date)] Cleaning up old HLS files for $stream" - rm -f "$output_dir/${stream}_*.ts" 2>/dev/null - rm -f "$output_dir/${stream}_*.m3u8" 2>/dev/null - rm -f "$output_dir/${stream}.m3u8" 2>/dev/null - - # Generate a unique timestamp prefix for this session + + # No cleanup here on purpose. Segments from a previous session are the archive + # until the S3 uploader has them, so they are left for the orphan reaper to age + # out rather than deleted on sight. The playlists are overwritten by FFmpeg + # anyway, since the filenames do not carry the session prefix. + # + # (The three `rm -f "$output_dir/${stream}_*.ts"` lines that used to sit here were + # inert regardless: the glob was inside the quotes, so they only ever tried to + # remove a file literally named `${stream}_*.ts`.) + + # Session id, used to keep one FFmpeg run's segments distinct from the next. + # + # Clock-derived so it stays readable, but `date +%s` only has second resolution: + # two starts inside the same second reuse the prefix, and the new session then + # writes ${stream}_hd__000000.ts straight over the previous session's + # segments. A fast crash-restart loop through check_streams (5s interval) can + # reach that, and the result is silent archive loss rather than a visible error. + # + # Bump until the prefix is unused. Previous sessions' segments are still on disk + # until the orphan reaper takes them, which is exactly what makes this check work. local timestamp_prefix=$(date +%s) + while compgen -G "$output_dir/${stream}_*_${timestamp_prefix}_*.ts" >/dev/null; do + timestamp_prefix=$((timestamp_prefix + 1)) + done - # Start FFmpeg with filter_complex for synchronized multi-bitrate HLS + # The ladder itself: either three real encodes, or the same bitstream copied + # into three renditions. Everything after this point is identical, so the + # output layout does not depend on the mode. + local ladder_args=() + + if [[ "$ABR_MODE" == "copy" ]]; then + echo "[$(date)] ABR_MODE=copy - remuxing $stream_key without transcoding" + + # Segment boundaries land on the publisher's keyframes, so the incoming + # GOP must already match hls_time (the dev publisher sends a 2s GOP). + ladder_args=( + -map 0:v -map 0:a + -map 0:v -map 0:a + -map 0:v -map 0:a + -c copy + -avoid_negative_ts make_zero -fflags +genpts + ) + else + ladder_args=( + -filter_complex + "[0:v]split=3[v1][v2][v3]; \ + [v1]scale=w=854:h=480[v1out]; \ + [v2]scale=w=1280:h=720[v2out]; \ + [v3]scale=w=1920:h=1080[v3out]" + -map "[v1out]" -c:v:0 libx264 -b:v:0 1500k -maxrate:v:0 2000k -bufsize:v:0 3000k + -preset:v:0 veryfast -profile:v:0 baseline -g 60 -keyint_min 60 -sc_threshold 0 + -force_key_frames "expr:gte(t,n_forced*2)" + -map "[v2out]" -c:v:1 libx264 -b:v:1 3500k -maxrate:v:1 4000k -bufsize:v:1 8000k + -preset:v:1 veryfast -profile:v:1 main -g 60 -keyint_min 60 -sc_threshold 0 + -force_key_frames "expr:gte(t,n_forced*2)" + -map "[v3out]" -c:v:2 libx264 -b:v:2 6000k -maxrate:v:2 6500k -bufsize:v:2 13000k + -preset:v:2 faster -profile:v:2 main -g 60 -keyint_min 60 -sc_threshold 0 + -force_key_frames "expr:gte(t,n_forced*2)" + -map 0:a -c:a:0 aac -b:a:0 128k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" + -map 0:a -c:a:1 aac -b:a:1 160k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" + -map 0:a -c:a:2 aac -b:a:2 192k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" + -avoid_negative_ts make_zero -fflags +genpts + ) + fi + + # Start FFmpeg for synchronized multi-bitrate HLS ffmpeg -f flv -i "$SRS_RTMP_URL/$app/$stream" \ - -filter_complex \ - "[0:v]split=3[v1][v2][v3]; \ - [v1]scale=w=854:h=480[v1out]; \ - [v2]scale=w=1280:h=720[v2out]; \ - [v3]scale=w=1920:h=1080[v3out]" \ - -map "[v1out]" -c:v:0 libx264 -b:v:0 1500k -maxrate:v:0 2000k -bufsize:v:0 3000k \ - -preset:v:0 veryfast -profile:v:0 baseline -g 60 -keyint_min 60 -sc_threshold 0 \ - -force_key_frames "expr:gte(t,n_forced*2)" \ - -map "[v2out]" -c:v:1 libx264 -b:v:1 3500k -maxrate:v:1 4000k -bufsize:v:1 8000k \ - -preset:v:1 veryfast -profile:v:1 main -g 60 -keyint_min 60 -sc_threshold 0 \ - -force_key_frames "expr:gte(t,n_forced*2)" \ - -map "[v3out]" -c:v:2 libx264 -b:v:2 6000k -maxrate:v:2 6500k -bufsize:v:2 13000k \ - -preset:v:2 faster -profile:v:2 main -g 60 -keyint_min 60 -sc_threshold 0 \ - -force_key_frames "expr:gte(t,n_forced*2)" \ - -map 0:a -c:a:0 aac -b:a:0 128k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" \ - -map 0:a -c:a:1 aac -b:a:1 160k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" \ - -map 0:a -c:a:2 aac -b:a:2 192k -ac 2 -af "aresample=async=1:min_hard_comp=0.100000:first_pts=0" \ - -avoid_negative_ts make_zero -fflags +genpts \ + "${ladder_args[@]}" \ -f hls \ -hls_time 2 \ - -hls_list_size 60 \ - -hls_delete_threshold 60 \ + -hls_list_size "$DVR_WINDOW_SEGMENTS" \ + -hls_delete_threshold "$HLS_DELETE_THRESHOLD" \ -hls_flags independent_segments+delete_segments+program_date_time+discont_start \ -hls_segment_type mpegts \ -start_number 0 \ - -hls_segment_filename "$output_dir/${stream}_%v_${timestamp_prefix}_%05d.ts" \ + -hls_segment_filename "$output_dir/${stream}_%v_${timestamp_prefix}_%06d.ts" \ -master_pl_name "${stream}_master.m3u8" \ -var_stream_map "v:0,a:0,name:sd v:1,a:1,name:hd v:2,a:2,name:fhd" \ "$output_dir/${stream}_%v.m3u8" \ - 2>&1 | sed "s/^/[FFmpeg $stream] /" & - + > >(sed "s/^/[FFmpeg $stream] /") 2>&1 & + + # Process substitution rather than `| sed`, because for a backgrounded pipeline + # `$!` is the PID of the *last* stage. It used to be sed's, so stop_ffmpeg killed + # the log prefixer and left FFmpeg encoding forever; only the pgrep fallback above + # ever cleaned those up, and then only if the same stream came back. This way $! + # is FFmpeg itself and the sed exits on its own when FFmpeg closes the pipe. local pid=$! FFMPEG_PIDS[$stream_key]=$pid STREAM_APPS[$stream_key]="$app" - + FFMPEG_STARTED[$stream_key]=$(date +%s) + + # Copy mode advertises one indistinguishable rendition three times, which + # hides the quality menu. Patch the master once FFmpeg has written it. + if [[ "$ABR_MODE" == "copy" ]]; then + watch_copy_mode_master "$output_dir/${stream}_master.m3u8" "$pid" & + fi + echo "[$(date)] Started FFmpeg for $stream_key with PID $pid" } @@ -128,17 +278,94 @@ stop_ffmpeg() { fi fi - # Clean up HLS files + # Retire the playlists so a player gets a 404 rather than a frozen window. + # + # Deliberately not `rm -f "$OUTPUT_BASE_DIR/${stream}"*` as before: that glob + # took the segments with it, and the segments are the archive until the S3 + # uploader has confirmed copies. The orphan reaper ages them out instead. local stream="${stream_key#*/}" - rm -f "$OUTPUT_BASE_DIR/${stream}"* - + rm -f "$OUTPUT_BASE_DIR/${stream}_"*.m3u8 "$OUTPUT_BASE_DIR/${stream}.m3u8" 2>/dev/null + unset FFMPEG_PIDS[$stream_key] unset STREAM_APPS[$stream_key] - + unset FFMPEG_STARTED[$stream_key] + echo "[$(date)] Stopped FFmpeg for $stream_key" fi } +# Newest mtime across a stream's playlists, as an age in seconds. -1 when none exist. +# +# The master playlist is written once and then left alone, so the variant playlists +# are what actually move; taking the maximum lets them dominate. +playlist_age() { + local stream=$1 + local newest=0 + local mtime + + for playlist in "$OUTPUT_BASE_DIR/${stream}_"*.m3u8; do + [[ -f "$playlist" ]] || continue + mtime=$(stat -c %Y "$playlist" 2>/dev/null) || continue + [[ "$mtime" -gt "$newest" ]] && newest=$mtime + done + + if [[ "$newest" -eq 0 ]]; then + echo -1 + return + fi + + echo $(( $(date +%s) - newest )) +} + +# FFmpeg can hold its PID while its input has gone away, which check_streams cannot +# detect because it only compares PIDs against the SRS stream list. Restart a stream +# whose playlists have stopped advancing while SRS still reports it as publishing. +check_stalled() { + local stream_key=$1 + local stream="${stream_key#*/}" + # Captured before stop_ffmpeg, which unsets it. + local app="${STREAM_APPS[$stream_key]}" + + [[ "$SEGMENT_STALL_SECONDS" -gt 0 ]] || return 0 + [[ -n "$app" ]] || return 0 + + local started="${FFMPEG_STARTED[$stream_key]:-0}" + if [[ $(( $(date +%s) - started )) -lt "$STARTUP_GRACE_SECONDS" ]]; then + return 0 + fi + + local age + age=$(playlist_age "$stream") + + if [[ "$age" -lt 0 ]]; then + echo "[$(date)] $stream_key wrote no playlist within ${STARTUP_GRACE_SECONDS}s, restarting FFmpeg" + stop_ffmpeg "$stream_key" + start_ffmpeg "$app" "$stream" + return + fi + + if [[ "$age" -gt "$SEGMENT_STALL_SECONDS" ]]; then + echo "[$(date)] $stream_key stalled: no playlist update for ${age}s, restarting FFmpeg" + stop_ffmpeg "$stream_key" + start_ffmpeg "$app" "$stream" + fi +} + +# Segments left behind by a previous FFmpeg session, which FFmpeg itself will never +# delete. Retention sits above the playlist window, so a segment the current playlist +# still references can never be caught here. +reap_orphan_segments() { + [[ "$ORPHAN_RETENTION_MINUTES" -gt 0 ]] || return 0 + + local orphans + orphans=$(find "$OUTPUT_BASE_DIR" -maxdepth 1 -name '*.ts' -mmin +"$ORPHAN_RETENTION_MINUTES" | wc -l) + + [[ "$orphans" -gt 0 ]] || return 0 + + find "$OUTPUT_BASE_DIR" -maxdepth 1 -name '*.ts' -mmin +"$ORPHAN_RETENTION_MINUTES" -delete + echo "[$(date)] Reaped $orphans orphaned segment(s) older than ${ORPHAN_RETENTION_MINUTES}m" +} + # Function to check SRS API for active streams check_streams() { # Get current streams from SRS API @@ -177,6 +404,9 @@ check_streams() { # Skip if already processing or if it's a quality variant from old setup if [[ ! "$stream" =~ _(fhd|hd|sd|ld)$ ]]; then start_ffmpeg "$app" "$stream" + # No-op for a process that was just started; the startup grace + # covers it. + check_stalled "$stream_key" fi fi fi @@ -221,6 +451,8 @@ echo "[$(date)] Starting monitoring loop..." SIGNAL_FILE="/tmp/check_streams" touch "$SIGNAL_FILE" +last_reap=$(date +%s) + # Monitor both timer and signal file while true; do # Check streams immediately if signal file was modified @@ -236,6 +468,12 @@ while true; do # Regular interval check check_streams - + + now=$(date +%s) + if [[ $(( now - last_reap )) -ge "$REAP_INTERVAL_SECONDS" ]]; then + reap_orphan_segments + last_reap=$now + fi + sleep "$CHECK_INTERVAL" done \ No newline at end of file diff --git a/docker/origin-nginx/nginx.conf b/docker/origin-nginx/nginx.conf index 9cc6e1e..ef98f95 100644 --- a/docker/origin-nginx/nginx.conf +++ b/docker/origin-nginx/nginx.conf @@ -27,9 +27,12 @@ http { gzip_vary on; gzip_proxied any; gzip_comp_level 6; + # video/mp2t is deliberately absent: MPEG-TS is already compressed, so gzipping + # segments burns CPU on every request for no size gain. Playlists do compress, + # and at a 60 minute DVR window they are large enough for it to matter. gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss - application/vnd.apple.mpegurl video/mp2t; + application/vnd.apple.mpegurl; # Upstream for Laravel authentication service upstream laravel_auth { diff --git a/docs/admin/auto-mode.md b/docs/admin/auto-mode.md new file mode 100644 index 0000000..c0642ae --- /dev/null +++ b/docs/admin/auto-mode.md @@ -0,0 +1,88 @@ +# Auto mode + +How a show starts and stops without anyone pressing a button, and where the safety net is. + +Applies only to shows with **Auto mode** on. Everything else is driven by hand from the show's status control. + +## The two rules + +Auto mode is one switch that turns on two independent rules. `shows:check-auto-mode` runs every minute (`app/Console/Kernel.php`) and applies them. + +### 1. Start when the source comes online + +A show goes live when **all** of these hold: + +- `auto_mode` is on +- `status` is `scheduled` +- `scheduled_start` has passed +- its **source is online** + +The source check is the point. A show that went live purely on the clock would open an empty stream and a black recording. Auto mode waits for the encoder. + +If the source comes up late, the show goes live late - at the next minute tick after the source reports online. `actual_start` is stamped then, so the recording in-point matches what was really broadcast, not the schedule. + +If the source never comes up, the show stays `scheduled` and nothing is recorded. + +### 2. Stop at the hard stop, whatever the source is doing + +A live show ends when: + +- `auto_mode` is on +- `status` is `live` +- the **hard stop** has passed + +The hard stop is `auto_stop_at`, and falls back to `scheduled_end` when that is empty. + +No source check here, deliberately. This is the safety net: a dance scheduled 22:00-01:00 where the encoder keeps pushing after the room empties, and nobody remembers to press End Stream, would otherwise record until someone notices in the morning. The hard stop cuts it. + +## Hard stop versus scheduled end + +They answer different questions, which is why they are separate fields: + +| Field | Question it answers | Used by | +|---|---|---| +| `scheduled_end` | When does the programme guide say this slot is over? | public schedule, the grid, "up next" | +| `auto_stop_at` | What is the last moment this may still be recording? | auto mode only | + +Leave the hard stop empty and it *is* the scheduled end - the behaviour before the field existed. Set it later than the scheduled end when a slot habitually overruns and you would rather have the tail than a cut. Set it earlier when the recording must not run past a point, whatever the guide says. + +The form defaults the hard stop to the scheduled end when auto mode is switched on, so the safe behaviour is what you get without thinking about it. + +## What auto mode does not do + +- **It does not cancel.** A show whose source never comes online stays `scheduled`. Cancelling is a decision, so it stays with the operator. +- **It does not restart.** Once a show has ended, auto mode will not bring it back if the source returns. Start it by hand, or schedule the next slot. +- **It does not touch a live show's status.** Going live and ending both run through `Show::goLive()` and `Show::endLivestream()`, the same methods the buttons call, so viewer notification and timestamps behave identically either way. +- **It is not autoscaling.** Server capacity is provisioned by hand; see `docs/admin/rebuild-plan.md` 2.9. + +## Operating it + +On the show form: + +``` +Auto mode ☑ start when the source comes online, stop at the hard stop +Hard stop [ 2026-08-01 01:30 ] defaults to the scheduled end +``` + +With auto mode off, both rules are off and the hard stop is ignored. + +## Where it lives + +| Piece | File | +|---|---| +| The two rules, once a minute | `app/Console/Commands/CheckAutoModeShows.php` | +| Schedule entry | `app/Console/Kernel.php` | +| `autoStopAt()`, `isPastAutoStop()` | `app/Models/Show.php` | +| Column | `database/migrations/2026_07_30_120000_add_auto_stop_at_to_shows_table.php` | +| Tests | `tests/Unit/Commands/CheckAutoModeShowsTest.php`, `tests/Feature/Manage/ShowsTest.php` | + +## Log lines to look for + +Both rules log at info with the show id and title. When a show stopped and you want to know why: + +``` +Auto mode: hard stop reached, ending show + hard_stop: 2026-08-01T01:30:00+02:00 + explicit_hard_stop: true <- an operator set it, it was not the scheduled end + source_status: online <- the encoder was still pushing; the net did its job +``` diff --git a/docs/admin/current-filament-features.md b/docs/admin/current-filament-features.md new file mode 100644 index 0000000..9e04211 --- /dev/null +++ b/docs/admin/current-filament-features.md @@ -0,0 +1,269 @@ +# Current Filament Admin: Feature Inventory + +Snapshot of everything `/admin` does today. This is the parity contract for the Inertia rebuild: every row here must have either a replacement or an explicit "dropped, because X" decision. + +Source of truth: `app/Filament/**`, `app/Providers/Filament/AdminPanelProvider.php`, `resources/views/filament/**`. + +## 1. Panel shell + +`app/Providers/Filament/AdminPanelProvider.php` + +| Aspect | Current value | +|---|---| +| Path | `/admin`, default panel | +| Auth | Filament's own `->login()` screen at `/admin/login` (separate from the app's OIDC login at `/login`) | +| Gate | `User::canAccessPanel()` — `hasPermission('filament.access') || isStaff()` (`app/Models/User.php:378`) | +| Brand | Brand name from `BrandingService`, `favicon.svg` | +| Theme | primary = Purple, gray = Slate, sidebar collapsible on desktop, `maxContentWidth('100%')` | +| Nav groups | Streaming, Infrastructure, User Management, Chat (+ an undeclared `Content` group used by RecordingResource) | +| Dashboard | Filament default `Dashboard` page + `AccountWidget` + `FilamentInfoWidget` + all auto-discovered widgets | + +Notes / bugs to carry over consciously: +- `RecordingResource` declares `navigationGroup = 'Content'`, which is not in `navigationGroups()`. It renders as an ad-hoc group. +- Guests hitting `/admin` redirect to `/admin/login`, not to the app's OIDC flow. Two login screens exist. +- `HandleInertiaRequests` already shares `auth.can_access_filament` to the SPA (`app/Http/Middleware/HandleInertiaRequests.php:78`) using `$user->can('filament.access')`, a *different* check than `canAccessPanel()`. The rebuild should unify these. + +## 2. Resources + +### 2.1 Sources (`SourceResource`) — Streaming, sort 1 + +Model `Source`. Nav badge = count of `ONLINE` sources, green. + +Form: +- Section "Basic Information" (2 col): `name` (required, live-on-blur, auto-slugs into `slug` **on create only**), `slug` labelled "Stream Name" (required, unique ignoring record, **disabled on edit** — it is the RTMP ingress path and the HLS route key, so changing it disconnects OBS and breaks playback; `Source::updating()` also reverts any slug write), `priority` (numeric 0..999, "higher first on homepage"), `description` textarea (full width). **No `status` field**: status is edited only through the table's Update Status action, so there is one path. +- Section "Stream Configuration" ("OBS Studio Configuration"): two read-only placeholders rendering raw HTML — `getRtmpServerUrl()` and `getObsStreamKey()` — each click-to-copy via inline `navigator.clipboard` JS. Both show "Will be generated on save" for new records. Below them, a form action **Regenerate Stream Key** (visible on edit only, confirm modal, `Str::random(32)`, saves, warns that active streams disconnect) sits next to the key it replaces. + +Table (poll 10s, default sort `priority` desc): +- `status` badge with per-state color + heroicon (signal / signal-slash / exclamation-triangle) +- `name` (searchable, sortable, bold) +- `slug` labelled "Stream Name", badge, copyable, searchable +- `priority` badge, sortable +- `shows_count` (relation count) "Total Shows", sortable +- `live_shows_count` computed via `liveShows()->count()`, badge green when > 0 +- `created_at`, `updated_at` — hidden by default, toggleable + +Filters: `status` select ("All statuses" placeholder). + +Row actions: +- **Update Status** — modal form with a status select prefilled from the record; saves and relies on the model observer to broadcast; success toast. This is the *only* way to change a source status. +- Edit +- Delete — blocked with a danger toast if `liveShows()` exists. + +Bulk actions (grouped): **Update Status** (same modal, applied to each, deselects after), Delete (blocked if any selected source has live shows). + +`EditSource` header actions: Delete only. + +Relation manager — **Shows**: columns `title`, `status` badge, `scheduled_start`, `viewer_count`; header Create; row Edit/Delete; bulk Delete. + +### 2.2 Shows (`ShowResource`) — Streaming, sort 2 + +Model `Show`. Nav badge: `"{n} live"` (green) else `"{n} upcoming"` (amber) else none. + +Form sections: +1. **Show Information** (2 col): `title` (live-on-blur; on create only, sets `slug` = `Str::slug(title-YYYY-MM-DD)`), `slug` (unique), `source_id` select (required, options from `Source::ordered()`, searchable, preload), `server_id` select (optional, only `status = available` servers, by hostname), `description` textarea full width. +2. **Schedule** (2 col, all `Europe/Berlin`, seconds off): `scheduled_start` (required), `scheduled_end` (required, `after:scheduled_start`), `actual_start`, `actual_end`. +3. **Status & Settings** (2 col): `status` select (scheduled/live/ended/cancelled, **disabled while live**), `auto_mode` toggle, `recordable` toggle, `required_roles` checkbox list (options = `Role::pluck('name','slug')`, empty = public), `thumbnail_path` file upload (image, S3 disk, `shows/thumbnails`, max 5 MB, jpeg/png/webp, private visibility, preserve filenames, 250px preview, custom state loader), `tags` tags-input with 7 suggestions (Main Stage, Panel, Workshop, Performance, Interview, Opening Ceremony, Closing Ceremony). +4. **Statistics** (3 col, only on edit): read-only `viewer_count`, `peak_viewer_count`, `formatted_duration`. +5. **Additional Configuration** (collapsed): `metadata` key/value editor. + +Table (poll 5s, default sort `scheduled_start` asc): +- `thumbnail_url` image column (square, 40px) — uses the signed-URL accessor, not the raw path +- `title` (searchable, sortable, bold) +- `source.name` badge, searchable, sortable +- `status` badge — colors live/scheduled/ended/cancelled + icons +- `scheduled_start` `M j, Y H:i`, sortable +- `actual_start` "Went Live", placeholder "Not started", toggleable +- `viewer_count` badge (green when > 0), numeric, sortable +- `peak_viewer_count` "Peak", hidden by default +- `auto_mode` rendered as `Auto`/`Manual` badge with cog / hand-raised icons +- `required_roles` rendered as `Restricted`/`Public` badge via `hasAccessRestriction()`, lock / globe icons, hidden by default +- `tags` badge list, comma separated, hidden by default + +Filters: `hide_ended` (**on by default**, `status != ended`), `status` multi-select, `source` relationship select, `today` (`Show::today()` scope), `upcoming` (`Show::upcoming()` scope). + +Row actions: +- **Go Live** — visible only when `scheduled`; confirm modal ("will mark it as live and notify viewers"); calls `$show->goLive()`; success toast. +- **End Stream** — visible only when `live`; confirm modal; calls `$show->endLivestream()`. +- **View Statistics** — links to the custom statistics page. +- Edit +- Delete — blocked with a danger toast when `status === 'live'`. + +Bulk actions (grouped): **Cancel Shows** (calls `cancel()` on each `scheduled` record), Delete (blocked if any selected show is live). + +Header action: **Live Dashboard** — opens the Stream Control page in a new tab. + +Custom page actions — `EditShow`: **Capture Screenshot** (disabled unless status is `live` *and* a source is assigned; tooltip explains which precondition failed; calls `$show->captureScreenshot()`, refills the form, distinct toasts for success / null result / exception) + Delete. + +Custom page — `ViewShowStatistics` (`/admin/shows/{record}/statistics`): title "Statistics for {title}"; data from `ShowStatisticsService::getShowStatistics()` plus `getRealtimeStats()` when live. Blade renders: 4 stat cards (current / peak / average / total unique viewers, with a "● Live" marker), a Broadcast Information definition list (scheduled + actual start/end, duration), and further sections in `resources/views/filament/resources/show-resource/pages/view-show-statistics.blade.php`. + +Relation manager — **Viewers** (`viewerSessions`): `user.name`, `user.email`, `joined_at`, `left_at` (placeholder "Still watching"), computed `watch_duration` formatted `XhYmZs`, `ip_address` (hidden by default), `is_active` → `Active`/`Inactive` badge. Filter: "Currently Watching" (`active()` scope). No actions — read-only. + +### 2.3 Servers (`ServerResource`) — Infrastructure, sort 10 + +Model `Server`, slug `servers`. No nav badge. + +Form (single column): `hetzner_id` (disabled on edit), `hostname` (required), `ip`, `port` (numeric 1..65535, default 8080), `shared_secret` (disabled on edit, defaults to `Str::random(40)`, required), `type` select origin/edge (disabled on edit, default edge), `max_clients` (numeric, **only shown for edge**, default 100), `status` select (provisioning/active/deprovisioning/deleted/error, default active — manual override), `immutable` checkbox (edge only, default true, prevents autoscaler deletion), read-only `created_at` / `updated_at` as `diffForHumans()`. + +Table (`->poll()` default interval; query excludes `status = deleted`): +- `hetzner_id` "Server ID", searchable, `-` fallback +- `type` badge (origin = amber, edge = green) +- `hostname` searchable + copyable +- `ip` copyable +- `port` sortable +- `status` badge with 5-state color map +- `viewer_count` "Viewers" badge — shows `-` for origin; description line shows `N% capacity` for edge with `max_clients > 0` +- `last_heartbeat` icon column — check/x by `hasRecentHeartbeat()`, tooltip with `diffForHumans()` +- `health_status` badge (healthy/unhealthy/unknown), tooltip with last check time + `health_check_message`, edge only +- `max_clients` sortable + +Filters: `status` multi-select, `type` select. + +Row actions: Edit; **Install Script** (links to custom page); **Deprovision** (confirm, only when `hetzner_id` present, calls `$server->deprovision()`); **Delete** (confirm, only for manual servers with no `hetzner_id`). + +`ListServers` header actions: +- **New Manual Server** (create) +- **Enable Autoscaler** / **Disable Autoscaler** — mutually exclusive by `AutoscalerService::isAutoscalerEnabled()`, green / red +- **Provision Cloud Server** — modal with a type select (`Origin (ccx43 - High Performance)` / `Edge (cpx21 - Standard)`); refuses a second origin if one is active or provisioning (danger toast); otherwise creates a `provisioning` server row (hostname `pending`, port 443, random secret, max_clients 1000 origin / 100 edge) and dispatches `CreateVirtualMachineJob`. + +Custom page — `ViewInstallScript` (`/admin/servers/{record}/install-script`): title `Install Script - Server #{id} ({type})`. Generates the install script and cloud-init via `ServerProvisioningService`, then regex-extracts embedded configs into tabs: docker-compose, SRS conf, nginx (origin or edge), Caddyfile, plus FFmpeg placeholders for origin. Tab state in `activeTab`. Header actions: **Copy Install Script** (toast; copy done in JS), **Download Script** (`streamDownload` as `install-{id}.sh`), **Regenerate Scripts** (confirm; backfills `shared_secret` if missing). + +Relation manager — `UserRelationManager` (`user`): columns `sub`, `name`. **Declared in the file but not returned by `getRelations()`, so it is dead code today.** + +### 2.4 Users (`UserResource`) — User Management, sort 20 + +Model `User`, slug `users`. Globally searchable on `name`. + +Form: `sub` (disabled, required), `name` (disabled, required), `reg_id` (disabled, integer), `server_id` select (relationship on `server.hostname`, filtered to edge + active), read-only `updated_at` / `created_at` as `diffForHumans()`. + +Table: `sub`, `name` (searchable, sortable), `reg_id`. No filters, no row actions, no bulk actions. `ListUsers` has a Create header action; `EditUser` has Delete. + +Relation managers: +- **Roles**: `name` (bold), `slug` badge, `chat_color` color column (copyable), `priority` badge with a 4-tier color ramp, `assigned_at_login` toggle (disabled/read-only). Header **Attach** (role select from `Role::ordered()`, preloaded, success toast). Row **Detach** with confirm modal. Bulk Detach with confirm. Sort `priority` desc, paginate 10/25/50. +- **Messages**: `user.name`, `message`, `is_command`. Form exists (user select, message, is_command, timestamps) but no header/row actions are wired, so it is read-only in practice. + +### 2.5 Roles (`RoleResource`) — User Management, sort 21 + +> **Warning: parts of this resource no longer work.** Migrations on 2025-08-29 dropped +> `roles.is_staff` (`remove_is_staff_from_roles_table`) and `role_user.assigned_at`, +> `role_user.expires_at`, `role_user.assigned_by` (`simplify_role_user_table`). The resource +> was never updated, so its `is_staff` toggle and toggle column, and its Users relation +> manager's pivot columns, Active/Expired filters and attach form, all address columns that +> do not exist. They are described below as written, not as working. See +> `remaining-modules.md` §1 for what the replacement builds instead. + +Model `Role`. Nav badge = total role count. + +Form sections: +1. **Role Information** (2 col): `name` (live-on-blur → slug), `slug` (unique, "used for system identification"), `description` textarea. +2. **Chat Appearance** (3 col): `chat_color` color picker (default `#808080`), `priority` numeric (guidance: 100 admin, 90 moderator), `is_visible` toggle "Show in Chat". +3. **Settings** (2 col): `assigned_at_login` toggle (default true — synced from the registration system at login; off = persists), `is_staff` toggle, `permissions` tags input with suggestions `filament.access`, `admin.access`, `chat.moderate`, `chat.delete`, `chat.timeout`, `chat.slowmode`, `stream.manage`, `user.manage`. +4. **Additional Configuration** (collapsed): `metadata` key/value. + +Table (default sort `priority` desc): +- `name` bold, `slug` badge, `chat_color` color column (copyable, "Color copied" for 1.5 s) +- `priority` badge with tiered colors (>=100 red, >=90 amber, >=50 blue, else gray) +- `assigned_at_login` → `Auto-synced` / `Manual` badge with an explanatory tooltip +- `is_staff` **inline toggle column** (writes on click) +- `is_visible` **inline toggle column** ("Chat Badge") with tooltip +- `users_count` relation count badge, sortable +- `created_at` hidden by default + +Filters: three ternary filters — `is_staff`, `assigned_at_login`, `is_visible`, each with custom true/false/placeholder labels. + +Row actions: Edit; Delete blocked with a danger toast when the role has users. Bulk Delete has the same guard. + +Header action: **Create Default Roles** — visible only when `Role::count() === 0`; confirm modal; seeds Admin / Moderator / Super Sponsor / Sponsor / Attendee with fixed colors, priorities, `assigned_at_login`, `is_staff` and permission sets (see `RoleResource::createDefaultRoles()` for the exact payload — this is behaviour the rebuild must reproduce verbatim). + +Relation manager — **Users**: `name`, `email`, `pivot.assigned_at`, `pivot.expires_at` (placeholder "Never"), `pivot.assigned_by` badge (manual = green, login = amber, system = blue). Filters "Active Only" (`expires_at` null or future) and "Expired Only". Header **Attach** with a form of record select + `expires_at` datetime (Europe/Berlin, "empty = permanent") + `assigned_by` select (manual/system); sets `assigned_at = now()`; success toast. Row Detach + bulk Detach. + +### 2.6 Emotes (`EmoteResource`) — Chat, sort 30 + +Model `Emote`. Nav badge = pending count, amber when > 0, hidden at 0. + +Form: +1. **Emote Information** (2 col): `name` (required, unique, regex `^[a-z0-9_]+$`, max 20), `s3_key` file upload (image, cover resize, 1:1 crop, 64×64 target, S3 disk, `emotes/`, private, preserve filenames, custom state loader), `is_global` toggle ("If disabled, only the uploader can use this emote"), `is_approved` toggle. +2. **Metadata** (2 col, all disabled + `dehydrated(false)`): `uploadedBy`, `approvedBy`, `approved_at`, `usage_count`. + +Table (default sort `created_at` desc): `url` image (40px, square), `name` formatted as `:name:` (searchable, copyable), `uploadedBy.name` searchable, `is_global` boolean icon, `is_approved` boolean icon (green/amber), `usage_count` numeric sortable, `created_at` "Uploaded" toggleable. + +Filters: `approval_status` custom select (Pending Approval / Approved), `is_global` ternary. + +Row actions: **Approve** (only when not approved, confirm, `$emote->approve(auth()->user())`); **Reject** (only when not approved, confirm, "permanently delete the emote and its image", `$emote->reject()`); Edit; Delete. + +Bulk actions: **Approve Selected**, **Reject Selected** (both skip already-approved), and a grouped Delete. + +Create/Edit hooks: `CreateEmote` stamps `uploaded_by_user_id` and, if created pre-approved, `approved_by_user_id` + `approved_at`. `EditEmote` stamps approver + timestamp on the first transition to approved. + +### 2.7 Recordings (`RecordingResource`) — "Content", sort 50 + +Model `Recording`. No nav badge. + +Form (single column): `show_id` select (options are `"{title} ({source.name})"`, searchable, preload, nullable, reactive — **on change it auto-fills `title`, `description`, `date` from `actual_start`, and `duration` from `actual_end - actual_start`**), `title` (reactive → slug on create), `slug` (unique), `description`, `date` datetime (required, non-native), `duration` numeric with `seconds` suffix ("auto-filled via ffmpeg if empty"), `m3u8_url` (required, URL), `thumbnail_path` upload (image, cover resize to 1280×720, S3 `recordings/thumbnails`, private, "leave empty to auto-generate from first frame"), `is_published` toggle (default true), `required_roles` checkbox list. + +Table (default sort `date` desc): `thumbnail_url` image (80×45), `title`, `slug` (toggleable), `show.title` badge, `date` `M j, Y H:i`, `duration` formatted `H:MM:SS` / `M:SS` with `-` fallback, `views` numeric, `is_published` boolean icon, `required_roles` → Restricted/Public badge (hidden by default), `created_at` (hidden by default). + +Filters: `is_published` ternary. + +Row actions: **Regenerate Thumbnail** (visible when `m3u8_url` present; confirm; nulls `thumbnail_path` + `thumbnail_capture_error`, saves, dispatches `ProcessRecordingJob`, toast telling the user to refresh); Edit; Delete. `EditRecording` has the same action plus a redirect back to the edit page. Bulk: **Regenerate Thumbnails** (counts how many had an `m3u8_url`), Delete. + +## 3. Custom pages (non-resource) + +### 3.1 Stream Control (`Pages/Stream`) — Streaming, sort 3, `/admin/stream` + +Blade view is empty (``); all content comes from header actions + header widgets. + +Header actions, each with a confirm modal and a tooltip, firing `StreamStatusEvent`: +| Action | Event value | Tooltip | +|---|---|---| +| Set Stream Starting Soon (Start Servers) | `STARTING_SOON` | "Will start servers, takes around 6 minutes." | +| Set Stream Online | `ONLINE` | "Set this after you started the stream in obs for the first time." | +| Set Stream Technical Issue | `TECHNICAL_ISSUE` | "…will automatically activate upon stream disconnect." | +| Set Stream Offline (Delete Servers) — red | `OFFLINE` | "This sets the stream fully offline and deletes ALL Servers." | + +Header widgets: `ServerActive`, `Capacity`, `ViewCountChart`. + +### 3.2 Branding (`Pages/Branding`) — Streaming, sort 9 + +Form-only settings page over `BrandingSetting` / `BrandingService::EDITABLE`. Helper text for each field comes from `BrandingService::EDITABLE[$key]`. + +Sections: +1. **Identity** (2 col): `convention_name`*, `site_name`*, `identity_name`*, `identity_register_url` (url), `identity_logout_url` (url) +2. **Login screen** (2 col): `login_eyebrow`, `login_headline`*, `login_tagline`, `login_button_label`*, `login_body` textarea, `login_features` textarea +3. **Look** (2 col): `primary_color` color picker, `logo_path` upload (public disk, `branding/`, image editor enabled), `login_background_image` upload, `login_background_video` upload (mp4/webm) +4. **Footer links** (3 col): `support_url`, `imprint_url`, `privacy_url` + +Actions: **Save changes** (writes each `EDITABLE` key via `BrandingSetting::setValue`, toast) and **Reset to defaults** (confirm modal; deletes every `BrandingSetting` row, refills from `config/branding.php`; uploaded files are kept). + +## 4. Widgets + +| Widget | Type | Poll | Content | +|---|---|---|---| +| `ServerActive` | stats cards | 10s | One card per edge-server status: `Edge Server {status}` = count (grouped query) | +| `Capacity` | stats cards | 10s | Max clients (sum `max_clients` over active edge), Booting Capacity (same over provisioning edge), Waiting Users (`users.server_id IS NULL`) | +| `ViewCountChart` | line chart | none | the last 7 days as series, hourly average of `ViewCount.count` via `Flowframe\Trend`, x-axis = 24 fixed hour labels | + +`ViewCountChart` is stale — the dates are hardcoded to September 2023. The rebuild should make the range dynamic (per-event or last-N-days); flag it as a deliberate behaviour change rather than parity. + +## 5. Cross-cutting behaviour the rebuild must reproduce + +1. **Polling.** Sources 10s, Shows 5s, Servers default, widgets 10s. In Inertia these become `router.reload({ only: [...] })` on an interval or Echo-driven refreshes. +2. **Toasts.** Every mutating action emits a titled success/danger notification with a body. Needs a flash-message → toast pipeline in Inertia. +3. **Guarded deletes.** Shows (live), Sources (live shows), Roles (assigned users) block deletion with a danger toast instead of failing silently. These belong in policies/validation server-side so tests can assert them. +4. **Private S3 uploads.** `thumbnail_path`, `s3_key` are stored on the private `s3` disk and read back through signed-URL accessors (`thumbnail_url`, `url`). Filament's `FileUpload` handles the upload; Inertia needs an explicit upload endpoint plus the same signed-URL accessors. +5. **Enum casts.** `Source.status`, `Server.status`, `Server.type` are enum-cast, so several closures do `$state?->value ?? $state`. Serialize enums explicitly in props. +6. **Live-derived slugs.** Sources, Shows, Roles, Recordings all auto-slug from the name/title on the client while typing, and only on create for Shows/Recordings. +7. **Column visibility + persistence.** Many columns are `toggleable(isToggledHiddenByDefault: true)`. Filament persists this per user in the session. Decide whether to reimplement or drop. +8. **Default-on filter.** The Shows table hides ended shows unless the filter is switched off. Easy to lose in a rewrite. +9. **Inline toggle columns.** Roles' `is_staff` and `is_visible` write immediately on click from the table. +10. **Global search.** Only `User.name` is globally searchable (`ServerResource` explicitly opts out). +11. **Relation-manager pivot editing.** Role↔User attach carries `expires_at` and `assigned_by` pivot data plus an `assigned_at` stamp. +12. **Two auth entry points.** `/admin/login` (Filament) vs `/login` (OIDC). The rebuild should collapse to the OIDC flow plus an authorization gate. + +## 6. Known gaps / dead code found during the audit + +- `ServerResource/RelationManagers/UserRelationManager` is never registered. +- `UserResource` messages relation manager has a form but no way to open it. +- `RecordingResource` uses an undeclared nav group (`Content`). +- `ViewInstallScript` regex-extracts config blocks out of a generated shell script; the FFmpeg tabs are hardcoded placeholder comments. +- `ViewCountChart` uses hardcoded 2023 dates. +- `tests/Feature/Filament/AdminPanelTest.php` asserts on text that no longer exists in the form (`'Leave empty for locally managed servers'`), so parity claims based on it are unreliable until re-run. diff --git a/docs/admin/parity-checklist.md b/docs/admin/parity-checklist.md new file mode 100644 index 0000000..a8a1c1e --- /dev/null +++ b/docs/admin/parity-checklist.md @@ -0,0 +1,208 @@ +# Parity Checklist: /admin -> /manage + +Tick list for the cutover PR. Every line is either covered by a test in `tests/Feature/Manage/` (name the test), covered by a Playwright smoke spec, or marked `CHANGED` with a pointer to `rebuild-plan.md` section 2.9. Nothing ships unticked. + +Derived line by line from [`current-filament-features.md`](./current-filament-features.md). Do not edit this file to make it pass; edit the code. + +## Shell + +Covered by `tests/Feature/Manage/AccessTest.php` and `UploadTest.php`. + +- [x] `/manage` gated by `access-manage` (staff, `admin.access` or legacy `filament.access`) +- [x] Guest -> `/login` (OIDC). `CHANGED`: no `/manage/login` +- [x] Brand name and logo from `BrandingService`, not hardcoded +- [x] Nav groups: Overview, Streaming, Infrastructure, Administration — rail drops items whose route does not exist yet, asserted by `the_rail_only_advertises_routes_that_exist` +- [x] Rail badges: live/upcoming shows, online sources, pending emotes, role count, plus an alert count on Dashboard +- [x] Fluid content width +- [x] Upload endpoint covers all six purposes with the right disk, directory and visibility +- [x] Toasts survive the redirect and are not replayed on the next request + +## Dashboard + +`/manage` is the dashboard, not a redirect. Covered by `tests/Feature/Manage/DashboardTest.php`. +`CHANGED`: this replaces the three Filament widgets with one operator screen, per the +maintainer/producer brief. + +- [x] Capacity and edge-status cards (the Capacity and ServerActive widget equivalents) +- [x] Live viewer total and peak, polled every 5s +- [x] Per-server table: status, health, load %, viewers/max, heartbeat age with a stale marker +- [x] Alert list, danger before warning — sources in error, servers in error or failing health, stale heartbeats, live show on a non-online source, edge capacity ≥ 90%, viewers with no server assigned +- [x] Schedule block: what is on air plus everything starting in the next 6 hours, live first +- [x] Viewers per source +- [ ] `CHANGED`: view-count chart still deferred, the Filament widget hardcoded 2023 dates + +## Sources + +Form +- [ ] `name` required, max 255, live-slug into `slug` **on create only** +- [ ] `slug` "Stream Name", required, unique ignoring self, **read-only on edit** (RTMP ingress path + HLS route key) +- [ ] no `status` field — status changes go through the table action only +- [ ] `priority` numeric 0..999 +- [ ] `description` textarea +- [ ] OBS server URL block, copyable, "Will be generated on save" when new +- [ ] OBS stream key block, copyable, same empty state, **Regenerate Stream Key button directly below it** + +Table +- [ ] 10s poll, default sort `priority` desc +- [ ] columns: status badge, name, slug (copyable, "Stream Name"), priority, shows_count, live_shows_count, created_at + updated_at hidden by default +- [ ] `status` filter with "All statuses" + +Actions +- [ ] Update Status row action (modal prefilled from record, toast, observer broadcasts) +- [ ] Edit, Delete +- [ ] Delete blocked when the source has live shows, danger toast +- [ ] Bulk Update Status (deselects after), bulk Delete with the same guard +- [ ] Regenerate Stream Key in the Stream Configuration block, confirm copy warns about disconnects +- [ ] Shows tab: title, status, scheduled_start, viewer_count + create/edit/delete + +## Shows + +Form +- [ ] Show Information: title (live-slug `title-YYYY-MM-DD`, create only), slug unique, source required, server optional (`available` only), description +- [ ] Schedule: scheduled_start required, scheduled_end required + after start, actual_start, actual_end - all Europe/Berlin, no seconds +- [ ] Status & Settings: status select disabled while live, auto_mode, recordable, required_roles checkbox list, thumbnail upload (S3 private, 5 MB, jpeg/png/webp), tags with the 7 suggestions +- [ ] Statistics section renders on edit only (viewers, peak, formatted duration) +- [ ] Metadata key/value, collapsed + +Table +- [ ] 5s poll, default sort `scheduled_start` asc +- [ ] columns in order: thumbnail (signed URL), title, source, status, scheduled_start, actual_start, viewer_count, peak_viewer_count, auto_mode, required_roles, tags +- [ ] hidden by default: peak, access, tags, actual_start toggleable +- [ ] filters: hide_ended **default on**, status multi, source, today, upcoming + +Actions +- [ ] Go Live - scheduled only, confirm copy verbatim, calls `goLive()` +- [ ] End Stream - live only, confirm copy verbatim, calls `endLivestream()` +- [ ] View Statistics link +- [ ] Delete blocked while live +- [ ] Bulk Cancel Shows (scheduled only), bulk Delete blocked if any live +- [ ] Live Dashboard link to Stream Control +- [ ] Capture Screenshot: disabled with reason when not live / no source; success, null-result and exception toasts all distinct +- [ ] Statistics page: current/peak/average/unique viewer cards, live marker, broadcast info list, realtime stats while live +- [ ] Viewers tab: name, email, joined, left ("Still watching"), duration `XhYmZs`, IP hidden by default, active badge, "Currently Watching" filter, read-only + +## Servers + +All covered by `tests/Feature/Manage/ServersTest.php` unless noted. + +Form +- [x] hetzner_id disabled on edit — `updating_a_server_cannot_change_its_type_secret_or_hetzner_id` +- [x] hostname required, ip, port 1..65535 default 8080 — `the_create_form_rejects_an_incomplete_payload` +- [x] shared_secret disabled on edit, defaults to 40 random chars — same test as hetzner_id +- [x] type disabled on edit, default edge — same test +- [x] max_clients edge only, default 100 — `creating_an_origin_server_ignores_the_edge_only_fields` +- [x] status select, all five states, manual override — `creating_a_manual_edge_server` +- [x] immutable checkbox edge only, default true — `creating_a_manual_edge_server` +- [x] created_at / updated_at as relative times — rendered read-only on the detail page + +Table +- [x] polls (`usePoll(10000, { only: ['table'] })`); deleted hidden by default — `deleted_servers_are_hidden_until_the_status_filter_asks_for_them` +- [x] columns, in order — `the_list_declares_every_column_the_filament_table_had` +- [x] viewer_count `-` for origin, `N% capacity` for edge — `an_edge_row_reports_capacity_while_an_origin_row_reports_none` +- [x] heartbeat icon + tooltip — `a_stale_heartbeat_is_reported_as_a_danger_icon` +- [x] health badge, edge only — `an_edge_row_reports_capacity_while_an_origin_row_reports_none` +- [x] filters: status multi, type — `the_list_declares_the_status_and_type_filters`, `the_type_filter_narrows_the_list` +- [x] search over hostname and hetzner_id — `search_matches_hostname_and_hetzner_id` + +Actions +- [x] New Manual Server — `creating_a_manual_edge_server` +- [x] Enable / Disable Autoscaler, mutually exclusive by current state — `the_autoscaler_action_flips_with_its_current_state`, `the_autoscaler_can_be_switched_on_and_off` +- [x] Provision Cloud Server, ccx43/cpx21 labels, single-origin guard, `provisioning` row, `CreateVirtualMachineJob` — `provisioning_an_edge_server_...`, `provisioning_an_origin_server_...`, `a_second_origin_server_is_refused` (active + provisioning), `a_replacement_origin_may_be_provisioned_once_the_old_one_is_gone` +- [x] Deprovision - only with `hetzner_id`, confirm — `deprovisioning_dispatches_the_teardown_job`, `a_manual_server_cannot_be_deprovisioned` +- [x] Delete - only without `hetzner_id`, unassigns viewers — `deleting_a_manual_server_unassigns_its_viewers`, `a_cloud_server_cannot_be_deleted_outright` +- [x] Install script page: install + cloud-init, docker-compose, nginx, Caddyfile, SRS (origin only); copy; download as `install-{id}.sh`; regenerate backfills a missing `shared_secret` — `the_install_script_page_builds_a_tab_per_config`, `an_origin_server_gets_the_srs_tab_...`, `the_install_script_downloads_under_a_predictable_filename`, `regenerating_backfills_a_missing_shared_secret` +- [x] `CHANGED`: FFmpeg placeholder tabs dropped, and no tab ever renders empty — asserted in `the_install_script_page_builds_a_tab_per_config` +- [x] `CHANGED`: assigned users on the detail page (replaces the unregistered relation manager) — `the_detail_page_lists_the_viewers_assigned_to_the_server` +- [x] `CHANGED`: mutations need `stream.manage` — `a_moderator_may_read_the_list_but_is_offered_no_mutations`, `a_moderator_cannot_create_or_update_a_server`, `a_moderator_cannot_provision_or_touch_the_autoscaler`, `a_moderator_cannot_read_the_install_script` +- [ ] Smoke: install-script tabs, copy and download in a browser (Playwright, phase 7) + +## Users + +- [ ] Form: sub, name, reg_id all read-only; server_id select limited to active edge servers; relative timestamps +- [ ] Table: sub, name (searchable, sortable), reg_id +- [ ] Global search on name +- [ ] Create and Delete available +- [ ] Roles tab: name, slug, chat_color copyable, priority tiered badge, login-sync read-only toggle; attach with role select; detach with confirm; bulk detach; sort priority desc; per-page 10/25/50 +- [ ] `CHANGED`: messages tab reachable, read-only list + gated delete + +## Roles + +Form +- [ ] Role Information: name live-slug, slug unique, description +- [ ] Chat Appearance: chat_color default `#808080`, priority, is_visible +- [ ] Settings: assigned_at_login default true, is_staff, permissions tags with all 8 suggestions +- [ ] Metadata key/value collapsed + +Table +- [ ] default sort priority desc +- [ ] columns: name, slug, chat_color copyable ("Color copied"), priority with the 4-tier ramp, assigned_at_login as Auto-synced/Manual + tooltip, is_staff inline toggle, is_visible inline toggle + tooltip, users_count, created_at hidden +- [ ] three ternary filters with their custom labels + +Actions +- [ ] Delete blocked when the role has users, single and bulk +- [ ] Create Default Roles - visible only at zero roles; seeds Admin/Moderator/Super Sponsor/Sponsor/Attendee with the exact colors, priorities, flags and permission sets from `RoleResource::createDefaultRoles()` +- [ ] Users tab: name, email, pivot assigned_at, pivot expires_at ("Never"), pivot assigned_by badge; Active Only / Expired Only filters; attach form with expires_at + assigned_by and an `assigned_at` stamp; detach single + bulk + +## Emotes + +- [ ] Form: name regex `^[a-z0-9_]+$` max 20 unique; image upload 1:1 64x64 S3 private; is_global; is_approved; read-only uploader/approver/approved_at/usage_count +- [ ] Table: image, `:name:` copyable, uploader, is_global, is_approved (green/amber), usage_count, created_at +- [ ] Filters: approval status (pending/approved), is_global ternary +- [ ] Approve / Reject row actions, unapproved only, reject warns about permanent deletion +- [ ] Bulk approve / bulk reject skip already-approved; bulk delete +- [ ] Create stamps uploader and, when pre-approved, approver + timestamp +- [ ] Edit stamps approver + timestamp on first approval +- [ ] Nav badge = pending count, amber, hidden at zero + +## Recordings + +- [ ] Form: show select prefills title, description, date (`actual_start`) and duration (`actual_end - actual_start`); title live-slug on create; slug unique; date required; duration in seconds; m3u8_url required URL; thumbnail upload 1280x720 S3 private; is_published default true; required_roles +- [ ] Table: thumbnail 80x45, title, slug, show badge, date, duration `H:MM:SS`/`M:SS` with `-`, views, is_published, access badge hidden, created_at hidden; default sort date desc +- [ ] `is_published` filter +- [ ] Regenerate Thumbnail - only with an m3u8_url; nulls path + error, dispatches `ProcessRecordingJob` +- [ ] Bulk Regenerate Thumbnails reports how many had an m3u8_url +- [ ] Nav group is a declared group + +## Stream Control + +- [ ] Set Stream Starting Soon -> `STARTING_SOON`, confirm, tooltip about ~6 minutes +- [ ] Set Stream Online -> `ONLINE`, confirm, OBS tooltip +- [ ] Set Stream Technical Issue -> `TECHNICAL_ISSUE`, confirm, auto-activate tooltip +- [ ] Set Stream Offline -> `OFFLINE`, red, confirm, "deletes ALL Servers" tooltip +- [ ] Page shows the ServerActive, Capacity and view-count widgets + +## Settings (was Branding) + +Covered by `tests/Feature/Manage/SettingsTest.php`. `CHANGED`: the page is generated from +a registry in `config/settings.php` rather than a hand-written form, so a new knob is one +config entry. Route is `manage.settings`, not `manage.branding`. + +- [x] Identity: convention_name*, site_name*, identity_name*, register URL, logout URL +- [x] Login screen: eyebrow, headline*, tagline, button label*, body +- [x] Look: primary_color, logo, login background image, login background video (mp4/webm) - public disk, `branding/` +- [x] Footer links: support, imprint, privacy +- [x] Every `BrandingService::EDITABLE` key is still editable — `the_page_lists_every_registered_group_and_field` +- [x] Save writes through `BrandingSetting::setValue`; unregistered keys are ignored — `an_unregistered_key_is_ignored_rather_than_stored` +- [x] Reset to defaults: confirm, deletes all rows, falls back to `config/branding.php`, keeps uploaded files +- [x] Per-field "use the default", and each field reports whether it is overriding one +- [x] Validation: required copy cannot be emptied, URLs must be URLs, accent colour must be hex +- [x] `admin.access` only, not the whole manage gate — `only_administrators_can_read_or_change_the_settings` + +## Widgets + +- [ ] ServerActive: one card per edge status, 10s +- [ ] Capacity: max clients (active edge), booting capacity (provisioning edge), waiting users (`server_id IS NULL`), 10s +- [ ] `CHANGED`: view-count chart uses a selectable range instead of the hardcoded 2023 dates + +## Cross-cutting + +- [ ] Every mutating action flashes a toast with the same title and body as today +- [ ] Guarded deletes live in policies, asserted server-side +- [ ] Private S3 reads go through the signed-URL accessors +- [ ] Enums serialized explicitly in props +- [ ] Column-toggle state persists per user +- [ ] Polling pauses on hidden tab, dirty form and open dialog +- [ ] `./vendor/bin/pint` clean +- [ ] `php artisan test` green including the legacy Filament suite +- [ ] Playwright smoke green +- [ ] Operator walkthrough: source online -> go live -> viewers -> screenshot -> end -> statistics diff --git a/docs/admin/pretalx-import.md b/docs/admin/pretalx-import.md new file mode 100644 index 0000000..64173cf --- /dev/null +++ b/docs/admin/pretalx-import.md @@ -0,0 +1,64 @@ +# Importing the programme from pretalx + +How sessions in pretalx become shows, and why an imported session cannot be imported twice. + +Screen: the **Import from pretalx** button on the Shows table. It has no entry of its own in the rail - it belongs next to the programme it adds to. + +## Connecting + +Settings > Pretalx holds three values: + +| Field | Meaning | +|---|---| +| Instance URL | Root of the pretalx instance, e.g. `https://cfp.example.org` | +| Event slug | The slug in the pretalx URL, e.g. `my-con-2026` | +| API token | Optional. Only needed while the schedule is unpublished, or the event is private | + +**Test connection** checks the values as they stand in the form, saved or not, and reports what the credentials reach: how many events they see, and how many sessions the chosen event has in a published schedule. A successful test also loads the event list, after which the slug is a dropdown rather than a text field. The list is remembered per instance, so it survives a reload; test again to refresh it. + +The token is write-only: the settings page is told that one is stored, never what it is, and shows a masked value so a stored token is visible as "something is set". Leaving that masked value alone keeps the stored token; **Clear** removes it on the next save. + +Until both an instance URL and an event slug are stored, the Import screen is hidden: no rail entry, no button on the Shows table. + +The published schedule is read through the pretalx REST API and cached for five minutes. **Reload schedule** on the import screen drops that cache, for when a new schedule version was released mid-event. + +## Mapping rooms to channels + +Each pretalx room is pinned to one of our sources, and the mapping is saved per event, so it is decided once rather than on every import. + +**Only rooms with a channel have their sessions listed.** A convention schedules hundreds of sessions across dozens of rooms - Eurofurence 30 has 344 across 46 - and streams a handful of them. Listing the rest would bury the ones that matter, and they could not be imported anyway. Map a room and its sessions appear immediately, before saving. + +The mapping list itself shows only rooms that have sessions in the published schedule, plus any room already mapped, each with its session count. Rooms come from pretalx in its own order; a room that only appears on a slot is still offered, named `Room `. + +## What an import creates + +One show per selected slot: + +| Show field | From | +|---|---| +| Title | Submission title | +| Description | Submission abstract, falling back to its description. Markdown | +| Source | The channel its pretalx room is mapped to | +| Scheduled start / end | The slot's planned times | +| Status | `scheduled` | +| Slug | Title plus start date, made unique | + +Everything the streaming side owns - auto mode, recording, access restrictions - is left at its default and edited on the show afterwards. The import never touches an existing show. + +Descriptions are markdown, which is what pretalx abstracts are written in: `**_WE'RE BACK!!!_**` reaches viewers as bold italics rather than as asterisks. The stored value stays markdown so it can still be edited; it is rendered for display by `App\Support\Markdown`, which strips raw HTML and unsafe link schemes. Show descriptions written by hand in /manage take markdown too. + +Slots that are not actually scheduled (no room or no start time) are not listed at all: there is nothing to place on a timeline. + +## Import once + +The show carries the pretalx slot id, under a unique index. That is the whole ledger: + +- A slot with a show is listed as **Imported**, linking to it, and cannot be ticked. +- Re-posting an already imported slot is skipped and reported, not duplicated. +- **Deleting the show releases the slot**, and it becomes importable again. That is the intended way to redo an import after the programme team moved something. + +Nothing else is synced. Editing a show does not write back to pretalx (the API is read-only), and a later change in pretalx does not reach an already imported show - move it in the planner instead, or delete and re-import it. + +## Delays + +pretalx has no concept of a session running late: its API exposes planned times only, with no live status, actual start, or delay field. What is imported is the plan. Keeping the running order honest once the con is underway is the planner's job, and `actual_start` / `actual_end` on the show are what record what really happened. diff --git a/docs/admin/rebuild-plan.md b/docs/admin/rebuild-plan.md new file mode 100644 index 0000000..88642d3 --- /dev/null +++ b/docs/admin/rebuild-plan.md @@ -0,0 +1,405 @@ +# Admin Rebuild Plan: Filament -> Inertia v2 + +Companion to [`current-filament-features.md`](./current-filament-features.md), which is the parity contract. This file is the design spec plus the build/verify/cutover plan. + +Decisions taken (2026-07-30): + +| Decision | Choice | +|---|---| +| Visual direction | **Control Room**, dark-first | +| Cutover | **Parallel `/manage`**, delete `/admin` once the parity suite is green | +| Test depth | **Server-side parity tests per module + a small Playwright smoke set** | + +Baseline recorded before any change: `php artisan test tests/Feature/Filament/AdminPanelTest.php` -> 16 passed, 1 failed (`admin can create server` asserts helper text `Leave empty for locally managed servers`, which the form no longer contains). That assertion is stale, not a regression. + +--- + +## Part 1 - Design spec: Control Room + +### 1.1 Intent + +An operator watching a live event needs three things at a glance: is the stream up, is there enough edge capacity, and which shows are live right now. Everything else is CRUD that must not get in the way. So: high information density, a status strip that never scrolls away, numbers in tabular figures, colour reserved for state (never decoration). + +Neutral by construction. No convention wordmark, artwork or copy in the chrome. Brand name and logo come from `BrandingService` at runtime, same as the public site, so a different convention rebrands it without touching components. + +### 1.2 Layout skeleton + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ ● OFFLINE ▶ 3 live ▤ 4/6 edge ◉ 12 480 viewers user ↗ │ h-10, sticky +├────┬─────────────────────────────────────────────────────────────────┤ +│ ▣ │ Shows [ + New Show ] │ h-14 page header +│ ▤ │ ─────────────────────────────────────────────────────────────── │ +│ ▥ │ status ▾ source ▾ ☑ hide ended ⌕ search │ h-11 filter bar +│ ▦ │ ─────────────────────────────────────────────────────────────── │ +│ ▧ │ STATUS TITLE SOURCE VIEW PEAK SCHED │ h-8 header row +│ │ ● LIVE Opening Ceremony main 4 812 5 120 10:00 │ h-8 rows +│ │ ○ SCHED Dealers Den Tour stage-b – – 11:30 │ +│ ⚙ │ ─────────────────────────────────────────────────────────────── │ +│ │ 1–25 of 48 ‹ 1 2 3 › │ +└────┴─────────────────────────────────────────────────────────────────┘ + 240px sidebar fluid content, no max-width +``` + +- **Status strip** (`ManageStatusStrip.vue`): live show count, edge servers active/total, total viewers, and the current stream status from `StreamStatusEnum`. Polls independently of the page body via a partial reload of one prop. Clicking a segment deep-links to the relevant module. +- **Sidebar** (`ManageSidebar.vue`): permanent, 240px, always labelled, no collapse. An icon rail saves 180px and costs a guess on every click; this panel is used to edit things. Groups: Streaming / Infrastructure / User Management / Chat / Content / Settings. Badge counts sit at the right of their row (live shows, online sources, pending emotes, role count). +- **Page header**: title, optional subtitle, right-aligned primary + secondary actions. No breadcrumbs; the sidebar plus a title is enough at this depth. +- **Content**: fluid width, matching today's `maxContentWidth('100%')`. +- Detail pages are full pages with tabs, not drawers (the Cockpit option was not selected). Tabs are real URLs so they are linkable and testable: `/manage/shows/{show}` and `/manage/shows/{show}/statistics`, `/manage/shows/{show}/viewers`. + +### 1.3 Tokens + +`resources/css/app.css` already defines an `oklch` primary ramp at hue ~181 (teal/cyan) plus `--background`, `--foreground`, `--border`, `--ring` with a `.dark` block. The admin extends that, it does not fork it. Per the project rule, no `-gray-` utilities: neutral surfaces come from named tokens below and from `primary-900/950` where a tinted surface is wanted. + +Add to `@theme` and to both `:root` and `.dark`: + +```css +--surface-0 /* app background, deepest */ +--surface-1 /* rail, status strip */ +--surface-2 /* cards, table header, filter bar */ +--surface-3 /* row hover, popovers */ +--fg-1 /* primary text */ +--fg-2 /* labels, secondary */ +--fg-3 /* placeholders, disabled */ +--hairline /* 1px separators */ +--state-live /* primary-400, cyan */ +--state-ok /* green */ +--state-warn /* amber */ +--state-danger /* red */ +--state-idle /* fg-3 */ +``` + +Dark is not a mode here, it is the palette: the surface, foreground and state tokens hold their dark values at `:root`, so there is no class to set and no flash on first paint. `.manage-root` also sets `color-scheme: dark` so native controls (selects, checkboxes, date pickers, scrollbars) are drawn dark instead of in the OS light palette. A `.manage-light` block is kept unused in case a toggle is ever wanted. + +Status colour mapping is centralised once, server-side, so the table, badges and status strip can never drift: + +| Domain state | Token | Glyph | +|---|---|---| +| show live, source online, server active, session active, healthy | `state-live` / `state-ok` | `●` | +| scheduled, provisioning, pending approval, booting | `state-warn` | `○` / `◐` | +| ended, deleted, inactive, unknown | `state-idle` | `○` | +| cancelled, error, unhealthy, deprovisioning | `state-danger` | `▲` | + +### 1.4 Typography and density + +- UI text 13px/18px, labels 11px uppercase with 0.06em tracking, page titles 18px semibold. +- All numerics `font-variant-numeric: tabular-nums`, right-aligned in tables, thin space as thousands separator so digits do not jump while polling. +- Table rows 32px, header 28px, cell padding `px-3`. Comfortable mode (40px rows) behind a per-user preference is a nice-to-have, not phase 1. +- IDs, hostnames, stream keys, slugs and script bodies in the mono stack. + +### 1.5 Component inventory to build + +Under `resources/js/Components/Manage/`. Everything composes the existing `Components/ui/*` (reka-ui based) primitives already in the repo; new files are layout and data-display only. + +| Component | Responsibility | +|---|---| +| `ManageLayout.vue` | dark root, status strip, sidebar, page content slot, toast host | +| `ManageSidebar.vue` | permanent nav: groups, badge pills, active section | +| `ManageStatusStrip.vue` | global KPIs, own poll interval | +| `PageHeader.vue` | title, subtitle, action slots | +| `DataTable.vue` | renders a server-provided column set; sort links, row click, sticky header, empty state, loading shimmer during partial reloads | +| `DataTableColumnToggle.vue` | show/hide toggleable columns, persisted per user | +| `FilterBar.vue` | select / ternary / boolean-toggle / search filters, all bound to query string | +| `Pagination.vue` | page links + per-page select | +| `StatusBadge.vue` | takes the server-computed `{label, tone, icon}` triple | +| `StatCard.vue` | stat tiles (statistics page, later Stream Control) | +| `ActionButton.vue` | POST/DELETE with optional confirm dialog, disabled/tooltip reasons | +| `ConfirmDialog.vue` | modal heading/description/submit label, mirroring Filament's confirm modals | +| `FormSection.vue`, `FormField.vue`, `FormActions.vue` | one column of `label: control` rows, sticky save bar, collapsible variant | +| `ScheduleRow.vue` | start / editable duration / end on one line | +| `FileUploadField.vue` | image/video upload against the upload endpoint, preview, replace, remove | +| `CheckboxList.vue`, `ColorPicker.vue` | the Filament field types with no shadcn equivalent (tags and key/value were dropped with the fields that used them) | +| `CopyableText.vue` | click-to-copy with confirmation, replaces the inline `onclick` HTML in `SourceResource` | +| `Toast.vue`, `useToasts.js` | flash -> toast | +| `CodeBlock.vue` | mono, wrap toggle, copy, download (install script tabs) | +| `RelationPanel.vue` | embedded table + attach/detach for relation-manager equivalents | + +`lucide-vue-next`, `floating-vue`, `dayjs`, `radix-vue`/`reka-ui` are already installed. Add nothing but a chart library if `ChartLine.vue` needs one; prefer inline SVG for a single line chart and skip the dependency. + +--- + +## Part 2 - Architecture + +### 2.1 Routing + +New file `routes/manage.php`, required from `RouteServiceProvider`, prefix `/manage`, names `manage.*`, middleware `['web', 'auth:web', 'can:access-manage']`. + +``` +GET /manage manage.dashboard +GET /manage/sources manage.sources.index +GET /manage/sources/create manage.sources.create +POST /manage/sources manage.sources.store +GET /manage/sources/{source} manage.sources.edit +PUT /manage/sources/{source} manage.sources.update +DELETE /manage/sources/{source} manage.sources.destroy +POST /manage/sources/{source}/status manage.sources.status +POST /manage/sources/{source}/stream-key manage.sources.stream-key +POST /manage/sources/bulk/status manage.sources.bulk.status +DELETE /manage/sources/bulk manage.sources.bulk.destroy +...same shape for shows, servers, users, roles, emotes, recordings +GET /manage/shows/{show}/statistics manage.shows.statistics +GET /manage/shows/{show}/viewers manage.shows.viewers +POST /manage/shows/{show}/go-live manage.shows.go-live +POST /manage/shows/{show}/end manage.shows.end +POST /manage/shows/{show}/screenshot manage.shows.screenshot +GET /manage/servers/{server}/install-script manage.servers.install-script +GET /manage/servers/{server}/install-script/download +POST /manage/servers/{server}/deprovision +POST /manage/servers/provision manage.servers.provision +POST /manage/autoscaler/{state} manage.autoscaler +GET /manage/stream manage.stream +POST /manage/stream/status manage.stream.status +GET /manage/branding manage.branding +PUT /manage/branding manage.branding.update +DELETE /manage/branding manage.branding.reset +POST /manage/uploads manage.uploads.store +``` + +Every mutation is a POST/PUT/DELETE that redirects back with a flash. No JSON endpoints, no `fetch()` - this satisfies the project rule that data flows through Inertia props. Uploads are the one exception and go through Inertia's own multipart form support, still returning a redirect. + +### 2.2 Authorization + +Today's gate is `User::canAccessPanel()` = `hasPermission('filament.access') || isStaff()`, while `HandleInertiaRequests` shares `auth.can_access_filament` from `$user->can('filament.access')`. Two different checks. + +Unify: +- `Gate::define('access-manage', fn (User $u) => $u->hasPermission('admin.access') || $u->hasPermission('filament.access') || $u->isStaff())` in `AuthServiceProvider`, keeping `filament.access` accepted so existing role rows keep working. +- Add real policies: `ShowPolicy`, `SourcePolicy`, `ServerPolicy`, `UserPolicy`, `RolePolicy`, `EmotePolicy`, `RecordingPolicy`. The guarded deletes move here (`ShowPolicy::delete` false while live; `SourcePolicy::delete` false with live shows; `RolePolicy::delete` false with users) so both the UI and the tests read the same rule. +- Fine-grained action permissions map to the existing strings: `stream.manage` for go-live / end / stream status / provisioning / autoscaler, `user.manage` for users and roles, `chat.moderate` for emotes. +- Rename the shared prop to `auth.can_access_manage` and keep `can_access_filament` as an alias until `/admin` is gone. +- No `/manage/login`. Guests are redirected into the existing OIDC flow at `/login`; a signed-in user without the gate gets 403. This is a deliberate change from Filament's second login screen. + +### 2.3 List pages: one prop contract + +Every index page ships the same envelope so `DataTable.vue` and `FilterBar.vue` stay generic and the tests can assert on a stable shape: + +```php +[ + 'rows' => [...], // already formatted for display + 'columns' => [ // server-declared, ordered + ['key' => 'status', 'label' => 'Status', 'type' => 'badge', + 'sortable' => false, 'toggleable' => false, 'hiddenByDefault' => false], + ['key' => 'viewer_count', 'label' => 'Viewers', 'type' => 'number', + 'align' => 'right', 'sortable' => true], + ], + 'filters' => [ // declared once, rendered generically + ['key' => 'status', 'type' => 'select', 'label' => 'Status', + 'options' => [...], 'multiple' => true, 'value' => [...]], + ['key' => 'hide_ended', 'type' => 'boolean', 'label' => 'Hide ended', + 'value' => true, 'default' => true], + ], + 'sort' => ['key' => 'scheduled_start', 'dir' => 'asc'], + 'search' => 'opening', + 'meta' => ['page' => 1, 'perPage' => 25, 'total' => 48], + 'rowActions' => [...], // per-row, already visibility-filtered + 'bulkActions' => [...], + 'pageActions' => [...], +] +``` + +A small `App\Support\Manage\Table` builder produces this from a query plus a column/filter declaration, so the seven modules do not each hand-roll sorting, searching, filtering and pagination. Column `type` values: `text`, `number`, `badge`, `image`, `bool`, `datetime`, `duration`, `color`, `copyable`, `toggle`. + +Two things that are easy to lose and must be explicit in the declaration: +- `hide_ended` on Shows defaults to **on**. +- `hiddenByDefault` columns (peak viewers, tags, access, timestamps, IP) keep that flag, and the user's choice persists in the session keyed by table name. + +Badges never carry colour logic on the client. The server sends `['label' => 'LIVE', 'tone' => 'live', 'icon' => 'signal']` and `StatusBadge.vue` looks the tone up in the token map from 1.3. + +### 2.4 Polling + +Inertia v2's `usePoll` replaces Filament's `->poll()`: + +```js +usePoll(5000, { only: ['rows', 'meta'] }) // Shows index +usePoll(10000, { only: ['rows', 'meta'] }) // Sources index +usePoll(15000, { only: ['stats'] }) // widgets +usePoll(10000, { only: ['status'] }) // status strip, in ManageLayout +``` + +Rules: only ever reload the data props, never `columns`/`filters`/`rowActions`; pause while a form is dirty or a dialog is open (`usePoll`'s stop/start); pause when the tab is hidden. Where Echo already broadcasts (source status, stream status, show status), subscribe and trigger a single reload instead of adding a second interval - the channels exist in `routes/channels.php` and `bootstrap.js` already wires Echo. + +### 2.5 Actions, confirms and toasts + +Filament's action objects have five parts: label, icon, colour, visibility predicate, confirm modal copy. Model that server-side per row so the client stays dumb and the tests can assert visibility: + +```php +['name' => 'go_live', 'label' => 'Go Live', 'icon' => 'signal', 'tone' => 'ok', + 'method' => 'post', 'url' => route('manage.shows.go-live', $show), + 'confirm' => [ + 'heading' => 'Start Live Stream', + 'description' => 'Are you sure you want to start this show? This will mark it as live and notify viewers.', + 'submit' => 'Go Live', + ], + 'disabledReason' => null] +``` + +`disabledReason` carries the tooltip text Filament shows for the disabled screenshot button ("Show must be live to capture screenshot" / "Show must have a source"), so that behaviour survives. + +Toasts ride Inertia's own flash bag rather than a shared prop. `App\Support\Manage\Toast` writes `{tone, title, body}` under Inertia's flash session key; `inertia-laravel` then attaches it to the response. Same titles and bodies as today - the audit doc lists them and the tests assert them. + +Three things about that mechanism, all verified against the installed `inertia-laravel` 2.0.19 and `@inertiajs/vue3` 2.1.3: + +1. **`flash` is a top-level key on the page object, not a prop** (`Response::toResponse` merges `resolveFlashData()` as a sibling of `props`). So the client reads `usePage().flash?.toast`, and a feature test asserts on `->viewData('page')['flash']` rather than through `AssertableInertia`. That placement is the point: flash data never enters the browser's history state, so a back navigation cannot replay an old toast. +2. **`Inertia::flash()` stores through `session()->now()`**, which lands the key in `_flash.old`. `ageFlashData()` forgets old keys when the session saves, so a `now()` payload does not survive the redirect an action performs - the middleware's re-flash cannot save it. Since every manage mutation redirects, `Toast` flashes *forward* into the same session key instead. Revisit if the package starts flashing forward itself. +3. **The Vue adapter has no `Flash` component or `useFlash` composable at 2.1.3** (`@inertiajs/vue3` exports `router`, `usePage`, `Deferred`, `Form`, `Head`, `Link`, `useForm`, `usePoll`, `usePrefetch`, `useRemember`, `WhenVisible`). `useToasts` watches `page.flash.toast` itself; swap it for the adapter's own helper once the dependency is upgraded. + +Bulk actions POST an `ids[]` array. Guarded bulk deletes keep today's all-or-nothing semantics: if any selected record fails the policy, nothing is deleted and a danger toast explains why. + +### 2.6 Forms + +Server-declared sections, client-rendered fields; validation via Form Requests so the same rules serve both panels during the parallel phase. + +- Live slugging (`title` -> `slug`, create-only for Shows and Recordings, always for Sources and Roles) is a client watcher plus a `unique` rule server-side. +- Shows: `scheduled_end` must be `after:scheduled_start`; `status` select disabled while live; the Statistics section only renders on edit. +- Recordings: selecting a show prefills title, description, date and duration. Ship the candidate shows as a prop with `actual_start`/`actual_end` so the prefill happens client-side without an extra request. +- Servers: `hetzner_id`, `shared_secret` and `type` are disabled on edit; `max_clients` and `immutable` only render for edge. +- Emotes: `name` regex `^[a-z0-9_]+$`, max 20, unique; the approver/timestamp stamping stays server-side in the controller (mirroring `CreateEmote`/`EditEmote`). +- Branding: same four sections, helper text still sourced from `BrandingService::EDITABLE`, plus the reset-to-defaults confirm. + +Timezone: every datetime field is `Europe/Berlin` with seconds off, as today. Format on the server, send ISO strings plus a preformatted display string, and let `dayjs` handle only the display of relative times. + +### 2.7 Uploads + +`POST /manage/uploads` accepts one file plus a `purpose` (`show_thumbnail`, `recording_thumbnail`, `emote`, `branding_logo`, `branding_login_image`, `branding_login_video`). Purpose determines disk, directory, visibility, accepted mime types, max size and any resize, reproducing the per-field config from the audit. Returns a redirect back with the stored path flashed, which the form field then submits as a normal field value. + +Reads keep using the existing signed-URL accessors (`Show::thumbnail_url`, `Emote::url`, `Recording::thumbnail_url`) because the S3 objects are private. Branding uploads stay on the public disk. + +Emote 1:1 crop and the 64x64 / 1280x720 resizes happen server-side on upload; the client only previews. + +### 2.8 Navigation and badges + +One `ManageNavigation` service returns the rail structure with live badge counts (live/upcoming shows, online sources, pending emotes, role count) so the counts are computed in one place and shared as a prop on every `/manage` response. Badge queries are cheap counts; cache for 5s if the poll makes them hot. + +`RecordingResource`'s undeclared `Content` group becomes a real group in the structure. + +### 2.9 Deliberate behaviour changes + +Not parity gaps - decisions. Each needs a line in the cutover PR description. + +1. `/admin/login` disappears; authentication is OIDC only. +2. `ViewCountChart`'s hardcoded September-2023 dates are replaced by a selectable range (default: last 7 days, plus a per-show mode on the statistics page). Porting the hardcoded dates would ship a known-broken widget. +3. `ServerResource`'s unregistered `UserRelationManager` is not ported (dead code). Instead, the server detail page gets an "Assigned users" tab, which is what it was clearly for. +4. The Users module gains a real messages tab (read-only list with a delete action gated on `chat.moderate`); today's relation manager is unreachable. +5. Filament's global search (only `User.name`) becomes a rail search across shows, sources, servers and users - cheaper to do well than to deliberately restrict. +6. `ViewInstallScript`'s regex-extraction of config blocks out of a generated shell script is replaced by asking `ServerProvisioningService` for each config directly. The FFmpeg placeholder tabs are dropped rather than shipped as `# not available in current implementation`. +7. Server mutations require `stream.manage` (or admin). The Filament panel let anyone holding only `filament.access` - a chat moderator, say - edit, delete and deprovision infrastructure, because panel access was the only check. Reading the list is still open to every `access-manage` holder. `ServerPolicy` is the single place this lives. +8. **Autoscaling is removed, not ported.** The feature is gone from the product: capacity is provisioned by hand behind an nginx reverse proxy. Deleted with it: `AutoscalerService`, `AutoscalerAction`, `ScalingJob` and its every-minute schedule entry, the `stream.autoscale` config block, both Filament header actions, and the status-strip segment. The `servers.immutable` column only ever protected a server from autoscaler deletion, so the panel no longer offers it (the column stays; dropping it needs a migration and buys nothing). `StreamScalingListener` is *not* autoscaling and stays: it is what Stream Control's "start servers" and "delete servers" actions drive. +9. **No dashboard.** The status strip carries the numbers a dashboard would have shown, so `/manage` redirects to the first module. `Overview::edgeServerCards()` and `capacityCards()` are kept for the Stream Control page (phase 6); the view-count chart is deferred with the dashboard rather than ported with its hardcoded 2023 dates. +10. **Shows lost tags, metadata and server pinning**, at the operator's request. All three columns stay in the database; nothing in the panel reads or writes them. A live show's slug is also frozen server-side, not just disabled in the form: it is the URL people are watching. +11. **`ShowStatisticsService::getHourlyStats()` was MySQL-only.** `DATE_FORMAT` does not exist on Postgres or SQLite, so the statistics page 500'd everywhere except production. Bucketing now happens in PHP. This fixed the Filament page too. +12. **`Source::getRtmpServerUrl()` dereferenced null.** With no active origin server it read `->hostname` off `null` and took the page down - at exactly the moment an operator needs it. It returns null now and the field renders "No origin server is active". +13. The servers list hides deleted servers as a *filter default* rather than a hard query scope. In Filament the scope always applied, which made the "Deleted" option in the status filter dead - picking it could only return an empty table. Selecting it now works. + +--- + +## Part 3 - Build order + +Each phase is one PR, ends green, and leaves `/admin` fully working. + +**Phase 0 - foundations. DONE.** Tokens in `app.css`; `ManageLayout` + rail + status strip + toast host; `routes/manage.php` with only the dashboard; `access-manage` gate; the `Table` builder and `DataTable`/`FilterBar`/`Pagination`/`StatusBadge`/`ActionButton`/`ConfirmDialog` components; upload endpoint; `tests/Feature/Manage/AccessTest.php`. Dashboard shows the three widget equivalents (`ServerActive`, `Capacity`, view-count chart with a dynamic range). + +**Phase 1 - Servers. DONE.** All ten columns, both filters, deleted hidden by default; provision-cloud-server modal with the single-origin guard; autoscaler enable/disable; deprovision vs delete split by `hetzner_id`; install-script page with tabs, copy and download; assigned users on the detail page. `ServerPolicy` plus `tests/Feature/Manage/ServersTest.php` (38 cases, 301 assertions). Built along the way: `ServerFactory`, and the `FormSection` / `FormField` / `FormActions` / `CodeBlock` components the remaining modules reuse. + +**Phase 2 - Sources. DONE.** List (10s poll), status update single + bulk, guarded deletes, stream-key regeneration, OBS URL/key copy blocks via `CopyableText`, shows tab. `SourcePolicy` plus `tests/Feature/Manage/SourcesTest.php` (25 cases). + +**Phase 3 - Shows. DONE** (minus the deliberate cuts in 2.9). Biggest module. Five form sections, thumbnail upload, tags, role restriction, metadata; list with 12 columns, 5 filters (`hide_ended` default on), 5s poll; go-live / end / cancel-bulk / guarded delete; capture-screenshot with its disabled reasons; statistics page from `ShowStatisticsService` including realtime stats while live; viewers tab. + +**Phase 4 - Roles and Users.** Role form (4 sections, colour picker, permission tags, metadata), 3 ternary filters, inline `is_staff` / `is_visible` toggles, create-default-roles seeding action, users tab with pivot `expires_at` + `assigned_by` on attach. Users list, edit, roles tab, messages tab. + +**Phase 5 - Emotes and Recordings.** Emote approve/reject single + bulk with the approver stamping, pending badge; recording form with show prefill, thumbnail regeneration single + bulk dispatching `ProcessRecordingJob`. + +**Phase 6 - Stream Control and Branding.** Four stream-status actions with their exact confirm copy and tooltips; branding form with save + reset. + +**Phase 7 - parity gate and cutover.** See Part 5. + +--- + +## Part 4 - Test plan + +### 4.1 Server-side parity tests + +`tests/Feature/Manage/{Module}Test.php`, one per module, plus `AccessTest` and `NavigationTest`. Each module test covers: + +1. **Access** - guest redirects to `/login`; a signed-in user without the gate gets 403; a staff user gets 200. +2. **Index contract** - asserts the Inertia component name and that `columns` contains every key from the audit doc, in order: + ```php + $this->actingAs($this->admin)->get(route('manage.shows.index')) + ->assertInertia(fn (Assert $page) => $page + ->component('Manage/Shows/Index') + ->where('columns.*.key', [ + 'thumbnail','title','source','status','scheduled_start','actual_start', + 'viewer_count','peak_viewer_count','auto_mode','required_roles','tags', + ]) + ->where('filters.1.key', 'status') + ->where('filters.0.value', true) // hide_ended defaults on + ); + ``` + This is the mechanism that makes "feature parity" checkable rather than asserted by hand: the expected column and filter lists are transcribed from the audit doc, so a dropped column fails a test. +3. **Filters, sort, search, pagination** - one case per filter proving it changes the row set (including that `hide_ended` is applied without a query string), default sort direction, and that search matches the same fields Filament marked `searchable()`. +4. **Every action** - happy path (state changed, correct toast flashed, correct redirect) plus authorization (403 without the permission) plus each guard: + - deleting a live show is blocked and flashes a danger toast + - deleting a source with live shows is blocked + - deleting a role with users is blocked + - provisioning a second origin server is refused while one is active or provisioning + - screenshot capture is offered as disabled with the right reason when the show is not live or has no source + - bulk delete is all-or-nothing when one record fails the guard +5. **Forms** - validation rules (required, unique slug, `scheduled_end` after start, emote name regex, URL fields), create and update writing the expected columns, and the server-side stamping (emote approver + `approved_at`, role-user `assigned_at`). +6. **Uploads** - `Storage::fake('s3')`, one case per purpose asserting disk, directory, visibility and that the model stores the path while the accessor returns a signed URL. + +Reuse the existing setup from `tests/Feature/Filament/AdminPanelTest.php` (admin role with `admin.access` + `filament.access`, plus a plain user) - lift it into a `Tests\Concerns\CreatesManageUsers` trait so both suites share it during the parallel phase. + +Rough size: ~25-40 assertions per module, ~220 total. Runtime target under 20s on the existing Postgres setup. + +### 4.2 Playwright smoke set + +Four to six specs only, covering what feature tests structurally cannot: + +1. **Poll** - shows index; change `viewer_count` in the DB; assert the cell updates without a full navigation and that scroll position and column-toggle state survive. +2. **Upload** - attach a PNG to a show thumbnail, save, assert the preview and the table image render. +3. **Confirm dialog** - go-live on a scheduled show: dialog copy matches, cancel does nothing, confirm flips the badge to LIVE and raises a toast. +4. **Inline toggle** - flip a role's `is_staff` from the table and assert it persisted after a reload. +5. **Install script** - open the tabs, copy, download; assert the downloaded filename. +6. **Rail + status strip** - collapse/expand persists; a status-strip segment deep-links to the right module. + +`@playwright/test` as a dev dependency, one CI job, `php artisan serve --env=testing` against a seeded database. Keep it out of the default `php artisan test` path so the fast suite stays fast. + +### 4.3 The parity gate + +Cutover requires all of: + +- [ ] Every row in `current-filament-features.md` sections 2-4 maps to either a passing test or an entry in section 2.9 of this file. +- [ ] `php artisan test` green, including the legacy Filament suite (fix the one stale assertion in `AdminPanelTest` rather than deleting the test, so the old panel stays honest until it is removed). +- [ ] Playwright smoke green. +- [ ] `./vendor/bin/pint` clean. +- [ ] A manual walkthrough by an operator against a seeded database, exercising one live event end to end: source online -> show go-live -> viewers appear -> screenshot -> end -> statistics. + +A short checklist file (`docs/admin/parity-checklist.md`, generated by transcribing the audit doc's tables) is the artefact reviewers tick through in the cutover PR. + +--- + +## Part 5 - Cutover and Filament removal + +Phase 7, one PR, only once the gate above is fully green. + +1. `Route::redirect('/admin/{path?}', '/manage', 301)` with a `where('path', '.*')`, replacing the panel route registration. +2. Delete `app/Providers/Filament/AdminPanelProvider.php` and remove it from the providers array (`config/app.php:171`; there is no `bootstrap/providers.php` in this app). +3. Delete `app/Filament/` entirely (41 files, ~3 500 lines). +4. Delete `resources/views/filament/`. Keep `resources/views/server-provisioning/**` and the `Caddyfile`/`docker-compose`/`*-conf` blades - those are provisioning templates used by `ServerProvisioningService`, not admin views. +5. `composer remove filament/filament filament/upgrade` and drop `@php artisan filament:upgrade` from the `composer.json` scripts block. +6. Drop the transitive packages. Verified by grep: Livewire is referenced only in `config/livewire.php` (published config) and one option in `config/sentry.php`, never in `app/` outside `app/Filament`, so `livewire/livewire` goes with Filament - delete `config/livewire.php` and the Sentry option. `Flowframe\Trend` is used only by `app/Filament/Widgets/ViewCountChart.php`; either keep it for the new chart's hourly aggregation or replace it with a plain grouped query and remove `flowframe/laravel-trend`. +7. Delete `tests/Feature/Filament/AdminPanelTest.php` in this same PR, after confirming its 17 cases are all covered by `tests/Feature/Manage/*`. +8. Remove the `auth.can_access_filament` alias from `HandleInertiaRequests` and update any frontend reference to it. +9. Rename `filament.access` -> keep as-is. It is stored in `roles.permissions` rows in production; renaming needs a data migration and buys nothing. Document that the string is historical. +10. Update `CLAUDE.md`: drop "Admin Panel: Filament 3" from the tech stack, describe `/manage`, and note that `/admin` is a redirect. + +Rollback during the parallel phase is trivial (nothing was removed). After phase 7 it is a revert of that single PR, so keep it mechanical: no behaviour changes in the removal commit. + +### Risks + +| Risk | Mitigation | +|---|---| +| Cutover lands near a live event | Freeze phase 7 during event windows; the parallel panel means there is never pressure to ship it | +| A column, filter or guard silently dropped | The column/filter list assertions in 4.1 are transcribed from the audit doc, so drops fail tests | +| Private-S3 upload/signed-URL regressions | `Storage::fake('s3')` per purpose plus the Playwright upload spec | +| Polling load multiplies (7 tables x N operators) | `only:` partial reloads, pause on hidden tab / dirty form, Echo-driven refresh where a channel already exists, cached badge counts | +| Column-toggle and rail state lost on every navigation | Session-persisted toggles, `localStorage` rail state, asserted in the poll smoke spec | +| Livewire removal breaks something unrelated | Grep for Livewire usage outside `app/Filament` before removing; if anything turns up, keep the package | diff --git a/docs/admin/remaining-modules.md b/docs/admin/remaining-modules.md new file mode 100644 index 0000000..734df1b --- /dev/null +++ b/docs/admin/remaining-modules.md @@ -0,0 +1,335 @@ +# Remaining modules, and switching Filament off + +What is left between today's `/manage` and deleting `app/Filament`. Written to be built from directly: every module lists its routes, its prop contract, its actions with the copy they carry, and the tests that hold it to the audit. + +Companions: [`current-filament-features.md`](./current-filament-features.md) is the parity contract, [`rebuild-plan.md`](./rebuild-plan.md) is the architecture and the list of deliberate changes, [`parity-checklist.md`](./parity-checklist.md) is the tick list for the cutover PR. + +## Where things stand + +| Module | State | +|---|---| +| Servers | Done — `ServersTest` | +| Sources | Done — `SourcesTest` | +| Shows (+ planner, statistics) | Done — `ShowsTest`, `ShowPlannerTest` | +| Branding | Done, as config-driven `Manage/Settings` | +| Dashboard | Done (replaces the Capacity / ServerActive widgets) | +| **Roles** | Missing | +| **Users** | Missing | +| **Emotes** | Missing | +| **Recordings** | Missing | +| **Stream Control** | Missing | + +`Navigation.php` already advertises all five. `item()` drops any route that does not exist, so the sidebar grows as each lands and nothing 404s in the meantime. + +## Build order + +Roles first: Users' role tab and Shows' access restriction both read role data, and the pivot UI is shared. + +1. **Roles** — pivot UI, inline toggles, the default-roles action +2. **Users** — reuses the pivot panel from Roles +3. **Emotes** — small, self-contained +4. **Recordings** — medium, plus the new thumbnail/publish flow +5. **Stream Control** — small, but the confirm copy must be exact +6. **Remove Filament** + +--- + +## 1. Roles + +`RoleResource` + its Users relation manager. Audit §2.5. + +> ### The Filament Roles UI is partly fiction +> +> Two migrations on 2025-08-29 removed columns the resource still renders: +> +> - `2025_08_29_171750_remove_is_staff_from_roles_table` dropped `roles.is_staff` +> - `2025_08_29_170705_simplify_role_user_table` dropped `role_user.assigned_at`, +> `role_user.expires_at` and `role_user.assigned_by`, leaving `assigned_by_user_id` +> plus timestamps +> +> `RoleResource` was never updated. So today its `is_staff` toggle writes a field that is +> not fillable and does not exist, its `is_staff` **inline toggle column** would fail on +> write, and its Users relation manager's `pivot.assigned_at` / `pivot.expires_at` / +> `pivot.assigned_by` columns, its Active/Expired filters and its attach form all reference +> dropped columns. `createDefaultRoles()` passes `is_staff` too; it is silently discarded. +> +> **Build to the schema, not to the resource.** Role expiry is not a feature anyone has - +> it was deliberately removed - and reinstating it from a stale form would be inventing +> requirements. Anything below that the audit lists but the schema does not support is +> marked `DROPPED`, and the parity checklist entries for them are struck rather than ticked. +> +> `User::isStaff()` is `hasRole('admin')` and is unaffected. + +### Routes + +``` +GET /manage/roles manage.roles.index +GET /manage/roles/create manage.roles.create +POST /manage/roles manage.roles.store +GET /manage/roles/{role} manage.roles.edit +PUT /manage/roles/{role} manage.roles.update +DELETE /manage/roles/{role} manage.roles.destroy +POST /manage/roles/{role}/toggle manage.roles.toggle (is_visible) +POST /manage/roles/{role}/users manage.roles.users.attach +DELETE /manage/roles/{role}/users/{user} manage.roles.users.detach +POST /manage/roles/defaults manage.roles.defaults +``` + +### List + +Columns, in order: `name`, `slug`, `chat_color` (colour swatch, copyable), `priority` (tiered badge: ≥100 danger, ≥90 warn, ≥50 info, else idle), `login_sync` (Auto-synced / Manual badge), `is_visible` (**toggle column**, label "Chat badge"), `users_count`, `created_at` (hidden by default). Default sort `priority` desc. + +`DROPPED`: the `is_staff` column and its toggle — no such database column. + +Filters: two ternaries — `assigned_at_login` and `is_visible` — with their labels from the audit. `DROPPED`: the `is_staff` filter. + +The toggle column writes immediately via `manage.roles.toggle` with `field=is_visible`; anything else is a 422. `DataTable` already renders `Column::toggle()`; the cell value is `['value' => bool, 'url' => string]`. + +### Form + +Sections, one column of `label: control` rows: + +1. **Role information** — `name` (live-slug on create only), `slug` (unique), `description` +2. **Chat appearance** — `chat_color` (colour picker, default `#808080`), `priority`, `is_visible` +3. **Settings** — `assigned_at_login`, `permissions` (tags with the 8 suggestions: `filament.access`, `admin.access`, `chat.moderate`, `chat.delete`, `chat.timeout`, `chat.slowmode`, `stream.manage`, `user.manage`). `DROPPED`: `is_staff`. +4. **Users** (edit only) — the pivot panel below + +`permissions` needs a tags input. `TagsInput.vue` was deleted when Shows dropped tags; restore it from git history (`git log --diff-filter=D -- resources/js/Components/Manage/TagsInput.vue`) rather than rewriting it. + +`ColorPicker.vue` is new: a native `` beside a mono hex field, both bound to the same value, so a hex can be pasted as well as picked. + +### Users pivot panel + +New shared component `RelationPanel.vue`, used here and by Users. Table plus an attach form, no page navigation. + +Columns: `name`, `email`, `created_at` on the pivot as "Assigned", and who assigned it, resolved from `assigned_by_user_id` (falling back to "System" when null). + +`DROPPED`: the expiry column, the Active/Expired filters, and the `assigned_by` string badge — all reference dropped columns. + +Attach form: user select (searchable) only. The pivot records `assigned_by_user_id = auth()->id()` and its own timestamps. Toast "User assigned" / "The user has been assigned to this role." + +Detach: confirm, toast "User removed". + +### Actions + +- **Create Default Roles** — only when `Role::count() === 0`. Confirm heading "Create Default Roles", description "This will create the default set of roles for the system.", submit "Create Roles". Seeds Admin / Moderator / Super Sponsor / Sponsor / Attendee from `RoleResource::createDefaultRoles()` — colours, priorities, `assigned_at_login`, `is_visible` and permission sets, minus the `is_staff` key the database discards. Copy the array into `App\Support\Manage\DefaultRoles` before deleting the resource, and assert every surviving field of all five. Cross-check it against `RoleSeeder`, which already seeds roles and may be the better home. +- **Delete** — blocked while the role has users. Toast: "Cannot delete role" / "This role has assigned users. Remove all users before deleting." Same guard on bulk. + +### Policy + +`RolePolicy`: read for any `access-manage` holder; mutations need `user.manage` (or admin), matching the tightening applied to Servers and Shows. `delete()` returns false while `users()->exists()`. + +### Tests — `tests/Feature/Manage/RolesTest.php` + +Column and filter lists as corrected above; the priority ramp at each boundary (100, 90, 50, 0); the `is_visible` toggle persisting and an unknown field returning 422; delete blocked with users and allowed without; default-roles action hidden when roles exist and each of the five seeded correctly; attach stamping `assigned_by_user_id`; detach; `user.manage` required for every mutation. + +Add one regression test asserting `roles` has no `is_staff` column and `role_user` no `expires_at`, so nobody rebuilds the fiction from the old resource after it is deleted. + +--- + +## 2. Users + +`UserResource` + Roles and Messages relation managers. Audit §2.4. + +### Routes + +``` +GET /manage/users manage.users.index +GET /manage/users/{user} manage.users.edit +PUT /manage/users/{user} manage.users.update +DELETE /manage/users/{user} manage.users.destroy +POST /manage/users/{user}/roles manage.users.roles.attach +DELETE /manage/users/{user}/roles/{role} manage.users.roles.detach +DELETE /manage/users/{user}/messages/{message} manage.users.messages.destroy +``` + +No create route. Users arrive through OIDC; the Filament create form could only produce a broken row (`sub` is disabled and required). **CHANGED** — record it in rebuild-plan 2.9. + +### List + +Columns: `sub` (mono, copyable), `name` (searchable, sortable), `reg_id`, `roles` (badge list), `server` (hostname or "unassigned"). Search over `name` and `sub`. + +Filters: `has_server` ternary, `role` select (by slug). + +### Form + +Read-only: `sub`, `name`, `reg_id`, timestamps. Editable: `server_id`, limited to active edge servers, with an "Unassigned" option. + +Tabs on the detail page: +- **Roles** — `RelationPanel`, attach by role select, detach with confirm. Columns: name, slug, chat colour, priority, login-sync (read-only toggle), assigned at. Sort priority desc. Same pivot caveat as Roles: there is no expiry. +- **Messages** — read-only list (message, `is_command`, sent at) with a delete action gated on `chat.moderate`. **CHANGED**: unreachable in Filament today. + +### Policy + +`UserPolicy`: read for any `access-manage` holder; `update`/`delete` and role attach/detach need `user.manage`; message delete needs `chat.moderate`. + +### Tests — `tests/Feature/Manage/UsersTest.php` + +No create route exists; server select offers only active edge servers; assigning and clearing a server; role attach/detach; message delete permission split (a moderator may delete a message but not change a role); search over both fields. + +--- + +## 3. Emotes + +`EmoteResource`. Audit §2.6. + +### Routes + +``` +GET /manage/emotes manage.emotes.index +GET /manage/emotes/create manage.emotes.create +POST /manage/emotes manage.emotes.store +GET /manage/emotes/{emote} manage.emotes.edit +PUT /manage/emotes/{emote} manage.emotes.update +DELETE /manage/emotes/{emote} manage.emotes.destroy +POST /manage/emotes/{emote}/approve manage.emotes.approve +POST /manage/emotes/{emote}/reject manage.emotes.reject +POST /manage/emotes/bulk/approve manage.emotes.bulk.approve +POST /manage/emotes/bulk/reject manage.emotes.bulk.reject +``` + +### List + +Columns: `url` (image, 40px), `name` (rendered `:name:`, copyable, searchable), `uploadedBy.name`, `is_global` (bool), `is_approved` (bool, ok/warn), `usage_count`, `created_at`. Default sort `created_at` desc. + +Filters: approval status (Pending / Approved), `is_global` ternary. + +Nav badge: pending count, warn tone, hidden at zero. Already wired in `Navigation::badges()`. + +### Form + +`name` (required, unique, regex `^[a-z0-9_]+$`, max 20), image upload (purpose `emote`, already in `config/manage.php`: 1:1, 64×64, S3 private), `is_global`, `is_approved`. Read-only: uploader, approver, approved at, usage count. + +Server-side stamping, exactly as the Filament page classes did: create sets `uploaded_by_user_id`, and if created pre-approved also `approved_by_user_id` + `approved_at`; update stamps both on the first transition to approved. + +### Actions + +- **Approve** — unapproved only, confirm, `$emote->approve(auth()->user())` +- **Reject** — unapproved only, confirm, description "This will permanently delete the emote and its image.", `$emote->reject()` +- Bulk approve / bulk reject, both skipping already-approved records +- Policy: `chat.moderate` (or admin) for approve/reject; `user.manage` not required — this is chat moderation, not user administration + +### Tests — `tests/Feature/Manage/EmotesTest.php` + +Name regex and length; approve stamps approver and timestamp; reject deletes the row and the S3 object (`Storage::fake`); bulk actions skip approved; a moderator *can* approve (the one place `chat.moderate` is enough); pending badge count. + +--- + +## 4. Recordings + +`RecordingResource`, plus the thumbnail/publish flow decided on 2026-07-30. Audit §2.7. + +### Routes + +``` +GET /manage/recordings manage.recordings.index +GET /manage/recordings/create manage.recordings.create +POST /manage/recordings manage.recordings.store +GET /manage/recordings/{recording} manage.recordings.edit +PUT /manage/recordings/{recording} manage.recordings.update +DELETE /manage/recordings/{recording} manage.recordings.destroy +POST /manage/recordings/{recording}/publish manage.recordings.publish +POST /manage/recordings/{recording}/unpublish manage.recordings.unpublish +POST /manage/recordings/{recording}/thumbnail manage.recordings.thumbnail (grab a frame) +POST /manage/recordings/bulk/thumbnails manage.recordings.bulk.thumbnails +``` + +### Its own thumbnail + +The decision: **a recording's thumbnail is independent of the show's**. The show's is captured off the live stream and is read-only there; the recording's is set here, by upload (purpose `recording_thumbnail`) **or** by grabbing a frame at a timecode the operator picks. + +`ProcessRecordingJob` already captures a frame; extend it to accept an optional `atSeconds` so "grab frame at 00:04:12" reuses the existing ffmpeg path rather than adding a second one. Default stays first-frame when no timecode is given. + +Never copy `Show::$thumbnail_path` into a recording, and never write back — they are two different pictures of two different things. + +### Draft / published + +`is_published` already exists and already gates public visibility. Present it as an explicit state with two actions rather than a checkbox buried in the form: + +- **Publish** — confirm, "It becomes visible in the public archive." +- **Unpublish** — confirm, "It disappears from the public archive. The file is kept." + +New recordings are created as drafts regardless of what the form posts. **CHANGED** from Filament, where `is_published` defaulted to true. + +### Cut markers + +Show `actual_start` / `actual_end` from the linked show as read-only context with a link, so it is obvious where the in/out points came from. Editing them stays on the show. + +### Form + +`show_id` (searchable select, prefills title / description / date / duration from the show — ship candidates as a prop, no extra request), `title` (live-slug on create), `slug` (unique), `description`, `date`, `duration` (seconds, "auto-filled via ffmpeg if empty"), `m3u8_url` (required URL), thumbnail block (upload / grab-at-timecode / current preview), `required_roles` as the same **Public / Private** control Shows uses. + +### List + +Columns: thumbnail (80×45), title, slug (hidden), show badge, date, duration `H:MM:SS`, views, state badge (Draft / Published), access badge (hidden), created_at (hidden). Default sort `date` desc. Filters: state (draft/published), access. + +### Tests — `tests/Feature/Manage/RecordingsTest.php` + +Show prefill; slug uniqueness; created as draft; publish/unpublish round trip; thumbnail upload lands on the private disk and the accessor signs it; grab-at-timecode dispatches `ProcessRecordingJob` with the timecode; bulk regeneration counts only rows with an `m3u8_url`; a recording's thumbnail is untouched when the show's changes. + +--- + +## 5. Stream Control + +`Pages/Stream`. Audit §3.1. + +### Routes + +``` +GET /manage/stream manage.stream +POST /manage/stream/status manage.stream.status +``` + +### The four actions + +Copy is verbatim from the audit — these are the buttons someone presses under pressure, so the tooltips matter: + +| Action | Event value | Tone | Tooltip | +|---|---|---|---| +| Set Stream Starting Soon (Start Servers) | `STARTING_SOON` | warn | "Will start servers, takes around 6 minutes." | +| Set Stream Online | `ONLINE` | ok | "Set this after you started the stream in obs for the first time." | +| Set Stream Technical Issue | `TECHNICAL_ISSUE` | warn | "Set this if you have technical issues with the stream. Will automatically activate upon stream disconnect." | +| Set Stream Offline (Delete Servers) | `OFFLINE` | danger | "This sets the stream fully offline and deletes ALL Servers." | + +Each fires `StreamStatusEvent`, each confirms first. The offline one deletes every server through `StreamScalingListener` — its confirm should say so in the body, not just the tooltip. + +The page also shows the current status prominently (it is what the status strip reads) plus `Overview::edgeServerCards()` and `capacityCards()`, which have been waiting for exactly this page. + +### Policy + +`stream.manage` (or admin) for the endpoint. A read-only view for anyone else. + +### Tests — `tests/Feature/Manage/StreamControlTest.php` + +Each action dispatches its event with the right enum; an unknown status is a 422; a moderator gets the page but no actions and a 403 on POST; the cards report the same numbers as the status strip. + +--- + +## 6. Removing Filament + +Only once the parity checklist is fully ticked and `php artisan test` is green. + +1. `Route::redirect('/admin/{path?}', '/manage', 301)->where('path', '.*')`. +2. Delete `app/Providers/Filament/AdminPanelProvider.php`; remove it from `config/app.php:171` (there is no `bootstrap/providers.php`). +3. Delete `app/Filament/` — 41 files. +4. Delete `resources/views/filament/`. **Keep** `resources/views/server-provisioning/**` and the `Caddyfile` / `docker-compose` / `*-conf` blades: those are provisioning templates the install-script page renders, not admin views. +5. `composer remove filament/filament filament/upgrade`; drop `@php artisan filament:upgrade` from the `composer.json` scripts block. +6. Drop the transitive packages. Verified by grep: Livewire appears only in `config/livewire.php` and one `config/sentry.php` option, never in `app/` outside `app/Filament` — so `livewire/livewire` goes too, along with that config file and option. `flowframe/laravel-trend` was only used by the deleted `ViewCountChart`. +7. Delete `tests/Feature/Filament/AdminPanelTest.php`, after confirming its 17 cases are all covered under `tests/Feature/Manage/`. +8. Remove the `auth.can_access_filament` alias from `HandleInertiaRequests` and update the public layout's admin link to `manage.home`. +9. Leave the `filament.access` permission string alone. It is stored in `roles.permissions` rows in production; renaming needs a data migration and buys nothing. The `access-manage` gate already accepts it. Note in the code that the name is historical. +10. Update `CLAUDE.md`: drop "Admin Panel: Filament 3", describe `/manage`, note `/admin` is a redirect. + +Keep the removal commit mechanical — no behaviour changes in it — so it reverts cleanly if something surfaces. + +### Before deleting, lift these out + +Things that live only inside `app/Filament` and are still needed: + +- `RoleResource::createDefaultRoles()` — the five default roles, exact payload +- The OBS helper text and the install-script tab labels, if any wording is still only there +- Anything in `resources/views/filament/resources/**` that is a real template rather than admin chrome (check before the `rm`) + +## Concurrency note + +A second agent is working in the same panel and has been editing `SourceController`, `Navigation.php`, `AccessTest`, the Dashboard and the Settings module. Before starting a module, check `git status` for its files. Roles, Users, Emotes, Recordings and Stream Control are untouched by that work, so they are safe to take. diff --git a/docs/dev-stack.md b/docs/dev-stack.md new file mode 100644 index 0000000..f8872b4 --- /dev/null +++ b/docs/dev-stack.md @@ -0,0 +1,166 @@ +# Local streaming stack + +Two ways to get video on a laptop. Pick by what you are working on. + +| | What it runs | Use it for | +|---|---|---| +| **File loops** (`scripts/dev-streams.sh`) | ffmpeg writing HLS into `public/dev-streams/` | UI work. No Docker, starts in a second. | +| **Full stack** (`scripts/dev-stack.sh`) | SRS ingress, ABR transcoding, origin, edge, S3 | Deployment behaviour, DVR, thumbnails, load tests. | + +The Laravel app always stays native under Yerd. Containers reach it through +`host.docker.internal`. + +## File loops + +```bash +php artisan db:seed --class=DevStreamChannelsSeeder +./scripts/dev-streams.sh # Ctrl+C stops every channel +``` + +Set `DEV_STREAMS=true` in `.env`. `Source::getHlsUrl()` then returns +`/dev-streams//index.m3u8` instead of the edge proxy route, so the browse +hero and tile hover previews play real video with nothing else running. + +Four channels, each a different pattern so they are easy to tell apart: +`prime`, `dance-stage`, `panel-room`, `art-track`. + +## Full stack + +```bash +php artisan db:seed --class=DevStreamChannelsSeeder +./scripts/dev-stack.sh up +``` + +Leave `DEV_STREAMS` false here: the app should go through its own `/hls` routes, +which proxy to the edge server row on `localhost:8085`, exactly as in production. + +``` +publisher (ffmpeg, stands in for OBS) + | rtmp://localhost:1935/ingress/?secret= +SRS origin ──DVR mp4──> dvr-uploader ──> versitygw (S3 API) ──> recordings, thumbnails + | rtmp +ffmpeg-hls (480p/720p/1080p ladder, aligned GOPs; remuxed by default locally) + | shared volume +origin nginx :8083 ──> origin caddy :8070 + | +edge nginx :8081 (njs verifies ?t= tokens) ──> edge caddy ──> localhost:8085 ──> browser +``` + +The container configs in `docker/dev/` are generated copies of the production +ones in `docker/`, with upstreams pointed at compose service names. Regenerate +them if the production configs change. + +### Ports + +| Port | Service | Note | +|------|---------|------| +| 1935 | SRS RTMP ingress | point OBS here | +| 1985 | SRS HTTP API | `curl localhost:1985/api/v1/streams` | +| 8070 | origin caddy | debugging | +| 8085 | edge caddy | matches the seeded edge server row | +| 7075 | versitygw | S3 API; override with `DEV_S3_PORT` | + +8080 belongs to Yerd's daemon and 8081 to Reverb, so the edge avoids both. 7070 is +AnyDesk's default listener, which is why versitygw is not on it: AnyDesk wins the bind +and `dev-stack.sh up` fails part-way through with `address already in use`. Inside the +compose network the service is still on 7070, so only the host port moved. + +### Commands + +```bash +./scripts/dev-stack.sh up # start everything and begin publishing +./scripts/dev-stack.sh publish # restart broadcasters with fresh stream keys +./scripts/dev-stack.sh status # what SRS thinks is live, plus container state +./scripts/dev-stack.sh logs edge-nginx +./scripts/dev-stack.sh down # stop, keep volumes +./scripts/dev-stack.sh reset # stop and wipe HLS, DVR and S3 volumes +``` + +Stream keys are encrypted in the database, so the publisher containers get them +from `php artisan dev:stream-keys`, which `dev-stack.sh` calls for you. + +### Keeping the CPU quiet + +By default nothing in the stack encodes video after the first few seconds. Two +switches do the work, and both trade fidelity for headroom: + +| Variable | Default | What the default does | +|----------|---------|-----------------------| +| `DEV_PUBLISH_MODE` | `loop` | Encodes one clip per channel once, caches it in the `publisher-clips` volume, then pushes it endlessly with `-c copy`. Restarts are instant. The on-screen clock is frozen at capture time. | +| `DEV_ABR_MODE` | `copy` | Remuxes the incoming stream into all three renditions instead of encoding them. `sd`, `hd` and `fhd` all carry the publisher's picture, so switching quality in the player changes nothing visible. | +| `DEV_PUBLISH_LIMIT` | `0` (all) | Caps how many channels publish. | +| `DEV_PUBLISH_SIZE` | `1280x720` | Publisher resolution. | + +`DEV_ABR_MODE`, `DEV_PUBLISH_MODE`, `DEV_PUBLISH_SIZE` and +`DEV_PUBLISH_CLIP_SECONDS` are read by compose, so they work from `.env` or the +command line. `DEV_PUBLISH_LIMIT` is read by `dev-stack.sh` itself and has to +be set on the command line. + +So a laptop can hold five channels through the full path at roughly idle cost. +Flip either one back when the thing you are testing depends on it: + +```bash +DEV_PUBLISH_MODE=live ./scripts/dev-stack.sh publish # moving clock, real frames +DEV_ABR_MODE=transcode ./scripts/dev-stack.sh up # the real 480p/720p/1080p ladder +``` + +Copy mode cuts segments on the publisher's keyframes, so a publisher with a GOP +longer than `hls_time` (2s) produces long, ragged segments. The bundled +publisher already sends a 2s GOP; OBS needs its keyframe interval set to 2 as +well. + +### Playback tokens + +The edge is built from `docker/edge-nginx`, so it carries njs and +`hls-auth.js` and verifies `?t=` locally with an HMAC, exactly as +production does. It reads `HLS_VIEWER_SECRET`, `HLS_EMBED_SECRET`, +`HLS_TOKEN_LEEWAY` and `STREAM_SYSTEM_STREAMKEY` from your `.env`. Without a +viewer secret every tokenised request answers 403: + +```bash +openssl rand -hex 32 # HLS_VIEWER_SECRET +openssl rand -hex 32 # HLS_EMBED_SECRET +``` + +Rejections are logged with a reason (`expired`, `bad_signature`, +`source_mismatch`, ...) on the edge: + +```bash +./scripts/dev-stack.sh logs edge-nginx +``` + +Publisher authentication is a separate thing and is unchanged: SRS still calls +`/api/srs/auth` on publish and the app compares `?secret=` against the source's +stored `stream_key`. Playback tokens cover viewers, not broadcasters. + +### Storage + +versitygw serves a plain directory over the S3 API, so DVR uploads, recordings +and thumbnail writes all take the same code path they take against real object +storage. Point the app at it in `.env`: + +```dotenv +AWS_ACCESS_KEY_ID=devkey +AWS_SECRET_ACCESS_KEY=devsecret123 +AWS_DEFAULT_REGION=eu-central-1 +AWS_BUCKET=streaming +AWS_ENDPOINT=http://localhost:7075 +AWS_USE_PATH_STYLE_ENDPOINT=true +``` + +Swap the `s3` service for MinIO if you want a browser console; nothing else in +the stack cares which one is behind the endpoint. + +## Load testing + +```bash +./scripts/load-test.sh # 50 viewers, 60s, first channel +./scripts/load-test.sh 200 120 prime # 200 viewers, 120s, channel "prime" +``` + +Each viewer is an ffmpeg client pulling the real ladder through the edge, so +playlist refreshes, segment fetches and nginx caching all count. Every viewer +shares one source IP, and the edge rate-limits per IP (30 r/s, see +`docker/edge-nginx/nginx.conf`), so past a few hundred viewers you are measuring +the rate limiter rather than the server. The summary prints failure counts and +the most common ffmpeg errors, then the edge's response-code spread. diff --git a/docs/dvr-archive-plan.md b/docs/dvr-archive-plan.md new file mode 100644 index 0000000..ee893ba --- /dev/null +++ b/docs/dvr-archive-plan.md @@ -0,0 +1,607 @@ +# DVR archive redesign + +Replaces the SRS-MP4 -> concat -> trim -> re-encode pipeline with a segment-first +archive built from the HLS the transcoder already produces. + +Companion to `docs/streaming-auth-redesign.md`: archive playback runs over the same +edge and the same playback token, with no special casing. + +## Goals + +1. 60 minutes of live rewind so a late viewer can jump back to the start of a panel. +2. Reliable, resumable sync of every segment to the recordings S3, addressed by show slug. +3. Preview and trim recordings in the `/manage` panel. +4. Recording never stops for the duration of the con, and origin disk is released as + soon as S3 has a verified copy. + +## What exists today + +| Piece | File | Role | +|---|---|---| +| Transcoder | `docker/ffmpeg-hls/stream-manager.sh` | ABR ladder, 2s segments, 60-segment sliding window | +| SRS DVR | `docker/origin-srs/origin.conf` | writes segmented MP4 to `/dvr/recordings` | +| Uploader | `docker/dvr-uploader/uploader.py` | watchdog on `.mp4`/`.flv`, upload, delete | +| Trim | `dvr-extract.sh`, `app/Services/DvrExtractorService.php` | concat demuxer + `-ss`/`-t` copy | +| Convert | `dvr-process.sh` | second ABR encode from the trimmed MP4 | +| Model | `app/Models/Recording.php` | `m3u8_url`, `duration`, `thumbnail_path`, `show_id`, `slug` | +| Disks | `config/filesystems.php` | `s3` (app assets, thumbnails) and `dvr` (recordings bucket) | + +### Why the current cuts are rough + +- SRS writes each `dvr_duration` chunk with its own timestamp base. `concat` with + `-c copy` splices those bases, so every chunk boundary is a timestamp discontinuity. +- `-ss` after `-i` with `-c copy` keeps original timestamps and starts at the nearest + preceding keyframe, which gives a frozen head and A/V skew. +- `dvr_wait_keyframe` aligns video only. AAC priming at each splice accumulates drift. +- The copy -> re-encode -> `filter_complex concat` fallback chain in + `DvrExtractorService` means recordings are not all processed the same way. +- Everything is encoded twice: the live ladder, then a second ladder in `dvr-process.sh`. + +## Target design + +The transcoder already emits exactly what a VOD archive wants: + +``` +-force_key_frames "expr:gte(t,n_forced*2)" -g 60 -keyint_min 60 -sc_threshold 0 +-hls_time 2 -hls_flags independent_segments+program_date_time+discont_start +``` + +Every segment is independently decodable, boundaries are identical across `sd`/`hd`/`fhd`, +and `EXT-X-PROGRAM-DATE-TIME` anchors the timeline to wall clock. So: + +- **Trimming is playlist authoring.** Select a sub-range of segments, write a VOD + playlist. No decode, no concat, no `-ss`. Seams cannot exist because nothing is ever + cut inside a segment. +- **Cuts are non-destructive and re-editable.** A `Recording` is a `(archive prefix, + start PDT, end PDT)` view. Re-cutting rewrites a text file. +- **ABR survives into the archive** and the second encode pass disappears. + +### 1. Origin: DVR window and non-destructive session dirs + +Changes to `docker/ffmpeg-hls/stream-manager.sh`: + +```diff +- -hls_list_size 60 \ +- -hls_delete_threshold 60 \ ++ -hls_list_size "$DVR_WINDOW_SEGMENTS" \ ++ -hls_delete_threshold "$HLS_DELETE_THRESHOLD" \ +- -hls_segment_filename "$output_dir/${stream}_%v_${timestamp_prefix}_%05d.ts" \ ++ -hls_segment_filename "$output_dir/${stream}_%v_${timestamp_prefix}_%06d.ts" \ +``` + +`hls_list_size 1800` at `hls_time 2` is a 60 minute seekable window, and +`hls_delete_threshold 60` keeps a further two minutes on disk after a segment leaves the +playlist. Step 1 ships on its own, before any uploader exists, so FFmpeg must still be +the thing deleting segments or the disk grows without bound. Step 3 raises the threshold +to effectively infinite and hands deletion to the uploader's verified reaper. + +The output layout stays **flat**. Per-session subdirectories were considered and +rejected: origin nginx serves `root /var/www/hls` with `location ~ ^/live/(.+\.m3u8)$`, +and the app hands out a stable master URL (`/live/_master.m3u8`, see +`app/Models/User.php:119`). Session directories would move the master under a path that +changes on every reconnect, so keeping the URL stable would mean writing a second master +at the top level with rewritten variant paths. The segment filenames already carry +`${timestamp_prefix}`, which gives per-session uniqueness with none of that. + +What replaces the session directories is an **orphan reaper**. FFmpeg's +`delete_segments` only removes files that the *current* session wrote, so a publisher +reconnect strands the previous session's segments forever. The manager sweeps +`OUTPUT_BASE_DIR` every 5 minutes and deletes `.ts` files older than +`ORPHAN_RETENTION_MINUTES`, which derives from the window plus a margin (67m at the +defaults) so it can never reach a segment the live playlist still references. Step 3 +sets it to `0` once the uploader's verified reaper owns deletion. + +Two destructive cleanups had to change, because with the archive living on disk they +would delete unuploaded material: + +- `stop_ffmpeg` did `rm -f "$OUTPUT_BASE_DIR/${stream}"*`. That glob is outside the + quotes, so it expanded and took every segment with it. Now restricted to the playlists. +- `start_ffmpeg` did `rm -f "$output_dir/${stream}_*.ts"`. That glob is *inside* the + quotes, so it never expanded and the line was always inert. Removed rather than fixed. + +Add a freshness watchdog to the monitor loop: if SRS reports the stream as publishing +but the stream's playlists have stopped advancing for 15s, restart ffmpeg. Today a wedged +ffmpeg that has not exited is invisible to `check_streams`, which only compares PIDs +against the SRS stream list. Playlist mtime is the liveness signal rather than segment +mtime, because it is one `stat` per rendition instead of a scan of thousands of files. + +Bump `%05d` to `%06d`: 99999 segments is 55 hours, which a con-long stream reaches. + +**Session id collisions.** `timestamp_prefix=$(date +%s)` has one-second resolution, so +two FFmpeg starts inside the same second produce the same prefix and the second session +writes `${stream}_hd__000000.ts` directly over the first session's segments. The 30s +startup grace keeps the watchdog off this path, but a fast crash-restart loop through +`check_streams` (5s interval) reaches it, and the failure is silent archive loss rather +than an error. `start_ffmpeg` now bumps the prefix while any segment already carries it. +The check works precisely because the orphan reaper leaves old sessions' segments on disk. + +This matters beyond the transcoder: the segment filename is the archive's primary key, so +a collision corrupts S3 objects and index entries alike. + +One more lifecycle bug surfaced while testing the watchdog. FFmpeg was launched as +`ffmpeg ... 2>&1 | sed "s/^/[FFmpeg $stream] /" &`, and for a backgrounded pipeline `$!` +is the PID of the *last* stage. Every stored PID was sed's, so `stop_ffmpeg` killed the +log prefixer and left FFmpeg encoding indefinitely; only the `pgrep -f` fallback in +`start_ffmpeg` ever cleaned one up, and then only if the same stream came back. A stream +that ended for good leaked its encoder. Replaced with process substitution +(`> >(sed ...) 2>&1 &`), which makes `$!` FFmpeg's own PID and lets the sed exit when the +pipe closes. + +### 2. Indexing (part of the uploader, not a separate process) + +**Not a sidecar.** The uploader has to parse the rendition playlists anyway, because its +completion rule is "a segment is done once it appears in the playlist and is not the last +entry". That parse already yields filename, `EXTINF`, PDT and discontinuity position. +Indexing is a few lines on data already in hand. A separate process would duplicate the +parse, need its own view of the volume, and introduce a failure mode where the uploader +and the indexer disagree about what they saw. + +**The index is the playlist, persisted.** The live playlist holds the only record of when +a segment happened, and it forgets each entry after 60 minutes. Nothing more exotic than +that is going on, so the stored form is simply an HLS playlist per source per hour: + +``` +archive/{source}/{YYYYMMDD}/{HH}/index.m3u8 +``` + +``` +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:2 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-ARCHIVE-SESSION:1754156625 +#EXT-X-ARCHIVE-SEQ:28800 +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2026-08-02T16:00:00.000+0000 +mainstage_%v_1754156625_000042.ts +``` + +No JSON, no bespoke schema, no format conversion. Generating a cut becomes: concatenate +the hour files spanning the range, drop the entries outside it, prepend a header, append +`ENDLIST`. The stored form is already the target form, and the admin editor parses HLS +regardless. `#EXT-X-DISCONTINUITY` is carried through verbatim rather than round-tripped +through a boolean. + +`ARCHIVE-SEQ` and `ARCHIVE-SESSION` are custom tags. RFC 8216 requires clients to ignore +unrecognised `#EXT-X-` tags, so these travel harmlessly even if a playlist is served to a +player by accident. + +`%v` is substituted per rendition. Segment boundaries are identical across `sd`/`hd`/`fhd` +(shared input, `-force_key_frames "expr:gte(t,n_forced*2)"`), so one entry describes all +three and the index stays a third of the size. + +FFmpeg writes `EXT-X-PROGRAM-DATE-TIME` before *every* segment rather than once as an +anchor (verified against real output in the dev stack), so PDT is read directly and never +derived by accumulating `EXTINF`. + +The in-progress hour is re-uploaded once a minute; the hour is finalised when it rolls over. + +#### Three fields, three jobs + +The temptation is to order the archive by `pdt`, since it is the field that means +something to a human. Do not. Each field has exactly one job: + +| Field | Job | Clock-dependent | +|---|---|---| +| `seq` | **Ordering.** Indexer-assigned, global per source, monotonic, never reused | No | +| `session` + `n` | Provenance and dedupe key | Only via `session` | +| `pdt` | Display, and selecting cut points | Yes | + +`seq` is assigned by the indexer at the moment it first observes a segment. Observation +order follows playlist order, which is inherently monotonic, so `seq` needs no clock and +cannot go backwards. A clock jump then costs cut *accuracy* and can never corrupt archive +*order*. + +Some detail behind that, because the naive version of this rule is wrong: + +- Within a session, `n` is FFmpeg's own counter (`-start_number 0`, +1 per segment) and + involves no clock at all. +- `session` is `date +%s`, so it *is* clock-derived. "Order by session and n" therefore + does not avoid the clock across session boundaries, which is why `seq` exists. +- PDT is more robust than it first appears: FFmpeg's HLS muxer anchors wall clock once at + session start and then accumulates `EXTINF`, rather than re-reading the clock per + segment. A mid-stream NTP correction does not corrupt it. Observed in the dev stack, + consecutive PDTs were exactly 2.000s apart (`22:37:17.805` then `22:37:19.805`); clock + sampling would show jitter. +- The flip side: PDT tracks the *encoder's* timeline from that one anchor, not real time, + so it can drift from wall clock over a long session. Drift resets on every reconnect, + which makes a con-long stream that never reconnects the worst case. **Unmeasured.** + Worth measuring against a con-length run before trusting PDT for cut points; if drift is + material, the indexer can re-anchor by stamping its own observation time periodically + and interpolating. + +#### Guard the identical-boundaries assumption + +One index entry describing all three renditions relies on FFmpeg cutting them at the same +instants. That holds because of `-force_key_frames "expr:gte(t,n_forced*2)"` on a shared +input, but if it ever stops holding — `ABR_MODE=copy` with a publisher whose GOP does not +match `hls_time`, say — the index silently mislabels two renditions out of three, and +nothing downstream would notice. + +So the indexer asserts it rather than assuming it: periodically compare segment count and +PDT across the three playlists, and log loudly on divergence. Cheap, and it converts a +silent correctness bug into a visible one. + +### 3. S3 layout + +Two prefixes on the existing `dvr` disk, with different lifetimes: + +``` +archive/{source_slug}/{YYYYMMDD}/{HH}/{stream}_{rendition}_{session}_{seq}.ts +archive/{source_slug}/{YYYYMMDD}/{HH}/index.json + +recordings/{show_slug}/master.m3u8 +recordings/{show_slug}/{rendition}.m3u8 +recordings/{show_slug}/thumb.jpg +``` + +Segments are bucketed by the hour their *start* PDT falls in, so a segment straddling the +boundary belongs to the earlier hour and the index for hour H may describe a segment that +extends slightly into H+1. `{session}` is the ffmpeg session id, which keeps sequence +numbers unique across a publisher reconnect inside the same hour. + +#### Why hour buckets rather than a flat prefix + +Two conventions exist in the industry. Bounded VOD assets go flat, scoped by asset id: +Mux uses `{asset_id}/{rendition}/{seq}.ts`, and MediaLive's S3 output puts everything +under one prefix with the counter in the filename. Continuous DVR and timeshift go +time-bucketed: Flussonic DVR and Wowza nDVR both use `{stream}/{hour}/{segment}`, because +retention and range lookup are both by wall clock. A con-long stream is the second case. + +Prefix depth itself costs nothing. S3 has no directories, and since 2018 it auto-partitions, +so the old random-prefix guidance no longer applies. At 5400 PUT/hour/source, or 1.5/s +against a 3500/s per-prefix ceiling, no layout is under pressure. + +The deciding factor is reconciliation. The uploader's correctness rests on listing what S3 +actually holds and diffing it against disk. Flat over a 5-day stream is ~648k objects, or +~648 `list_objects_v2` pages per full reconcile. Hour-bucketed, only the hours in play get +re-listed, around 6 pages each. Prefix-scoped lifecycle rules (retain the days holding +published recordings, expire the rest) are a secondary benefit; expiry by object age works +without prefixes anyway. + +A `{rendition}/` directory level is deliberately absent: the filename already carries the +rendition, so it would add a level and a LIST dimension for nothing. + +The archive is keyed by source and wall-clock hour, not by show. This is what lets a +single continuous main stream span the whole con while still producing many recordings: +a `Recording` is a PDT range over the archive, and the generated playlist under +`recordings/{show_slug}/` points into `archive/`. Nothing is copied when you cut. + +Show slug addressing (the requirement) lives at the `recordings/` prefix, which is the +layer users and the player see. `Show::boot()` already generates +`Str::slug($title . '-' . $scheduled_start->format('Y-m-d'))`, so slugs are stable and unique. + +### 4. Uploader rewrite + +`docker/dvr-uploader/uploader.py` today matches `.mp4`/`.flv` only, watches +`/dvr/recordings`, and decides a file is finished by polling its size twice two seconds +apart. All three need to change. + +**Completion rule.** A segment is complete when it appears in the rendition playlist and +is not the last entry. That is exact and race-free; drop the size-polling heuristic. + +**Reconciling sweeper, not just a watcher.** inotify is the fast path; correctness comes +from a sweep every 30s that diffs local segments against a local manifest +(`sqlite`, one row per key: `path, key, size, etag, uploaded_at, verified_at`). Restart +after a crash rebuilds by re-listing the affected hour prefixes. + +**Verify before delete.** After upload, `head_object` the key and compare size and ETag. +Only a verified match sets `verified_at`. + +**Reaper.** Separate thread. Deletes a local segment only when both hold: + +1. `verified_at` is set, and a periodic `list_objects_v2` over the hour prefix still + confirms the key with the right size. +2. The segment is older than `DVR_WINDOW_SECONDS` (default 3600), so deletion never eats + into the live rewind window. + +**Backpressure.** If free disk drops below a threshold, log at error level, emit the +condition to the metrics endpoint, and shrink the effective DVR window before touching +anything unverified. Never delete an unverified segment silently. + +Keep `MAX_CONCURRENT_UPLOADS` and the semaphore, but retune `TransferConfig`: the 100MB +multipart threshold is sized for 800MB MP4s and is irrelevant for ~3MB segments. Small +files want more concurrency and no multipart. + +### 5. VOD playlist generation + +New `App\Services\ArchivePlaylistService`: + +- `index(string $source, CarbonInterval $range): Collection` reads and merges the hour + `index.json` shards covering the range. +- `build(Recording $recording): void` selects segments whose derived PDT falls in + `[starts_at, ends_at]`, writes one media playlist per rendition plus a master, and + puts them at `recordings/{show_slug}/`. + +Generated media playlist: + +``` +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-TARGETDURATION:2 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-PROGRAM-DATE-TIME:2026-08-02T16:43:46.000Z +#EXTINF:2.000000, +/archive/mainstage/20260802/16/mainstage_hd_1754156625_00042.ts +... +#EXT-X-ENDLIST +``` + +`PLAYLIST-TYPE:VOD` and `ENDLIST` are mandatory; without them players treat the archive +as live. Discontinuity flags from the index become `#EXT-X-DISCONTINUITY`. + +`RecordingService::extractDurationFromM3u8()` already sums `EXTINF` and follows master +playlists, so duration comes for free. Thumbnails keep working: `captureFrameAtTime` +against the generated VOD playlist. + +`DvrExtractorService`, `dvr-extract.sh`, `dvr-process.sh`, `ExtractDvrSegments` and the +SRS `dvr` config all retire once this is proven. + +### 6. Manage UI: preview and trim + +New module under `/manage`, following the existing `Sources`/`Shows` shape in +`routes/manage.php` and `resources/js/Pages/Manage/`. + +```php +Route::get('recordings', [RecordingController::class, 'index'])->name('recordings.index'); +Route::get('recordings/{recording}', [RecordingController::class, 'edit'])->name('recordings.edit'); +Route::put('recordings/{recording}', [RecordingController::class, 'update'])->name('recordings.update'); +Route::post('recordings/{recording}/cut', [RecordingController::class, 'cut'])->name('recordings.cut'); +Route::post('recordings/{recording}/publish', [RecordingController::class, 'publish'])->name('recordings.publish'); +Route::post('shows/{show}/recordings', [RecordingController::class, 'store'])->name('recordings.store'); +``` + +`Manage/Recordings/Index.vue` — list with show, source, date, duration, size, status +(`recording` / `ready` / `published`), reusing the existing table-column preferences +(`TableColumnController`). + +`Manage/Recordings/Editor.vue` — the trim screen: + +- `VideoPlayer` on a *preview* playlist covering the whole archive range plus padding, so + the operator can find the real start rather than being locked to the current cut. +- Scrubber with in/out handles, snapped to the 2s segment grid. Handle positions display + as both offset and absolute PDT. +- `[` / `]` set in/out at playhead; `J`/`K`/`L` shuttle; arrow keys nudge one segment. +- Discontinuities drawn as ticks on the scrubber, since those are the points a cut is + most likely to be wanted. +- Save regenerates the playlists and updates `starts_at`/`ends_at`. Non-destructive, so + it can be redone any number of times while the archive segments are retained. +- "Split here" creates a second `Recording` over the same archive from the playhead. + This is the workflow for slicing individual panels out of the con-long main stream. + +Frame-accurate starts are out of scope: granularity is one segment, 2s. If a specific +recording needs tighter, re-encode only the two boundary segments and leave the rest +untouched. + +### 7. Player: live rewind + +**Done.** `resources/js/Components/Player/VideoPlayer.vue` (renamed from `EfPlayer.vue` +during branding neutralisation) already supported `liveStreamType: 'live:dvr'` and +`seekToLive()`, but nothing ever passed `live:dvr`, so the seek bar never appeared and the +window would have stayed invisible no matter how large it was. `StreamPlayer.vue` now +defaults the prop to `'live:dvr'` and forwards it, leaving `'live'` available per-caller +for a source that keeps no window. + +`backBufferLength` is now derived from stream type: 30s for plain live, 90s for `live:dvr` +and on-demand. `-1` was rejected because it would hold the whole hour in memory; 90s +covers a short "what did they just say" rewind without a refetch. +`liveSyncDurationCount: 3` stays. + +`StageHero.vue` and `ShowTile.vue` build their own hls.js instances for muted preview +tiles and were deliberately left at their small buffers. + +Client-side parse cost is worth watching: hls.js re-parses the full 1800-entry playlist +on every 2s refresh. Fine on desktop, measurable on weak devices, so check the VRChat +and embed paths before rolling the window out to them. + +### 8. Edge + +Measured against real transcoder output (103 B per entry, since ffmpeg writes a +`PROGRAM-DATE-TIME` line per segment), an 1800-entry playlist is **179 KB raw and 10 KB at +`gzip -6`** — 5%, because the timestamp and filename sequences are almost perfectly +regular. Refreshed every 2s that is ~41 kbps per viewer, or ~82 Mbps of playlist traffic +at 2000 viewers. Uncompressed it would be ~716 Mbps, so compression is doing real work +here. + +`application/vnd.apple.mpegurl` is **already** in `gzip_types` in all three configs +(`docker/edge-nginx/nginx.conf:43`, the `edge/nginx-config.blade.php` mirror, and +`docker/origin-nginx/nginx.conf:30`), so nothing is needed here. + +`video/mp2t` was also in those `gzip_types` lists. MPEG-TS is already compressed, so with +`gzip_proxied any` the edge was spending level-6 gzip CPU on every video segment for no +size gain. **Removed** from all five configs (`docker/edge-nginx`, `docker/origin-nginx`, +`docker/dev/edge-nginx.conf`, `docker/dev/origin-nginx.conf`, and the edge provisioning +blade). Verified against the running stack: the playlist comes back +`Content-Encoding: gzip`, the segment comes back with a plain `Content-Length` and no +encoding. + +The 2s `proxy_cache_valid` plus `proxy_cache_lock` already means origin sees one playlist +fetch per 2s regardless of viewer count; only edge egress scales. + +Archive playback adds one location block for `/archive/` and `/recordings/`, proxying S3, +behind the same `auth_request /auth` as `/live/`. Segment caching there can be far more +aggressive than live: the objects are immutable. + +## Data model + +```php +Schema::table('recordings', function (Blueprint $table) { + $table->foreignId('source_id')->nullable()->after('show_id'); + $table->string('archive_prefix')->nullable(); // archive/mainstage + $table->timestampTz('starts_at')->nullable(); // cut in point, PDT + $table->timestampTz('ends_at')->nullable(); // cut out point, PDT + $table->timestampTz('archive_starts_at')->nullable(); // full available range + $table->timestampTz('archive_ends_at')->nullable(); + $table->string('status')->default('pending'); // pending|recording|ready|published|failed + $table->timestampTz('playlist_generated_at')->nullable(); + $table->unsignedBigInteger('size_bytes')->nullable(); + $table->unsignedInteger('segment_count')->nullable(); +}); +``` + +Optional `archive_hours` table (one row per source/hour: `source_id, hour, segment_count, +size_bytes, first_pdt, last_pdt, complete`) purely so the admin list does not have to read +S3. Individual segments stay out of Postgres; `index.json` in S3 is the source of truth. + +## Capacity + +Ladder totals 1500+3500+6000 kbps video and 128+160+192 kbps audio, about 11.5 Mbps, or +1.44 MB/s per source across all three renditions. + +| Quantity | Value | +|---|---| +| 60 min DVR window on disk, per source | ~5.2 GB, 5400 files | +| Archive per hour, per source | ~5.2 GB, 5400 objects | +| Archive for a 5-day continuous stream | ~620 GB, ~648k objects | +| Live playlist size at 1800 entries | 179 KB raw, 10 KB gzipped (measured) | +| Playlist traffic per viewer | ~41 kbps | + +620 GB for the main stream is the number worth a decision (see open questions). Origin +disk should be sized at 50 GB or more per source: the window itself is 5.2 GB, and the +rest is headroom for upload lag and for an S3 outage that stalls the reaper. + +## Failure modes + +| Failure | Handling | +|---|---| +| S3 unreachable | Segments accumulate on disk; reaper blocks on `verified_at`; disk watchdog alerts and degrades the window before anything unverified is dropped | +| Publisher reconnect | `discont_start`, indexer records the discontinuity, generated playlists emit `EXT-X-DISCONTINUITY` | +| ffmpeg wedged but alive | Playlist freshness watchdog restarts it | +| ffmpeg restart | Session prefix in every filename, collision-checked at start; `stop_ffmpeg` no longer wipes segments | +| Two restarts in one second | `start_ffmpeg` bumps the session prefix while it is already in use | +| Uploader crash | sqlite manifest plus prefix re-listing on boot; uploads are idempotent by key | +| Indexer gap | Index is rebuilt from the live playlist, which holds 60 minutes of history, so an outage shorter than the window loses nothing | +| Segment counter wrap | `%06d` (55h at 2s) plus a fresh session prefix on every restart | +| Origin disk full | Watchdog degrades the DVR window, then alerts loudly; never deletes unverified | +| Bad cut | Non-destructive; re-cut from the retained archive | + +## Sequencing + +1. **Done.** Origin changes in `docker/ffmpeg-hls/stream-manager.sh`: `hls_list_size 1800`, + `%06d`, scoped `stop_ffmpeg` cleanup, orphan reaper, stall watchdog, and the `$!` + process-substitution fix. Compose knobs in `docker-compose.dev.yml` (5 min window + locally) and the origin provisioning blade (60 min). Ships the 60 minute rewind alone. +2. **Done.** PDT drift measured and designed around: index entries carry both `pdt` and + `#EXT-X-ARCHIVE-OBSERVED`, so drift is correctable after the fact. +3. Uploader rewrite, indexing included: mount `hls-content`, `.ts` support, playlist-based + completion, manifest, verified reaper, upload throttle, hour-playlist index. +4. `ArchivePlaylistService` plus schema migration; generate VOD playlists for one show + end to end. +5. ~~Edge: `/archive/` and `/recordings/` locations behind the playback token.~~ **Dropped.** + Recordings are ordinary VOD: there is no live capacity to spread and nothing on the + media path the edge would protect. Playlists are rendered by the app per request and + segments are handed out as presigned URLs (`ARCHIVE_URL_TTL`, 24h by default). + + A public bucket was considered and rejected. `archive/` holds the raw continuous + capture of every source, including material never published and everything an operator + trimmed off, so public reads would expose far more than the published output. Signing + keeps the bucket private without a second delivery tier. + + Two consequences worth stating, because they are not obvious: + + - **Playlists cannot be stored.** A signed URL expires, so a playlist written to S3 + would be dead a day later. They are generated per request instead, which also puts + the access check on the request that mints the URLs. That is the only point at which + `required_roles` can actually be enforced. + - **No copying.** An earlier draft copied a cut's segments into a public prefix. With + signing there is no privacy reason left, and the remaining reason (archive expiry + breaking a published recording) is better solved by making retention + recording-aware: never expire a segment a published recording references. Zero + duplication instead of a second copy of everything published. + + Cost: a signed URL runs ~500 characters, so a 15 minute cut is ~201 KB of playlist + (22 KB gzipped) and an hour is roughly four times that. Acceptable for VOD, which + fetches the playlist once rather than every two seconds like live. +6. Manage UI: index, then the trim editor. +7. **Done.** Player: `live:dvr` wired through `StreamPlayer.vue`, stream-type-aware + `backBufferLength`. +8. Retire `DvrExtractorService`, `dvr-extract.sh`, `dvr-process.sh`, `ExtractDvrSegments`, + and the SRS `dvr` block. + +Keep the SRS MP4 DVR running as a cold backup through the next event. It costs disk and +nothing else, and it is the fallback if the segment archive misses something. + +### PDT drift: measured, then designed around + +Every cut point is a PDT, and PDT is anchored once at session start and then advances with +the incoming stream, so it tracks the publisher's clock rather than real time. + +**Measured in the dev stack:** 245 samples over 41 minutes, no re-anchor steps. Slope +-21.3 s/day (95% CI -35.0 to -7.6), PDT running ahead of wall clock, which extrapolates to +-106s over a 5-day con. + +**That number should not be trusted.** The dev publisher runs +`-fflags +genpts -re -stream_loop -1` over a 20s clip (`docker/dev/publish.sh:102`), so it +crosses ~123 loop boundaries in that window, each one regenerating timestamps. A few +milliseconds lost per boundary accumulates to precisely the ~0.6s total shift observed. It +measures the dev publisher, not the HLS muxer. + +**And no dev measurement can answer the real question.** Drift is the publisher's crystal +against the origin's. Two independent clocks at a typical +/-50ppm tolerance can separate +by several seconds a day each, and whichever encoder is on the con floor decides the +figure. A con-long session never reconnects, so nothing re-anchors it. + +So the design stops depending on the answer. Every index entry carries +`#EXT-X-ARCHIVE-OBSERVED`, the uploader's own wall clock at the moment it first saw that +segment complete: + +- `pdt` — the publisher's timeline. What an operator recognises, and what a cut is + expressed in. +- `observed` — the origin's wall clock. Independent of the publisher entirely. + +Drift is then the slope of `observed - pdt`, computable from the archive itself, after the +fact, per session. If it turns out to be negligible, nothing changes. If it is material, +`ArchivePlaylistService` corrects using a fit over that pair without re-archiving a byte. + +The constant offset between the two (a few seconds, since a segment is only indexed once +it is complete and no longer last in the playlist) carries no information. Only the slope +does. + +Cost is roughly 45 bytes per entry in a file read once per cut. Cheap insurance against a +number nobody can pin down before the event. + +## Decisions + +1. **Indexing lives in the uploader**, not a separate process, because the uploader must + parse the playlists anyway for its completion rule. +2. **The index is an HLS playlist per source per hour**, not JSON. One format instead of + three, and the stored form is already the form a cut needs. +3. **Ordering is `#EXT-X-ARCHIVE-SEQ`**, assigned by the uploader on first observation. + Never `pdt`, never `session`, both of which are clock-derived. +4. **Archive all three renditions by default**, via a per-source `archive_renditions` + setting. ~620 GB for a 5-day stream, and ABR survives into the archive. Drop the + always-on source to `hd` only (~190 GB) if the storage bill turns out to matter; the + setting exists so that is a config change, not a redesign. +5. **One bucket, two prefixes** on the existing `dvr` disk. `archive/` is bulk and + expiring, `recordings/` is small and permanent, and prefix-scoped lifecycle rules + express that without a second set of credentials to manage. +6. **Retention: raw segments live 30 days past publication of the recording that covers + them.** After that the archive is repacked with `-c copy` into 10-30s segments, which + cuts object count 5-15x losslessly. Repacking only ever happens after a cut is locked, + so re-cutting at full 2s granularity stays possible for a month. + +## Open questions + +1. ~~**Upload throttling.**~~ Resolved. `MAX_UPLOAD_RATE_MBPS` is now a real token bucket + over the read side of the upload, defaulting to **200 Mbps** on the origin. + + The cap exists so archive traffic cannot starve origin-to-edge egress, which shares the + same uplink and is the larger consumer (`sources x ladder x edges`, roughly 370 Mbps at + 8 sources and 4 edges). 200 Mbps is 20% of a 1 Gbps link. + + The binding constraint is the floor, not the ceiling. Sustained upload must exceed + `sources x 11.5 Mbps` or uploads fall permanently behind and the origin disk fills; + 200 Mbps carries 8 sources at 2x headroom. Raise it with the source count, never lower + it to save bandwidth. + + Because that failure is silent and surfaces hours later, the uploader watches its own + backlog and logs an error once it has grown for five consecutive minutes, naming the + source count, the required rate and the configured cap. Verified in the dev stack: at a + deliberately low 10 Mbps against ~69 Mbps of ingest, throughput fell from 534 to ~32 + segments a minute, the backlog climbed 525 -> 2635, and the guard fired with the correct + diagnosis. Restoring the cap drained the backlog to zero with `failed=0`. +2. ~~**PDT drift magnitude.**~~ Resolved by removing the dependency rather than by pinning + the number down: every index entry carries both `pdt` and `#EXT-X-ARCHIVE-OBSERVED`, so + drift is measurable from the archive after the fact and correctable without + re-archiving. See *PDT drift* above. diff --git a/docs/dvr-archive-testing.md b/docs/dvr-archive-testing.md new file mode 100644 index 0000000..ef20ebc --- /dev/null +++ b/docs/dvr-archive-testing.md @@ -0,0 +1,232 @@ +# DVR archive: end-to-end test record + +What was tested, what broke, and why each fix is the right one. Companion to +`docs/dvr-archive-plan.md`, which describes the design this validates. + +## What was under test + +The whole path, from a publisher pushing RTMP to a viewer playing a trimmed recording: + +``` +publisher -> SRS -> ffmpeg ABR ladder -> HLS segments on origin disk + -> archive_uploader.py -> S3 (segments + hour index) +show markers -> ArchivePlaylistService -> VOD playlist (signed segment URLs) -> player +``` + +The claim being checked is the one the whole redesign rests on: **cutting a recording is +playlist authoring, not media processing.** No decode, no concat, no `-ss`, no re-encode, +and therefore none of the seams the old MP4 pipeline produced. + +## Environment + +Local dev stack (`./scripts/dev-stack.sh up`), six publishers looping a clip into SRS, +`ABR_MODE=copy`. The stream ran continuously for about two hours, which also exercises the +case that motivated the redesign: a source that never stops, so nothing can wait for it to +end. + +Dev differs from production in two ways that matter when reading these numbers: + +- `ABR_MODE=copy` means all three renditions carry the same bitstream, so their file sizes + are identical. Production transcodes a real 1500/3500/6000 kbps ladder. +- The publisher loops a 20s clip with `-stream_loop -1 -fflags +genpts`, which regenerates + timestamps at every loop boundary. This matters for drift (below). + +## Results + +### Archive capture + +``` +indexed=768 uploaded=2304 verified=2304 reaped=0 failed=0 +``` + +768 logical segments, 2304 uploads, exactly the 1:3 ratio expected from one index entry +describing three renditions. Zero rendition-misalignment warnings, so the assumption that +FFmpeg cuts all renditions at identical instants held throughout. + +Renditions archived: `sd 2849`, `hd 2852`, `fhd 2852`. Multi-quality survives into the +archive, which is one of the things the old re-encoding pipeline threw away. + +### Disk release + +``` +local segments 2796 (6 sources x 3 renditions x 150-segment window, plus in flight) +oldest local 354s (against a 300s dev window) +S3 segments 26013 and climbing +``` + +Local disk stays bounded by the rewind window while S3 keeps everything. Deletion only +happens after S3 has confirmed the object, so the "check S3, then delete" requirement holds +literally. + +### Upload throttle and its failure mode + +Deliberately misconfigured to 10 Mbps against ~69 Mbps of ingest: + +``` +throughput 534/min -> ~32/min +backlog 525 -> 1062 -> 1598 -> 2078 -> 2635 + +ERROR Upload backlog has grown for 5 minutes straight (525 -> 1062 -> 1598 -> 2078 -> 2635). + The archive is not keeping up with ingest and the origin disk will fill. + 6 source(s) need ~69 Mbps sustained; cap is 10.0 Mbps. +``` + +Restoring the cap drained the backlog to zero with `failed=0`, which also exercises crash +recovery: every segment queued during the throttled period was picked up from the manifest. + +### Cutting + +Show aired 14 minutes on a continuously running source, then cut and re-cut: + +``` +draft cut 419 segments 839s (13:59) expected ~840s +re-cut 300 segments 601s (10:01) delta -238s (trimmed 2 min from each end) +playlists 3 renditions, VOD + ENDLIST, every segment URL signed +size 135 KB raw / 15 KB gzipped +``` + +Playback of the trimmed cut, fetched over presigned HTTP: + +``` +duration 600.640989 +frame=18000 exactly 300 segments x 2s x 30fps +hard errors: none +``` + +Frame-exact. Nothing dropped, nothing duplicated, no seams. + +## Issues found and fixed + +### 1. Encoders leaked forever (pre-existing, production impact) + +`stream-manager.sh` launched FFmpeg as `ffmpeg ... 2>&1 | sed ... &` and stored `$!`. For a +backgrounded pipeline `$!` is the PID of the **last** stage, so every stored PID was sed's. +`stop_ffmpeg` killed the log prefixer and left FFmpeg encoding indefinitely. + +Demonstrated directly: killing the stored PID left the producer alive. + +Only the `pgrep -f` fallback in `start_ffmpeg` ever cleaned one up, and only if the same +stream came back; a stream that ended for good leaked its encoder permanently. + +**Fix:** process substitution, `> >(sed ...) 2>&1 &`, so `$!` is FFmpeg's own PID and the +sed exits when the pipe closes. Verified: repeated watchdog restarts leave zero strays. + +### 2. `stop_ffmpeg` deleted the archive (pre-existing) + +`rm -f "$OUTPUT_BASE_DIR/${stream}"*` — the glob sits outside the quotes, so it expanded and +took every segment with it. Harmless when segments were disposable; fatal once they are the +archive. + +**Fix:** restricted to playlists. Segments are left for the reaper, which only deletes what +S3 has confirmed. + +Worth noting the sibling: `start_ffmpeg` had `rm -f "$output_dir/${stream}_*.ts"` with the +glob **inside** the quotes, so it never expanded and had always been inert. Removed rather +than "fixed", since fixing it would have introduced the bug it appeared to have. + +### 3. Session id collisions (pre-existing, silent data loss) + +`timestamp_prefix=$(date +%s)` has one-second resolution. Two FFmpeg starts inside the same +second produce the same prefix, and the second session then writes +`${stream}_hd__000000.ts` directly over the first session's segments. A fast +crash-restart loop through `check_streams` (5s interval) reaches this. + +The failure is silent archive corruption rather than an error, and the segment filename is +the archive's primary key, so a collision corrupts S3 objects and index entries alike. + +**Fix:** bump the prefix while any segment already carries it. Works because the orphan +reaper leaves previous sessions' segments on disk. + +### 4. PDT drift: measured, then designed around + +Measured 245 samples over 41 minutes: slope -21.3 s/day (95% CI -35.0 to -7.6), which +extrapolates to -106s over a five day event. + +**That number is not trustworthy**, and chasing a better one would have been wasted effort. +The dev publisher crosses ~123 `-stream_loop` boundaries in that window, each regenerating +timestamps; a few milliseconds lost per boundary accounts for the entire observed shift. It +measures the publisher, not the muxer. + +More importantly, no dev measurement can answer the real question. Drift is the publisher's +crystal against the origin's, and whichever encoder is on the floor decides it. + +**Fix:** stop depending on the answer. Every index entry now carries +`#EXT-X-ARCHIVE-OBSERVED` (the origin's own wall clock at first observation) alongside `pdt` +(the publisher's timeline). Drift becomes the slope of `observed - pdt`, computable from the +archive itself, after the fact, per session, and correctable without re-archiving a byte. +Cost is ~45 bytes per entry in a file read once per cut. + +### 5. Ordering must not touch a clock + +The first design ordered segments by `session` + `n`. `n` is FFmpeg's own counter and is +clock-free, but `session` is `date +%s` and therefore is not, so ordering across session +boundaries still depended on the clock. + +**Fix:** `#EXT-X-ARCHIVE-SEQ`, assigned by the uploader on first observation. Observation +order follows playlist order, which is inherently monotonic. A clock jump now costs cut +*accuracy* and can never corrupt archive *order*. + +### 6. Cut markers silently shifted by the timezone offset (introduced here) + +The most subtle one, and the reason the end-to-end test was worth running. + +The migration made `recordings.starts_at/ends_at` `timestamptz`, which looks like the more +careful choice. Laravel serialises datetimes for Postgres as `Y-m-d H:i:s` with **no +offset**, so a Carbon in the app timezone (Europe/Berlin) arrives as bare wall-clock digits +and Postgres reads them in the session timezone (UTC). The instant moves by the offset on +write. + +It hid well: in memory the value is correct, so a cut built immediately after saving worked +and reported the right duration. Only a later re-read showed the shift, as a recording that +had built fine resolving to zero segments — surfacing as "no archived segments cover that +range", which reads like expiry or an upload lag rather than a timezone bug. + +**Fix:** `timestamp without time zone`, matching `shows.actual_start` and `recordings.date` +and the rest of the schema. Comparisons stay correct because `ArchivePlaylistService` +normalises both ends to UTC before comparing against segment timestamps, which is the only +place the distinction actually matters. The existing rows are converted with +`USING starts_at AT TIME ZONE 'UTC'`, preserving the instant Postgres currently holds. + +Related guard added in the service: markers are explicitly `->utc()`-normalised before use, +so a naive local string arriving from a `datetime-local` input cannot shift into an hour +bucket that holds no segments. + +### 7. Empty segment range raised a bare `ValueError` + +`renderMediaPlaylist` called `max()` on an empty array when a cut resolved to nothing. Reachable +from the request path, not just from `build()`: a recording whose hours have expired out of the +archive resolves to zero segments. + +**Fix:** an explicit exception naming the likely cause, which the playlist controller turns +into `410 Gone`. + +## A false alarm worth recording + +The first service-generated cut produced 399 `non monotonically increasing dts` warnings — +exactly the class of defect the whole project exists to eliminate. It was not one. + +Ruled out in turn: tag order (identical count when reordered), `PROGRAM-DATE-TIME` presence +(identical with it stripped), the archive copies (40 archive segments decode with zero +warnings), and session boundaries (the range was a single contiguous session with no +discontinuities). + +The warnings come from the **null muxer**, which enforces monotonic DTS while remuxing — +something no player does. The decisive measurement was the frame count: 26,940 frames for +449 segments, exactly `449 x 2s x 30fps`. Content is bit-intact. + +Recorded because an earlier 31-segment test showed zero warnings and nearly let this be +filed as "fine" without checking, and because `-f null` is a misleading way to test HLS +correctness. + +## Still not covered + +- **Production ABR.** Everything above ran with `ABR_MODE=copy`. A real transcode ladder + should be exercised before the event. +- **Archive expiry.** Retention is designed (never expire a segment a published recording + references) but not implemented or tested. +- **Reaper under S3 outage.** The code refuses to delete unverified segments; the path has + not been exercised with S3 actually unavailable. +- **The manage UI.** The cut editor and the create-from-show action are wired but have only + been exercised through the service and controller layers, not clicked through in a browser. +- **Long-run session behaviour.** The longest continuous run so far is about two hours, not + the five to seven days the main source will actually see. diff --git a/docs/streaming-auth-redesign.md b/docs/streaming-auth-redesign.md new file mode 100644 index 0000000..10229bb --- /dev/null +++ b/docs/streaming-auth-redesign.md @@ -0,0 +1,201 @@ +# Streaming auth + delivery redesign + +Status: proposal. Nothing here is implemented yet. + +## What we do today + +Playback goes through Laravel twice per playlist refresh: + +1. `Source::getHlsUrl()` returns `route('hls.master')`, i.e. `/hls/{slug}/master.m3u8` on the app domain. +2. `HlsController::master()` resolves the viewer (session cookie, or `?streamkey=`), writes a heartbeat, picks an edge via `User::getOrAssignServer()`, then makes a **blocking server-side HTTP request** to that edge for the real master playlist and rewrites variant lines to `/hls/{variant}.m3u8?streamkey=…`. +3. `HlsController::variant()` repeats all of that, then rewrites every `.ts` line to an absolute edge URL with `?streamkey=…` appended. +4. Edge nginx runs `auth_request /auth` on `.ts` only, which calls `/api/hls/auth`. `HlsSessionController::auth()` identifies the viewer from streamkey / `hls_ctx` / an IP-keyed cache fallback, checks the source is `ONLINE`, and mints a session UUID. + +Edge selection is sticky per user row: `users.server_id` + `users.streamkey`, set by `assignServerToUser()`, backfilled by `ServerAssignmentJob`, and announced by the `ServerAssignmentChanged` broadcast. + +## Why it needs to change + +**PHP is in the media hot path.** A viewer refetches the variant playlist every segment duration (~2-4s). At 5,000 viewers that is roughly 1,250-2,500 requests/sec into Laravel, and each one performs an outbound HTTP call to an edge before it can answer. Two network hops and a PHP worker per playlist refresh is the single biggest scaling wall. + +**The playlist caches are per-user, so they barely work.** Cache keys are `hls_master:{stream}:{host}:{port}:{streamkey}` and `hls_variant:{variant}:{host}:{port}:{streamkey}`. N viewers produce N cache entries for identical content. The playlist body is the same for everyone except for the streamkey embedded in it, which is exactly the thing that should not be in there. + +**Segment auth hits PHP per segment.** The nginx auth cache key is `"$remote_addr:$arg_streamkey:$uri"`. `$uri` changes for every segment, so the 1-minute `proxy_cache_valid` never helps: every viewer, every segment, one subrequest to Laravel. + +**Heartbeat writes are on the read path.** `trackUserAccess()` is guarded by a 60s cache key, but when it does fire it runs a `SourceUser` upsert *and* a table-wide `UPDATE … SET left_at` across the source. That cleanup does not belong in a request. + +**streamkey is a permanent bearer credential in a query string.** 32 random chars, never rotated, never expiring, no revocation. It is written into playlist bodies, into edge nginx access logs, into browser history, and into an nginx cache key. One leaked link is permanent free access to the stream. + +**Playlists are unauthenticated at the edge.** Only `.ts` has `auth_request`. Anyone who knows an edge hostname can pull `/live/{slug}_fhd.m3u8` directly; the Laravel proxy is the only thing gating playlists, and it is bypassable. + +**Four identity mechanisms.** Session cookie, streamkey, `hls_ctx`, and an IP+stream cache fallback. The IP fallback is unsound behind CGNAT or convention NAT, where hundreds of attendees share an address. + +**Sticky per-user edge assignment is the wrong primitive.** It costs a DB write per viewer, needs a broadcast when a server drains, load-balances on a `viewer_count` that lags by up to a heartbeat interval, and pins a user globally rather than per playback session. + +## Target architecture + +One principle: **Laravel authorizes once, edges enforce statelessly, PHP never touches the media path.** + +### 1. Signed playback token replaces streamkey + +Issue a short-lived signed token instead of a permanent secret. Claims: + +``` +{ + typ: "viewer" | "embed", + src: , // exactly one, never a wildcard; this IS the entitlement + sub: , // viewer tokens + kid: , // embed tokens, checked against a pushed allowlist + edge: , // which edge this session is pinned to + sid: ,// for counting, not for auth + exp: // 15 minutes out; absent on embed keys +} +``` + +Encoded as `v1.base64url(claims json).base64url(hmac_sha256(body, secret))`, where the signed body includes the version prefix so it cannot be downgraded. Every edge holds the shared secret and verifies **locally, in-process, with no network call**. `exp` is the revocation mechanism. + +Laravel mints this in the page controller and hands it to the player as an Inertia prop, so there is no extra request to get one. + +### 2. Token transport + +Two viable options; they differ mostly in operational cost. + +**Option A - cookie (cleanest).** Set the token as a cookie scoped to the edge domain, `Secure`, `HttpOnly`, `SameSite=None`. URLs then contain no credential at all, so playlist and segment URLs are byte-identical for every viewer and nginx/CDN cache hit rate approaches 100%. Requires: edges verify the cookie (see enforcement below), CORS switches from `Access-Control-Allow-Origin: *` to a specific origin plus `Access-Control-Allow-Credentials: true` (the wildcard is illegal with credentials), and hls.js needs `xhrSetup` with `withCredentials = true`. + +**Option B - query parameter.** `?t=`. Works everywhere with no CORS or cookie-domain work, and cache keys already exclude query args (`proxy_cache_key "$scheme$proxy_host$uri"`), so shared caching still works. Cost: the token appears in logs and browser history. Acceptable *because* it expires in 15 minutes, which is the whole point of dropping the permanent streamkey. + +Recommendation: ship Option B first (it is a drop-in replacement for the current `?streamkey=` shape and needs no CORS changes), then move to cookies once edges are verifying locally. + +### 3. Edge enforcement point + +The check must happen on the edge with no callback to Laravel. Ranked: + +- **nginx `secure_link` module** - already compiled into the standard nginx build, so no image change. Verifies an expiring signed URL entirely in nginx. Limitation: MD5-based and the "payload" is whatever you can encode into the URL, so entitlement claims get crude. Fastest path to killing the PHP subrequest. +- **nginx + njs (`ngx_http_js_module`)** - real HMAC-SHA256 over a real JSON payload, still all in nginx. Needs `nginx-module-njs` added to the edge image (currently `nginx:alpine`, per `install.sh`). This is the option that fits the token design above properly. +- **Caddy** - Caddy already fronts nginx on every edge (`8080` -> `8081`), so it is a natural gate, but JWT verification needs an `xcaddy` custom build. +- **OpenResty + lua-resty-jwt** - most flexible, biggest change to the edge image. + +Recommendation: **njs**, with `secure_link` as the fallback if adding the module to the edge image turns out to be painful. Whichever we pick, enforcement moves to `.m3u8` **and** `.ts`, closing the current playlist hole. + +### 4. Delete the Laravel HLS proxy + +The player points at the edge directly: `https://{edge}/live/{slug}_master.m3u8`. The master playlist is static per source (it only changes when the variant set changes) so it can be generated once and cached for 30s. Variant playlists keep relative segment paths, so there is nothing to rewrite. `HlsController` goes away entirely, and with it the outbound HTTP call, the regex rewriting, and the per-user playlist cache. + +### 5. Edge selection: session-sticky weighted pick, no DB state + +Per-request round robin is wrong for HLS: a viewer bouncing between edges mid-stream loses segment cache locality and can land on an edge whose playlist is a few segments behind, which the player sees as a stall. What we want is stickiness *for the duration of a playback session*, without a database column. + +At page render, pick an edge weighted by current free capacity (from the cached `viewer_count` the edges already report), and put the hostname in the token's `edge` claim. The token carries the assignment, so there is no `users.server_id`, no write, and no broadcast. Token refresh reuses the same edge unless that edge is draining, in which case the next token moves the viewer and the player reloads the manifest. + +Deletions this enables: `users.server_id` and `users.streamkey` columns, `User::assignServerToUser()`, `User::getOrAssignServer()`, `ServerAssignmentJob`, `ServerAssignmentChanged`, and the `has_server_assignment` Inertia prop. + +Longer term, a **CDN or anycast layer in front of the edges** is strictly better: once segment URLs are user-agnostic and `immutable`, they cache trivially, and edge selection stops being our problem. Worth costing out separately. + +### 6. Viewer counting off the request path + +Edges already POST aggregate counts to `/api/hls/heartbeat`. Extend that to be the only counting mechanism: an agent on each edge tails the nginx access log, counts distinct `sid` claims per stream over the last 30s, and posts every 10-15s. That is one request per edge per 15s instead of one per viewer per segment. + +For per-attendee presence (which attendee watched which show), use the **Reverb presence channel the player is already joined to**. It is accurate, free, and completely decoupled from HLS. The `SourceUser` heartbeat writes come off the request path, and the stale-session sweep moves into the existing `CleanupStaleViewerSessionsJob` schedule. + +### 7. Cache policy, top to bottom + +| Layer | Policy | Note | +|---|---|---| +| Master playlist | `max-age=30` | static per source | +| Variant playlist | `max-age=1`, `stale-while-revalidate`, `proxy_cache_lock on` | cache key `$uri` only, already correct | +| Segments | `max-age=31536000, immutable` | requires globally unique segment names; currently only 2m | +| Token verification | none needed | local HMAC, no cache to invalidate | +| Source-online check | removed | if a source drops, the playlist stops advancing and the player handles it | + +If we ever move to LL-HLS, partial segments break the immutable-segment assumption and this table needs revisiting. + +### 8. Expiry, refresh, and revocation + +A 15-minute TTL is the revocation mechanism: a ban takes effect within 15 minutes at worst, at zero hot-path cost. Expiry must never be visible to a viewer, which takes three layers. + +**Push refresh at T-3min.** The server pushes a fresh token over the Reverb private user channel the player is already joined to. No polling, no `fetch()`. + +**60s skew grace at the edge.** Edges accept a token up to 60 seconds past `exp`. Absorbs clock drift and a slow refresh. Stateless, so it costs nothing. + +**403 recovery.** If the WebSocket is down and the token genuinely expires, the next playlist fetch 403s and hls.js raises `manifestLoadError` / `fragLoadError`. The player responds with `router.reload({ only: ['playbackToken'] })` - an Inertia visit, not a raw `fetch()`, so it respects the project rule. Buffer is ~20-30s and the round trip is a few hundred ms, so the viewer sees nothing. + +If that reload comes back with no token (logged out, banned, ticket revoked), that is the intended kill: tear down the player and show a "session ended" overlay. + +Token expiry is not session expiry. The Laravel session cookie stays long-lived; the token is a short-lived capability derived from it. + +#### Rotation with query-param transport + +A query param does not inherit into relative segment URLs, and rotating it means changing URLs already in flight. hls.js runs `xhrSetup` *after* `xhr.open()`, so the URL cannot be mutated there. A custom loader is required: + +```js +class TokenLoader extends Hls.DefaultConfig.loader { + load(context, config, callbacks) { + context.url = withToken(context.url, currentToken.value); + super.load(context, config, callbacks); + } +} +``` + +It reads `currentToken` on every request, so a Reverb push rotates transparently with no manifest reload. Written once, ~15 lines. This is a real cost of query-param transport that cookie transport would not have; it is accepted because embed keys (below) cannot use cookies at all, so the query-param path has to exist regardless. + +### 9. Embed keys for VRChat and other integrations + +Long-lived embeds are a **separate token type**, not a viewer token with a distant `exp`. + +| | Viewer token | Embed key | +|---|---|---| +| `typ` | `viewer` | `embed` | +| `sub` | user id | embed key id | +| `src` | slug or `*` | one slug, never `*` | +| TTL | 15 min | no `exp`; stable for the world's lifetime | +| Revoke | let it expire | edge-pushed allowlist | +| Edge | weighted pick per session | pinned to one edge | +| Secret | `HLS_VIEWER_SECRET` | `HLS_EMBED_SECRET` | + +Separate secrets, so a leak of the viewer secret cannot mint embed keys. + +**Revocation is mandatory.** Long-lived plus leaked is exactly the streamkey failure we are removing. Each embed key carries a `kid`, and edges hold a small allowlist of valid `kid`s refreshed from Laravel every 30-60s - one request per edge per minute, cheap because embed keys are a handful of rows rather than thousands of users. Revoking removes the row and propagates within a minute. The allowlist entry also carries per-key policy the edge enforces locally: rate limit, max concurrent, allowed `Referer`. + +**VRChat constraints that shape this:** + +- VRChat video players (AVPro / Unity) use their own HTTP stack. No cookies, no JS. Query param is the only transport that works. +- World creators bake a **static URL** into the world; it can never rotate. So embed keys must not lean on `exp` - an `exp` at event end forces re-baking the world every year. Stability is the feature; revocation happens through the `kid` allowlist instead. +- AVPro on Quest/Android handles ABR switching poorly. Hand embeds a **fixed rendition** (`{slug}_hd.m3u8`) rather than a master with a full ladder. +- Serve embeds from a distinct host/path, e.g. `https://embed.stream.../live/{slug}.m3u8?k=`, so they can be rate-limited and cached separately and a leaked embed key cannot be swapped for a viewer token or reach an attendee-restricted source. + +**Capacity.** Embed load is expected to be small, so no separate edge pool. Each key gets a nullable `edge_server_id` and is **pinned to a single chosen edge**; a viral world then cannot spill onto the edges serving attendees. Null falls back to the normal weighted pick. + +Pinning has one failure mode that matters more here than for viewers: the baked URL cannot be changed, so if the pinned edge is deprovisioned or dies, the embed goes dark with no client-side recovery. Guards: + +- The embed URL must stay on the **embed hostname**, never an edge hostname. `edge_server_id` decides where that hostname *resolves or proxies*, so re-pinning is a DNS or config change rather than a URL change. +- Block deprovisioning an edge that has embed keys pinned to it, or force a re-pin first. `Server::isInUse()` and the deprovision flow need to know about embed keys. +- Health check the pinned edge and alert in `/manage` when a key's edge is unhealthy, since nothing on the VRChat side will report the failure. + +**Management** lives in `/manage`, not Filament (Filament is being phased out here). Resourceful Inertia CRUD matching the existing pattern - `Manage\EmbedKeyController`, `App\Http\Requests\Manage\*`, `resources/js/Pages/Manage/EmbedKeys/*`, routes under `manage.embed-keys.*`. Create, name, pin to an edge, revoke, plus last-used and live viewer count per key. Names like "VRChat main stage", "Second Life lobby", "hallway display". + +### 10. Entitlements + +**Revised during implementation: there is no `ent` claim.** Restricted-show access is role-based (`Show::$required_roles` checked via `User::hasAnyRole()`), and an edge has no way to know a show's required roles without asking Laravel, which is the thing we are removing. Instead the token is **bound to exactly one source slug and never a wildcard**, entitlement is checked once at mint time with `canBeAccessedBy()`, and the binding is what carries that decision to the edge. The edge only has to check three things, all local: signature valid, not expired, `src` matches the slug being requested. + +Consequence: switching source means a new token. That is free, because switching means an Inertia page visit, which re-renders with a fresh token anyway. + +A user's entitlement change takes effect on the next mint, bounded by the same 15-minute TTL. + +Original plan, kept for context - restricted-show access (`canBeAccessedBy`) becomes the `ent` claim, so the edge decides locally instead of asking Laravel. Changing a user's entitlement invalidates nothing directly; it takes effect on the next token issue, bounded by the same 15-minute TTL. + +## Decisions made + +1. **Edge enforcement: nginx + njs.** Real HMAC-SHA256 over a JSON payload, verified in-process on the edge. Requires adding `nginx-module-njs` to the edge image (currently `nginx:alpine`, written by `install.sh`). `secure_link` stays as the fallback if the image change proves painful. +2. **Edge routing: app-side weighted pick.** Laravel chooses an edge at page render, weighted by reported free capacity, and puts the hostname in the token's `edge` claim. Session-sticky with no DB state. A CDN or anycast layer in front remains the better long-term answer and should be costed separately. +3. **Token transport: query param.** `?t=`. Drop-in for the current `?streamkey=` shape, no CORS work, and cache keys already exclude query args. Also the only transport VRChat's player can use. Cookies stay an option for viewer tokens later. + +## Rough sequencing + +1. ~~Token mint + verify in Laravel, issued alongside the existing streamkey. Nothing breaks.~~ **Done.** `PlaybackTokenService`, `PlaybackToken`, `PlaybackTokenTypeEnum`, `InvalidPlaybackTokenException`, `stream.token.*` config, and a `playback` Inertia prop on `ShowPlayer` / `ExternalStream` that nothing consumes yet. Inert until `HLS_VIEWER_SECRET` is set. +2. Add njs to the edge image; enforce on `.m3u8` and `.ts`, accepting *either* token or streamkey. +3. Point the player at edge URLs directly via `TokenLoader`; delete `HlsController`. +4. Reverb token push at T-3min, 60s edge skew grace, and the 403 -> `router.reload` recovery path. +5. Move counting to log-tailing plus Reverb presence; take the heartbeat writes out of the request path. +6. Switch edge selection to the token claim; drop `users.server_id`, `ServerAssignmentJob`, and friends. +7. Embed keys: model + `kid` allowlist endpoint, edge allowlist refresh, `/manage` CRUD with edge pinning. +8. Stop issuing streamkeys; drop the column and the streamkey acceptance path. +9. Segment cache lifetime to immutable once segment names are unique. diff --git a/dvr-process.sh b/dvr-process.sh index b9a9ebd..58e0fc9 100755 --- a/dvr-process.sh +++ b/dvr-process.sh @@ -27,7 +27,7 @@ API_KEY="${RECORDING_API_KEY}" S3_ALIAS="${S3_ALIAS:-eventwolf}" # mc alias for DVR S3 bucket S3_BUCKET="${S3_BUCKET:-recording}" S3_BASE_PATH="${S3_BASE_PATH:-on-demand}" -EVENT_SLUG="${EVENT_SLUG:-ef29}" # Event identifier (e.g., ef29 for Eurofurence 29) +EVENT_SLUG="${EVENT_SLUG:-event}" # Event identifier, set per convention/year TEMP_DIR="${TEMP_DIR:-/tmp/dvr-processing}" DVR_SOURCE_DIR="${DVR_SOURCE_DIR:-/var/dvr}" # Directory containing DVR m3u8/ts files diff --git a/package-lock.json b/package-lock.json index 3aa53dd..7331b72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,11 @@ { - "name": "html", + "name": "ef-streaming", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "html", "dependencies": { "@headlessui/vue": "^1.7.23", - "@silvermine/videojs-chromecast": "^1.5.0", "dayjs": "^1.11.18", "floating-vue": "^5.2.2", "hls.js": "^1.6.11", @@ -17,10 +15,7 @@ "radix-vue": "^1.9.17", "reka-ui": "^2.5.0", "tw-animate-css": "^1.3.7", - "video.js": "^8.23.4", - "videojs-contrib-quality-levels": "^4.1.0", - "videojs-hls-quality-selector": "^2.0.0", - "videojs-hotkeys": "^0.2.30", + "vidstack": "^1.15.6", "vue-axios": "^3.5.2", "vue-cookies": "^1.8.3", "vue3-emoji-picker": "^1.1.7" @@ -76,15 +71,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", @@ -1015,18 +1001,6 @@ "win32" ] }, - "node_modules/@silvermine/videojs-chromecast": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@silvermine/videojs-chromecast/-/videojs-chromecast-1.5.0.tgz", - "integrity": "sha512-oDWu0WT6NORWqpUHf5xg+GoLlxA/YV7guNDOGsDV51gOqYiKb2HoPXodDfhdzwHczUmPFmbyPwfSC1E+etAOmQ==", - "license": "MIT", - "dependencies": { - "webcomponents.js": "git+https://git@github.com/webcomponents/webcomponentsjs.git#v0.7.24" - }, - "peerDependencies": { - "video.js": ">= 6 < 9" - } - }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -1376,60 +1350,18 @@ "@types/lodash": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", "license": "MIT" }, - "node_modules/@videojs/http-streaming": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.2.tgz", - "integrity": "sha512-VBQ3W4wnKnVKb/limLdtSD2rAd5cmHN70xoMf4OmuDd0t2kfJX04G+sfw6u2j8oOm2BXYM9E1f4acHruqKnM1g==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "aes-decrypter": "^4.0.2", - "global": "^4.4.0", - "m3u8-parser": "^7.2.0", - "mpd-parser": "^1.3.1", - "mux.js": "7.1.0", - "video.js": "^7 || ^8" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "video.js": "^8.19.0" - } - }, - "node_modules/@videojs/vhs-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz", - "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - } - }, - "node_modules/@videojs/xhr": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz", - "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "global": "~4.4.0", - "is-function": "^1.0.1" - } - }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1585,25 +1517,16 @@ "vue": "^3.5.0" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aes-decrypter": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz", - "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "global": "^4.4.0", - "pkcs7": "^1.0.4" + "node": ">=0.4.0" } }, "node_modules/aria-hidden": { @@ -1839,11 +1762,6 @@ "node": ">=8" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2078,6 +1996,12 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fscreen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz", + "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2139,16 +2063,6 @@ "node": ">= 0.4" } }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2219,12 +2133,6 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "license": "ISC" }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "license": "MIT" - }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", @@ -2503,6 +2411,15 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lit-html": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/lodash-es": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", @@ -2519,17 +2436,6 @@ "vue": ">=3.0.1" } }, - "node_modules/m3u8-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", - "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "global": "^4.4.0" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2548,6 +2454,15 @@ "node": ">= 0.4" } }, + "node_modules/media-captions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/media-captions/-/media-captions-1.0.4.tgz", + "integrity": "sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2569,15 +2484,6 @@ "node": ">= 0.6" } }, - "node_modules/min-document": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", - "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", - "license": "MIT", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -2611,38 +2517,6 @@ "node": ">= 18" } }, - "node_modules/mpd-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz", - "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.0.0", - "@xmldom/xmldom": "^0.8.3", - "global": "^4.4.0" - }, - "bin": { - "mpd-to-m3u8-json": "bin/parse.js" - } - }, - "node_modules/mux.js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz", - "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.11.2", - "global": "^4.4.0" - }, - "bin": { - "muxjs-transmux": "bin/transmux.js" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -2716,18 +2590,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkcs7": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", - "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.5.5" - }, - "bin": { - "pkcs7": "bin/cli.js" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2763,15 +2625,6 @@ "dev": true, "license": "MIT" }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3237,6 +3090,19 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "license": "Unlicense" }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -3268,75 +3134,20 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/video.js": { - "version": "8.23.4", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.23.4.tgz", - "integrity": "sha512-qI0VTlYmKzEqRsz1Nppdfcaww4RSxZAq77z2oNSl3cNg2h6do5C8Ffl0KqWQ1OpD8desWXsCrde7tKJ9gGTEyQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "^3.17.2", - "@videojs/vhs-utils": "^4.1.1", - "@videojs/xhr": "2.7.0", - "aes-decrypter": "^4.0.2", - "global": "4.4.0", - "m3u8-parser": "^7.2.0", - "mpd-parser": "^1.3.1", - "mux.js": "^7.0.1", - "videojs-contrib-quality-levels": "4.1.0", - "videojs-font": "4.2.0", - "videojs-vtt.js": "0.15.5" - } - }, - "node_modules/videojs-contrib-quality-levels": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", - "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", - "license": "Apache-2.0", - "dependencies": { - "global": "^4.4.0" - }, - "engines": { - "node": ">=16", - "npm": ">=8" - }, - "peerDependencies": { - "video.js": "^8" - } - }, - "node_modules/videojs-font": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz", - "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==", - "license": "Apache-2.0" - }, - "node_modules/videojs-hls-quality-selector": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/videojs-hls-quality-selector/-/videojs-hls-quality-selector-2.0.0.tgz", - "integrity": "sha512-x0AQKGwryDdD94s1it+Jolb6j1mg4Q+c7g1PlCIG6dXBdipVPaZmg71fxaFZJgx1k326DFnRaWrLxQ72/TKd2A==", + "node_modules/vidstack": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/vidstack/-/vidstack-1.15.6.tgz", + "integrity": "sha512-bdPbguNooX7Og6xqhqlRoowciTL7DibntuUwFrW6Y7K2t1jcYX7yTd05k/hfEOJrTGaY2VpU5hLQpFJDNFzK4A==", "license": "MIT", "dependencies": { - "global": "^4.4.0", - "video.js": "^8" + "@floating-ui/dom": "^1.6.10", + "fscreen": "^1.2.0", + "lit-html": "^2.8.0", + "media-captions": "^1.0.4", + "unplugin": "^1.12.0" }, "engines": { - "node": ">=14", - "npm": ">=6" - } - }, - "node_modules/videojs-hotkeys": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/videojs-hotkeys/-/videojs-hotkeys-0.2.30.tgz", - "integrity": "sha512-G8kEQZPapoWDoEajh2Nroy4bCN1qVEul5AuzZqBS7ZCG45K7hqTYKgf1+fmYvG8m8u84sZmVMUvSWZBjaFW66Q==", - "license": "Apache-2.0" - }, - "node_modules/videojs-vtt.js": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", - "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", - "license": "Apache-2.0", - "dependencies": { - "global": "^4.3.1" + "node": ">=18" } }, "node_modules/vite": { @@ -3516,10 +3327,11 @@ "node": ">=16.0.0" } }, - "node_modules/webcomponents.js": { - "version": "0.7.24", - "resolved": "git+https://git@github.com/webcomponents/webcomponentsjs.git#8a2e40557b177e2cca0def2553f84c8269c8f93e", - "license": "BSD-3-Clause" + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" }, "node_modules/yallist": { "version": "5.0.0", diff --git a/package.json b/package.json index f5bce66..4b89efe 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ }, "dependencies": { "@headlessui/vue": "^1.7.23", - "@silvermine/videojs-chromecast": "^1.5.0", "dayjs": "^1.11.18", "floating-vue": "^5.2.2", "hls.js": "^1.6.11", @@ -34,10 +33,7 @@ "radix-vue": "^1.9.17", "reka-ui": "^2.5.0", "tw-animate-css": "^1.3.7", - "video.js": "^8.23.4", - "videojs-contrib-quality-levels": "^4.1.0", - "videojs-hls-quality-selector": "^2.0.0", - "videojs-hotkeys": "^0.2.30", + "vidstack": "^1.15.6", "vue-axios": "^3.5.2", "vue-cookies": "^1.8.3", "vue3-emoji-picker": "^1.1.7" diff --git a/public/css/filament/filament/app.css b/public/css/filament/filament/app.css deleted file mode 100644 index 35d6345..0000000 --- a/public/css/filament/filament/app.css +++ /dev/null @@ -1 +0,0 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border-color:rgba(var(--gray-200),1);border-style:solid;border-width:0;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-400),1);opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-400),1);opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid transparent;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:transparent}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:transparent}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:3em;margin-top:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.6em;margin-top:1.6em;padding-inline-start:1em;quotes:"\201C""\201D""\2018""\2019"}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-weight:800;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-weight:700;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-weight:600;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-weight:500;padding-inline-end:.375em;padding-bottom:.1875em;padding-top:.1875em;padding-inline-start:.375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;color:var(--tw-prose-pre-code);font-size:.875em;font-weight:400;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;overflow-x:auto;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-top:.8571429em;padding-inline-start:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.7142857;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em;vertical-align:bottom}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-kbd:#111827;--tw-prose-kbd-shadows:17 24 39;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:rgba(0,0,0,.5);--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-top:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.7142857}.prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em;margin-top:1.1428571em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-bottom:.8888889em;margin-top:.8888889em}.prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em;margin-top:1.3333333em;padding-inline-start:1.1111111em}.prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:2.1428571em;line-height:1.2;margin-bottom:.8em;margin-top:0}.prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.4285714em;line-height:1.4;margin-bottom:.8em;margin-top:1.6em}.prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-bottom:.4444444em;margin-top:1.5555556em}.prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){line-height:1.4285714;margin-bottom:.5714286em;margin-top:1.4285714em}.prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7142857em;margin-top:1.7142857em}.prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7142857em;margin-top:1.7142857em}.prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7142857em;margin-top:1.7142857em}.prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;font-size:.8571429em;padding-inline-end:.3571429em;padding-bottom:.1428571em;padding-top:.1428571em;padding-inline-start:.3571429em}.prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em}.prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em}.prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.25rem;font-size:.8571429em;line-height:1.6666667;margin-bottom:1.6666667em;margin-top:1.6666667em;padding-inline-end:1em;padding-bottom:.6666667em;padding-top:.6666667em;padding-inline-start:1em}.prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em;margin-top:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em;margin-top:1.1428571em;padding-inline-start:1.5714286em}.prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.2857143em;margin-top:.2857143em}.prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4285714em}.prose-sm :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5714286em;margin-top:.5714286em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5714286em;margin-top:.5714286em}.prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em;margin-top:1.1428571em}.prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.2857143em;padding-inline-start:1.5714286em}.prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2.8571429em;margin-top:2.8571429em}.prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.6666667em;padding-inline-start:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.6666667em;padding-top:.6666667em;padding-inline-start:1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7142857em;margin-top:1.7142857em}.prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8571429em;line-height:1.3333333;margin-top:.6666667em}.prose-sm :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-base{font-size:1rem;line-height:1.75}.prose-base :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose-base :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose-base :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.6em;margin-top:1.6em;padding-inline-start:1em}.prose-base :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:2.25em;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.prose-base :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.5em;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.prose-base :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.25em;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.prose-base :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.prose-base :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose-base :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose-base :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-base :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose-base :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;font-size:.875em;padding-inline-end:.375em;padding-bottom:.1875em;padding-top:.1875em;padding-inline-start:.375em}.prose-base :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em}.prose-base :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em}.prose-base :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.prose-base :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.375rem;font-size:.875em;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-top:.8571429em;padding-inline-start:1.1428571em}.prose-base :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose-base :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose-base :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.prose-base :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose-base :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose-base :where(.prose-base>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose-base :where(.prose-base>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose-base>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose-base :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose-base :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose-base :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:3em;margin-top:3em}.prose-base :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-base :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-base :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-base :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-base :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.7142857}.prose-base :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em}.prose-base :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-base :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-base :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-top:.5714286em;padding-inline-start:.5714286em}.prose-base :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-base :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-base :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose-base :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-base :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose-base :where(.prose-base>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-base :where(.prose-base>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-lg{font-size:1.125rem;line-height:1.7777778}.prose-lg :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em;margin-top:1.3333333em}.prose-lg :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.2222222em;line-height:1.4545455;margin-bottom:1.0909091em;margin-top:1.0909091em}.prose-lg :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.6666667em;margin-top:1.6666667em;padding-inline-start:1em}.prose-lg :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:2.6666667em;line-height:1;margin-bottom:.8333333em;margin-top:0}.prose-lg :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.6666667em;line-height:1.3333333;margin-bottom:1.0666667em;margin-top:1.8666667em}.prose-lg :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:1.3333333em;line-height:1.5;margin-bottom:.6666667em;margin-top:1.6666667em}.prose-lg :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){line-height:1.5555556;margin-bottom:.4444444em;margin-top:1.7777778em}.prose-lg :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7777778em;margin-top:1.7777778em}.prose-lg :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7777778em;margin-top:1.7777778em}.prose-lg :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-lg :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7777778em;margin-top:1.7777778em}.prose-lg :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;font-size:.8888889em;padding-inline-end:.4444444em;padding-bottom:.2222222em;padding-top:.2222222em;padding-inline-start:.4444444em}.prose-lg :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em}.prose-lg :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8666667em}.prose-lg :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em}.prose-lg :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.375rem;font-size:.8888889em;line-height:1.75;margin-bottom:2em;margin-top:2em;padding-inline-end:1.5em;padding-bottom:1em;padding-top:1em;padding-inline-start:1.5em}.prose-lg :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em;margin-top:1.3333333em;padding-inline-start:1.5555556em}.prose-lg :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em;margin-top:1.3333333em;padding-inline-start:1.5555556em}.prose-lg :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.6666667em;margin-top:.6666667em}.prose-lg :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4444444em}.prose-lg :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.4444444em}.prose-lg :where(.prose-lg>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.8888889em;margin-top:.8888889em}.prose-lg :where(.prose-lg>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.8888889em;margin-top:.8888889em}.prose-lg :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em;margin-top:1.3333333em}.prose-lg :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.6666667em;padding-inline-start:1.5555556em}.prose-lg :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:3.1111111em;margin-top:3.1111111em}.prose-lg :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-lg :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-lg :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-lg :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-lg :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em;line-height:1.5}.prose-lg :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.75em;padding-bottom:.75em;padding-inline-start:.75em}.prose-lg :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-lg :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-lg :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.75em;padding-bottom:.75em;padding-top:.75em;padding-inline-start:.75em}.prose-lg :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-lg :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-lg :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.7777778em;margin-top:1.7777778em}.prose-lg :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose-lg :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.8888889em;line-height:1.5;margin-top:1em}.prose-lg :where(.prose-lg>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-lg :where(.prose-lg>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-4{inset:1rem}.inset-x-0{left:0;right:0}.inset-x-4{left:1rem;right:1rem}.inset-y-0{bottom:0;top:0}.-bottom-1\/2{bottom:-50%}.-top-1{top:-.25rem}.-top-1\/2{top:-50%}.-top-2{top:-.5rem}.-top-3{top:-.75rem}.bottom-0{bottom:0}.bottom-1\/2{bottom:50%}.end-0{inset-inline-end:0}.end-4{inset-inline-end:1rem}.end-6{inset-inline-end:1.5rem}.left-3{left:.75rem}.start-0{inset-inline-start:0}.start-full{inset-inline-start:100%}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-4{top:1rem}.top-6{top:1.5rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[1\]{z-index:1}.order-first{order:-9999}.col-\[--col-span-default\]{grid-column:var(--col-span-default)}.col-span-full{grid-column:1/-1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.col-start-\[--col-start-default\]{grid-column-start:var(--col-start-default)}.row-start-2{grid-row-start:2}.-m-0\.5{margin:-.125rem}.-m-1{margin:-.25rem}.-m-1\.5{margin:-.375rem}.-m-2{margin:-.5rem}.-m-2\.5{margin:-.625rem}.-m-3{margin:-.75rem}.-m-3\.5{margin:-.875rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.-my-1{margin-bottom:-.25rem;margin-top:-.25rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-auto{margin-left:auto;margin-right:auto}.my-16{margin-bottom:4rem;margin-top:4rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.my-auto{margin-bottom:auto;margin-top:auto}.\!mt-0{margin-top:0!important}.-mb-4{margin-bottom:-1rem}.-mb-6{margin-bottom:-1.5rem}.-me-2{margin-inline-end:-.5rem}.-ms-0\.5{margin-inline-start:-.125rem}.-ms-1{margin-inline-start:-.25rem}.-ms-2{margin-inline-start:-.5rem}.-mt-3{margin-top:-.75rem}.-mt-4{margin-top:-1rem}.-mt-6{margin-top:-1.5rem}.-mt-7{margin-top:-1.75rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.me-1{margin-inline-end:.25rem}.me-3{margin-inline-end:.75rem}.me-4{margin-inline-end:1rem}.me-6{margin-inline-end:1.5rem}.ml-auto{margin-left:auto}.ms-1{margin-inline-start:.25rem}.ms-6{margin-inline-start:1.5rem}.ms-auto{margin-inline-start:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.mt-auto{margin-top:auto}.line-clamp-\[--line-clamp\]{-webkit-box-orient:vertical;-webkit-line-clamp:var(--line-clamp);display:-webkit-box;overflow:hidden}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.contents{display:contents}.hidden{display:none}.h-0{height:0}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-16{height:4rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-96{height:24rem}.h-\[100dvh\],.h-dvh{height:100dvh}.h-full{height:100%}.h-screen{height:100vh}.max-h-96{max-height:24rem}.min-h-\[theme\(spacing\.48\)\]{min-height:12rem}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-16{width:4rem}.w-20{width:5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[--sidebar-width\]{width:var(--sidebar-width)}.w-\[calc\(100\%\+2rem\)\]{width:calc(100% + 2rem)}.w-auto{width:auto}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:0}.min-w-48{min-width:12rem}.min-w-\[theme\(spacing\.4\)\]{min-width:1rem}.min-w-\[theme\(spacing\.5\)\]{min-width:1.25rem}.min-w-\[theme\(spacing\.6\)\]{min-width:1.5rem}.min-w-\[theme\(spacing\.8\)\]{min-width:2rem}.\!max-w-2xl{max-width:42rem!important}.\!max-w-3xl{max-width:48rem!important}.\!max-w-4xl{max-width:56rem!important}.\!max-w-5xl{max-width:64rem!important}.\!max-w-6xl{max-width:72rem!important}.\!max-w-7xl{max-width:80rem!important}.\!max-w-\[14rem\]{max-width:14rem!important}.\!max-w-lg{max-width:32rem!important}.\!max-w-md{max-width:28rem!important}.\!max-w-sm{max-width:24rem!important}.\!max-w-xl{max-width:36rem!important}.\!max-w-xs{max-width:20rem!important}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-fit{max-width:-moz-fit-content;max-width:fit-content}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-max{max-width:-moz-max-content;max-width:max-content}.max-w-md{max-width:28rem}.max-w-min{max-width:-moz-min-content;max-width:min-content}.max-w-none{max-width:none}.max-w-prose{max-width:65ch}.max-w-screen-2xl{max-width:1536px}.max-w-screen-lg{max-width:1024px}.max-w-screen-md{max-width:768px}.max-w-screen-sm{max-width:640px}.max-w-screen-xl{max-width:1280px}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.table-auto{table-layout:auto}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-x-1\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-1\/4{--tw-translate-x:-25%}.-translate-x-12{--tw-translate-x:-3rem}.-translate-x-12,.-translate-x-5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-5{--tw-translate-x:-1.25rem}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-12{--tw-translate-y:-3rem}.-translate-y-12,.-translate-y-3\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-3\/4{--tw-translate-y:-75%}.translate-x-0{--tw-translate-x:0px}.translate-x-0,.translate-x-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-12{--tw-translate-x:3rem}.translate-x-5{--tw-translate-x:1.25rem}.translate-x-5,.translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x:100%}.translate-y-12{--tw-translate-y:3rem}.-rotate-180,.translate-y-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-180{--tw-rotate:-180deg}.rotate-180{--tw-rotate:180deg}.rotate-180,.scale-100{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.scale-95,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-move{cursor:move}.cursor-pointer{cursor:pointer}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.resize-none{resize:none}.resize{resize:both}.scroll-mt-9{scroll-margin-top:2.25rem}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.columns-\[--cols-default\]{-moz-columns:var(--cols-default);columns:var(--cols-default)}.break-inside-avoid{-moz-column-break-inside:avoid;break-inside:avoid}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.grid-flow-col{grid-auto-flow:column}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-\[--cols-default\]{grid-template-columns:var(--cols-default)}.grid-cols-\[1fr_auto_1fr\]{grid-template-columns:1fr auto 1fr}.grid-cols-\[repeat\(7\2c minmax\(theme\(spacing\.7\)\2c 1fr\)\)\]{grid-template-columns:repeat(7,minmax(1.75rem,1fr))}.grid-cols-\[repeat\(auto-fit\2c minmax\(0\2c 1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.grid-rows-\[1fr_auto_1fr\]{grid-template-rows:1fr auto 1fr}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-start{justify-items:start}.justify-items-center{justify-items:center}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-1\.5{-moz-column-gap:.375rem;column-gap:.375rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-2\.5{-moz-column-gap:.625rem;column-gap:.625rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-5{-moz-column-gap:1.25rem;column-gap:1.25rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-2{row-gap:.5rem}.gap-y-3{row-gap:.75rem}.gap-y-4{row-gap:1rem}.gap-y-6{row-gap:1.5rem}.gap-y-7{row-gap:1.75rem}.gap-y-8{row-gap:2rem}.gap-y-px{row-gap:1px}.-space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-.25rem*var(--tw-space-x-reverse))}.-space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-.5rem*var(--tw-space-x-reverse))}.-space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-.75rem*var(--tw-space-x-reverse))}.-space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1rem*var(--tw-space-x-reverse))}.-space-x-5>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1.25rem*var(--tw-space-x-reverse))}.-space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1.5rem*var(--tw-space-x-reverse))}.-space-x-7>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1.75rem*var(--tw-space-x-reverse))}.-space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-2rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(-2rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-left-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-right-width:calc(1px*var(--tw-divide-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgba(var(--gray-100),var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgba(var(--gray-200),var(--tw-divide-opacity,1))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.justify-self-center{justify-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-x-clip{overflow-x:clip}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-b-xl{border-bottom-left-radius:.75rem;border-bottom-right-radius:.75rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-x-\[0\.5px\]{border-left-width:.5px;border-right-width:.5px}.border-y{border-bottom-width:1px;border-top-width:1px}.\!border-t-0{border-top-width:0!important}.border-b{border-bottom-width:1px}.border-b-0{border-bottom-width:0}.border-e{border-inline-end-width:1px}.border-s{border-inline-start-width:1px}.border-t{border-top-width:1px}.\!border-none{border-style:none!important}.border-none{border-style:none}.border-gray-100{--tw-border-opacity:1;border-color:rgba(var(--gray-100),var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgba(var(--gray-200),var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgba(var(--gray-300),var(--tw-border-opacity,1))}.border-gray-600{--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity,1))}.border-primary-500{--tw-border-opacity:1;border-color:rgba(var(--primary-500),var(--tw-border-opacity,1))}.border-primary-600{--tw-border-opacity:1;border-color:rgba(var(--primary-600),var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.border-t-gray-200{--tw-border-opacity:1;border-top-color:rgba(var(--gray-200),var(--tw-border-opacity,1))}.\!bg-gray-50{--tw-bg-opacity:1!important;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))!important}.\!bg-gray-700{--tw-bg-opacity:1!important;background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1))!important}.bg-black\/50{background-color:rgba(0,0,0,.5)}.bg-custom-100{--tw-bg-opacity:1;background-color:rgba(var(--c-100),var(--tw-bg-opacity,1))}.bg-custom-50{--tw-bg-opacity:1;background-color:rgba(var(--c-50),var(--tw-bg-opacity,1))}.bg-custom-600{--tw-bg-opacity:1;background-color:rgba(var(--c-600),var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgba(var(--gray-100),var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(var(--gray-200),var(--tw-bg-opacity,1))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgba(var(--gray-300),var(--tw-bg-opacity,1))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgba(var(--gray-400),var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.bg-gray-950\/50{background-color:rgba(var(--gray-950),.5)}.bg-primary-500{--tw-bg-opacity:1;background-color:rgba(var(--primary-500),var(--tw-bg-opacity,1))}.bg-primary-600{--tw-bg-opacity:1;background-color:rgba(var(--primary-600),var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/0{background-color:hsla(0,0%,100%,0)}.bg-white\/10{background-color:hsla(0,0%,100%,.1)}.\!bg-none{background-image:none!important}.bg-cover{background-size:cover}.bg-center{background-position:50%}.object-cover{-o-object-fit:cover;object-fit:cover}.object-center{-o-object-position:center;object-position:center}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-2\.5{padding-bottom:.625rem;padding-top:.625rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-3\.5{padding-bottom:.875rem;padding-top:.875rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pe-0{padding-inline-end:0}.pe-1{padding-inline-end:.25rem}.pe-2{padding-inline-end:.5rem}.pe-3{padding-inline-end:.75rem}.pe-4{padding-inline-end:1rem}.pe-6{padding-inline-end:1.5rem}.pe-8{padding-inline-end:2rem}.ps-0{padding-inline-start:0}.ps-1{padding-inline-start:.25rem}.ps-2{padding-inline-start:.5rem}.ps-3{padding-inline-start:.75rem}.ps-4{padding-inline-start:1rem}.ps-\[5\.25rem\]{padding-inline-start:5.25rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-justify{text-align:justify}.text-start{text-align:start}.text-end{text-align:end}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.align-bottom{vertical-align:bottom}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.font-serif{font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-extralight{font-weight:200}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.font-thin{font-weight:100}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-loose{line-height:2}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.text-custom-400{--tw-text-opacity:1;color:rgba(var(--c-400),var(--tw-text-opacity,1))}.text-custom-50{--tw-text-opacity:1;color:rgba(var(--c-50),var(--tw-text-opacity,1))}.text-custom-500{--tw-text-opacity:1;color:rgba(var(--c-500),var(--tw-text-opacity,1))}.text-custom-600{--tw-text-opacity:1;color:rgba(var(--c-600),var(--tw-text-opacity,1))}.text-custom-700\/50{color:rgba(var(--c-700),.5)}.text-danger-600{--tw-text-opacity:1;color:rgba(var(--danger-600),var(--tw-text-opacity,1))}.text-gray-100{--tw-text-opacity:1;color:rgba(var(--gray-100),var(--tw-text-opacity,1))}.text-gray-200{--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgba(var(--gray-600),var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgba(var(--gray-700),var(--tw-text-opacity,1))}.text-gray-700\/50{color:rgba(var(--gray-700),.5)}.text-gray-950{--tw-text-opacity:1;color:rgba(var(--gray-950),var(--tw-text-opacity,1))}.text-primary-400{--tw-text-opacity:1;color:rgba(var(--primary-400),var(--tw-text-opacity,1))}.text-primary-500{--tw-text-opacity:1;color:rgba(var(--primary-500),var(--tw-text-opacity,1))}.text-primary-600{--tw-text-opacity:1;color:rgba(var(--primary-600),var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-0{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-1,.ring-2{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-4{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-custom-600{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--c-600),var(--tw-ring-opacity,1))}.ring-custom-600\/10{--tw-ring-color:rgba(var(--c-600),0.1)}.ring-custom-600\/20{--tw-ring-color:rgba(var(--c-600),0.2)}.ring-danger-600{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-600),var(--tw-ring-opacity,1))}.ring-gray-200{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-200),var(--tw-ring-opacity,1))}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-300),var(--tw-ring-opacity,1))}.ring-gray-600\/10{--tw-ring-color:rgba(var(--gray-600),0.1)}.ring-gray-900\/10{--tw-ring-color:rgba(var(--gray-900),0.1)}.ring-gray-950\/10{--tw-ring-color:rgba(var(--gray-950),0.1)}.ring-gray-950\/5{--tw-ring-color:rgba(var(--gray-950),0.05)}.ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}.ring-white\/10{--tw-ring-color:hsla(0,0%,100%,.1)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.delay-100{transition-delay:.1s}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-75{transition-duration:75ms}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[transform\:translateZ\(0\)\]{transform:translateZ(0)}.dark\:prose-invert:is(.dark *){--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-kbd:var(--tw-prose-invert-kbd);--tw-prose-kbd-shadows:var(--tw-prose-invert-kbd-shadows);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}.placeholder\:text-gray-400::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.placeholder\:text-gray-400::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-y-0:before{bottom:0;content:var(--tw-content);top:0}.before\:start-0:before{content:var(--tw-content);inset-inline-start:0}.before\:h-full:before{content:var(--tw-content);height:100%}.before\:w-0\.5:before{content:var(--tw-content);width:.125rem}.before\:bg-primary-600:before{--tw-bg-opacity:1;background-color:rgba(var(--primary-600),var(--tw-bg-opacity,1));content:var(--tw-content)}.first\:border-s-0:first-child{border-inline-start-width:0}.first\:border-t-0:first-child{border-top-width:0}.last\:border-e-0:last-child{border-inline-end-width:0}.first-of-type\:ps-1:first-of-type{padding-inline-start:.25rem}.last-of-type\:pe-1:last-of-type{padding-inline-end:.25rem}.checked\:ring-0:checked{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-within\:bg-gray-50:focus-within{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.hover\:bg-custom-400\/10:hover{background-color:rgba(var(--c-400),.1)}.hover\:bg-custom-50:hover{--tw-bg-opacity:1;background-color:rgba(var(--c-50),var(--tw-bg-opacity,1))}.hover\:bg-custom-500:hover{--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgba(var(--gray-100),var(--tw-bg-opacity,1))}.hover\:bg-gray-400\/10:hover{background-color:rgba(var(--gray-400),.1)}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.hover\:text-custom-600:hover{--tw-text-opacity:1;color:rgba(var(--c-600),var(--tw-text-opacity,1))}.hover\:text-custom-700\/75:hover{color:rgba(var(--c-700),.75)}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgba(var(--gray-700),var(--tw-text-opacity,1))}.hover\:text-gray-700\/75:hover{color:rgba(var(--gray-700),.75)}.hover\:opacity-100:hover{opacity:1}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-0:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-danger-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-600),var(--tw-ring-opacity,1))}.focus\:ring-primary-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-600),var(--tw-ring-opacity,1))}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px}.checked\:focus\:ring-danger-500\/50:focus:checked{--tw-ring-color:rgba(var(--danger-500),0.5)}.checked\:focus\:ring-primary-500\/50:focus:checked{--tw-ring-color:rgba(var(--primary-500),0.5)}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-primary-500:focus-visible{--tw-border-opacity:1;border-color:rgba(var(--primary-500),var(--tw-border-opacity,1))}.focus-visible\:bg-custom-50:focus-visible{--tw-bg-opacity:1;background-color:rgba(var(--c-50),var(--tw-bg-opacity,1))}.focus-visible\:bg-gray-100:focus-visible{--tw-bg-opacity:1;background-color:rgba(var(--gray-100),var(--tw-bg-opacity,1))}.focus-visible\:bg-gray-50:focus-visible{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.focus-visible\:text-custom-700\/75:focus-visible{color:rgba(var(--c-700),.75)}.focus-visible\:text-gray-500:focus-visible{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.focus-visible\:text-gray-700\/75:focus-visible{color:rgba(var(--gray-700),.75)}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.focus-visible\:ring-custom-500\/50:focus-visible{--tw-ring-color:rgba(var(--c-500),0.5)}.focus-visible\:ring-custom-600:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--c-600),var(--tw-ring-opacity,1))}.focus-visible\:ring-gray-400\/40:focus-visible{--tw-ring-color:rgba(var(--gray-400),0.4)}.focus-visible\:ring-primary-500:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity,1))}.focus-visible\:ring-primary-600:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-600),var(--tw-ring-opacity,1))}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px}.enabled\:cursor-wait:enabled{cursor:wait}.enabled\:opacity-70:enabled{opacity:.7}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:bg-gray-50:disabled{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.disabled\:text-gray-50:disabled{--tw-text-opacity:1;color:rgba(var(--gray-50),var(--tw-text-opacity,1))}.disabled\:text-gray-500:disabled{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.disabled\:opacity-70:disabled{opacity:.7}.disabled\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled{-webkit-text-fill-color:rgba(var(--gray-500),1)}.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::-moz-placeholder{-webkit-text-fill-color:rgba(var(--gray-400),1)}.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::placeholder{-webkit-text-fill-color:rgba(var(--gray-400),1)}.disabled\:checked\:bg-gray-400:checked:disabled{--tw-bg-opacity:1;background-color:rgba(var(--gray-400),var(--tw-bg-opacity,1))}.disabled\:checked\:text-gray-400:checked:disabled{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.group\/item:first-child .group-first\/item\:rounded-s-lg{border-end-start-radius:.5rem;border-start-start-radius:.5rem}.group\/item:last-child .group-last\/item\:rounded-e-lg{border-end-end-radius:.5rem;border-start-end-radius:.5rem}.group:hover .group-hover\:text-gray-500,.group\/button:hover .group-hover\/button\:text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.group:hover .group-hover\:text-gray-700{--tw-text-opacity:1;color:rgba(var(--gray-700),var(--tw-text-opacity,1))}.group\/item:hover .group-hover\/item\:underline,.group\/link:hover .group-hover\/link\:underline{text-decoration-line:underline}.group:focus-visible .group-focus-visible\:text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.group:focus-visible .group-focus-visible\:text-gray-700{--tw-text-opacity:1;color:rgba(var(--gray-700),var(--tw-text-opacity,1))}.group\/item:focus-visible .group-focus-visible\/item\:underline{text-decoration-line:underline}.group\/link:focus-visible .group-focus-visible\/link\:underline{text-decoration-line:underline}.dark\:flex:is(.dark *){display:flex}.dark\:hidden:is(.dark *){display:none}.dark\:divide-white\/10:is(.dark *)>:not([hidden])~:not([hidden]){border-color:hsla(0,0%,100%,.1)}.dark\:divide-white\/5:is(.dark *)>:not([hidden])~:not([hidden]){border-color:hsla(0,0%,100%,.05)}.dark\:border-gray-600:is(.dark *){--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity,1))}.dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgba(var(--gray-700),var(--tw-border-opacity,1))}.dark\:border-primary-500:is(.dark *){--tw-border-opacity:1;border-color:rgba(var(--primary-500),var(--tw-border-opacity,1))}.dark\:border-white\/10:is(.dark *){border-color:hsla(0,0%,100%,.1)}.dark\:border-white\/5:is(.dark *){border-color:hsla(0,0%,100%,.05)}.dark\:border-t-white\/10:is(.dark *){border-top-color:hsla(0,0%,100%,.1)}.dark\:\!bg-gray-700:is(.dark *){--tw-bg-opacity:1!important;background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1))!important}.dark\:bg-custom-400\/10:is(.dark *){background-color:rgba(var(--c-400),.1)}.dark\:bg-custom-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity,1))}.dark\:bg-custom-500\/20:is(.dark *){background-color:rgba(var(--c-500),.2)}.dark\:bg-gray-400\/10:is(.dark *){background-color:rgba(var(--gray-400),.1)}.dark\:bg-gray-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-500),var(--tw-bg-opacity,1))}.dark\:bg-gray-500\/20:is(.dark *){background-color:rgba(var(--gray-500),.2)}.dark\:bg-gray-600:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-600),var(--tw-bg-opacity,1))}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity,1))}.dark\:bg-gray-900:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity,1))}.dark\:bg-gray-900\/30:is(.dark *){background-color:rgba(var(--gray-900),.3)}.dark\:bg-gray-950:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-950),var(--tw-bg-opacity,1))}.dark\:bg-gray-950\/75:is(.dark *){background-color:rgba(var(--gray-950),.75)}.dark\:bg-primary-400:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--primary-400),var(--tw-bg-opacity,1))}.dark\:bg-primary-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--primary-500),var(--tw-bg-opacity,1))}.dark\:bg-transparent:is(.dark *){background-color:transparent}.dark\:bg-white\/10:is(.dark *){background-color:hsla(0,0%,100%,.1)}.dark\:bg-white\/5:is(.dark *){background-color:hsla(0,0%,100%,.05)}.dark\:text-custom-300\/50:is(.dark *){color:rgba(var(--c-300),.5)}.dark\:text-custom-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--c-400),var(--tw-text-opacity,1))}.dark\:text-custom-400\/10:is(.dark *){color:rgba(var(--c-400),.1)}.dark\:text-danger-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--danger-400),var(--tw-text-opacity,1))}.dark\:text-danger-500:is(.dark *){--tw-text-opacity:1;color:rgba(var(--danger-500),var(--tw-text-opacity,1))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity,1))}.dark\:text-gray-300\/50:is(.dark *){color:rgba(var(--gray-300),.5)}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.dark\:text-gray-500:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.dark\:text-gray-700:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-700),var(--tw-text-opacity,1))}.dark\:text-gray-800:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-800),var(--tw-text-opacity,1))}.dark\:text-primary-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--primary-400),var(--tw-text-opacity,1))}.dark\:text-primary-500:is(.dark *){--tw-text-opacity:1;color:rgba(var(--primary-500),var(--tw-text-opacity,1))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:text-white\/5:is(.dark *){color:hsla(0,0%,100%,.05)}.dark\:ring-custom-400\/30:is(.dark *){--tw-ring-color:rgba(var(--c-400),0.3)}.dark\:ring-custom-500:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--c-500),var(--tw-ring-opacity,1))}.dark\:ring-danger-500:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-500),var(--tw-ring-opacity,1))}.dark\:ring-gray-400\/20:is(.dark *){--tw-ring-color:rgba(var(--gray-400),0.2)}.dark\:ring-gray-50\/10:is(.dark *){--tw-ring-color:rgba(var(--gray-50),0.1)}.dark\:ring-gray-700:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-700),var(--tw-ring-opacity,1))}.dark\:ring-gray-900:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-900),var(--tw-ring-opacity,1))}.dark\:ring-white\/10:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.1)}.dark\:ring-white\/20:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.2)}.dark\:placeholder\:text-gray-500:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.dark\:placeholder\:text-gray-500:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.dark\:before\:bg-primary-500:is(.dark *):before{--tw-bg-opacity:1;background-color:rgba(var(--primary-500),var(--tw-bg-opacity,1));content:var(--tw-content)}.dark\:checked\:bg-danger-500:checked:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--danger-500),var(--tw-bg-opacity,1))}.dark\:checked\:bg-primary-500:checked:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--primary-500),var(--tw-bg-opacity,1))}.dark\:focus-within\:bg-white\/5:focus-within:is(.dark *){background-color:hsla(0,0%,100%,.05)}.dark\:hover\:bg-custom-400:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-400),var(--tw-bg-opacity,1))}.dark\:hover\:bg-custom-400\/10:hover:is(.dark *){background-color:rgba(var(--c-400),.1)}.dark\:hover\:bg-white\/10:hover:is(.dark *){background-color:hsla(0,0%,100%,.1)}.dark\:hover\:bg-white\/5:hover:is(.dark *){background-color:hsla(0,0%,100%,.05)}.dark\:hover\:text-custom-300:hover:is(.dark *){--tw-text-opacity:1;color:rgba(var(--c-300),var(--tw-text-opacity,1))}.dark\:hover\:text-custom-300\/75:hover:is(.dark *){color:rgba(var(--c-300),.75)}.dark\:hover\:text-gray-200:hover:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity,1))}.dark\:hover\:text-gray-300\/75:hover:is(.dark *){color:rgba(var(--gray-300),.75)}.dark\:hover\:text-gray-400:hover:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.dark\:hover\:ring-white\/20:hover:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.2)}.dark\:focus\:ring-danger-500:focus:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-500),var(--tw-ring-opacity,1))}.dark\:focus\:ring-primary-500:focus:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity,1))}.dark\:checked\:focus\:ring-danger-400\/50:focus:checked:is(.dark *){--tw-ring-color:rgba(var(--danger-400),0.5)}.dark\:checked\:focus\:ring-primary-400\/50:focus:checked:is(.dark *){--tw-ring-color:rgba(var(--primary-400),0.5)}.dark\:focus-visible\:border-primary-500:focus-visible:is(.dark *){--tw-border-opacity:1;border-color:rgba(var(--primary-500),var(--tw-border-opacity,1))}.dark\:focus-visible\:bg-custom-400\/10:focus-visible:is(.dark *){background-color:rgba(var(--c-400),.1)}.dark\:focus-visible\:bg-white\/5:focus-visible:is(.dark *){background-color:hsla(0,0%,100%,.05)}.dark\:focus-visible\:text-custom-300\/75:focus-visible:is(.dark *){color:rgba(var(--c-300),.75)}.dark\:focus-visible\:text-gray-300\/75:focus-visible:is(.dark *){color:rgba(var(--gray-300),.75)}.dark\:focus-visible\:text-gray-400:focus-visible:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.dark\:focus-visible\:ring-custom-400\/50:focus-visible:is(.dark *){--tw-ring-color:rgba(var(--c-400),0.5)}.dark\:focus-visible\:ring-custom-500:focus-visible:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--c-500),var(--tw-ring-opacity,1))}.dark\:focus-visible\:ring-primary-500:focus-visible:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity,1))}.dark\:focus-visible\:ring-offset-gray-900:focus-visible:is(.dark *){--tw-ring-offset-color:rgba(var(--gray-900),1)}.dark\:disabled\:bg-transparent:disabled:is(.dark *){background-color:transparent}.dark\:disabled\:text-gray-400:disabled:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.dark\:disabled\:ring-white\/10:disabled:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.1)}.dark\:disabled\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled:is(.dark *){-webkit-text-fill-color:rgba(var(--gray-400),1)}.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::-moz-placeholder{-webkit-text-fill-color:rgba(var(--gray-500),1)}.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::placeholder{-webkit-text-fill-color:rgba(var(--gray-500),1)}.dark\:disabled\:checked\:bg-gray-600:checked:disabled:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-600),var(--tw-bg-opacity,1))}.group\/button:hover .dark\:group-hover\/button\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.group:hover .dark\:group-hover\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity,1))}.group:hover .dark\:group-hover\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.group:focus-visible .dark\:group-focus-visible\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity,1))}.group:focus-visible .dark\:group-focus-visible\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}@media (min-width:640px){.sm\:relative{position:relative}.sm\:inset-x-auto{left:auto;right:auto}.sm\:end-0{inset-inline-end:0}.sm\:col-\[--col-span-sm\]{grid-column:var(--col-span-sm)}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-start-\[--col-start-sm\]{grid-column-start:var(--col-start-sm)}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.sm\:ms-auto{margin-inline-start:auto}.sm\:mt-7{margin-top:1.75rem}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:table-cell{display:table-cell}.sm\:grid{display:grid}.sm\:inline-grid{display:inline-grid}.sm\:hidden{display:none}.sm\:w-\[calc\(100\%\+3rem\)\]{width:calc(100% + 3rem)}.sm\:w-screen{width:100vw}.sm\:max-w-sm{max-width:24rem}.sm\:columns-\[--cols-sm\]{-moz-columns:var(--cols-sm);columns:var(--cols-sm)}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[--cols-sm\]{grid-template-columns:var(--cols-sm)}.sm\:grid-cols-\[repeat\(auto-fit\2c minmax\(0\2c 1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:grid-rows-\[1fr_auto_3fr\]{grid-template-rows:1fr auto 3fr}.sm\:flex-row{flex-direction:row}.sm\:flex-nowrap{flex-wrap:nowrap}.sm\:items-start{align-items:flex-start}.sm\:items-end{align-items:flex-end}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-1{gap:.25rem}.sm\:gap-3{gap:.75rem}.sm\:gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.sm\:rounded-xl{border-radius:.75rem}.sm\:p-10{padding:2.5rem}.sm\:px-12{padding-left:3rem;padding-right:3rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.sm\:pe-3{padding-inline-end:.75rem}.sm\:pe-6{padding-inline-end:1.5rem}.sm\:ps-3{padding-inline-start:.75rem}.sm\:ps-6{padding-inline-start:1.5rem}.sm\:pt-1\.5{padding-top:.375rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:leading-6{line-height:1.5rem}.sm\:first-of-type\:ps-3:first-of-type{padding-inline-start:.75rem}.sm\:first-of-type\:ps-6:first-of-type{padding-inline-start:1.5rem}.sm\:last-of-type\:pe-3:last-of-type{padding-inline-end:.75rem}.sm\:last-of-type\:pe-6:last-of-type{padding-inline-end:1.5rem}}@media (min-width:768px){.md\:bottom-4{bottom:1rem}.md\:order-first{order:-9999}.md\:col-\[--col-span-md\]{grid-column:var(--col-span-md)}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-start-\[--col-start-md\]{grid-column-start:var(--col-start-md)}.md\:block{display:block}.md\:flex{display:flex}.md\:table-cell{display:table-cell}.md\:inline-grid{display:inline-grid}.md\:hidden{display:none}.md\:w-max{width:-moz-max-content;width:max-content}.md\:max-w-60{max-width:15rem}.md\:columns-\[--cols-md\]{-moz-columns:var(--cols-md);columns:var(--cols-md)}.md\:grid-flow-col{grid-auto-flow:column}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-\[--cols-md\]{grid-template-columns:var(--cols-md)}.md\:flex-row{flex-direction:row}.md\:items-start{align-items:flex-start}.md\:items-end{align-items:flex-end}.md\:items-center{align-items:center}.md\:justify-end{justify-content:flex-end}.md\:gap-1{gap:.25rem}.md\:gap-3{gap:.75rem}.md\:divide-y-0>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(0px*var(--tw-divide-y-reverse));border-top-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.md\:overflow-x-auto{overflow-x:auto}.md\:rounded-xl{border-radius:.75rem}.md\:p-20{padding:5rem}.md\:px-6{padding-left:1.5rem;padding-right:1.5rem}.md\:pe-6{padding-inline-end:1.5rem}.md\:ps-3{padding-inline-start:.75rem}}@media (min-width:1024px){.lg\:sticky{position:sticky}.lg\:z-0{z-index:0}.lg\:col-\[--col-span-lg\]{grid-column:var(--col-span-lg)}.lg\:col-start-\[--col-start-lg\]{grid-column-start:var(--col-start-lg)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:table-cell{display:table-cell}.lg\:inline-grid{display:inline-grid}.lg\:hidden{display:none}.lg\:h-full{height:100%}.lg\:max-w-xs{max-width:20rem}.lg\:-translate-x-full{--tw-translate-x:-100%}.lg\:-translate-x-full,.lg\:translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.lg\:translate-x-0{--tw-translate-x:0px}.lg\:columns-\[--cols-lg\]{-moz-columns:var(--cols-lg);columns:var(--cols-lg)}.lg\:grid-cols-\[--cols-lg\]{grid-template-columns:var(--cols-lg)}.lg\:flex-row{flex-direction:row}.lg\:items-start{align-items:flex-start}.lg\:items-end{align-items:flex-end}.lg\:items-center{align-items:center}.lg\:gap-1{gap:.25rem}.lg\:gap-3{gap:.75rem}.lg\:bg-transparent{background-color:transparent}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pe-8{padding-inline-end:2rem}.lg\:shadow-none{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.lg\:shadow-none,.lg\:shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.lg\:shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.lg\:ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.lg\:transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.lg\:transition-none{transition-property:none}.lg\:delay-100{transition-delay:.1s}.dark\:lg\:bg-transparent:is(.dark *){background-color:transparent}}@media (min-width:1280px){.xl\:col-\[--col-span-xl\]{grid-column:var(--col-span-xl)}.xl\:col-start-\[--col-start-xl\]{grid-column-start:var(--col-start-xl)}.xl\:block{display:block}.xl\:table-cell{display:table-cell}.xl\:inline-grid{display:inline-grid}.xl\:hidden{display:none}.xl\:columns-\[--cols-xl\]{-moz-columns:var(--cols-xl);columns:var(--cols-xl)}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-\[--cols-xl\]{grid-template-columns:var(--cols-xl)}.xl\:flex-row{flex-direction:row}.xl\:items-start{align-items:flex-start}.xl\:items-end{align-items:flex-end}.xl\:items-center{align-items:center}.xl\:gap-1{gap:.25rem}.xl\:gap-3{gap:.75rem}}@media (min-width:1536px){.\32xl\:col-\[--col-span-2xl\]{grid-column:var(--col-span-2xl)}.\32xl\:col-start-\[--col-start-2xl\]{grid-column-start:var(--col-start-2xl)}.\32xl\:block{display:block}.\32xl\:table-cell{display:table-cell}.\32xl\:inline-grid{display:inline-grid}.\32xl\:hidden{display:none}.\32xl\:columns-\[--cols-2xl\]{-moz-columns:var(--cols-2xl);columns:var(--cols-2xl)}.\32xl\:grid-cols-\[--cols-2xl\]{grid-template-columns:var(--cols-2xl)}.\32xl\:flex-row{flex-direction:row}.\32xl\:items-start{align-items:flex-start}.\32xl\:items-end{align-items:flex-end}.\32xl\:items-center{align-items:center}.\32xl\:gap-1{gap:.25rem}.\32xl\:gap-3{gap:.75rem}}.ltr\:hidden:where([dir=ltr],[dir=ltr] *){display:none}.rtl\:hidden:where([dir=rtl],[dir=rtl] *){display:none}.rtl\:-translate-x-0:where([dir=rtl],[dir=rtl] *){--tw-translate-x:-0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:-translate-x-5:where([dir=rtl],[dir=rtl] *){--tw-translate-x:-1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:-translate-x-full:where([dir=rtl],[dir=rtl] *){--tw-translate-x:-100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:translate-x-1\/2:where([dir=rtl],[dir=rtl] *){--tw-translate-x:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:translate-x-1\/4:where([dir=rtl],[dir=rtl] *){--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:translate-x-full:where([dir=rtl],[dir=rtl] *){--tw-translate-x:100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:rotate-180:where([dir=rtl],[dir=rtl] *){--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:flex-row-reverse:where([dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.rtl\:divide-x-reverse:where([dir=rtl],[dir=rtl] *)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:1}@media (min-width:1024px){.rtl\:lg\:-translate-x-0:where([dir=rtl],[dir=rtl] *){--tw-translate-x:-0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:lg\:translate-x-full:where([dir=rtl],[dir=rtl] *){--tw-translate-x:100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}}.\[\&\.trix-active\]\:bg-gray-50.trix-active{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.\[\&\.trix-active\]\:text-primary-600.trix-active{--tw-text-opacity:1;color:rgba(var(--primary-600),var(--tw-text-opacity,1))}.dark\:\[\&\.trix-active\]\:bg-white\/5.trix-active:is(.dark *){background-color:hsla(0,0%,100%,.05)}.dark\:\[\&\.trix-active\]\:text-primary-400.trix-active:is(.dark *){--tw-text-opacity:1;color:rgba(var(--primary-400),var(--tw-text-opacity,1))}.\[\&\:\:-ms-reveal\]\:hidden::-ms-reveal{display:none}.\[\&\:not\(\:first-of-type\)\]\:border-s:not(:first-of-type){border-inline-start-width:1px}.\[\&\:not\(\:has\(\.fi-ac-action\:focus\)\)\]\:focus-within\:ring-2:focus-within:not(:has(.fi-ac-action:focus)){--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.\[\&\:not\(\:has\(\.fi-ac-action\:focus\)\)\]\:focus-within\:ring-danger-600:focus-within:not(:has(.fi-ac-action:focus)){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-600),var(--tw-ring-opacity,1))}.\[\&\:not\(\:has\(\.fi-ac-action\:focus\)\)\]\:focus-within\:ring-primary-600:focus-within:not(:has(.fi-ac-action:focus)){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-600),var(--tw-ring-opacity,1))}.dark\:\[\&\:not\(\:has\(\.fi-ac-action\:focus\)\)\]\:focus-within\:ring-danger-500:focus-within:not(:has(.fi-ac-action:focus)):is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--danger-500),var(--tw-ring-opacity,1))}.dark\:\[\&\:not\(\:has\(\.fi-ac-action\:focus\)\)\]\:focus-within\:ring-primary-500:focus-within:not(:has(.fi-ac-action:focus)):is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity,1))}.\[\&\:not\(\:last-of-type\)\]\:border-e:not(:last-of-type){border-inline-end-width:1px}.\[\&\:not\(\:nth-child\(1_of_\.fi-btn\)\)\]\:shadow-\[-1px_0_0_0_theme\(colors\.gray\.200\)\]:not(:nth-child(1 of .fi-btn)){--tw-shadow:-1px 0 0 0 rgba(var(--gray-200),1);--tw-shadow-colored:-1px 0 0 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark\:\[\&\:not\(\:nth-child\(1_of_\.fi-btn\)\)\]\:shadow-\[-1px_0_0_0_theme\(colors\.white\/20\%\)\]:not(:nth-child(1 of .fi-btn)):is(.dark *){--tw-shadow:-1px 0 0 0 hsla(0,0%,100%,.2);--tw-shadow-colored:-1px 0 0 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.\[\&\:not\(\:nth-last-child\(1_of_\.fi-btn\)\)\]\:me-px:not(:nth-last-child(1 of .fi-btn)){margin-inline-end:1px}.\[\&\:nth-child\(1_of_\.fi-btn\)\]\:rounded-s-lg:nth-child(1 of .fi-btn){border-end-start-radius:.5rem;border-start-start-radius:.5rem}.\[\&\:nth-last-child\(1_of_\.fi-btn\)\]\:rounded-e-lg:nth-last-child(1 of .fi-btn){border-end-end-radius:.5rem;border-start-end-radius:.5rem}.\[\&\>\*\:first-child\]\:relative>:first-child{position:relative}.\[\&\>\*\:first-child\]\:mt-0>:first-child{margin-top:0}.\[\&\>\*\:first-child\]\:before\:absolute>:first-child:before{content:var(--tw-content);position:absolute}.\[\&\>\*\:first-child\]\:before\:inset-y-0>:first-child:before{bottom:0;content:var(--tw-content);top:0}.\[\&\>\*\:first-child\]\:before\:start-0>:first-child:before{content:var(--tw-content);inset-inline-start:0}.\[\&\>\*\:first-child\]\:before\:w-0\.5>:first-child:before{content:var(--tw-content);width:.125rem}.\[\&\>\*\:first-child\]\:before\:bg-primary-600>:first-child:before{--tw-bg-opacity:1;background-color:rgba(var(--primary-600),var(--tw-bg-opacity,1));content:var(--tw-content)}.\[\&\>\*\:first-child\]\:dark\:before\:bg-primary-500:is(.dark *)>:first-child:before{--tw-bg-opacity:1;background-color:rgba(var(--primary-500),var(--tw-bg-opacity,1));content:var(--tw-content)}.\[\&\>\*\:last-child\]\:mb-0>:last-child{margin-bottom:0}.\[\&_\.choices\\_\\_inner\]\:ps-0 .choices__inner{padding-inline-start:0}.\[\&_\.fi-badge-delete-button\]\:hidden .fi-badge-delete-button{display:none}.\[\&_\.filepond--root\]\:font-sans .filepond--root{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.\[\&_optgroup\]\:bg-white optgroup{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.\[\&_optgroup\]\:dark\:bg-gray-900:is(.dark *) optgroup{--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity,1))}.\[\&_option\]\:bg-white option{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.\[\&_option\]\:dark\:bg-gray-900:is(.dark *) option{--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity,1))}:checked+*>.\[\:checked\+\*\>\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}@media(hover:hover){.\[\@media\(hover\:hover\)\]\:transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[\@media\(hover\:hover\)\]\:duration-75{transition-duration:75ms}}input:checked+.\[input\:checked\+\&\]\:bg-custom-600{--tw-bg-opacity:1;background-color:rgba(var(--c-600),var(--tw-bg-opacity,1))}input:checked+.\[input\:checked\+\&\]\:bg-gray-400{--tw-bg-opacity:1;background-color:rgba(var(--gray-400),var(--tw-bg-opacity,1))}input:checked+.\[input\:checked\+\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}input:checked+.\[input\:checked\+\&\]\:ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:checked+.\[input\:checked\+\&\]\:hover\:bg-custom-500:hover{--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity,1))}input:checked+.\[input\:checked\+\&\]\:hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgba(var(--gray-300),var(--tw-bg-opacity,1))}input:checked+.dark\:\[input\:checked\+\&\]\:bg-custom-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity,1))}input:checked+.dark\:\[input\:checked\+\&\]\:bg-gray-600:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-600),var(--tw-bg-opacity,1))}input:checked+.dark\:\[input\:checked\+\&\]\:hover\:bg-custom-400:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-400),var(--tw-bg-opacity,1))}input:checked+.dark\:\[input\:checked\+\&\]\:hover\:bg-gray-500:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-500),var(--tw-bg-opacity,1))}input:checked:focus-visible+.\[input\:checked\:focus-visible\+\&\]\:ring-custom-500\/50{--tw-ring-color:rgba(var(--c-500),0.5)}input:checked:focus-visible+.dark\:\[input\:checked\:focus-visible\+\&\]\:ring-custom-400\/50:is(.dark *){--tw-ring-color:rgba(var(--c-400),0.5)}input:focus-visible+.\[input\:focus-visible\+\&\]\:z-10{z-index:10}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-gray-950\/10{--tw-ring-color:rgba(var(--gray-950),0.1)}input:focus-visible+.dark\:\[input\:focus-visible\+\&\]\:ring-white\/20:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.2)} \ No newline at end of file diff --git a/public/css/filament/forms/forms.css b/public/css/filament/forms/forms.css deleted file mode 100644 index 5b26f40..0000000 --- a/public/css/filament/forms/forms.css +++ /dev/null @@ -1,49 +0,0 @@ -input::-webkit-datetime-edit{display:block;padding:0}.cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}.filepond--assistant{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.filepond--browser.filepond--browser{font-size:0;left:1em;margin:0;opacity:0;padding:0;position:absolute;top:1.75em;width:calc(100% - 2em)}.filepond--data{border:none;contain:strict;height:0;margin:0;padding:0;visibility:hidden;width:0}.filepond--data,.filepond--drip{pointer-events:none;position:absolute}.filepond--drip{background:#00000003;border-radius:.5em;inset:0;opacity:.1;overflow:hidden}.filepond--drip-blob{background:#292625;border-radius:50%;height:8em;margin-left:-4em;margin-top:-4em;transform-origin:center center;width:8em}.filepond--drip-blob,.filepond--drop-label{left:0;position:absolute;top:0;will-change:transform,opacity}.filepond--drop-label{align-items:center;color:#4f4f4f;display:flex;height:0;justify-content:center;margin:0;right:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.filepond--drop-label.filepond--drop-label label{display:block;margin:0;padding:.5em}.filepond--drop-label label{cursor:default;font-size:.875em;font-weight:400;line-height:1.5;text-align:center}.filepond--label-action{-webkit-text-decoration-skip:ink;cursor:pointer;text-decoration:underline;text-decoration-color:#a7a4a4;text-decoration-skip-ink:auto}.filepond--root[data-disabled] .filepond--drop-label label{opacity:.5}.filepond--file-action-button.filepond--file-action-button{border:none;font-family:inherit;font-size:1em;height:1.625em;line-height:inherit;margin:0;outline:none;padding:0;width:1.625em;will-change:transform,opacity}.filepond--file-action-button.filepond--file-action-button span{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.filepond--file-action-button.filepond--file-action-button svg{height:100%;width:100%}.filepond--file-action-button.filepond--file-action-button:after{content:"";inset:-.75em;position:absolute}.filepond--file-action-button{background-color:#00000080;background-image:none;border-radius:50%;box-shadow:0 0 #fff0;color:#fff;cursor:auto;transition:box-shadow .25s ease-in}.filepond--file-action-button:focus,.filepond--file-action-button:hover{box-shadow:0 0 0 .125em #ffffffe6}.filepond--file-action-button[disabled]{background-color:#00000040;color:#ffffff80}.filepond--file-action-button[hidden]{display:none}.filepond--file-info{align-items:flex-start;display:flex;flex:1;flex-direction:column;margin:0 .5em 0 0;min-width:0;pointer-events:none;position:static;-webkit-user-select:none;-moz-user-select:none;user-select:none;will-change:transform,opacity}.filepond--file-info *{margin:0}.filepond--file-info .filepond--file-info-main{font-size:.75em;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%}.filepond--file-info .filepond--file-info-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out;white-space:nowrap}.filepond--file-info .filepond--file-info-sub:empty{display:none}.filepond--file-status{align-items:flex-end;display:flex;flex-direction:column;flex-grow:0;flex-shrink:0;margin:0;min-width:2.25em;pointer-events:none;position:static;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;will-change:transform,opacity}.filepond--file-status *{margin:0;white-space:nowrap}.filepond--file-status .filepond--file-status-main{font-size:.75em;line-height:1.2}.filepond--file-status .filepond--file-status-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out}.filepond--file-wrapper.filepond--file-wrapper{border:none;height:100%;margin:0;min-width:0;padding:0}.filepond--file-wrapper.filepond--file-wrapper>legend{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.filepond--file{align-items:flex-start;border-radius:.5em;color:#fff;display:flex;height:100%;padding:.5625em;position:static}.filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.filepond--file .filepond--processing-complete-indicator{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:3}.filepond--file .filepond--file-action-button,.filepond--file .filepond--processing-complete-indicator,.filepond--file .filepond--progress-indicator{position:absolute}.filepond--file [data-align*=left]{left:.5625em}.filepond--file [data-align*=right]{right:.5625em}.filepond--file [data-align*=center]{left:calc(50% - .8125em)}.filepond--file [data-align*=bottom]{bottom:1.125em}.filepond--file [data-align=center]{top:calc(50% - .8125em)}.filepond--file .filepond--progress-indicator{margin-top:.1875em}.filepond--file .filepond--progress-indicator[data-align*=right]{margin-right:.1875em}.filepond--file .filepond--progress-indicator[data-align*=left]{margin-left:.1875em}[data-filepond-item-state*=error] .filepond--file-info,[data-filepond-item-state*=invalid] .filepond--file-info,[data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}[data-filepond-item-state~=processing] .filepond--file-status-sub{opacity:0}[data-filepond-item-state~=processing] .filepond--action-abort-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-error] .filepond--file-status-sub{opacity:0}[data-filepond-item-state=processing-error] .filepond--action-retry-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{animation:fall .5s linear .125s both}[data-filepond-item-state=processing-complete] .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-complete] .filepond--file-info-sub,[data-filepond-item-state=processing-complete] .filepond--processing-complete-indicator:not([style*=hidden])~.filepond--file-status .filepond--file-status-sub{opacity:0}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-info .filepond--file-info-sub{opacity:.5}[data-filepond-item-state*=error] .filepond--file-wrapper,[data-filepond-item-state*=error] .filepond--panel,[data-filepond-item-state*=invalid] .filepond--file-wrapper,[data-filepond-item-state*=invalid] .filepond--panel{animation:shake .65s linear both}[data-filepond-item-state*=busy] .filepond--progress-indicator svg{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}@keyframes shake{10%,90%{transform:translate(-.0625em)}20%,80%{transform:translate(.125em)}30%,50%,70%{transform:translate(-.25em)}40%,60%{transform:translate(.25em)}}@keyframes fall{0%{animation-timing-function:ease-out;opacity:0;transform:scale(.5)}70%{animation-timing-function:ease-in-out;opacity:1;transform:scale(1.1)}to{animation-timing-function:ease-out;transform:scale(1)}}.filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.filepond--hopper[data-hopper-state=drag-over]:after{content:"";inset:0;position:absolute;z-index:100}.filepond--progress-indicator{z-index:103}.filepond--file-action-button{z-index:102}.filepond--file-status{z-index:101}.filepond--file-info{z-index:100}.filepond--item{left:0;margin:.25em;padding:0;position:absolute;right:0;top:0;touch-action:auto;will-change:transform,opacity;z-index:1}.filepond--item>.filepond--panel{z-index:-1}.filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em #00000040}.filepond--item>.filepond--file-wrapper,.filepond--item>.filepond--panel{transition:opacity .15s ease-out}.filepond--item[data-drag-state]{cursor:grab}.filepond--item[data-drag-state]>.filepond--panel{box-shadow:0 0 0 transparent;transition:box-shadow .125s ease-in-out}.filepond--item[data-drag-state=drag]{cursor:grabbing}.filepond--item[data-drag-state=drag]>.filepond--panel{box-shadow:0 .125em .3125em #00000053}.filepond--item[data-drag-state]:not([data-drag-state=idle]){z-index:2}.filepond--item-panel{background-color:#64605e}[data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}[data-filepond-item-state*=error] .filepond--item-panel,[data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.filepond--item-panel{border-radius:.5em;transition:background-color .25s}.filepond--list-scroller{left:0;margin:0;position:absolute;right:0;top:0;will-change:transform}.filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.filepond--list-scroller[data-state=overflow]{-webkit-overflow-scrolling:touch;-webkit-mask:linear-gradient(180deg,#000 calc(100% - .5em),transparent);mask:linear-gradient(180deg,#000 calc(100% - .5em),transparent);overflow-x:hidden;overflow-y:scroll}.filepond--list-scroller::-webkit-scrollbar{background:transparent}.filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.filepond--list-scroller::-webkit-scrollbar-thumb{background-clip:content-box;background-color:#0000004d;border:.3125em solid transparent;border-radius:99999px}.filepond--list.filepond--list{list-style-type:none;margin:0;padding:0;position:absolute;top:0;will-change:transform}.filepond--list{left:.75em;right:.75em}.filepond--root[data-style-panel-layout~=integrated]{height:100%;margin:0;max-width:none;width:100%}.filepond--root[data-style-panel-layout~=circle] .filepond--panel-root,.filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root{border-radius:0}.filepond--root[data-style-panel-layout~=circle] .filepond--panel-root>*,.filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root>*{display:none}.filepond--root[data-style-panel-layout~=circle] .filepond--drop-label,.filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{align-items:center;bottom:0;display:flex;height:auto;justify-content:center;z-index:7}.filepond--root[data-style-panel-layout~=circle] .filepond--item-panel,.filepond--root[data-style-panel-layout~=integrated] .filepond--item-panel{display:none}.filepond--root[data-style-panel-layout~=compact] .filepond--list-scroller,.filepond--root[data-style-panel-layout~=integrated] .filepond--list-scroller{height:100%;margin-bottom:0;margin-top:0;overflow:hidden}.filepond--root[data-style-panel-layout~=compact] .filepond--list,.filepond--root[data-style-panel-layout~=integrated] .filepond--list{height:100%;left:0;right:0}.filepond--root[data-style-panel-layout~=compact] .filepond--item,.filepond--root[data-style-panel-layout~=integrated] .filepond--item{margin:0}.filepond--root[data-style-panel-layout~=compact] .filepond--file-wrapper,.filepond--root[data-style-panel-layout~=integrated] .filepond--file-wrapper{height:100%}.filepond--root[data-style-panel-layout~=compact] .filepond--drop-label,.filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{z-index:7}.filepond--root[data-style-panel-layout~=circle]{border-radius:99999rem;overflow:hidden}.filepond--root[data-style-panel-layout~=circle]>.filepond--panel{border-radius:inherit}.filepond--root[data-style-panel-layout~=circle] .filepond--file-info,.filepond--root[data-style-panel-layout~=circle] .filepond--file-status,.filepond--root[data-style-panel-layout~=circle]>.filepond--panel>*{display:none}@media not all and (min-resolution:.001dpcm){@supports (-webkit-appearance:none) and (stroke-color:transparent){.filepond--root[data-style-panel-layout~=circle]{will-change:transform}}}.filepond--panel-root{background-color:#f1f0ef;border-radius:.5em}.filepond--panel{height:100%!important;left:0;margin:0;pointer-events:none;position:absolute;right:0;top:0}.filepond-panel:not([data-scalable=false]){height:auto!important}.filepond--panel[data-scalable=false]>div{display:none}.filepond--panel[data-scalable=true]{background-color:transparent!important;border:none!important;transform-style:preserve-3d}.filepond--panel-bottom,.filepond--panel-center,.filepond--panel-top{left:0;margin:0;padding:0;position:absolute;right:0;top:0}.filepond--panel-bottom,.filepond--panel-top{height:.5em}.filepond--panel-top{border-bottom:none!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.filepond--panel-top:after{background-color:inherit;bottom:-1px;content:"";height:2px;left:0;position:absolute;right:0}.filepond--panel-bottom,.filepond--panel-center{backface-visibility:hidden;transform:translate3d(0,.5em,0);transform-origin:left top;will-change:transform}.filepond--panel-bottom{border-top:none!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.filepond--panel-bottom:before{background-color:inherit;content:"";height:2px;left:0;position:absolute;right:0;top:-1px}.filepond--panel-center{border-bottom:none!important;border-radius:0!important;border-top:none!important;height:100px!important}.filepond--panel-center:not([style]){visibility:hidden}.filepond--progress-indicator{color:#fff;height:1.25em;margin:0;pointer-events:none;position:static;width:1.25em;will-change:transform,opacity}.filepond--progress-indicator svg{height:100%;transform-box:fill-box;vertical-align:top;width:100%}.filepond--progress-indicator path{fill:none;stroke:currentColor}.filepond--list-scroller{z-index:6}.filepond--drop-label{z-index:5}.filepond--drip{z-index:3}.filepond--root>.filepond--panel{z-index:2}.filepond--browser{z-index:1}.filepond--root{box-sizing:border-box;contain:layout style size;direction:ltr;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:1rem;font-weight:450;line-height:normal;margin-bottom:1em;position:relative;text-align:left;text-rendering:optimizeLegibility}.filepond--root *{box-sizing:inherit;line-height:inherit}.filepond--root :not(text){font-size:inherit}.filepond--root[data-disabled]{pointer-events:none}.filepond--root[data-disabled] .filepond--list-scroller{pointer-events:all}.filepond--root[data-disabled] .filepond--list{pointer-events:none}.filepond--root .filepond--drop-label{min-height:4.75em}.filepond--root .filepond--list-scroller{margin-bottom:1em;margin-top:1em}.filepond--root .filepond--credits{bottom:-14px;color:inherit;font-size:11px;line-height:.85;opacity:.4;position:absolute;right:0;text-decoration:none;z-index:3}.filepond--root .filepond--credits[style]{bottom:auto;margin-top:14px;top:0}.filepond--action-edit-item.filepond--action-edit-item{height:2em;padding:.1875em;width:2em}.filepond--action-edit-item.filepond--action-edit-item[data-align*=center]{margin-left:-.1875em}.filepond--action-edit-item.filepond--action-edit-item[data-align*=bottom]{margin-bottom:-.1875em}.filepond--action-edit-item-alt{background:transparent;border:none;color:inherit;font-family:inherit;line-height:inherit;margin:0 0 0 .25em;outline:none;padding:0;pointer-events:all;position:absolute}.filepond--action-edit-item-alt svg{height:1.3125em;width:1.3125em}.filepond--action-edit-item-alt span{font-size:0;opacity:0}.filepond--root[data-style-panel-layout~=circle] .filepond--action-edit-item{opacity:1!important;visibility:visible!important}.filepond--image-preview-markup{left:0;position:absolute;top:0}.filepond--image-preview-wrapper{z-index:2}.filepond--image-preview-overlay{display:block;left:0;margin:0;max-height:7rem;min-height:5rem;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%;z-index:2}.filepond--image-preview-overlay svg{color:inherit;height:auto;max-height:inherit;width:100%}.filepond--image-preview-overlay-idle{color:#282828d9;mix-blend-mode:multiply}.filepond--image-preview-overlay-success{color:#369763;mix-blend-mode:normal}.filepond--image-preview-overlay-failure{color:#c44e47;mix-blend-mode:normal}@supports (-webkit-marquee-repetition:infinite) and ((-o-object-fit:fill) or (object-fit:fill)){.filepond--image-preview-overlay-idle{mix-blend-mode:normal}}.filepond--image-preview-wrapper{background:#00000003;border-radius:.45em;height:100%;left:0;margin:0;overflow:hidden;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.filepond--image-preview{align-items:center;background:#222;display:flex;height:100%;left:0;pointer-events:none;position:absolute;top:0;width:100%;will-change:transform,opacity;z-index:1}.filepond--image-clip{margin:0 auto;overflow:hidden;position:relative}.filepond--image-clip[data-transparency-indicator=grid] canvas,.filepond--image-clip[data-transparency-indicator=grid] img{background-color:#fff;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' fill='%23eee'%3E%3Cpath d='M0 0h50v50H0M50 50h50v50H50'/%3E%3C/svg%3E");background-size:1.25em 1.25em}.filepond--image-bitmap,.filepond--image-vector{left:0;position:absolute;top:0;will-change:transform}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper{border-radius:0}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview{align-items:center;display:flex;height:100%;justify-content:center}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper{border-radius:99999rem}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay{bottom:0;top:auto;transform:scaleY(-1)}.filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*=center]){margin-bottom:.325em}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left]{left:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right]{right:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right]{margin-bottom:.5125em}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center]{margin-bottom:.1875em;margin-left:.1875em;margin-top:0}.filepond--media-preview audio{display:none}.filepond--media-preview .audioplayer{margin:2.3em auto auto;width:calc(100% - 1.4em)}.filepond--media-preview .playpausebtn{background-position:50%;background-repeat:no-repeat;border:none;border-radius:25px;cursor:pointer;float:left;height:25px;margin-right:.3em;margin-top:.3em;outline:none;width:25px}.filepond--media-preview .playpausebtn:hover{background-color:#00000080}.filepond--media-preview .play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAyElEQVQ4T9XUsWoCQRRG4XPaFL5SfIy8gKYKBCysrax8Ahs7qzQ2qVIFOwsrsbEWLEK6EBFGBrIQhN2d3dnGgalm+Jh7789Ix8uOPe4YDCH0gZ66atKW0pJDCE/AEngDXtRjCpwCRucbGANzNVTBqWBhfAJDdV+GNgWj8wtM41bPt3AbsDB2f69d/0dzwC0wUDe54A8wAWbqJbfkD+BZPeQO5QsYqYu6LKb0MIb7VT3VYfG8CnwEHtT3FKi4c8e/TZMyk3LYFrwCgMdHFbRDKS8AAAAASUVORK5CYII=)}.filepond--media-preview .pause{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAh0lEQVQ4T+2UsQkCURBE30PLMbAMMResQrAPsQ0TK9AqDKxGZeTLD74aGNwlhzfZssvADDMrPcOe+RggYZIJcG2s2KinMidZAvu6u6uzT8u+JCeZArfmcKUeK+EaONTdQy23bxgJX8aPHvIHsSnVuzTx36rn2pQFsGuqN//ZlK7vbIDvq6vkJ9yteBXzecYbAAAAAElFTkSuQmCC)}.filepond--media-preview .timeline{background:#ffffff4d;border-radius:15px;float:left;height:3px;margin-top:1em;width:calc(100% - 2.5em)}.filepond--media-preview .playhead{background:#fff;border-radius:50%;height:13px;margin-top:-5px;width:13px}.filepond--media-preview-wrapper{background:#00000003;border-radius:.45em;height:100%;left:0;margin:0;overflow:hidden;pointer-events:auto;position:absolute;right:0;top:0}.filepond--media-preview-wrapper:before{background:linear-gradient(180deg,#000,#0000);content:" ";filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#000000",endColorstr="#00000000",GradientType=0);height:2em;position:absolute;width:100%;z-index:3}.filepond--media-preview{display:block;height:100%;position:relative;transform-origin:center center;width:100%;will-change:transform,opacity;z-index:1}.filepond--media-preview audio,.filepond--media-preview video{width:100%;will-change:transform}.filepond--root{--tw-bg-opacity:1;--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-color:rgba(var(--gray-950),0.1);background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-radius:.5rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);margin-bottom:0;overflow:hidden}.filepond--root:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.2);background-color:hsla(0,0%,100%,.05)}.filepond--root[data-disabled=disabled]{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.filepond--root[data-disabled=disabled]:is(.dark *){--tw-ring-color:hsla(0,0%,100%,.1);background-color:transparent}.filepond--panel-root{background-color:transparent}.filepond--drop-label label{--tw-text-opacity:1;color:rgba(var(--gray-600),var(--tw-text-opacity,1));font-size:.875rem;line-height:1.25rem;padding:.75rem!important}.filepond--drop-label label:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.filepond--label-action{--tw-text-opacity:1;color:rgba(var(--primary-600),var(--tw-text-opacity,1));font-weight:500;text-decoration-line:none;transition-duration:75ms;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.filepond--label-action:hover{--tw-text-opacity:1;color:rgba(var(--primary-500),var(--tw-text-opacity,1))}.filepond--label-action:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.filepond--label-action:hover:is(.dark *){--tw-text-opacity:1;color:rgba(var(--primary-500),var(--tw-text-opacity,1))}.filepond--drip-blob{--tw-bg-opacity:1;background-color:rgba(var(--gray-400),var(--tw-bg-opacity,1))}.filepond--drip-blob:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-500),var(--tw-bg-opacity,1))}.filepond--root[data-style-panel-layout=grid] .filepond--item{display:inline;width:calc(50% - .5rem)}@media (min-width:1024px){.filepond--root[data-style-panel-layout=grid] .filepond--item{width:calc(33.33% - .5rem)}}.filepond--download-icon{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));display:inline-block;height:1rem;margin-inline-end:.25rem;pointer-events:auto;vertical-align:bottom;width:1rem}.filepond--download-icon:hover{background-color:hsla(0,0%,100%,.7)}.filepond--download-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtNE03IDEwbDUgNSA1LTVNMTIgMTVWMyIvPjwvc3ZnPg==);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtNE03IDEwbDUgNSA1LTVNMTIgMTVWMyIvPjwvc3ZnPg==);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100%;mask-size:100%}.filepond--open-icon{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));display:inline-block;height:1rem;margin-inline-end:.25rem;pointer-events:auto;vertical-align:bottom;width:1rem}.filepond--open-icon:hover{background-color:hsla(0,0%,100%,.7)}.filepond--open-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJoLTYgdy02IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJoLTYgdy02IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100%;mask-size:100%}.filepond--file-action-button.filepond--action-edit-item{background-color:rgba(0,0,0,.5)}.cropper-drag-box.cropper-crop.cropper-modal{background-color:rgba(var(--gray-100),.5);opacity:1}.cropper-drag-box.cropper-crop.cropper-modal:is(.dark *){background-color:rgba(var(--gray-900),.8)}.fi-fo-file-upload-circle-cropper .cropper-face,.fi-fo-file-upload-circle-cropper .cropper-view-box{border-radius:50%}.CodeMirror{color:#000;direction:ltr;font-family:monospace;height:300px}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{background-color:#f7f7f7;border-right:1px solid #ddd;white-space:nowrap}.CodeMirror-linenumber{color:#999;min-width:20px;padding:0 3px 0 5px;text-align:right;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{background:#7e7;border:0!important;width:auto}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.cm-fat-cursor{caret-color:transparent}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{inset:-50px 0 0;overflow:hidden;position:absolute}.CodeMirror-ruler{border-left:1px solid #ccc;bottom:0;position:absolute;top:0}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:#ff96004d}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{background:#fff;overflow:hidden;position:relative}.CodeMirror-scroll{height:100%;margin-bottom:-50px;margin-right:-50px;outline:0;overflow:scroll!important;padding-bottom:50px;position:relative;z-index:0}.CodeMirror-sizer{border-right:50px solid transparent;position:relative}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{display:none;outline:0;position:absolute;z-index:6}.CodeMirror-vscrollbar{overflow-x:hidden;overflow-y:scroll;right:0;top:0}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-x:scroll;overflow-y:hidden}.CodeMirror-scrollbar-filler{bottom:0;right:0}.CodeMirror-gutter-filler{bottom:0;left:0}.CodeMirror-gutters{left:0;min-height:100%;position:absolute;top:0;z-index:3}.CodeMirror-gutter{display:inline-block;height:100%;margin-bottom:-50px;vertical-align:top;white-space:normal}.CodeMirror-gutter-wrapper{background:0 0!important;border:none!important;position:absolute;z-index:4}.CodeMirror-gutter-background{bottom:0;position:absolute;top:0;z-index:4}.CodeMirror-gutter-elt{cursor:default;position:absolute;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{word-wrap:normal;-webkit-tap-highlight-color:transparent;background:0 0;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-variant-ligatures:contextual;line-height:inherit;margin:0;overflow:visible;position:relative;white-space:pre;z-index:2}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{inset:0;position:absolute;z-index:0}.CodeMirror-linewidget{padding:.1px;position:relative;z-index:2}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{box-sizing:content-box}.CodeMirror-measure{height:0;overflow:hidden;position:absolute;visibility:hidden;width:100%}.CodeMirror-cursor{pointer-events:none;position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{position:relative;visibility:hidden;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:#ff06}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:0 0}.EasyMDEContainer{display:block}.CodeMirror-rtl pre{direction:rtl}.EasyMDEContainer.sided--no-fullscreen{display:flex;flex-direction:row;flex-wrap:wrap}.EasyMDEContainer .CodeMirror{word-wrap:break-word;border:1px solid #ced4da;border-bottom-left-radius:4px;border-bottom-right-radius:4px;box-sizing:border-box;font:inherit;height:auto;padding:10px;z-index:0}.EasyMDEContainer .CodeMirror-scroll{cursor:text}.EasyMDEContainer .CodeMirror-fullscreen{background:#fff;border-bottom-right-radius:0!important;border-right:none!important;height:auto;inset:50px 0 0;position:fixed!important;z-index:8}.EasyMDEContainer .CodeMirror-sided{width:50%!important}.EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided{border-bottom-right-radius:0;border-right:none!important;flex:1 1 auto;position:relative}.EasyMDEContainer .CodeMirror-placeholder{opacity:.5}.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected{background:#d9d9d9}.editor-toolbar{border-left:1px solid #ced4da;border-right:1px solid #ced4da;border-top:1px solid #ced4da;border-top-left-radius:4px;border-top-right-radius:4px;padding:9px 10px;position:relative;-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none}.editor-toolbar.fullscreen{background:#fff;border:0;box-sizing:border-box;height:50px;left:0;opacity:1;padding-bottom:10px;padding-top:10px;position:fixed;top:0;width:100%;z-index:9}.editor-toolbar.fullscreen:before{background:linear-gradient(90deg,#fff 0,#fff0);height:50px;left:0;margin:0;padding:0;position:fixed;top:0;width:20px}.editor-toolbar.fullscreen:after{background:linear-gradient(90deg,#fff0 0,#fff);height:50px;margin:0;padding:0;position:fixed;right:0;top:0;width:20px}.EasyMDEContainer.sided--no-fullscreen .editor-toolbar{width:100%}.editor-toolbar .easymde-dropdown,.editor-toolbar button{background:0 0;border:1px solid transparent;border-radius:3px;cursor:pointer;display:inline-block;height:30px;margin:0;padding:0;text-align:center;text-decoration:none!important}.editor-toolbar button{font-weight:700;min-width:30px;padding:0 6px;white-space:nowrap}.editor-toolbar button.active,.editor-toolbar button:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar i.separator{border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;display:inline-block;margin:0 6px;text-indent:-10px;width:0}.editor-toolbar button:after{font-family:Arial,Helvetica Neue,Helvetica,sans-serif;font-size:65%;position:relative;top:2px;vertical-align:text-bottom}.editor-toolbar button.heading-1:after{content:"1"}.editor-toolbar button.heading-2:after{content:"2"}.editor-toolbar button.heading-3:after{content:"3"}.editor-toolbar button.heading-bigger:after{content:"\25b2"}.editor-toolbar button.heading-smaller:after{content:"\25bc"}.editor-toolbar.disabled-for-preview button:not(.no-disable){opacity:.6;pointer-events:none}@media only screen and (max-width:700px){.editor-toolbar i.no-mobile{display:none}}.editor-statusbar{color:#959694;font-size:12px;padding:8px 10px;text-align:right}.EasyMDEContainer.sided--no-fullscreen .editor-statusbar{width:100%}.editor-statusbar span{display:inline-block;margin-left:1em;min-width:4em}.editor-statusbar .lines:before{content:"lines: "}.editor-statusbar .words:before{content:"words: "}.editor-statusbar .characters:before{content:"characters: "}.editor-preview-full{height:100%;left:0;position:absolute;top:0;width:100%;z-index:7}.editor-preview-full,.editor-preview-side{box-sizing:border-box;display:none;overflow:auto}.editor-preview-side{word-wrap:break-word;border:1px solid #ddd;bottom:0;position:fixed;right:0;top:50px;width:50%;z-index:9}.editor-preview-active-side{display:block}.EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side{flex:1 1 auto;height:auto;position:static}.editor-preview-active{display:block}.editor-preview{background:#fafafa;padding:10px}.editor-preview>p{margin-top:0}.editor-preview pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th{border:1px solid #ddd;padding:5px}.cm-s-easymde .cm-tag{color:#63a35c}.cm-s-easymde .cm-attribute{color:#795da3}.cm-s-easymde .cm-string{color:#183691}.cm-s-easymde .cm-header-1{font-size:calc(1.375rem + 1.5vw)}.cm-s-easymde .cm-header-2{font-size:calc(1.325rem + .9vw)}.cm-s-easymde .cm-header-3{font-size:calc(1.3rem + .6vw)}.cm-s-easymde .cm-header-4{font-size:calc(1.275rem + .3vw)}.cm-s-easymde .cm-header-5{font-size:1.25rem}.cm-s-easymde .cm-header-6{font-size:1rem}.cm-s-easymde .cm-header-1,.cm-s-easymde .cm-header-2,.cm-s-easymde .cm-header-3,.cm-s-easymde .cm-header-4,.cm-s-easymde .cm-header-5,.cm-s-easymde .cm-header-6{line-height:1.2;margin-bottom:.5rem}.cm-s-easymde .cm-comment{background:#0000000d;border-radius:2px}.cm-s-easymde .cm-link{color:#7f8c8d}.cm-s-easymde .cm-url{color:#aab2b3}.cm-s-easymde .cm-quote{color:#7f8c8d;font-style:italic}.editor-toolbar .easymde-dropdown{border:1px solid #fff;border-radius:0;position:relative}.editor-toolbar .easymde-dropdown,.editor-toolbar .easymde-dropdown:hover{background:linear-gradient(to bottom right,#fff 0 84%,#333 50% 100%)}.easymde-dropdown-content{background-color:#f9f9f9;box-shadow:0 8px 16px #0003;display:block;padding:8px;position:absolute;top:30px;visibility:hidden;z-index:2}.easymde-dropdown:active .easymde-dropdown-content,.easymde-dropdown:focus .easymde-dropdown-content,.easymde-dropdown:focus-within .easymde-dropdown-content{visibility:visible}.easymde-dropdown-content button{display:block}span[data-img-src]:after{background-image:var(--bg-image);background-repeat:no-repeat;background-size:contain;content:"";display:block;height:0;max-height:100%;max-width:100%;padding-top:var(--height);width:var(--width)}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:#ff000026}:root{--color-cm-red:#991b1b;--color-cm-orange:#9a3412;--color-cm-amber:#92400e;--color-cm-yellow:#854d0e;--color-cm-lime:#3f6212;--color-cm-green:#166534;--color-cm-emerald:#065f46;--color-cm-teal:#115e59;--color-cm-cyan:#155e75;--color-cm-sky:#075985;--color-cm-blue:#1e40af;--color-cm-indigo:#3730a3;--color-cm-violet:#5b21b6;--color-cm-purple:#6b21a8;--color-cm-fuchsia:#86198f;--color-cm-pink:#9d174d;--color-cm-rose:#9f1239;--color-cm-gray:#18181b;--color-cm-gray-muted:#71717a;--color-cm-gray-background:#e4e4e7}.dark{--color-cm-red:#f87171;--color-cm-orange:#fb923c;--color-cm-amber:#fbbf24;--color-cm-yellow:#facc15;--color-cm-lime:#a3e635;--color-cm-green:#4ade80;--color-cm-emerald:#4ade80;--color-cm-teal:#2dd4bf;--color-cm-cyan:#22d3ee;--color-cm-sky:#38bdf8;--color-cm-blue:#60a5fa;--color-cm-indigo:#818cf8;--color-cm-violet:#a78bfa;--color-cm-purple:#c084fc;--color-cm-fuchsia:#e879f9;--color-cm-pink:#f472b6;--color-cm-rose:#fb7185;--color-cm-gray:#fafafa;--color-cm-gray-muted:#a1a1aa;--color-cm-gray-background:#52525b}.cm-s-easymde .cm-comment{background-color:transparent;color:var(--color-cm-gray-muted)}.EasyMDEContainer .CodeMirror-cursor{border-color:currentColor}.dark .EasyMDEContainer .cm-s-easymde span.CodeMirror-selectedtext{filter:invert(100%)}.EasyMDEContainer .cm-s-easymde .cm-keyword{color:var(--color-cm-violet)}.EasyMDEContainer .cm-s-easymde .cm-atom{color:var(--color-cm-blue)}.EasyMDEContainer .cm-s-easymde .cm-number{color:var(--color-cm-green)}.EasyMDEContainer .cm-s-easymde .cm-def{color:var(--color-cm-blue)}.EasyMDEContainer .cm-s-easymde .cm-variable{color:var(--color-cm-yellow)}.EasyMDEContainer .cm-s-easymde .cm-variable-2{color:var(--color-cm-blue)}.EasyMDEContainer .cm-s-easymde .cm-variable-3{color:var(--color-cm-emerald)}.EasyMDEContainer .cm-s-easymde .cm-operator,.EasyMDEContainer .cm-s-easymde .cm-property{color:var(--color-cm-gray)}.EasyMDEContainer .cm-s-easymde .cm-string,.EasyMDEContainer .cm-s-easymde .cm-string-2{color:var(--color-cm-rose)}.EasyMDEContainer .cm-s-easymde .cm-meta{color:var(--color-cm-gray-muted)}.EasyMDEContainer .cm-s-easymde .cm-error{color:var(--color-cm-red)}.EasyMDEContainer .cm-s-easymde .cm-qualifier{color:var(--color-cm-gray-muted)}.EasyMDEContainer .cm-s-easymde .cm-builtin{color:var(--color-cm-violet)}.EasyMDEContainer .cm-s-easymde .cm-bracket{color:var(--color-cm-gray-muted)}.EasyMDEContainer .cm-s-easymde .cm-tag{color:var(--color-cm-green)}.EasyMDEContainer .cm-s-easymde .cm-attribute{color:var(--color-cm-blue)}.EasyMDEContainer .cm-s-easymde .cm-hr{color:var(--color-cm-gray-muted)}.EasyMDEContainer .cm-s-easymde .cm-formatting-quote{color:var(--color-cm-sky)}.EasyMDEContainer .cm-s-easymde .cm-formatting-quote+.cm-quote{color:var(--color-cm-gray-muted)}.EasyMDEContainer .cm-s-easymde .cm-formatting-list,.EasyMDEContainer .cm-s-easymde .cm-formatting-list+.cm-variable-2,.EasyMDEContainer .cm-s-easymde .cm-tab+.cm-variable-2{color:var(--color-cm-gray)}.EasyMDEContainer .cm-s-easymde .cm-link{color:var(--color-cm-blue)}.EasyMDEContainer .cm-s-easymde .cm-tag{color:var(--color-cm-red)}.EasyMDEContainer .cm-s-easymde .cm-attribute{color:var(--color-cm-amber)}.EasyMDEContainer .cm-s-easymde .cm-attribute+.cm-string{color:var(--color-cm-green)}.EasyMDEContainer .cm-s-easymde .cm-formatting-code+.cm-comment:not(.cm-formatting-code){background-color:var(--color-cm-gray-background);color:var(--color-cm-gray)}.EasyMDEContainer .cm-s-easymde .cm-header-1{font-size:1.875rem;line-height:2.25rem}.EasyMDEContainer .cm-s-easymde .cm-header-2{font-size:1.5rem;line-height:2rem}.EasyMDEContainer .cm-s-easymde .cm-header-3{font-size:1.25rem;line-height:1.75rem}.EasyMDEContainer .cm-s-easymde .cm-header-4{font-size:1.125rem;line-height:1.75rem}.EasyMDEContainer .cm-s-easymde .cm-header-5{font-size:1rem;line-height:1.5rem}.EasyMDEContainer .cm-s-easymde .cm-header-6{font-size:.875rem;line-height:1.25rem}.EasyMDEContainer .cm-s-easymde .cm-comment{background-image:none}.EasyMDEContainer .CodeMirror,.EasyMDEContainer .cm-s-easymde .cm-formatting-code-block,.EasyMDEContainer .cm-s-easymde .cm-tab+.cm-comment{background-color:transparent;color:inherit}.EasyMDEContainer .CodeMirror{border-style:none;padding:.375rem .75rem}.EasyMDEContainer .CodeMirror-scroll{height:auto}.EasyMDEContainer .editor-toolbar{--tw-border-opacity:1;border-color:rgba(var(--gray-200),var(--tw-border-opacity,1));border-radius:0;border-width:0 0 1px;-moz-column-gap:.25rem;column-gap:.25rem;display:flex;overflow-x:auto;padding:.5rem .625rem}.EasyMDEContainer .editor-toolbar:is(.dark *){border-color:hsla(0,0%,100%,.1)}.EasyMDEContainer .editor-toolbar button{border-radius:.5rem;border-style:none;cursor:pointer;display:grid;height:2rem;padding:0;place-content:center;transition-duration:75ms;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);width:2rem}.EasyMDEContainer .editor-toolbar button:hover{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar button:focus-visible{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar button:hover:is(.dark *){background-color:hsla(0,0%,100%,.05)}.EasyMDEContainer .editor-toolbar button:focus-visible:is(.dark *){background-color:hsla(0,0%,100%,.05)}.EasyMDEContainer .editor-toolbar button.active{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar button.active:is(.dark *){background-color:hsla(0,0%,100%,.05)}.EasyMDEContainer .editor-toolbar button:before{--tw-bg-opacity:1;background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1));display:block;height:1.25rem;width:1.25rem}.EasyMDEContainer .editor-toolbar button:is(.dark *):before{--tw-bg-opacity:1;background-color:rgba(var(--gray-300),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar button:before{content:"";-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.EasyMDEContainer .editor-toolbar button.active:before{--tw-bg-opacity:1;background-color:rgba(var(--primary-600),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar button.active:is(.dark *):before{--tw-bg-opacity:1;background-color:rgba(var(--primary-400),var(--tw-bg-opacity,1))}.EasyMDEContainer .editor-toolbar .separator{border-style:none;margin:0!important;width:.25rem}.EasyMDEContainer .editor-toolbar .bold:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M4 3a1 1 0 0 1 1-1h6a4.5 4.5 0 0 1 3.274 7.587A4.75 4.75 0 0 1 11.25 18H5a1 1 0 0 1-1-1V3Zm2.5 5.5v-4H11a2 2 0 1 1 0 4H6.5Zm0 2.5v4.5h4.75a2.25 2.25 0 0 0 0-4.5H6.5Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M4 3a1 1 0 0 1 1-1h6a4.5 4.5 0 0 1 3.274 7.587A4.75 4.75 0 0 1 11.25 18H5a1 1 0 0 1-1-1V3Zm2.5 5.5v-4H11a2 2 0 1 1 0 4H6.5Zm0 2.5v4.5h4.75a2.25 2.25 0 0 0 0-4.5H6.5Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .italic:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .strikethrough:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M11.617 3.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642 1.476-.152.566-.007 1.313.684 2.1.528.6 1.273 1.1 2.128 1.446h7.879a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5h3.813a5.976 5.976 0 0 1-.447-.456C5.18 7.479 4.798 6.231 5.11 5.066c.312-1.164 1.268-2.055 2.61-2.509 1.336-.451 2.877-.42 4.286-.043.856.23 1.684.592 2.409 1.074a.75.75 0 1 1-.83 1.25 6.723 6.723 0 0 0-1.968-.875Zm1.909 8.123a.75.75 0 0 1 1.015.309c.53.99.607 2.062.18 3.01-.421.94-1.289 1.648-2.441 2.038-1.336.452-2.877.42-4.286.043-1.409-.377-2.759-1.121-3.69-2.18a.75.75 0 1 1 1.127-.99c.696.791 1.765 1.403 2.952 1.721 1.186.318 2.418.323 3.416-.015.853-.288 1.34-.756 1.555-1.232.21-.467.205-1.049-.136-1.69a.75.75 0 0 1 .308-1.014Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M11.617 3.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642 1.476-.152.566-.007 1.313.684 2.1.528.6 1.273 1.1 2.128 1.446h7.879a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5h3.813a5.976 5.976 0 0 1-.447-.456C5.18 7.479 4.798 6.231 5.11 5.066c.312-1.164 1.268-2.055 2.61-2.509 1.336-.451 2.877-.42 4.286-.043.856.23 1.684.592 2.409 1.074a.75.75 0 1 1-.83 1.25 6.723 6.723 0 0 0-1.968-.875Zm1.909 8.123a.75.75 0 0 1 1.015.309c.53.99.607 2.062.18 3.01-.421.94-1.289 1.648-2.441 2.038-1.336.452-2.877.42-4.286.043-1.409-.377-2.759-1.121-3.69-2.18a.75.75 0 1 1 1.127-.99c.696.791 1.765 1.403 2.952 1.721 1.186.318 2.418.323 3.416-.015.853-.288 1.34-.756 1.555-1.232.21-.467.205-1.049-.136-1.69a.75.75 0 0 1 .308-1.014Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .link:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath d='M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z'/%3E%3Cpath d='M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath d='M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z'/%3E%3Cpath d='M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .heading:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4ZM13 8.75a.75.75 0 0 1 .75-.75h1.75a.75.75 0 0 1 .75.75v5.75h1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5h1v-5h-1a.75.75 0 0 1-.75-.75Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4ZM13 8.75a.75.75 0 0 1 .75-.75h1.75a.75.75 0 0 1 .75.75v5.75h1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5h1v-5h-1a.75.75 0 0 1-.75-.75Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .quote:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902 1.168.188 2.352.327 3.55.414.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.783.783 0 0 1 .642-.413 41.102 41.102 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41.289 41.289 0 0 0 10 2ZM6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902 1.168.188 2.352.327 3.55.414.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.783.783 0 0 1 .642-.413 41.102 41.102 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41.289 41.289 0 0 0 10 2ZM6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .code:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Zm7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06Zm-2.343-3.209a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Zm7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06Zm-2.343-3.209a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .unordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M6 4.75A.75.75 0 0 1 6.75 4h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 4.75ZM6 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 10Zm0 5.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75a.75.75 0 0 1-.75-.75ZM1.99 4.75a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01Zm0 10.5a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01Zm0-5.25a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1V10Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M6 4.75A.75.75 0 0 1 6.75 4h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 4.75ZM6 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 10Zm0 5.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75a.75.75 0 0 1-.75-.75ZM1.99 4.75a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01Zm0 10.5a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01Zm0-5.25a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1V10Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .ordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath d='M3 1.25a.75.75 0 0 0 0 1.5h.25v2.5a.75.75 0 0 0 1.5 0V2A.75.75 0 0 0 4 1.25H3Zm-.03 7.404a3.5 3.5 0 0 1 1.524-.12.034.034 0 0 1-.012.012L2.415 9.579A.75.75 0 0 0 2 10.25v1c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371 0-.647-.429-1.327-1.193-1.451a5.03 5.03 0 0 0-2.277.155.75.75 0 0 0 .44 1.434ZM7.75 3a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm-5.125-1.625a.75.75 0 0 0 0 1.5h1.5a.125.125 0 0 1 0 .25H3.5a.75.75 0 0 0 0 1.5h.625a.125.125 0 0 1 0 .25h-1.5a.75.75 0 0 0 0 1.5h1.5a1.625 1.625 0 0 0 1.37-2.5 1.625 1.625 0 0 0-1.37-2.5h-1.5Z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath d='M3 1.25a.75.75 0 0 0 0 1.5h.25v2.5a.75.75 0 0 0 1.5 0V2A.75.75 0 0 0 4 1.25H3Zm-.03 7.404a3.5 3.5 0 0 1 1.524-.12.034.034 0 0 1-.012.012L2.415 9.579A.75.75 0 0 0 2 10.25v1c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371 0-.647-.429-1.327-1.193-1.451a5.03 5.03 0 0 0-2.277.155.75.75 0 0 0 .44 1.434ZM7.75 3a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5Zm-5.125-1.625a.75.75 0 0 0 0 1.5h1.5a.125.125 0 0 1 0 .25H3.5a.75.75 0 0 0 0 1.5h.625a.125.125 0 0 1 0 .25h-1.5a.75.75 0 0 0 0 1.5h1.5a1.625 1.625 0 0 0 1.37-2.5 1.625 1.625 0 0 0-1.37-2.5h-1.5Z'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .table:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25l.01 9.5A2.25 2.25 0 0 1 16.76 17H3.26A2.267 2.267 0 0 1 1 14.74l-.01-9.5Zm8.26 9.52v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.615c0 .414.336.75.75.75h5.373a.75.75 0 0 0 .627-.74Zm1.5 0a.75.75 0 0 0 .627.74h5.373a.75.75 0 0 0 .75-.75v-.615a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625Zm6.75-3.63v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75Zm-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75ZM17.5 7.5v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75Zm-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25l.01 9.5A2.25 2.25 0 0 1 16.76 17H3.26A2.267 2.267 0 0 1 1 14.74l-.01-9.5Zm8.26 9.52v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.615c0 .414.336.75.75.75h5.373a.75.75 0 0 0 .627-.74Zm1.5 0a.75.75 0 0 0 .627.74h5.373a.75.75 0 0 0 .75-.75v-.615a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625Zm6.75-3.63v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75Zm-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75ZM17.5 7.5v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75Zm-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .upload-image:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0l-2.97 2.97ZM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0l-2.97 2.97ZM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .undo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M7.793 2.232a.75.75 0 0 1-.025 1.06L3.622 7.25h10.003a5.375 5.375 0 0 1 0 10.75H10.75a.75.75 0 0 1 0-1.5h2.875a3.875 3.875 0 0 0 0-7.75H3.622l4.146 3.957a.75.75 0 0 1-1.036 1.085l-5.5-5.25a.75.75 0 0 1 0-1.085l5.5-5.25a.75.75 0 0 1 1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M7.793 2.232a.75.75 0 0 1-.025 1.06L3.622 7.25h10.003a5.375 5.375 0 0 1 0 10.75H10.75a.75.75 0 0 1 0-1.5h2.875a3.875 3.875 0 0 0 0-7.75H3.622l4.146 3.957a.75.75 0 0 1-1.036 1.085l-5.5-5.25a.75.75 0 0 1 0-1.085l5.5-5.25a.75.75 0 0 1 1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-toolbar .redo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M12.207 2.232a.75.75 0 0 0 .025 1.06l4.146 3.958H6.375a5.375 5.375 0 0 0 0 10.75H9.25a.75.75 0 0 0 0-1.5H6.375a3.875 3.875 0 0 1 0-7.75h10.003l-4.146 3.957a.75.75 0 0 0 1.036 1.085l5.5-5.25a.75.75 0 0 0 0-1.085l-5.5-5.25a.75.75 0 0 0-1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='size-5'%3E%3Cpath fill-rule='evenodd' d='M12.207 2.232a.75.75 0 0 0 .025 1.06l4.146 3.958H6.375a5.375 5.375 0 0 0 0 10.75H9.25a.75.75 0 0 0 0-1.5H6.375a3.875 3.875 0 0 1 0-7.75h10.003l-4.146 3.957a.75.75 0 0 0 1.036 1.085l5.5-5.25a.75.75 0 0 0 0-1.085l-5.5-5.25a.75.75 0 0 0-1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.EasyMDEContainer .editor-statusbar{display:none}.fi-fo-rich-editor trix-toolbar .trix-dialogs{position:relative}.fi-fo-rich-editor trix-toolbar .trix-dialog{--tw-bg-opacity:1;--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1));border-radius:.5rem;bottom:auto;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);left:0;padding:.5rem;position:absolute;right:0;top:1rem}.fi-fo-rich-editor trix-toolbar .trix-dialog:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields{display:flex;flex-direction:column;gap:.5rem;width:100%}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-button-group{display:flex;gap:.5rem}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-color:rgba(var(--gray-950),0.1);background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-radius:.375rem;border-style:none;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);color:rgba(var(--gray-950),var(--tw-text-opacity,1));display:block;font-size:.875rem;line-height:1.25rem;outline:2px solid transparent;outline-offset:2px;padding-bottom:.375rem;padding-inline-end:.75rem;padding-top:.375rem;padding-inline-start:.75rem;transition-duration:75ms;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);width:100%}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input:focus-within{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-600),var(--tw-ring-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input:is(.dark *){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-color:hsla(0,0%,100%,.2);background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1));color:rgb(255 255 255/var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input:focus-within:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-600),var(--tw-ring-opacity,1))}@media (min-width:640px){.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-input{font-size:.875rem;line-height:1.5rem}}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-button-group .trix-button{--tw-bg-opacity:1;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-200),var(--tw-ring-opacity,1));background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1));border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);font-size:.75rem;line-height:1rem;padding:.125rem .5rem}.fi-fo-rich-editor trix-toolbar .trix-dialog__link-fields .trix-button-group .trix-button:is(.dark *){--tw-bg-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-600),var(--tw-ring-opacity,1));background-color:rgba(var(--gray-700),var(--tw-bg-opacity,1))}.fi-fo-rich-editor trix-editor:empty:before{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-editor:empty:is(.dark *):before{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.fi-fo-rich-editor trix-editor:empty:before{content:attr(placeholder)}.fi-fo-rich-editor trix-editor.prose :where(ol):not(:where([class~=not-prose] *)),.fi-fo-rich-editor trix-editor.prose :where(ul):not(:where([class~=not-prose] *)){padding-inline-end:0!important;padding-inline-start:1.625em!important}.fi-fo-rich-editor trix-editor.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-inline-end:0!important;padding-inline-start:.375em!important}select:not(.choices){background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E")}[dir=rtl] select{background-position:left .5rem center!important}.choices{outline:2px solid transparent;outline-offset:2px;position:relative}.choices [hidden]{display:none!important}.choices[data-type*=select-one] .has-no-choices{display:none}.choices[data-type*=select-one] .choices__input{display:block;margin:0;width:100%}.choices__inner{background-repeat:no-repeat;outline:2px solid transparent;outline-offset:2px;padding-bottom:.375rem;padding-inline-end:2rem;padding-top:.375rem;padding-inline-start:.75rem}@media (min-width:640px){.choices__inner{font-size:.875rem;line-height:1.5rem}}.choices__inner{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-size:1.5em 1.5em;&:has(.choices__button){padding-inline-end:3.5rem}}.choices.is-disabled .choices__inner{cursor:default}[dir=rtl] .choices__inner{background-position:left .5rem center}.choices__list--single{display:inline-block}.choices__list--single .choices__item{--tw-text-opacity:1;color:rgba(var(--gray-950),var(--tw-text-opacity,1))}.choices__list--single .choices__item:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.choices.is-disabled .choices__list--single .choices__item{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices.is-disabled .choices__list--single .choices__item:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.choices__list--multiple{display:flex;flex-wrap:wrap;gap:.375rem}.choices__list--multiple:not(:empty){margin-bottom:.25rem;margin-left:-.25rem;margin-right:-.25rem;padding-bottom:.125rem;padding-top:.125rem}.choices__list--multiple .choices__item{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-inset:inset;--tw-ring-color:rgba(var(--primary-600),0.1);align-items:center;background-color:rgba(var(--primary-50),var(--tw-bg-opacity,1));border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);color:rgba(var(--primary-600),var(--tw-text-opacity,1));display:inline-flex;font-size:.75rem;font-weight:500;gap:.25rem;line-height:1rem;padding:.25rem .5rem;word-break:break-all}.choices__list--multiple .choices__item:is(.dark *){--tw-text-opacity:1;--tw-ring-color:rgba(var(--primary-400),0.3);background-color:rgba(var(--primary-400),.1);color:rgba(var(--primary-400),var(--tw-text-opacity,1))}.choices__list--dropdown,.choices__list[aria-expanded]{--tw-bg-opacity:1;--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-color:rgba(var(--gray-950),0.05);background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-radius:.5rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);display:none;font-size:.875rem;line-height:1.25rem;margin-top:.5rem;overflow:hidden;overflow-wrap:break-word;position:absolute;top:100%;width:100%;will-change:visibility;z-index:10}.choices__list--dropdown:is(.dark *),.choices__list[aria-expanded]:is(.dark *){--tw-bg-opacity:1;--tw-ring-color:hsla(0,0%,100%,.1);background-color:rgba(var(--gray-900),var(--tw-bg-opacity,1))}.is-active.choices__list--dropdown,.is-active.choices__list[aria-expanded]{display:block;padding:.25rem}.choices__list--dropdown .choices__list,.choices__list[aria-expanded] .choices__list{max-height:15rem;overflow:auto;will-change:scroll-position}.choices__item--choice{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1));padding:.5rem;transition-duration:75ms;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.choices__item--choice:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.choices__item--choice.choices__item--selectable{--tw-text-opacity:1;border-radius:.375rem;color:rgba(var(--gray-950),var(--tw-text-opacity,1))}.choices__item--choice.choices__item--selectable:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.choices__list--dropdown .choices__item--selectable.is-highlighted,.choices__list[aria-expanded] .choices__item--selectable.is-highlighted{--tw-bg-opacity:1;background-color:rgba(var(--gray-50),var(--tw-bg-opacity,1))}.choices__list--dropdown .choices__item--selectable.is-highlighted:is(.dark *),.choices__list[aria-expanded] .choices__item--selectable.is-highlighted:is(.dark *){background-color:hsla(0,0%,100%,.05)}.choices__item{cursor:default}.choices__item--disabled{pointer-events:none}.choices__item--disabled:disabled{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices__item--disabled:disabled:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.choices.is-disabled .choices__placeholder.choices__item,.choices__placeholder.choices__item{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1));cursor:default}.choices.is-disabled .choices__placeholder.choices__item:is(.dark *),.choices__placeholder.choices__item:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices__button{background-color:transparent;background-position:50%;background-repeat:no-repeat;border-width:0;outline:2px solid transparent;outline-offset:2px;text-indent:-9999px}.choices[data-type*=select-one] .choices__button{height:1rem;inset-inline-end:0;margin-inline-end:2.25rem;opacity:.5;padding:0;position:absolute;transition-duration:75ms;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);width:1rem}.choices[data-type*=select-one] .choices__button:is(.dark *){opacity:.4}.choices[data-type*=select-one] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJtMi41OTIuMDQ0IDE4LjM2NCAxOC4zNjQtMi41NDggMi41NDhMLjA0NCAyLjU5MnoiLz48cGF0aCBkPSJNMCAxOC4zNjQgMTguMzY0IDBsMi41NDggMi41NDhMMi41NDggMjAuOTEyeiIvPjwvZz48L3N2Zz4=);background-size:.7142em .7142em;top:calc(50% - .5714em)}.dark .choices[data-type*=select-one] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJtMi41OTIuMDQ0IDE4LjM2NCAxOC4zNjQtMi41NDggMi41NDhMLjA0NCAyLjU5MnoiLz48cGF0aCBkPSJNMCAxOC4zNjQgMTguMzY0IDBsMi41NDggMi41NDhMMi41NDggMjAuOTEyeiIvPjwvZz48L3N2Zz4=)}.choices[data-type*=select-multiple] .choices__button{height:1rem;opacity:.5;width:1rem}.choices[data-type*=select-multiple] .choices__button:is(.dark *){opacity:.4}.choices[data-type*=select-multiple] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJtMi41OTIuMDQ0IDE4LjM2NCAxOC4zNjQtMi41NDggMi41NDhMLjA0NCAyLjU5MnoiLz48cGF0aCBkPSJNMCAxOC4zNjQgMTguMzY0IDBsMi41NDggMi41NDhMMi41NDggMjAuOTEyeiIvPjwvZz48L3N2Zz4=);background-size:.7142em .7142em}.dark .choices[data-type*=select-multiple] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJtMi41OTIuMDQ0IDE4LjM2NCAxOC4zNjQtMi41NDggMi41NDhMLjA0NCAyLjU5MnoiLz48cGF0aCBkPSJNMCAxOC4zNjQgMTguMzY0IDBsMi41NDggMi41NDhMMi41NDggMjAuOTEyeiIvPjwvZz48L3N2Zz4=)}.choices[data-type*=select-multiple] .choices__button:focus-visible,.choices[data-type*=select-multiple] .choices__button:hover,.choices[data-type*=select-one] .choices__button:focus-visible,.choices[data-type*=select-one] .choices__button:hover{opacity:.7}.choices[data-type*=select-multiple] .choices__button:focus-visible:is(.dark *),.choices[data-type*=select-multiple] .choices__button:hover:is(.dark *),.choices[data-type*=select-one] .choices__button:focus-visible:is(.dark *),.choices[data-type*=select-one] .choices__button:hover:is(.dark *){opacity:.6}.choices.is-disabled .choices__button,.choices[data-type*=select-one] .choices__item[data-value=""] .choices__button{display:none}.choices__input{--tw-text-opacity:1;background-color:transparent!important;border-style:none;color:rgba(var(--gray-950),var(--tw-text-opacity,1));font-size:1rem!important;line-height:1.5rem!important;padding:0!important;transition-duration:75ms;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.choices__input::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.choices__input::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.choices__input:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important}.choices__input:disabled{--tw-text-opacity:1;-webkit-text-fill-color:rgba(var(--gray-500),1);color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices__input:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.choices__input:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices__input:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.choices__input:disabled:is(.dark *){--tw-text-opacity:1;-webkit-text-fill-color:rgba(var(--gray-400),1);color:rgba(var(--gray-400),var(--tw-text-opacity,1))}@media (min-width:640px){.choices__input{font-size:.875rem!important;line-height:1.5rem}}.choices__list--dropdown .choices__input{padding:.5rem!important}.choices__input::-webkit-search-cancel-button,.choices__input::-webkit-search-decoration,.choices__input::-webkit-search-results-button,.choices__input::-webkit-search-results-decoration{display:none}.choices__input::-ms-clear,.choices__input::-ms-reveal{display:none;height:0;width:0}.choices__group{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1));padding:1rem .5rem .5rem}.choices__group:first-child{padding-top:.5rem}.choices__group:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity,1))}.webkit-calendar-picker-indicator\:opacity-0::-webkit-calendar-picker-indicator{opacity:0}/*! Bundled license information: - -cropperjs/dist/cropper.min.css: - (*! - * Cropper.js v1.6.2 - * https://fengyuanchen.github.io/cropperjs - * - * Copyright 2015-present Chen Fengyuan - * Released under the MIT license - * - * Date: 2024-04-21T07:43:02.731Z - *) - -filepond/dist/filepond.min.css: - (*! - * FilePond 4.32.8 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css: - (*! - * FilePondPluginImageEdit 1.6.3 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css: - (*! - * FilePondPluginImagePreview 4.6.12 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-media-preview/dist/filepond-plugin-media-preview.css: - (*! - * FilePondPluginmediaPreview 1.0.11 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit undefined for details. - *) - -easymde/dist/easymde.min.css: - (** - * easymde v2.20.0 - * Copyright Jeroen Akkerman - * @link https://github.com/ionaru/easy-markdown-editor - * @license MIT - *) -*/ \ No newline at end of file diff --git a/public/css/filament/support/support.css b/public/css/filament/support/support.css deleted file mode 100644 index a80d070..0000000 --- a/public/css/filament/support/support.css +++ /dev/null @@ -1 +0,0 @@ -.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3} \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index e69de29..0000000 diff --git a/public/favicon.svg b/public/favicon.svg index 9cae7c6..37311bc 100755 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,8 +1,6 @@ - - - - - - - + + + + + diff --git a/public/js/filament/filament/app.js b/public/js/filament/filament/app.js deleted file mode 100644 index 9ff5c43..0000000 --- a/public/js/filament/filament/app.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{var Z=Object.create,L=Object.defineProperty,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,re=Object.getOwnPropertyNames,ne=Object.getOwnPropertyDescriptor,ae=s=>L(s,"__esModule",{value:!0}),ie=(s,n)=>()=>(n||(n={exports:{}},s(n.exports,n)),n.exports),oe=(s,n,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let d of re(n))!te.call(s,d)&&d!=="default"&&L(s,d,{get:()=>n[d],enumerable:!(p=ne(n,d))||p.enumerable});return s},se=s=>oe(ae(L(s!=null?Z(ee(s)):{},"default",s&&s.__esModule&&"default"in s?{get:()=>s.default,enumerable:!0}:{value:s,enumerable:!0})),s),fe=ie((s,n)=>{(function(p,d,M){if(!p)return;for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},g={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},y={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},q={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},S,w=1;w<20;++w)h[111+w]="f"+w;for(w=0;w<=9;++w)h[w+96]=w.toString();function C(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function T(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return h[e.which]?h[e.which]:g[e.which]?g[e.which]:String.fromCharCode(e.which).toLowerCase()}function V(e,t){return e.sort().join(",")===t.sort().join(",")}function $(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function B(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function H(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function O(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!S){S={};for(var e in h)e>95&&e<112||h.hasOwnProperty(e)&&(S[h[e]]=e)}return S}function U(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function I(e,t){var a,c,b,P=[];for(a=X(e),b=0;b1){z(r,m,o,l);return}f=I(r,l),t._callbacks[f.key]=t._callbacks[f.key]||[],j(f.key,f.modifiers,{type:f.action},i,r,u),t._callbacks[f.key][i?"unshift":"push"]({callback:o,modifiers:f.modifiers,action:f.action,seq:i,level:u,combo:r})}t._bindMultiple=function(r,o,l){for(var i=0;i-1||D(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var c=e.composedPath()[0];c!==e.target&&(t=c)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(h[t]=e[t]);S=null},v.init=function(){var e=v(d);for(var t in e)t.charAt(0)!=="_"&&(v[t]=function(a){return function(){return e[a].apply(e,arguments)}}(t))},v.init(),p.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=se(fe());(function(s){if(s){var n={},p=s.prototype.stopCallback;s.prototype.stopCallback=function(d,M,h,g){var y=this;return y.paused?!0:n[h]||n[g]?!1:p.call(y,d,M,h)},s.prototype.bindGlobal=function(d,M,h){var g=this;if(g.bind(d,M,h),d instanceof Array){for(var y=0;y{s.directive("mousetrap",(n,{modifiers:p,expression:d},{evaluate:M})=>{let h=()=>d?M(d):n.click();p=p.map(g=>g.replace(/--/g," ").replace(/-/g,"+").replace(/\bslash\b/g,"/")),p.includes("global")&&(p=p.filter(g=>g!=="global"),R.default.bindGlobal(p,g=>{g.preventDefault(),h()})),R.default.bind(p,g=>{g.preventDefault(),h()})})},F=le;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(F),window.Alpine.store("sidebar",{isOpen:window.Alpine.$persist(!0).as("isOpen"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),groupIsCollapsed:function(n){return this.collapsedGroups.includes(n)},collapseGroup:function(n){this.collapsedGroups.includes(n)||(this.collapsedGroups=this.collapsedGroups.concat(n))},toggleCollapsedGroup:function(n){this.collapsedGroups=this.collapsedGroups.includes(n)?this.collapsedGroups.filter(p=>p!==n):this.collapsedGroups.concat(n)},close:function(){this.isOpen=!1},open:function(){this.isOpen=!0}});let s=localStorage.getItem("theme")??getComputedStyle(document.documentElement).getPropertyValue("--default-theme-mode");window.Alpine.store("theme",s==="dark"||s==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let p=n.detail;localStorage.setItem("theme",p),p==="system"&&(p=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",p)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});})(); diff --git a/public/js/filament/filament/echo.js b/public/js/filament/filament/echo.js deleted file mode 100644 index 65edaf5..0000000 --- a/public/js/filament/filament/echo.js +++ /dev/null @@ -1,13 +0,0 @@ -(()=>{var Ci=Object.create;var he=Object.defineProperty;var Ti=Object.getOwnPropertyDescriptor;var Pi=Object.getOwnPropertyNames;var xi=Object.getPrototypeOf,Oi=Object.prototype.hasOwnProperty;var Ai=(l,h)=>()=>(h||l((h={exports:{}}).exports,h),h.exports);var Ei=(l,h,a,c)=>{if(h&&typeof h=="object"||typeof h=="function")for(let s of Pi(h))!Oi.call(l,s)&&s!==a&&he(l,s,{get:()=>h[s],enumerable:!(c=Ti(h,s))||c.enumerable});return l};var Li=(l,h,a)=>(a=l!=null?Ci(xi(l)):{},Ei(h||!l||!l.__esModule?he(a,"default",{value:l,enumerable:!0}):a,l));var me=Ai((vt,It)=>{(function(h,a){typeof vt=="object"&&typeof It=="object"?It.exports=a():typeof define=="function"&&define.amd?define([],a):typeof vt=="object"?vt.Pusher=a():h.Pusher=a()})(window,function(){return function(l){var h={};function a(c){if(h[c])return h[c].exports;var s=h[c]={i:c,l:!1,exports:{}};return l[c].call(s.exports,s,s.exports,a),s.l=!0,s.exports}return a.m=l,a.c=h,a.d=function(c,s,f){a.o(c,s)||Object.defineProperty(c,s,{enumerable:!0,get:f})},a.r=function(c){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(c,"__esModule",{value:!0})},a.t=function(c,s){if(s&1&&(c=a(c)),s&8||s&4&&typeof c=="object"&&c&&c.__esModule)return c;var f=Object.create(null);if(a.r(f),Object.defineProperty(f,"default",{enumerable:!0,value:c}),s&2&&typeof c!="string")for(var d in c)a.d(f,d,function(N){return c[N]}.bind(null,d));return f},a.n=function(c){var s=c&&c.__esModule?function(){return c.default}:function(){return c};return a.d(s,"a",s),s},a.o=function(c,s){return Object.prototype.hasOwnProperty.call(c,s)},a.p="",a(a.s=2)}([function(l,h,a){"use strict";var c=this&&this.__extends||function(){var b=function(v,y){return b=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(w,O){w.__proto__=O}||function(w,O){for(var I in O)O.hasOwnProperty(I)&&(w[I]=O[I])},b(v,y)};return function(v,y){b(v,y);function w(){this.constructor=v}v.prototype=y===null?Object.create(y):(w.prototype=y.prototype,new w)}}();Object.defineProperty(h,"__esModule",{value:!0});var s=256,f=function(){function b(v){v===void 0&&(v="="),this._paddingCharacter=v}return b.prototype.encodedLength=function(v){return this._paddingCharacter?(v+2)/3*4|0:(v*8+5)/6|0},b.prototype.encode=function(v){for(var y="",w=0;w>>3*6&63),y+=this._encodeByte(O>>>2*6&63),y+=this._encodeByte(O>>>1*6&63),y+=this._encodeByte(O>>>0*6&63)}var I=v.length-w;if(I>0){var O=v[w]<<16|(I===2?v[w+1]<<8:0);y+=this._encodeByte(O>>>3*6&63),y+=this._encodeByte(O>>>2*6&63),I===2?y+=this._encodeByte(O>>>1*6&63):y+=this._paddingCharacter||"",y+=this._paddingCharacter||""}return y},b.prototype.maxDecodedLength=function(v){return this._paddingCharacter?v/4*3|0:(v*6+7)/8|0},b.prototype.decodedLength=function(v){return this.maxDecodedLength(v.length-this._getPaddingLength(v))},b.prototype.decode=function(v){if(v.length===0)return new Uint8Array(0);for(var y=this._getPaddingLength(v),w=v.length-y,O=new Uint8Array(this.maxDecodedLength(w)),I=0,q=0,M=0,J=0,F=0,z=0,B=0;q>>4,O[I++]=F<<4|z>>>2,O[I++]=z<<6|B,M|=J&s,M|=F&s,M|=z&s,M|=B&s;if(q>>4,M|=J&s,M|=F&s),q>>2,M|=z&s),q>>8&6,y+=51-v>>>8&-75,y+=61-v>>>8&-15,y+=62-v>>>8&3,String.fromCharCode(y)},b.prototype._decodeChar=function(v){var y=s;return y+=(42-v&v-44)>>>8&-s+v-43+62,y+=(46-v&v-48)>>>8&-s+v-47+63,y+=(47-v&v-58)>>>8&-s+v-48+52,y+=(64-v&v-91)>>>8&-s+v-65+0,y+=(96-v&v-123)>>>8&-s+v-97+26,y},b.prototype._getPaddingLength=function(v){var y=0;if(this._paddingCharacter){for(var w=v.length-1;w>=0&&v[w]===this._paddingCharacter;w--)y++;if(v.length<4||y>2)throw new Error("Base64Coder: incorrect padding")}return y},b}();h.Coder=f;var d=new f;function N(b){return d.encode(b)}h.encode=N;function P(b){return d.decode(b)}h.decode=P;var T=function(b){c(v,b);function v(){return b!==null&&b.apply(this,arguments)||this}return v.prototype._encodeByte=function(y){var w=y;return w+=65,w+=25-y>>>8&6,w+=51-y>>>8&-75,w+=61-y>>>8&-13,w+=62-y>>>8&49,String.fromCharCode(w)},v.prototype._decodeChar=function(y){var w=s;return w+=(44-y&y-46)>>>8&-s+y-45+62,w+=(94-y&y-96)>>>8&-s+y-95+63,w+=(47-y&y-58)>>>8&-s+y-48+52,w+=(64-y&y-91)>>>8&-s+y-65+0,w+=(96-y&y-123)>>>8&-s+y-97+26,w},v}(f);h.URLSafeCoder=T;var S=new T;function C(b){return S.encode(b)}h.encodeURLSafe=C;function x(b){return S.decode(b)}h.decodeURLSafe=x,h.encodedLength=function(b){return d.encodedLength(b)},h.maxDecodedLength=function(b){return d.maxDecodedLength(b)},h.decodedLength=function(b){return d.decodedLength(b)}},function(l,h,a){"use strict";Object.defineProperty(h,"__esModule",{value:!0});var c="utf8: invalid string",s="utf8: invalid source encoding";function f(P){for(var T=new Uint8Array(d(P)),S=0,C=0;C>6,T[S++]=128|x&63):x<55296?(T[S++]=224|x>>12,T[S++]=128|x>>6&63,T[S++]=128|x&63):(C++,x=(x&1023)<<10,x|=P.charCodeAt(C)&1023,x+=65536,T[S++]=240|x>>18,T[S++]=128|x>>12&63,T[S++]=128|x>>6&63,T[S++]=128|x&63)}return T}h.encode=f;function d(P){for(var T=0,S=0;S=P.length-1)throw new Error(c);S++,T+=4}else throw new Error(c)}return T}h.encodedLength=d;function N(P){for(var T=[],S=0;S=P.length)throw new Error(s);var b=P[++S];if((b&192)!==128)throw new Error(s);C=(C&31)<<6|b&63,x=128}else if(C<240){if(S>=P.length-1)throw new Error(s);var b=P[++S],v=P[++S];if((b&192)!==128||(v&192)!==128)throw new Error(s);C=(C&15)<<12|(b&63)<<6|v&63,x=2048}else if(C<248){if(S>=P.length-2)throw new Error(s);var b=P[++S],v=P[++S],y=P[++S];if((b&192)!==128||(v&192)!==128||(y&192)!==128)throw new Error(s);C=(C&15)<<18|(b&63)<<12|(v&63)<<6|y&63,x=65536}else throw new Error(s);if(C=55296&&C<=57343)throw new Error(s);if(C>=65536){if(C>1114111)throw new Error(s);C-=65536,T.push(String.fromCharCode(55296|C>>10)),C=56320|C&1023}}T.push(String.fromCharCode(C))}return T.join("")}h.decode=N},function(l,h,a){l.exports=a(3).default},function(l,h,a){"use strict";a.r(h);var c=function(){function e(t,n){this.lastId=0,this.prefix=t,this.name=n}return e.prototype.create=function(t){this.lastId++;var n=this.lastId,r=this.prefix+n,i=this.name+"["+n+"]",o=!1,u=function(){o||(t.apply(null,arguments),o=!0)};return this[n]=u,{number:n,id:r,name:i,callback:u}},e.prototype.remove=function(t){delete this[t.number]},e}(),s=new c("_pusher_script_","Pusher.ScriptReceivers"),f={VERSION:"7.6.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,cluster:"mt1",userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""},d=f,N=function(){function e(t){this.options=t,this.receivers=t.receivers||s,this.loading={}}return e.prototype.load=function(t,n,r){var i=this;if(i.loading[t]&&i.loading[t].length>0)i.loading[t].push(r);else{i.loading[t]=[r];var o=m.createScriptRequest(i.getPath(t,n)),u=i.receivers.create(function(p){if(i.receivers.remove(u),i.loading[t]){var _=i.loading[t];delete i.loading[t];for(var g=function(E){E||o.cleanup()},k=0;k<_.length;k++)_[k](p,g)}});o.send(u)}},e.prototype.getRoot=function(t){var n,r=m.getDocument().location.protocol;return t&&t.useTLS||r==="https:"?n=this.options.cdn_https:n=this.options.cdn_http,n.replace(/\/*$/,"")+"/"+this.options.version},e.prototype.getPath=function(t,n){return this.getRoot(n)+"/"+t+this.options.suffix+".js"},e}(),P=N,T=new c("_pusher_dependencies","Pusher.DependenciesReceivers"),S=new P({cdn_http:d.cdn_http,cdn_https:d.cdn_https,version:d.VERSION,suffix:d.dependency_suffix,receivers:T}),C={baseUrl:"https://pusher.com",urls:{authenticationEndpoint:{path:"/docs/channels/server_api/authenticating_users"},authorizationEndpoint:{path:"/docs/channels/server_api/authorizing-users/"},javascriptQuickStart:{path:"/docs/javascript_quick_start"},triggeringClientEvents:{path:"/docs/client_api_guide/client_events#trigger-events"},encryptedChannelSupport:{fullUrl:"https://github.com/pusher/pusher-js/tree/cc491015371a4bde5743d1c87a0fbac0feb53195#encrypted-channel-support"}}},x=function(e){var t="See:",n=C.urls[e];if(!n)return"";var r;return n.fullUrl?r=n.fullUrl:n.path&&(r=C.baseUrl+n.path),r?t+" "+r:""},b={buildLogSuffix:x},v;(function(e){e.UserAuthentication="user-authentication",e.ChannelAuthorization="channel-authorization"})(v||(v={}));var y=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),w=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),O=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),I=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),q=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),M=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),J=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),F=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),z=function(e){y(t,e);function t(n){var r=this.constructor,i=e.call(this,n)||this;return Object.setPrototypeOf(i,r.prototype),i}return t}(Error),B=function(e){y(t,e);function t(n,r){var i=this.constructor,o=e.call(this,r)||this;return o.status=n,Object.setPrototypeOf(o,i.prototype),o}return t}(Error),ke=function(e,t,n,r,i){var o=m.createXHR();o.open("POST",n.endpoint,!0),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded");for(var u in n.headers)o.setRequestHeader(u,n.headers[u]);if(n.headersProvider!=null){var p=n.headersProvider();for(var u in p)o.setRequestHeader(u,p[u])}return o.onreadystatechange=function(){if(o.readyState===4)if(o.status===200){var _=void 0,g=!1;try{_=JSON.parse(o.responseText),g=!0}catch{i(new B(200,"JSON returned from "+r.toString()+" endpoint was invalid, yet status code was 200. Data was: "+o.responseText),null)}g&&i(null,_)}else{var k="";switch(r){case v.UserAuthentication:k=b.buildLogSuffix("authenticationEndpoint");break;case v.ChannelAuthorization:k="Clients must be authorized to join private or presence channels. "+b.buildLogSuffix("authorizationEndpoint");break}i(new B(o.status,"Unable to retrieve auth string from "+r.toString()+" endpoint - "+("received status: "+o.status+" from "+n.endpoint+". "+k)),null)}},o.send(t),o},Se=ke;function Ce(e){return Ee(Oe(e))}for(var nt=String.fromCharCode,Z="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",Te={},ct=0,Pe=Z.length;ct>>6)+nt(128|t&63):nt(224|t>>>12&15)+nt(128|t>>>6&63)+nt(128|t&63)},Oe=function(e){return e.replace(/[^\x00-\x7F]/g,xe)},Ae=function(e){var t=[0,2,1][e.length%3],n=e.charCodeAt(0)<<16|(e.length>1?e.charCodeAt(1):0)<<8|(e.length>2?e.charCodeAt(2):0),r=[Z.charAt(n>>>18),Z.charAt(n>>>12&63),t>=2?"=":Z.charAt(n>>>6&63),t>=1?"=":Z.charAt(n&63)];return r.join("")},Ee=window.btoa||function(e){return e.replace(/[\s\S]{1,3}/g,Ae)},Le=function(){function e(t,n,r,i){var o=this;this.clear=n,this.timer=t(function(){o.timer&&(o.timer=i(o.timer))},r)}return e.prototype.isRunning=function(){return this.timer!==null},e.prototype.ensureAborted=function(){this.timer&&(this.clear(this.timer),this.timer=null)},e}(),jt=Le,Nt=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}();function Re(e){window.clearTimeout(e)}function Ie(e){window.clearInterval(e)}var Q=function(e){Nt(t,e);function t(n,r){return e.call(this,setTimeout,Re,n,function(i){return r(),null})||this}return t}(jt),je=function(e){Nt(t,e);function t(n,r){return e.call(this,setInterval,Ie,n,function(i){return r(),i})||this}return t}(jt),Ne={now:function(){return Date.now?Date.now():new Date().valueOf()},defer:function(e){return new Q(0,e)},method:function(e){for(var t=[],n=1;n0)for(var i=0;i=1002&&e.code<=1004?"backoff":null:e.code===4e3?"tls_only":e.code<4100?"refused":e.code<4200?"backoff":e.code<4300?"retry":"refused"},getCloseError:function(e){return e.code!==1e3&&e.code!==1001?{type:"PusherError",data:{code:e.code,message:e.reason||e.message}}:null}},K=Vt,Cn=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),Tn=function(e){Cn(t,e);function t(n,r){var i=e.call(this)||this;return i.id=n,i.transport=r,i.activityTimeout=r.activityTimeout,i.bindListeners(),i}return t.prototype.handlesActivityChecks=function(){return this.transport.handlesActivityChecks()},t.prototype.send=function(n){return this.transport.send(n)},t.prototype.send_event=function(n,r,i){var o={event:n,data:r};return i&&(o.channel=i),A.debug("Event sent",o),this.send(K.encodeMessage(o))},t.prototype.ping=function(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})},t.prototype.close=function(){this.transport.close()},t.prototype.bindListeners=function(){var n=this,r={message:function(o){var u;try{u=K.decodeMessage(o)}catch(p){n.emit("error",{type:"MessageParseError",error:p,data:o.data})}if(u!==void 0){switch(A.debug("Event recd",u),u.event){case"pusher:error":n.emit("error",{type:"PusherError",data:u.data});break;case"pusher:ping":n.emit("ping");break;case"pusher:pong":n.emit("pong");break}n.emit("message",u)}},activity:function(){n.emit("activity")},error:function(o){n.emit("error",o)},closed:function(o){i(),o&&o.code&&n.handleCloseEvent(o),n.transport=null,n.emit("closed")}},i=function(){W(r,function(o,u){n.transport.unbind(u,o)})};W(r,function(o,u){n.transport.bind(u,o)})},t.prototype.handleCloseEvent=function(n){var r=K.getCloseAction(n),i=K.getCloseError(n);i&&this.emit("error",i),r&&this.emit(r,{action:r,error:i})},t}(V),Pn=Tn,xn=function(){function e(t,n){this.transport=t,this.callback=n,this.bindListeners()}return e.prototype.close=function(){this.unbindListeners(),this.transport.close()},e.prototype.bindListeners=function(){var t=this;this.onMessage=function(n){t.unbindListeners();var r;try{r=K.processHandshake(n)}catch(i){t.finish("error",{error:i}),t.transport.close();return}r.action==="connected"?t.finish("connected",{connection:new Pn(r.id,t.transport),activityTimeout:r.activityTimeout}):(t.finish(r.action,{error:r.error}),t.transport.close())},this.onClosed=function(n){t.unbindListeners();var r=K.getCloseAction(n)||"backoff",i=K.getCloseError(n);t.finish(r,{error:i})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)},e.prototype.unbindListeners=function(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)},e.prototype.finish=function(t,n){this.callback(U({transport:this.transport,action:t},n))},e}(),On=xn,An=function(){function e(t,n){this.timeline=t,this.options=n||{}}return e.prototype.send=function(t,n){this.timeline.isEmpty()||this.timeline.send(m.TimelineTransport.getAgent(this,t),n)},e}(),En=An,Ln=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),Rn=function(e){Ln(t,e);function t(n,r){var i=e.call(this,function(o,u){A.debug("No callbacks on "+n+" for "+o)})||this;return i.name=n,i.pusher=r,i.subscribed=!1,i.subscriptionPending=!1,i.subscriptionCancelled=!1,i}return t.prototype.authorize=function(n,r){return r(null,{auth:""})},t.prototype.trigger=function(n,r){if(n.indexOf("client-")!==0)throw new w("Event '"+n+"' does not start with 'client-'");if(!this.subscribed){var i=b.buildLogSuffix("triggeringClientEvents");A.warn("Client event triggered before channel 'subscription_succeeded' event . "+i)}return this.pusher.send_event(n,r,this.name)},t.prototype.disconnect=function(){this.subscribed=!1,this.subscriptionPending=!1},t.prototype.handleEvent=function(n){var r=n.event,i=n.data;if(r==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(n);else if(r==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(n);else if(r.indexOf("pusher_internal:")!==0){var o={};this.emit(r,i,o)}},t.prototype.handleSubscriptionSucceededEvent=function(n){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",n.data)},t.prototype.handleSubscriptionCountEvent=function(n){n.data.subscription_count&&(this.subscriptionCount=n.data.subscription_count),this.emit("pusher:subscription_count",n.data)},t.prototype.subscribe=function(){var n=this;this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,function(r,i){r?(n.subscriptionPending=!1,A.error(r.toString()),n.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:r.message},r instanceof B?{status:r.status}:{}))):n.pusher.send_event("pusher:subscribe",{auth:i.auth,channel_data:i.channel_data,channel:n.name})}))},t.prototype.unsubscribe=function(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})},t.prototype.cancelSubscription=function(){this.subscriptionCancelled=!0},t.prototype.reinstateSubscription=function(){this.subscriptionCancelled=!1},t}(V),bt=Rn,In=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),jn=function(e){In(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.authorize=function(n,r){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:n},r)},t}(bt),mt=jn,Nn=function(){function e(){this.reset()}return e.prototype.get=function(t){return Object.prototype.hasOwnProperty.call(this.members,t)?{id:t,info:this.members[t]}:null},e.prototype.each=function(t){var n=this;W(this.members,function(r,i){t(n.get(i))})},e.prototype.setMyID=function(t){this.myID=t},e.prototype.onSubscription=function(t){this.members=t.presence.hash,this.count=t.presence.count,this.me=this.get(this.myID)},e.prototype.addMember=function(t){return this.get(t.user_id)===null&&this.count++,this.members[t.user_id]=t.user_info,this.get(t.user_id)},e.prototype.removeMember=function(t){var n=this.get(t.user_id);return n&&(delete this.members[t.user_id],this.count--),n},e.prototype.reset=function(){this.members={},this.count=0,this.myID=null,this.me=null},e}(),qn=Nn,Un=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),Dn=function(e,t,n,r){function i(o){return o instanceof n?o:new n(function(u){u(o)})}return new(n||(n=Promise))(function(o,u){function p(k){try{g(r.next(k))}catch(E){u(E)}}function _(k){try{g(r.throw(k))}catch(E){u(E)}}function g(k){k.done?o(k.value):i(k.value).then(p,_)}g((r=r.apply(e,t||[])).next())})},Hn=function(e,t){var n={label:0,sent:function(){if(o[0]&1)throw o[1];return o[1]},trys:[],ops:[]},r,i,o,u;return u={next:p(0),throw:p(1),return:p(2)},typeof Symbol=="function"&&(u[Symbol.iterator]=function(){return this}),u;function p(g){return function(k){return _([g,k])}}function _(g){if(r)throw new TypeError("Generator is already executing.");for(;n;)try{if(r=1,i&&(o=g[0]&2?i.return:g[0]?i.throw||((o=i.return)&&o.call(i),0):i.next)&&!(o=o.call(i,g[1])).done)return o;switch(i=0,o&&(g=[g[0]&2,o.value]),g[0]){case 0:case 1:o=g;break;case 4:return n.label++,{value:g[1],done:!1};case 5:n.label++,i=g[1],g=[0];continue;case 7:g=n.ops.pop(),n.trys.pop();continue;default:if(o=n.trys,!(o=o.length>0&&o[o.length-1])&&(g[0]===6||g[0]===2)){n=0;continue}if(g[0]===3&&(!o||g[1]>o[0]&&g[1]0&&this.emit("connecting_in",Math.round(n/1e3)),this.retryTimer=new Q(n||0,function(){r.disconnectInternally(),r.connect()})},t.prototype.clearRetryTimer=function(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)},t.prototype.setUnavailableTimer=function(){var n=this;this.unavailableTimer=new Q(this.options.unavailableTimeout,function(){n.updateState("unavailable")})},t.prototype.clearUnavailableTimer=function(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()},t.prototype.sendActivityCheck=function(){var n=this;this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new Q(this.options.pongTimeout,function(){n.timeline.error({pong_timed_out:n.options.pongTimeout}),n.retryIn(0)})},t.prototype.resetActivityCheck=function(){var n=this;this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new Q(this.activityTimeout,function(){n.sendActivityCheck()}))},t.prototype.stopActivityCheck=function(){this.activityTimer&&this.activityTimer.ensureAborted()},t.prototype.buildConnectionCallbacks=function(n){var r=this;return U({},n,{message:function(i){r.resetActivityCheck(),r.emit("message",i)},ping:function(){r.send_event("pusher:pong",{})},activity:function(){r.resetActivityCheck()},error:function(i){r.emit("error",i)},closed:function(){r.abandonConnection(),r.shouldRetry()&&r.retryIn(1e3)}})},t.prototype.buildHandshakeCallbacks=function(n){var r=this;return U({},n,{connected:function(i){r.activityTimeout=Math.min(r.options.activityTimeout,i.activityTimeout,i.connection.activityTimeout||1/0),r.clearUnavailableTimer(),r.setConnection(i.connection),r.socket_id=r.connection.id,r.updateState("connected",{socket_id:r.socket_id})}})},t.prototype.buildErrorCallbacks=function(){var n=this,r=function(i){return function(o){o.error&&n.emit("error",{type:"WebSocketError",error:o.error}),i(o)}};return{tls_only:r(function(){n.usingTLS=!0,n.updateStrategy(),n.retryIn(0)}),refused:r(function(){n.disconnect()}),backoff:r(function(){n.retryIn(1e3)}),retry:r(function(){n.retryIn(0)})}},t.prototype.setConnection=function(n){this.connection=n;for(var r in this.connectionCallbacks)this.connection.bind(r,this.connectionCallbacks[r]);this.resetActivityCheck()},t.prototype.abandonConnection=function(){if(this.connection){this.stopActivityCheck();for(var n in this.connectionCallbacks)this.connection.unbind(n,this.connectionCallbacks[n]);var r=this.connection;return this.connection=null,r}},t.prototype.updateState=function(n,r){var i=this.state;if(this.state=n,i!==n){var o=n;o==="connected"&&(o+=" with new socket ID "+r.socket_id),A.debug("State changed",i+" -> "+o),this.timeline.info({state:n,params:r}),this.emit("state_change",{previous:i,current:n}),this.emit(n,r)}},t.prototype.shouldRetry=function(){return this.state==="connecting"||this.state==="connected"},t}(V),Gn=Vn,Qn=function(){function e(){this.channels={}}return e.prototype.add=function(t,n){return this.channels[t]||(this.channels[t]=Yn(t,n)),this.channels[t]},e.prototype.all=function(){return Ue(this.channels)},e.prototype.find=function(t){return this.channels[t]},e.prototype.remove=function(t){var n=this.channels[t];return delete this.channels[t],n},e.prototype.disconnect=function(){W(this.channels,function(t){t.disconnect()})},e}(),Kn=Qn;function Yn(e,t){if(e.indexOf("private-encrypted-")===0){if(t.config.nacl)return G.createEncryptedChannel(e,t,t.config.nacl);var n="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",r=b.buildLogSuffix("encryptedChannelSupport");throw new J(n+". "+r)}else{if(e.indexOf("private-")===0)return G.createPrivateChannel(e,t);if(e.indexOf("presence-")===0)return G.createPresenceChannel(e,t);if(e.indexOf("#")===0)throw new O('Cannot create a channel with name "'+e+'".');return G.createChannel(e,t)}}var $n={createChannels:function(){return new Kn},createConnectionManager:function(e,t){return new Gn(e,t)},createChannel:function(e,t){return new bt(e,t)},createPrivateChannel:function(e,t){return new mt(e,t)},createPresenceChannel:function(e,t){return new zn(e,t)},createEncryptedChannel:function(e,t,n){return new Jn(e,t,n)},createTimelineSender:function(e,t){return new En(e,t)},createHandshake:function(e,t){return new On(e,t)},createAssistantToTheTransportManager:function(e,t,n){return new Sn(e,t,n)}},G=$n,Zn=function(){function e(t){this.options=t||{},this.livesLeft=this.options.lives||1/0}return e.prototype.getAssistant=function(t){return G.createAssistantToTheTransportManager(this,t,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})},e.prototype.isAlive=function(){return this.livesLeft>0},e.prototype.reportDeath=function(){this.livesLeft-=1},e}(),Gt=Zn,tr=function(){function e(t,n){this.strategies=t,this.loop=!!n.loop,this.failFast=!!n.failFast,this.timeout=n.timeout,this.timeoutLimit=n.timeoutLimit}return e.prototype.isSupported=function(){return zt(this.strategies,j.method("isSupported"))},e.prototype.connect=function(t,n){var r=this,i=this.strategies,o=0,u=this.timeout,p=null,_=function(g,k){k?n(null,k):(o=o+1,r.loop&&(o=o%i.length),o0&&(o=new Q(r.timeout,function(){u.abort(),i(!0)})),u=t.connect(n,function(p,_){p&&o&&o.isRunning()&&!r.failFast||(o&&o.ensureAborted(),i(p,_))}),{abort:function(){o&&o.ensureAborted(),u.abort()},forceMinPriority:function(p){u.forceMinPriority(p)}}},e}(),Y=tr,er=function(){function e(t){this.strategies=t}return e.prototype.isSupported=function(){return zt(this.strategies,j.method("isSupported"))},e.prototype.connect=function(t,n){return nr(this.strategies,t,function(r,i){return function(o,u){if(i[r].error=o,o){rr(i)&&n(!0);return}rt(i,function(p){p.forceMinPriority(u.transport.priority)}),n(null,u)}})},e}(),kt=er;function nr(e,t,n){var r=Dt(e,function(i,o,u,p){return i.connect(t,n(o,p))});return{abort:function(){rt(r,ir)},forceMinPriority:function(i){rt(r,function(o){o.forceMinPriority(i)})}}}function rr(e){return Me(e,function(t){return!!t.error})}function ir(e){!e.error&&!e.aborted&&(e.abort(),e.aborted=!0)}var or=function(){function e(t,n,r){this.strategy=t,this.transports=n,this.ttl=r.ttl||1800*1e3,this.usingTLS=r.useTLS,this.timeline=r.timeline}return e.prototype.isSupported=function(){return this.strategy.isSupported()},e.prototype.connect=function(t,n){var r=this.usingTLS,i=ar(r),o=[this.strategy];if(i&&i.timestamp+this.ttl>=j.now()){var u=this.transports[i.transport];u&&(this.timeline.info({cached:!0,transport:i.transport,latency:i.latency}),o.push(new Y([u],{timeout:i.latency*2+1e3,failFast:!0})))}var p=j.now(),_=o.pop().connect(t,function g(k,E){k?(Qt(r),o.length>0?(p=j.now(),_=o.pop().connect(t,g)):n(k)):(cr(r,E.transport.name,j.now()-p),n(null,E))});return{abort:function(){_.abort()},forceMinPriority:function(g){t=g,_&&_.forceMinPriority(g)}}},e}(),sr=or;function St(e){return"pusherTransport"+(e?"TLS":"NonTLS")}function ar(e){var t=m.getLocalStorage();if(t)try{var n=t[St(e)];if(n)return JSON.parse(n)}catch{Qt(e)}return null}function cr(e,t,n){var r=m.getLocalStorage();if(r)try{r[St(e)]=ut({timestamp:j.now(),transport:t,latency:n})}catch{}}function Qt(e){var t=m.getLocalStorage();if(t)try{delete t[St(e)]}catch{}}var ur=function(){function e(t,n){var r=n.delay;this.strategy=t,this.options={delay:r}}return e.prototype.isSupported=function(){return this.strategy.isSupported()},e.prototype.connect=function(t,n){var r=this.strategy,i,o=new Q(this.options.delay,function(){i=r.connect(t,n)});return{abort:function(){o.ensureAborted(),i&&i.abort()},forceMinPriority:function(u){t=u,i&&i.forceMinPriority(u)}}},e}(),lt=ur,hr=function(){function e(t,n,r){this.test=t,this.trueBranch=n,this.falseBranch=r}return e.prototype.isSupported=function(){var t=this.test()?this.trueBranch:this.falseBranch;return t.isSupported()},e.prototype.connect=function(t,n){var r=this.test()?this.trueBranch:this.falseBranch;return r.connect(t,n)},e}(),it=hr,lr=function(){function e(t){this.strategy=t}return e.prototype.isSupported=function(){return this.strategy.isSupported()},e.prototype.connect=function(t,n){var r=this.strategy.connect(t,function(i,o){o&&r.abort(),n(i,o)});return r},e}(),fr=lr;function ot(e){return function(){return e.isSupported()}}var pr=function(e,t,n){var r={};function i(ce,mi,wi,ki,Si){var ue=n(e,ce,mi,wi,ki,Si);return r[ce]=ue,ue}var o=Object.assign({},t,{hostNonTLS:e.wsHost+":"+e.wsPort,hostTLS:e.wsHost+":"+e.wssPort,httpPath:e.wsPath}),u=Object.assign({},o,{useTLS:!0}),p=Object.assign({},t,{hostNonTLS:e.httpHost+":"+e.httpPort,hostTLS:e.httpHost+":"+e.httpsPort,httpPath:e.httpPath}),_={loop:!0,timeout:15e3,timeoutLimit:6e4},g=new Gt({lives:2,minPingDelay:1e4,maxPingDelay:e.activityTimeout}),k=new Gt({lives:2,minPingDelay:1e4,maxPingDelay:e.activityTimeout}),E=i("ws","ws",3,o,g),X=i("wss","ws",3,u,g),vi=i("sockjs","sockjs",1,p),ne=i("xhr_streaming","xhr_streaming",1,p,k),yi=i("xdr_streaming","xdr_streaming",1,p,k),re=i("xhr_polling","xhr_polling",1,p),gi=i("xdr_polling","xdr_polling",1,p),ie=new Y([E],_),_i=new Y([X],_),bi=new Y([vi],_),oe=new Y([new it(ot(ne),ne,yi)],_),se=new Y([new it(ot(re),re,gi)],_),ae=new Y([new it(ot(oe),new kt([oe,new lt(se,{delay:4e3})]),se)],_),xt=new it(ot(ae),ae,bi),Ot;return t.useTLS?Ot=new kt([ie,new lt(xt,{delay:2e3})]):Ot=new kt([ie,new lt(_i,{delay:2e3}),new lt(xt,{delay:5e3})]),new sr(new fr(new it(ot(E),Ot,xt)),r,{ttl:18e5,timeline:t.timeline,useTLS:t.useTLS})},dr=pr,vr=function(){var e=this;e.timeline.info(e.buildTimelineMessage({transport:e.name+(e.options.useTLS?"s":"")})),e.hooks.isInitialized()?e.changeState("initialized"):e.hooks.file?(e.changeState("initializing"),S.load(e.hooks.file,{useTLS:e.options.useTLS},function(t,n){e.hooks.isInitialized()?(e.changeState("initialized"),n(!0)):(t&&e.onError(t),e.onClose(),n(!1))})):e.onClose()},yr={getRequest:function(e){var t=new window.XDomainRequest;return t.ontimeout=function(){e.emit("error",new I),e.close()},t.onerror=function(n){e.emit("error",n),e.close()},t.onprogress=function(){t.responseText&&t.responseText.length>0&&e.onChunk(200,t.responseText)},t.onload=function(){t.responseText&&t.responseText.length>0&&e.onChunk(200,t.responseText),e.emit("finished",200),e.close()},t},abortRequest:function(e){e.ontimeout=e.onerror=e.onprogress=e.onload=null,e.abort()}},gr=yr,_r=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var o in i)i.hasOwnProperty(o)&&(r[o]=i[o])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),br=256*1024,mr=function(e){_r(t,e);function t(n,r,i){var o=e.call(this)||this;return o.hooks=n,o.method=r,o.url=i,o}return t.prototype.start=function(n){var r=this;this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=function(){r.close()},m.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(n)},t.prototype.close=function(){this.unloader&&(m.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)},t.prototype.onChunk=function(n,r){for(;;){var i=this.advanceBuffer(r);if(i)this.emit("chunk",{status:n,data:i});else break}this.isBufferTooLong(r)&&this.emit("buffer_too_long")},t.prototype.advanceBuffer=function(n){var r=n.slice(this.position),i=r.indexOf(` -`);return i!==-1?(this.position+=i+1,r.slice(0,i)):null},t.prototype.isBufferTooLong=function(n){return this.position===n.length&&n.length>br},t}(V),wr=mr,Ct;(function(e){e[e.CONNECTING=0]="CONNECTING",e[e.OPEN=1]="OPEN",e[e.CLOSED=3]="CLOSED"})(Ct||(Ct={}));var $=Ct,kr=1,Sr=function(){function e(t,n){this.hooks=t,this.session=Yt(1e3)+"/"+xr(8),this.location=Cr(n),this.readyState=$.CONNECTING,this.openStream()}return e.prototype.send=function(t){return this.sendRaw(JSON.stringify([t]))},e.prototype.ping=function(){this.hooks.sendHeartbeat(this)},e.prototype.close=function(t,n){this.onClose(t,n,!0)},e.prototype.sendRaw=function(t){if(this.readyState===$.OPEN)try{return m.createSocketRequest("POST",Kt(Tr(this.location,this.session))).start(t),!0}catch{return!1}else return!1},e.prototype.reconnect=function(){this.closeStream(),this.openStream()},e.prototype.onClose=function(t,n,r){this.closeStream(),this.readyState=$.CLOSED,this.onclose&&this.onclose({code:t,reason:n,wasClean:r})},e.prototype.onChunk=function(t){if(t.status===200){this.readyState===$.OPEN&&this.onActivity();var n,r=t.data.slice(0,1);switch(r){case"o":n=JSON.parse(t.data.slice(1)||"{}"),this.onOpen(n);break;case"a":n=JSON.parse(t.data.slice(1)||"[]");for(var i=0;i0&&e.onChunk(n.status,n.responseText);break;case 4:n.responseText&&n.responseText.length>0&&e.onChunk(n.status,n.responseText),e.emit("finished",n.status),e.close();break}},n},abortRequest:function(e){e.onreadystatechange=null,e.abort()}},jr=Ir,Nr={createStreamingSocket:function(e){return this.createSocket(Er,e)},createPollingSocket:function(e){return this.createSocket(Rr,e)},createSocket:function(e,t){return new Or(e,t)},createXHR:function(e,t){return this.createRequest(jr,e,t)},createRequest:function(e,t,n){return new wr(e,t,n)}},$t=Nr;$t.createXDR=function(e,t){return this.createRequest(gr,e,t)};var qr=$t,Ur={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:s,DependenciesReceivers:T,getDefaultStrategy:dr,Transports:_n,transportConnectionInitializer:vr,HTTPFactory:qr,TimelineTransport:Ze,getXHRAPI:function(){return window.XMLHttpRequest},getWebSocketAPI:function(){return window.WebSocket||window.MozWebSocket},setup:function(e){var t=this;window.Pusher=e;var n=function(){t.onDocumentBody(e.ready)};window.JSON?n():S.load("json2",{},n)},getDocument:function(){return document},getProtocol:function(){return this.getDocument().location.protocol},getAuthorizers:function(){return{ajax:Se,jsonp:We}},onDocumentBody:function(e){var t=this;document.body?e():setTimeout(function(){t.onDocumentBody(e)},0)},createJSONPRequest:function(e,t){return new Ke(e,t)},createScriptRequest:function(e){return new Ge(e)},getLocalStorage:function(){try{return window.localStorage}catch{return}},createXHR:function(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest:function(){var e=this.getXHRAPI();return new e},createMicrosoftXHR:function(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork:function(){return wn},createWebSocket:function(e){var t=this.getWebSocketAPI();return new t(e)},createSocketRequest:function(e,t){if(this.isXHRSupported())return this.HTTPFactory.createXHR(e,t);if(this.isXDRSupported(t.indexOf("https:")===0))return this.HTTPFactory.createXDR(e,t);throw"Cross-origin HTTP requests are not supported"},isXHRSupported:function(){var e=this.getXHRAPI();return!!e&&new e().withCredentials!==void 0},isXDRSupported:function(e){var t=e?"https:":"http:",n=this.getProtocol();return!!window.XDomainRequest&&n===t},addUnloadListener:function(e){window.addEventListener!==void 0?window.addEventListener("unload",e,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",e)},removeUnloadListener:function(e){window.addEventListener!==void 0?window.removeEventListener("unload",e,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",e)},randomInt:function(e){var t=function(){var n=window.crypto||window.msCrypto,r=n.getRandomValues(new Uint32Array(1))[0];return r/Math.pow(2,32)};return Math.floor(t()*e)}},m=Ur,Tt;(function(e){e[e.ERROR=3]="ERROR",e[e.INFO=6]="INFO",e[e.DEBUG=7]="DEBUG"})(Tt||(Tt={}));var ft=Tt,Dr=function(){function e(t,n,r){this.key=t,this.session=n,this.events=[],this.options=r||{},this.sent=0,this.uniqueID=0}return e.prototype.log=function(t,n){t<=this.options.level&&(this.events.push(U({},n,{timestamp:j.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())},e.prototype.error=function(t){this.log(ft.ERROR,t)},e.prototype.info=function(t){this.log(ft.INFO,t)},e.prototype.debug=function(t){this.log(ft.DEBUG,t)},e.prototype.isEmpty=function(){return this.events.length===0},e.prototype.send=function(t,n){var r=this,i=U({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],t(i,function(o,u){o||r.sent++,n&&n(o,u)}),!0},e.prototype.generateUniqueID=function(){return this.uniqueID++,this.uniqueID},e}(),Hr=Dr,Mr=function(){function e(t,n,r,i){this.name=t,this.priority=n,this.transport=r,this.options=i||{}}return e.prototype.isSupported=function(){return this.transport.isSupported({useTLS:this.options.useTLS})},e.prototype.connect=function(t,n){var r=this;if(this.isSupported()){if(this.priority"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function Ii(l){if(l===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return l}function ji(l,h){if(h&&(typeof h=="object"||typeof h=="function"))return h;if(h!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return Ii(l)}function H(l){var h=Ri();return function(){var c=pt(l),s;if(h){var f=pt(this).constructor;s=Reflect.construct(c,arguments,f)}else s=c.apply(this,arguments);return ji(this,s)}}var Et=function(){function l(){L(this,l)}return R(l,[{key:"listenForWhisper",value:function(a,c){return this.listen(".client-"+a,c)}},{key:"notification",value:function(a){return this.listen(".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated",a)}},{key:"stopListeningForWhisper",value:function(a,c){return this.stopListening(".client-"+a,c)}}]),l}(),de=function(){function l(h){L(this,l),this.namespace=h}return R(l,[{key:"format",value:function(a){return[".","\\"].includes(a.charAt(0))?a.substring(1):(this.namespace&&(a=this.namespace+"."+a),a.replace(/\./g,"\\"))}},{key:"setNamespace",value:function(a){this.namespace=a}}]),l}();function Ni(l){try{new l}catch(h){if(h.message.includes("is not a constructor"))return!1}return!0}var Lt=function(l){D(a,l);var h=H(a);function a(c,s,f){var d;return L(this,a),d=h.call(this),d.name=s,d.pusher=c,d.options=f,d.eventFormatter=new de(d.options.namespace),d.subscribe(),d}return R(a,[{key:"subscribe",value:function(){this.subscription=this.pusher.subscribe(this.name)}},{key:"unsubscribe",value:function(){this.pusher.unsubscribe(this.name)}},{key:"listen",value:function(s,f){return this.on(this.eventFormatter.format(s),f),this}},{key:"listenToAll",value:function(s){var f=this;return this.subscription.bind_global(function(d,N){if(!d.startsWith("pusher:")){var P=f.options.namespace.replace(/\./g,"\\"),T=d.startsWith(P)?d.substring(P.length+1):"."+d;s(T,N)}}),this}},{key:"stopListening",value:function(s,f){return f?this.subscription.unbind(this.eventFormatter.format(s),f):this.subscription.unbind(this.eventFormatter.format(s)),this}},{key:"stopListeningToAll",value:function(s){return s?this.subscription.unbind_global(s):this.subscription.unbind_global(),this}},{key:"subscribed",value:function(s){return this.on("pusher:subscription_succeeded",function(){s()}),this}},{key:"error",value:function(s){return this.on("pusher:subscription_error",function(f){s(f)}),this}},{key:"on",value:function(s,f){return this.subscription.bind(s,f),this}}]),a}(Et),ve=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"whisper",value:function(s,f){return this.pusher.channels.channels[this.name].trigger("client-".concat(s),f),this}}]),a}(Lt),qi=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"whisper",value:function(s,f){return this.pusher.channels.channels[this.name].trigger("client-".concat(s),f),this}}]),a}(Lt),Ui=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"here",value:function(s){return this.on("pusher:subscription_succeeded",function(f){s(Object.keys(f.members).map(function(d){return f.members[d]}))}),this}},{key:"joining",value:function(s){return this.on("pusher:member_added",function(f){s(f.info)}),this}},{key:"whisper",value:function(s,f){return this.pusher.channels.channels[this.name].trigger("client-".concat(s),f),this}},{key:"leaving",value:function(s){return this.on("pusher:member_removed",function(f){s(f.info)}),this}}]),a}(ve),ye=function(l){D(a,l);var h=H(a);function a(c,s,f){var d;return L(this,a),d=h.call(this),d.events={},d.listeners={},d.name=s,d.socket=c,d.options=f,d.eventFormatter=new de(d.options.namespace),d.subscribe(),d}return R(a,[{key:"subscribe",value:function(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}},{key:"unsubscribe",value:function(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}},{key:"listen",value:function(s,f){return this.on(this.eventFormatter.format(s),f),this}},{key:"stopListening",value:function(s,f){return this.unbindEvent(this.eventFormatter.format(s),f),this}},{key:"subscribed",value:function(s){return this.on("connect",function(f){s(f)}),this}},{key:"error",value:function(s){return this}},{key:"on",value:function(s,f){var d=this;return this.listeners[s]=this.listeners[s]||[],this.events[s]||(this.events[s]=function(N,P){d.name===N&&d.listeners[s]&&d.listeners[s].forEach(function(T){return T(P)})},this.socket.on(s,this.events[s])),this.listeners[s].push(f),this}},{key:"unbind",value:function(){var s=this;Object.keys(this.events).forEach(function(f){s.unbindEvent(f)})}},{key:"unbindEvent",value:function(s,f){this.listeners[s]=this.listeners[s]||[],f&&(this.listeners[s]=this.listeners[s].filter(function(d){return d!==f})),(!f||this.listeners[s].length===0)&&(this.events[s]&&(this.socket.removeListener(s,this.events[s]),delete this.events[s]),delete this.listeners[s])}}]),a}(Et),ge=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"whisper",value:function(s,f){return this.socket.emit("client event",{channel:this.name,event:"client-".concat(s),data:f}),this}}]),a}(ye),Di=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"here",value:function(s){return this.on("presence:subscribed",function(f){s(f.map(function(d){return d.user_info}))}),this}},{key:"joining",value:function(s){return this.on("presence:joining",function(f){return s(f.user_info)}),this}},{key:"whisper",value:function(s,f){return this.socket.emit("client event",{channel:this.name,event:"client-".concat(s),data:f}),this}},{key:"leaving",value:function(s){return this.on("presence:leaving",function(f){return s(f.user_info)}),this}}]),a}(ge),dt=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"subscribe",value:function(){}},{key:"unsubscribe",value:function(){}},{key:"listen",value:function(s,f){return this}},{key:"listenToAll",value:function(s){return this}},{key:"stopListening",value:function(s,f){return this}},{key:"subscribed",value:function(s){return this}},{key:"error",value:function(s){return this}},{key:"on",value:function(s,f){return this}}]),a}(Et),_e=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"whisper",value:function(s,f){return this}}]),a}(dt),Hi=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"whisper",value:function(s,f){return this}}]),a}(dt),Mi=function(l){D(a,l);var h=H(a);function a(){return L(this,a),h.apply(this,arguments)}return R(a,[{key:"here",value:function(s){return this}},{key:"joining",value:function(s){return this}},{key:"whisper",value:function(s,f){return this}},{key:"leaving",value:function(s){return this}}]),a}(_e),Rt=function(){function l(h){L(this,l),this._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},broadcaster:"pusher",csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"},this.setOptions(h),this.connect()}return R(l,[{key:"setOptions",value:function(a){this.options=at(this._defaultOptions,a);var c=this.csrfToken();return c&&(this.options.auth.headers["X-CSRF-TOKEN"]=c,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=c),c=this.options.bearerToken,c&&(this.options.auth.headers.Authorization="Bearer "+c,this.options.userAuthentication.headers.Authorization="Bearer "+c),a}},{key:"csrfToken",value:function(){var a;return typeof window<"u"&&window.Laravel&&window.Laravel.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"&&(a=document.querySelector('meta[name="csrf-token"]'))?a.getAttribute("content"):null}}]),l}(),fe=function(l){D(a,l);var h=H(a);function a(){var c;return L(this,a),c=h.apply(this,arguments),c.channels={},c}return R(a,[{key:"connect",value:function(){typeof this.options.client<"u"?this.pusher=this.options.client:this.options.Pusher?this.pusher=new this.options.Pusher(this.options.key,this.options):this.pusher=new Pusher(this.options.key,this.options)}},{key:"signin",value:function(){this.pusher.signin()}},{key:"listen",value:function(s,f,d){return this.channel(s).listen(f,d)}},{key:"channel",value:function(s){return this.channels[s]||(this.channels[s]=new Lt(this.pusher,s,this.options)),this.channels[s]}},{key:"privateChannel",value:function(s){return this.channels["private-"+s]||(this.channels["private-"+s]=new ve(this.pusher,"private-"+s,this.options)),this.channels["private-"+s]}},{key:"encryptedPrivateChannel",value:function(s){return this.channels["private-encrypted-"+s]||(this.channels["private-encrypted-"+s]=new qi(this.pusher,"private-encrypted-"+s,this.options)),this.channels["private-encrypted-"+s]}},{key:"presenceChannel",value:function(s){return this.channels["presence-"+s]||(this.channels["presence-"+s]=new Ui(this.pusher,"presence-"+s,this.options)),this.channels["presence-"+s]}},{key:"leave",value:function(s){var f=this,d=[s,"private-"+s,"private-encrypted-"+s,"presence-"+s];d.forEach(function(N,P){f.leaveChannel(N)})}},{key:"leaveChannel",value:function(s){this.channels[s]&&(this.channels[s].unsubscribe(),delete this.channels[s])}},{key:"socketId",value:function(){return this.pusher.connection.socket_id}},{key:"disconnect",value:function(){this.pusher.disconnect()}}]),a}(Rt),pe=function(l){D(a,l);var h=H(a);function a(){var c;return L(this,a),c=h.apply(this,arguments),c.channels={},c}return R(a,[{key:"connect",value:function(){var s=this,f=this.getSocketIO();return this.socket=f(this.options.host,this.options),this.socket.on("reconnect",function(){Object.values(s.channels).forEach(function(d){d.subscribe()})}),this.socket}},{key:"getSocketIO",value:function(){if(typeof this.options.client<"u")return this.options.client;if(typeof io<"u")return io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}},{key:"listen",value:function(s,f,d){return this.channel(s).listen(f,d)}},{key:"channel",value:function(s){return this.channels[s]||(this.channels[s]=new ye(this.socket,s,this.options)),this.channels[s]}},{key:"privateChannel",value:function(s){return this.channels["private-"+s]||(this.channels["private-"+s]=new ge(this.socket,"private-"+s,this.options)),this.channels["private-"+s]}},{key:"presenceChannel",value:function(s){return this.channels["presence-"+s]||(this.channels["presence-"+s]=new Di(this.socket,"presence-"+s,this.options)),this.channels["presence-"+s]}},{key:"leave",value:function(s){var f=this,d=[s,"private-"+s,"presence-"+s];d.forEach(function(N){f.leaveChannel(N)})}},{key:"leaveChannel",value:function(s){this.channels[s]&&(this.channels[s].unsubscribe(),delete this.channels[s])}},{key:"socketId",value:function(){return this.socket.id}},{key:"disconnect",value:function(){this.socket.disconnect()}}]),a}(Rt),zi=function(l){D(a,l);var h=H(a);function a(){var c;return L(this,a),c=h.apply(this,arguments),c.channels={},c}return R(a,[{key:"connect",value:function(){}},{key:"listen",value:function(s,f,d){return new dt}},{key:"channel",value:function(s){return new dt}},{key:"privateChannel",value:function(s){return new _e}},{key:"encryptedPrivateChannel",value:function(s){return new Hi}},{key:"presenceChannel",value:function(s){return new Mi}},{key:"leave",value:function(s){}},{key:"leaveChannel",value:function(s){}},{key:"socketId",value:function(){return"fake-socket-id"}},{key:"disconnect",value:function(){}}]),a}(Rt),be=function(){function l(h){L(this,l),this.options=h,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}return R(l,[{key:"channel",value:function(a){return this.connector.channel(a)}},{key:"connect",value:function(){if(this.options.broadcaster=="reverb")this.connector=new fe(at(at({},this.options),{cluster:""}));else if(this.options.broadcaster=="pusher")this.connector=new fe(this.options);else if(this.options.broadcaster=="socket.io")this.connector=new pe(this.options);else if(this.options.broadcaster=="null")this.connector=new zi(this.options);else if(typeof this.options.broadcaster=="function"&&Ni(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error("Broadcaster ".concat(st(this.options.broadcaster)," ").concat(this.options.broadcaster," is not supported."))}},{key:"disconnect",value:function(){this.connector.disconnect()}},{key:"join",value:function(a){return this.connector.presenceChannel(a)}},{key:"leave",value:function(a){this.connector.leave(a)}},{key:"leaveChannel",value:function(a){this.connector.leaveChannel(a)}},{key:"leaveAllChannels",value:function(){for(var a in this.connector.channels)this.leaveChannel(a)}},{key:"listen",value:function(a,c,s){return this.connector.listen(a,c,s)}},{key:"private",value:function(a){return this.connector.privateChannel(a)}},{key:"encryptedPrivate",value:function(a){if(this.connector instanceof pe)throw new Error("Broadcaster ".concat(st(this.options.broadcaster)," ").concat(this.options.broadcaster," does not support encrypted private channels."));return this.connector.encryptedPrivateChannel(a)}},{key:"socketId",value:function(){return this.connector.socketId()}},{key:"registerInterceptors",value:function(){typeof Vue=="function"&&Vue.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),(typeof Turbo>"u"?"undefined":st(Turbo))==="object"&&this.registerTurboRequestInterceptor()}},{key:"registerVueRequestInterceptor",value:function(){var a=this;Vue.http.interceptors.push(function(c,s){a.socketId()&&c.headers.set("X-Socket-ID",a.socketId()),s()})}},{key:"registerAxiosRequestInterceptor",value:function(){var a=this;axios.interceptors.request.use(function(c){return a.socketId()&&(c.headers["X-Socket-Id"]=a.socketId()),c})}},{key:"registerjQueryAjaxSetup",value:function(){var a=this;typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter(function(c,s,f){a.socketId()&&f.setRequestHeader("X-Socket-Id",a.socketId())})}},{key:"registerTurboRequestInterceptor",value:function(){var a=this;document.addEventListener("turbo:before-fetch-request",function(c){c.detail.fetchOptions.headers["X-Socket-Id"]=a.socketId()})}}]),l}();var we=Li(me(),1);window.EchoFactory=be;window.Pusher=we.default;})(); -/*! Bundled license information: - -pusher-js/dist/web/pusher.js: - (*! - * Pusher JavaScript Library v7.6.0 - * https://pusher.com/ - * - * Copyright 2020, Pusher - * Released under the MIT licence. - *) -*/ diff --git a/public/js/filament/forms/components/color-picker.js b/public/js/filament/forms/components/color-picker.js deleted file mode 100644 index 6d80712..0000000 --- a/public/js/filament/forms/components/color-picker.js +++ /dev/null @@ -1 +0,0 @@ -var c=(e,t=0,r=1)=>e>r?r:eMath.round(r*e)/r;var nt={grad:360/400,turn:360,rad:360/(Math.PI*2)},F=e=>G(v(e)),v=e=>(e[0]==="#"&&(e=e.substring(1)),e.length<6?{r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:e.length===4?n(parseInt(e[3]+e[3],16)/255,2):1}:{r:parseInt(e.substring(0,2),16),g:parseInt(e.substring(2,4),16),b:parseInt(e.substring(4,6),16),a:e.length===8?n(parseInt(e.substring(6,8),16)/255,2):1}),at=(e,t="deg")=>Number(e)*(nt[t]||1),it=e=>{let r=/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?lt({h:at(r[1],r[2]),s:Number(r[3]),l:Number(r[4]),a:r[5]===void 0?1:Number(r[5])/(r[6]?100:1)}):{h:0,s:0,v:0,a:1}},J=it,lt=({h:e,s:t,l:r,a:o})=>(t*=(r<50?r:100-r)/100,{h:e,s:t>0?2*t/(r+t)*100:0,v:r+t,a:o}),X=e=>ct(A(e)),Y=({h:e,s:t,v:r,a:o})=>{let s=(200-t)*r/100;return{h:n(e),s:n(s>0&&s<200?t*r/100/(s<=100?s:200-s)*100:0),l:n(s/2),a:n(o,2)}};var d=e=>{let{h:t,s:r,l:o}=Y(e);return`hsl(${t}, ${r}%, ${o}%)`},$=e=>{let{h:t,s:r,l:o,a:s}=Y(e);return`hsla(${t}, ${r}%, ${o}%, ${s})`},A=({h:e,s:t,v:r,a:o})=>{e=e/360*6,t=t/100,r=r/100;let s=Math.floor(e),a=r*(1-t),i=r*(1-(e-s)*t),l=r*(1-(1-e+s)*t),q=s%6;return{r:n([r,i,a,a,l,r][q]*255),g:n([l,r,r,i,a,a][q]*255),b:n([a,a,l,r,r,i][q]*255),a:n(o,2)}},B=e=>{let{r:t,g:r,b:o}=A(e);return`rgb(${t}, ${r}, ${o})`},D=e=>{let{r:t,g:r,b:o,a:s}=A(e);return`rgba(${t}, ${r}, ${o}, ${s})`};var I=e=>{let r=/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?G({r:Number(r[1])/(r[2]?100/255:1),g:Number(r[3])/(r[4]?100/255:1),b:Number(r[5])/(r[6]?100/255:1),a:r[7]===void 0?1:Number(r[7])/(r[8]?100:1)}):{h:0,s:0,v:0,a:1}},U=I,b=e=>{let t=e.toString(16);return t.length<2?"0"+t:t},ct=({r:e,g:t,b:r,a:o})=>{let s=o<1?b(n(o*255)):"";return"#"+b(e)+b(t)+b(r)+s},G=({r:e,g:t,b:r,a:o})=>{let s=Math.max(e,t,r),a=s-Math.min(e,t,r),i=a?s===e?(t-r)/a:s===t?2+(r-e)/a:4+(e-t)/a:0;return{h:n(60*(i<0?i+6:i)),s:n(s?a/s*100:0),v:n(s/255*100),a:o}};var L=(e,t)=>{if(e===t)return!0;for(let r in e)if(e[r]!==t[r])return!1;return!0},h=(e,t)=>e.replace(/\s/g,"")===t.replace(/\s/g,""),K=(e,t)=>e.toLowerCase()===t.toLowerCase()?!0:L(v(e),v(t));var Q={},H=e=>{let t=Q[e];return t||(t=document.createElement("template"),t.innerHTML=e,Q[e]=t),t},f=(e,t,r)=>{e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))};var m=!1,O=e=>"touches"in e,pt=e=>m&&!O(e)?!1:(m||(m=O(e)),!0),W=(e,t)=>{let r=O(t)?t.touches[0]:t,o=e.el.getBoundingClientRect();f(e.el,"move",e.getMove({x:c((r.pageX-(o.left+window.pageXOffset))/o.width),y:c((r.pageY-(o.top+window.pageYOffset))/o.height)}))},ut=(e,t)=>{let r=t.keyCode;r>40||e.xy&&r<37||r<33||(t.preventDefault(),f(e.el,"move",e.getMove({x:r===39?.01:r===37?-.01:r===34?.05:r===33?-.05:r===35?1:r===36?-1:0,y:r===40?.01:r===38?-.01:0},!0)))},u=class{constructor(t,r,o,s){let a=H(`
`);t.appendChild(a.content.cloneNode(!0));let i=t.querySelector(`[part=${r}]`);i.addEventListener("mousedown",this),i.addEventListener("touchstart",this),i.addEventListener("keydown",this),this.el=i,this.xy=s,this.nodes=[i.firstChild,i]}set dragging(t){let r=t?document.addEventListener:document.removeEventListener;r(m?"touchmove":"mousemove",this),r(m?"touchend":"mouseup",this)}handleEvent(t){switch(t.type){case"mousedown":case"touchstart":if(t.preventDefault(),!pt(t)||!m&&t.button!=0)return;this.el.focus(),W(this,t),this.dragging=!0;break;case"mousemove":case"touchmove":t.preventDefault(),W(this,t);break;case"mouseup":case"touchend":this.dragging=!1;break;case"keydown":ut(this,t);break}}style(t){t.forEach((r,o)=>{for(let s in r)this.nodes[o].style.setProperty(s,r[s])})}};var S=class extends u{constructor(t){super(t,"hue",'aria-label="Hue" aria-valuemin="0" aria-valuemax="360"',!1)}update({h:t}){this.h=t,this.style([{left:`${t/360*100}%`,color:d({h:t,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuenow",`${n(t)}`)}getMove(t,r){return{h:r?c(this.h+t.x*360,0,360):360*t.x}}};var T=class extends u{constructor(t){super(t,"saturation",'aria-label="Color"',!0)}update(t){this.hsva=t,this.style([{top:`${100-t.v}%`,left:`${t.s}%`,color:d(t)},{"background-color":d({h:t.h,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuetext",`Saturation ${n(t.s)}%, Brightness ${n(t.v)}%`)}getMove(t,r){return{s:r?c(this.hsva.s+t.x*100,0,100):t.x*100,v:r?c(this.hsva.v-t.y*100,0,100):Math.round(100-t.y*100)}}};var Z=':host{display:flex;flex-direction:column;position:relative;width:200px;height:200px;user-select:none;-webkit-user-select:none;cursor:default}:host([hidden]){display:none!important}[role=slider]{position:relative;touch-action:none;user-select:none;-webkit-user-select:none;outline:0}[role=slider]:last-child{border-radius:0 0 8px 8px}[part$=pointer]{position:absolute;z-index:1;box-sizing:border-box;width:28px;height:28px;display:flex;place-content:center center;transform:translate(-50%,-50%);background-color:#fff;border:2px solid #fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2)}[part$=pointer]::after{content:"";width:100%;height:100%;border-radius:inherit;background-color:currentColor}[role=slider]:focus [part$=pointer]{transform:translate(-50%,-50%) scale(1.1)}';var tt="[part=hue]{flex:0 0 24px;background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}[part=hue-pointer]{top:50%;z-index:2}";var rt="[part=saturation]{flex-grow:1;border-color:transparent;border-bottom:12px solid #000;border-radius:8px 8px 0 0;background-image:linear-gradient(to top,#000,transparent),linear-gradient(to right,#fff,rgba(255,255,255,0));box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part=saturation-pointer]{z-index:3}";var w=Symbol("same"),R=Symbol("color"),et=Symbol("hsva"),_=Symbol("update"),ot=Symbol("parts"),g=Symbol("css"),x=Symbol("sliders"),p=class extends HTMLElement{static get observedAttributes(){return["color"]}get[g](){return[Z,tt,rt]}get[x](){return[T,S]}get color(){return this[R]}set color(t){if(!this[w](t)){let r=this.colorModel.toHsva(t);this[_](r),this[R]=t}}constructor(){super();let t=H(``),r=this.attachShadow({mode:"open"});r.appendChild(t.content.cloneNode(!0)),r.addEventListener("move",this),this[ot]=this[x].map(o=>new o(r))}connectedCallback(){if(this.hasOwnProperty("color")){let t=this.color;delete this.color,this.color=t}else this.color||(this.color=this.colorModel.defaultColor)}attributeChangedCallback(t,r,o){let s=this.colorModel.fromAttr(o);this[w](s)||(this.color=s)}handleEvent(t){let r=this[et],o={...r,...t.detail};this[_](o);let s;!L(o,r)&&!this[w](s=this.colorModel.fromHsva(o))&&(this[R]=s,f(this,"color-changed",{value:s}))}[w](t){return this.color&&this.colorModel.equal(t,this.color)}[_](t){this[et]=t,this[ot].forEach(r=>r.update(t))}};var dt={defaultColor:"#000",toHsva:F,fromHsva:({h:e,s:t,v:r})=>X({h:e,s:t,v:r,a:1}),equal:K,fromAttr:e=>e},y=class extends p{get colorModel(){return dt}};var P=class extends y{};customElements.define("hex-color-picker",P);var ht={defaultColor:"hsl(0, 0%, 0%)",toHsva:J,fromHsva:d,equal:h,fromAttr:e=>e},M=class extends p{get colorModel(){return ht}};var z=class extends M{};customElements.define("hsl-string-color-picker",z);var mt={defaultColor:"rgb(0, 0, 0)",toHsva:U,fromHsva:B,equal:h,fromAttr:e=>e},C=class extends p{get colorModel(){return mt}};var V=class extends C{};customElements.define("rgb-string-color-picker",V);var k=class extends u{constructor(t){super(t,"alpha",'aria-label="Alpha" aria-valuemin="0" aria-valuemax="1"',!1)}update(t){this.hsva=t;let r=$({...t,a:0}),o=$({...t,a:1}),s=t.a*100;this.style([{left:`${s}%`,color:$(t)},{"--gradient":`linear-gradient(90deg, ${r}, ${o}`}]);let a=n(s);this.el.setAttribute("aria-valuenow",`${a}`),this.el.setAttribute("aria-valuetext",`${a}%`)}getMove(t,r){return{a:r?c(this.hsva.a+t.x):t.x}}};var st=`[part=alpha]{flex:0 0 24px}[part=alpha]::after{display:block;content:"";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-image:var(--gradient);box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part^=alpha]{background-color:#fff;background-image:url('data:image/svg+xml,')}[part=alpha-pointer]{top:50%}`;var E=class extends p{get[g](){return[...super[g],st]}get[x](){return[...super[x],k]}};var ft={defaultColor:"rgba(0, 0, 0, 1)",toHsva:I,fromHsva:D,equal:h,fromAttr:e=>e},N=class extends E{get colorModel(){return ft}};var j=class extends N{};customElements.define("rgba-string-color-picker",j);function gt({isAutofocused:e,isDisabled:t,isLive:r,isLiveDebounced:o,isLiveOnBlur:s,liveDebounce:a,state:i}){return{state:i,init:function(){this.state===null||this.state===""||this.setState(this.state),e&&this.togglePanelVisibility(this.$refs.input),this.$refs.input.addEventListener("change",l=>{this.setState(l.target.value)}),this.$refs.panel.addEventListener("color-changed",l=>{this.setState(l.detail.value),!(s||!(r||o))&&setTimeout(()=>{this.state===l.detail.value&&this.commitState()},o?a:250)}),(r||o||s)&&new MutationObserver(()=>this.isOpen()?null:this.commitState()).observe(this.$refs.panel,{attributes:!0,childList:!0})},togglePanelVisibility:function(){t||this.$refs.panel.toggle(this.$refs.input)},setState:function(l){this.state=l,this.$refs.input.value=l,this.$refs.panel.color=l},isOpen:function(){return this.$refs.panel.style.display==="block"},commitState:function(){JSON.stringify(this.$wire.__instance.canonical)!==JSON.stringify(this.$wire.__instance.ephemeral)&&this.$wire.$commit()}}}export{gt as default}; diff --git a/public/js/filament/forms/components/date-time-picker.js b/public/js/filament/forms/components/date-time-picker.js deleted file mode 100644 index 309bdcd..0000000 --- a/public/js/filament/forms/components/date-time-picker.js +++ /dev/null @@ -1 +0,0 @@ -var bi=Object.create;var mn=Object.defineProperty;var ki=Object.getOwnPropertyDescriptor;var ji=Object.getOwnPropertyNames;var Hi=Object.getPrototypeOf,Ti=Object.prototype.hasOwnProperty;var b=(n,t)=>()=>(t||n((t={exports:{}}).exports,t),t.exports);var wi=(n,t,s,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of ji(t))!Ti.call(n,e)&&e!==s&&mn(n,e,{get:()=>t[e],enumerable:!(i=ki(t,e))||i.enumerable});return n};var de=(n,t,s)=>(s=n!=null?bi(Hi(n)):{},wi(t||!n||!n.__esModule?mn(s,"default",{value:n,enumerable:!0}):s,n));var jn=b((je,He)=>{(function(n,t){typeof je=="object"&&typeof He<"u"?He.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_plugin_advancedFormat=t()})(je,function(){"use strict";return function(n,t){var s=t.prototype,i=s.format;s.format=function(e){var r=this,u=this.$locale();if(!this.isValid())return i.bind(this)(e);var d=this.$utils(),o=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(a){switch(a){case"Q":return Math.ceil((r.$M+1)/3);case"Do":return u.ordinal(r.$D);case"gggg":return r.weekYear();case"GGGG":return r.isoWeekYear();case"wo":return u.ordinal(r.week(),"W");case"w":case"ww":return d.s(r.week(),a==="w"?1:2,"0");case"W":case"WW":return d.s(r.isoWeek(),a==="W"?1:2,"0");case"k":case"kk":return d.s(String(r.$H===0?24:r.$H),a==="k"?1:2,"0");case"X":return Math.floor(r.$d.getTime()/1e3);case"x":return r.$d.getTime();case"z":return"["+r.offsetName()+"]";case"zzz":return"["+r.offsetName("long")+"]";default:return a}});return i.bind(this)(o)}}})});var Hn=b((Te,we)=>{(function(n,t){typeof Te=="object"&&typeof we<"u"?we.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_plugin_customParseFormat=t()})(Te,function(){"use strict";var n={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},t=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,s=/\d/,i=/\d\d/,e=/\d\d?/,r=/\d*[^-_:/,()\s\d]+/,u={},d=function(m){return(m=+m)+(m>68?1900:2e3)},o=function(m){return function(Y){this[m]=+Y}},a=[/[+-]\d\d:?(\d\d)?|Z/,function(m){(this.zone||(this.zone={})).offset=function(Y){if(!Y||Y==="Z")return 0;var L=Y.match(/([+-]|\d\d)/g),D=60*L[1]+(+L[2]||0);return D===0?0:L[0]==="+"?-D:D}(m)}],_=function(m){var Y=u[m];return Y&&(Y.indexOf?Y:Y.s.concat(Y.f))},y=function(m,Y){var L,D=u.meridiem;if(D){for(var w=1;w<=24;w+=1)if(m.indexOf(D(w,0,Y))>-1){L=w>12;break}}else L=m===(Y?"pm":"PM");return L},f={A:[r,function(m){this.afternoon=y(m,!1)}],a:[r,function(m){this.afternoon=y(m,!0)}],Q:[s,function(m){this.month=3*(m-1)+1}],S:[s,function(m){this.milliseconds=100*+m}],SS:[i,function(m){this.milliseconds=10*+m}],SSS:[/\d{3}/,function(m){this.milliseconds=+m}],s:[e,o("seconds")],ss:[e,o("seconds")],m:[e,o("minutes")],mm:[e,o("minutes")],H:[e,o("hours")],h:[e,o("hours")],HH:[e,o("hours")],hh:[e,o("hours")],D:[e,o("day")],DD:[i,o("day")],Do:[r,function(m){var Y=u.ordinal,L=m.match(/\d+/);if(this.day=L[0],Y)for(var D=1;D<=31;D+=1)Y(D).replace(/\[|\]/g,"")===m&&(this.day=D)}],w:[e,o("week")],ww:[i,o("week")],M:[e,o("month")],MM:[i,o("month")],MMM:[r,function(m){var Y=_("months"),L=(_("monthsShort")||Y.map(function(D){return D.slice(0,3)})).indexOf(m)+1;if(L<1)throw new Error;this.month=L%12||L}],MMMM:[r,function(m){var Y=_("months").indexOf(m)+1;if(Y<1)throw new Error;this.month=Y%12||Y}],Y:[/[+-]?\d+/,o("year")],YY:[i,function(m){this.year=d(m)}],YYYY:[/\d{4}/,o("year")],Z:a,ZZ:a};function l(m){var Y,L;Y=m,L=u&&u.formats;for(var D=(m=Y.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function($,j,W){var U=W&&W.toUpperCase();return j||L[W]||n[W]||L[U].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(g,h,c){return h||c.slice(1)})})).match(t),w=D.length,v=0;v-1)return new Date((M==="X"?1e3:1)*p);var T=l(M)(p),I=T.year,N=T.month,E=T.day,P=T.hours,B=T.minutes,Q=T.seconds,re=T.milliseconds,Z=T.zone,J=T.week,G=new Date,X=E||(I||N?1:G.getDate()),ee=I||G.getFullYear(),le=0;I&&!N||(le=N>0?N-1:G.getMonth());var me,pe=P||0,De=B||0,Le=Q||0,ge=re||0;return Z?new Date(Date.UTC(ee,le,X,pe,De,Le,ge+60*Z.offset*1e3)):S?new Date(Date.UTC(ee,le,X,pe,De,Le,ge)):(me=new Date(ee,le,X,pe,De,Le,ge),J&&(me=k(me).week(J).toDate()),me)}catch{return new Date("")}}(C,x,A,L),this.init(),U&&U!==!0&&(this.$L=this.locale(U).$L),W&&C!=this.format(x)&&(this.$d=new Date("")),u={}}else if(x instanceof Array)for(var g=x.length,h=1;h<=g;h+=1){q[1]=x[h-1];var c=L.apply(this,q);if(c.isValid()){this.$d=c.$d,this.$L=c.$L,this.init();break}h===g&&(this.$d=new Date(""))}else w.call(this,v)}}})});var Tn=b(($e,Ce)=>{(function(n,t){typeof $e=="object"&&typeof Ce<"u"?Ce.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_plugin_localeData=t()})($e,function(){"use strict";return function(n,t,s){var i=t.prototype,e=function(a){return a&&(a.indexOf?a:a.s)},r=function(a,_,y,f,l){var m=a.name?a:a.$locale(),Y=e(m[_]),L=e(m[y]),D=Y||L.map(function(v){return v.slice(0,f)});if(!l)return D;var w=m.weekStart;return D.map(function(v,C){return D[(C+(w||0))%7]})},u=function(){return s.Ls[s.locale()]},d=function(a,_){return a.formats[_]||function(y){return y.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(f,l,m){return l||m.slice(1)})}(a.formats[_.toUpperCase()])},o=function(){var a=this;return{months:function(_){return _?_.format("MMMM"):r(a,"months")},monthsShort:function(_){return _?_.format("MMM"):r(a,"monthsShort","months",3)},firstDayOfWeek:function(){return a.$locale().weekStart||0},weekdays:function(_){return _?_.format("dddd"):r(a,"weekdays")},weekdaysMin:function(_){return _?_.format("dd"):r(a,"weekdaysMin","weekdays",2)},weekdaysShort:function(_){return _?_.format("ddd"):r(a,"weekdaysShort","weekdays",3)},longDateFormat:function(_){return d(a.$locale(),_)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}};i.localeData=function(){return o.bind(this)()},s.localeData=function(){var a=u();return{firstDayOfWeek:function(){return a.weekStart||0},weekdays:function(){return s.weekdays()},weekdaysShort:function(){return s.weekdaysShort()},weekdaysMin:function(){return s.weekdaysMin()},months:function(){return s.months()},monthsShort:function(){return s.monthsShort()},longDateFormat:function(_){return d(a,_)},meridiem:a.meridiem,ordinal:a.ordinal}},s.months=function(){return r(u(),"months")},s.monthsShort=function(){return r(u(),"monthsShort","months",3)},s.weekdays=function(a){return r(u(),"weekdays",null,null,a)},s.weekdaysShort=function(a){return r(u(),"weekdaysShort","weekdays",3,a)},s.weekdaysMin=function(a){return r(u(),"weekdaysMin","weekdays",2,a)}}})});var wn=b((Oe,ze)=>{(function(n,t){typeof Oe=="object"&&typeof ze<"u"?ze.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_plugin_timezone=t()})(Oe,function(){"use strict";var n={year:0,month:1,day:2,hour:3,minute:4,second:5},t={};return function(s,i,e){var r,u=function(_,y,f){f===void 0&&(f={});var l=new Date(_),m=function(Y,L){L===void 0&&(L={});var D=L.timeZoneName||"short",w=Y+"|"+D,v=t[w];return v||(v=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:Y,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:D}),t[w]=v),v}(y,f);return m.formatToParts(l)},d=function(_,y){for(var f=u(_,y),l=[],m=0;m=0&&(l[w]=parseInt(D,10))}var v=l[3],C=v===24?0:v,A=l[0]+"-"+l[1]+"-"+l[2]+" "+C+":"+l[4]+":"+l[5]+":000",q=+_;return(e.utc(A).valueOf()-(q-=q%1e3))/6e4},o=i.prototype;o.tz=function(_,y){_===void 0&&(_=r);var f,l=this.utcOffset(),m=this.toDate(),Y=m.toLocaleString("en-US",{timeZone:_}),L=Math.round((m-new Date(Y))/1e3/60),D=15*-Math.round(m.getTimezoneOffset()/15)-L;if(!Number(D))f=this.utcOffset(0,y);else if(f=e(Y,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(D,!0),y){var w=f.utcOffset();f=f.add(l-w,"minute")}return f.$x.$timezone=_,f},o.offsetName=function(_){var y=this.$x.$timezone||e.tz.guess(),f=u(this.valueOf(),y,{timeZoneName:_}).find(function(l){return l.type.toLowerCase()==="timezonename"});return f&&f.value};var a=o.startOf;o.startOf=function(_,y){if(!this.$x||!this.$x.$timezone)return a.call(this,_,y);var f=e(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return a.call(f,_,y).tz(this.$x.$timezone,!0)},e.tz=function(_,y,f){var l=f&&y,m=f||y||r,Y=d(+e(),m);if(typeof _!="string")return e(_).tz(m);var L=function(C,A,q){var x=C-60*A*1e3,$=d(x,q);if(A===$)return[x,A];var j=d(x-=60*($-A)*1e3,q);return $===j?[x,$]:[C-60*Math.min($,j)*1e3,Math.max($,j)]}(e.utc(_,l).valueOf(),Y,m),D=L[0],w=L[1],v=e(D).utcOffset(w);return v.$x.$timezone=m,v},e.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},e.tz.setDefault=function(_){r=_}}})});var $n=b((Ae,Ie)=>{(function(n,t){typeof Ae=="object"&&typeof Ie<"u"?Ie.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_plugin_utc=t()})(Ae,function(){"use strict";var n="minute",t=/[+-]\d\d(?::?\d\d)?/g,s=/([+-]|\d\d)/g;return function(i,e,r){var u=e.prototype;r.utc=function(l){var m={date:l,utc:!0,args:arguments};return new e(m)},u.utc=function(l){var m=r(this.toDate(),{locale:this.$L,utc:!0});return l?m.add(this.utcOffset(),n):m},u.local=function(){return r(this.toDate(),{locale:this.$L,utc:!1})};var d=u.parse;u.parse=function(l){l.utc&&(this.$u=!0),this.$utils().u(l.$offset)||(this.$offset=l.$offset),d.call(this,l)};var o=u.init;u.init=function(){if(this.$u){var l=this.$d;this.$y=l.getUTCFullYear(),this.$M=l.getUTCMonth(),this.$D=l.getUTCDate(),this.$W=l.getUTCDay(),this.$H=l.getUTCHours(),this.$m=l.getUTCMinutes(),this.$s=l.getUTCSeconds(),this.$ms=l.getUTCMilliseconds()}else o.call(this)};var a=u.utcOffset;u.utcOffset=function(l,m){var Y=this.$utils().u;if(Y(l))return this.$u?0:Y(this.$offset)?a.call(this):this.$offset;if(typeof l=="string"&&(l=function(v){v===void 0&&(v="");var C=v.match(t);if(!C)return null;var A=(""+C[0]).match(s)||["-",0,0],q=A[0],x=60*+A[1]+ +A[2];return x===0?0:q==="+"?x:-x}(l),l===null))return this;var L=Math.abs(l)<=16?60*l:l,D=this;if(m)return D.$offset=L,D.$u=l===0,D;if(l!==0){var w=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(D=this.local().add(L+w,n)).$offset=L,D.$x.$localOffset=w}else D=this.utc();return D};var _=u.format;u.format=function(l){var m=l||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return _.call(this,m)},u.valueOf=function(){var l=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*l},u.isUTC=function(){return!!this.$u},u.toISOString=function(){return this.toDate().toISOString()},u.toString=function(){return this.toDate().toUTCString()};var y=u.toDate;u.toDate=function(l){return l==="s"&&this.$offset?r(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():y.call(this)};var f=u.diff;u.diff=function(l,m,Y){if(l&&this.$u===l.$u)return f.call(this,l,m,Y);var L=this.local(),D=r(l).local();return f.call(L,D,m,Y)}}})});var H=b((qe,xe)=>{(function(n,t){typeof qe=="object"&&typeof xe<"u"?xe.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs=t()})(qe,function(){"use strict";var n=1e3,t=6e4,s=36e5,i="millisecond",e="second",r="minute",u="hour",d="day",o="week",a="month",_="quarter",y="year",f="date",l="Invalid Date",m=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,Y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,L={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(g){var h=["th","st","nd","rd"],c=g%100;return"["+g+(h[(c-20)%10]||h[c]||h[0])+"]"}},D=function(g,h,c){var p=String(g);return!p||p.length>=h?g:""+Array(h+1-p.length).join(c)+g},w={s:D,z:function(g){var h=-g.utcOffset(),c=Math.abs(h),p=Math.floor(c/60),M=c%60;return(h<=0?"+":"-")+D(p,2,"0")+":"+D(M,2,"0")},m:function g(h,c){if(h.date()1)return g(k[0])}else{var T=h.name;C[T]=h,M=T}return!p&&M&&(v=M),M||!p&&v},$=function(g,h){if(q(g))return g.clone();var c=typeof h=="object"?h:{};return c.date=g,c.args=arguments,new W(c)},j=w;j.l=x,j.i=q,j.w=function(g,h){return $(g,{locale:h.$L,utc:h.$u,x:h.$x,$offset:h.$offset})};var W=function(){function g(c){this.$L=x(c.locale,null,!0),this.parse(c),this.$x=this.$x||c.x||{},this[A]=!0}var h=g.prototype;return h.parse=function(c){this.$d=function(p){var M=p.date,S=p.utc;if(M===null)return new Date(NaN);if(j.u(M))return new Date;if(M instanceof Date)return new Date(M);if(typeof M=="string"&&!/Z$/i.test(M)){var k=M.match(m);if(k){var T=k[2]-1||0,I=(k[7]||"0").substring(0,3);return S?new Date(Date.UTC(k[1],T,k[3]||1,k[4]||0,k[5]||0,k[6]||0,I)):new Date(k[1],T,k[3]||1,k[4]||0,k[5]||0,k[6]||0,I)}}return new Date(M)}(c),this.init()},h.init=function(){var c=this.$d;this.$y=c.getFullYear(),this.$M=c.getMonth(),this.$D=c.getDate(),this.$W=c.getDay(),this.$H=c.getHours(),this.$m=c.getMinutes(),this.$s=c.getSeconds(),this.$ms=c.getMilliseconds()},h.$utils=function(){return j},h.isValid=function(){return this.$d.toString()!==l},h.isSame=function(c,p){var M=$(c);return this.startOf(p)<=M&&M<=this.endOf(p)},h.isAfter=function(c,p){return $(c){(function(n,t){typeof Ne=="object"&&typeof Ee<"u"?Ee.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ar=t(n.dayjs)})(Ne,function(n){"use strict";function t(d){return d&&typeof d=="object"&&"default"in d?d:{default:d}}var s=t(n),i="\u064A\u0646\u0627\u064A\u0631_\u0641\u0628\u0631\u0627\u064A\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064A\u0644_\u0645\u0627\u064A\u0648_\u064A\u0648\u0646\u064A\u0648_\u064A\u0648\u0644\u064A\u0648_\u0623\u063A\u0633\u0637\u0633_\u0633\u0628\u062A\u0645\u0628\u0631_\u0623\u0643\u062A\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062F\u064A\u0633\u0645\u0628\u0631".split("_"),e={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},r={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},u={name:"ar",weekdays:"\u0627\u0644\u0623\u062D\u062F_\u0627\u0644\u0625\u062B\u0646\u064A\u0646_\u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062E\u0645\u064A\u0633_\u0627\u0644\u062C\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062A".split("_"),weekdaysShort:"\u0623\u062D\u062F_\u0625\u062B\u0646\u064A\u0646_\u062B\u0644\u0627\u062B\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062E\u0645\u064A\u0633_\u062C\u0645\u0639\u0629_\u0633\u0628\u062A".split("_"),weekdaysMin:"\u062D_\u0646_\u062B_\u0631_\u062E_\u062C_\u0633".split("_"),months:i,monthsShort:i,weekStart:6,meridiem:function(d){return d>12?"\u0645":"\u0635"},relativeTime:{future:"\u0628\u0639\u062F %s",past:"\u0645\u0646\u0630 %s",s:"\u062B\u0627\u0646\u064A\u0629 \u0648\u0627\u062D\u062F\u0629",m:"\u062F\u0642\u064A\u0642\u0629 \u0648\u0627\u062D\u062F\u0629",mm:"%d \u062F\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629 \u0648\u0627\u062D\u062F\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062A",d:"\u064A\u0648\u0645 \u0648\u0627\u062D\u062F",dd:"%d \u0623\u064A\u0627\u0645",M:"\u0634\u0647\u0631 \u0648\u0627\u062D\u062F",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0639\u0627\u0645 \u0648\u0627\u062D\u062F",yy:"%d \u0623\u0639\u0648\u0627\u0645"},preparse:function(d){return d.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(o){return r[o]}).replace(/،/g,",")},postformat:function(d){return d.replace(/\d/g,function(o){return e[o]}).replace(/,/g,"\u060C")},ordinal:function(d){return d},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200FM/\u200FYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"}};return s.default.locale(u,null,!0),u})});var On=b((Fe,Je)=>{(function(n,t){typeof Fe=="object"&&typeof Je<"u"?Je.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_bs=t(n.dayjs)})(Fe,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"bs",weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010Detvrtak_petak_subota".split("_"),months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),weekStart:1,weekdaysShort:"ned._pon._uto._sri._\u010Det._pet._sub.".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010De_pe_su".split("_"),ordinal:function(e){return e},formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"}};return s.default.locale(i,null,!0),i})});var zn=b((We,Ue)=>{(function(n,t){typeof We=="object"&&typeof Ue<"u"?Ue.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ca=t(n.dayjs)})(We,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"ca",weekdays:"Diumenge_Dilluns_Dimarts_Dimecres_Dijous_Divendres_Dissabte".split("_"),weekdaysShort:"Dg._Dl._Dt._Dc._Dj._Dv._Ds.".split("_"),weekdaysMin:"Dg_Dl_Dt_Dc_Dj_Dv_Ds".split("_"),months:"Gener_Febrer_Mar\xE7_Abril_Maig_Juny_Juliol_Agost_Setembre_Octubre_Novembre_Desembre".split("_"),monthsShort:"Gen._Febr._Mar\xE7_Abr._Maig_Juny_Jul._Ag._Set._Oct._Nov._Des.".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",LLL:"D MMMM [de] YYYY [a les] H:mm",LLLL:"dddd D MMMM [de] YYYY [a les] H:mm",ll:"D MMM YYYY",lll:"D MMM YYYY, H:mm",llll:"ddd D MMM YYYY, H:mm"},relativeTime:{future:"d'aqu\xED %s",past:"fa %s",s:"uns segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},ordinal:function(e){return""+e+(e===1||e===3?"r":e===2?"n":e===4?"t":"\xE8")}};return s.default.locale(i,null,!0),i})});var Pe=b((Ye,An)=>{(function(n,t){typeof Ye=="object"&&typeof An<"u"?t(Ye,H()):typeof define=="function"&&define.amd?define(["exports","dayjs"],t):t((n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ku={},n.dayjs)})(Ye,function(n,t){"use strict";function s(o){return o&&typeof o=="object"&&"default"in o?o:{default:o}}var i=s(t),e={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},r={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},u=["\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645","\u0634\u0648\u0628\u0627\u062A","\u0626\u0627\u062F\u0627\u0631","\u0646\u06CC\u0633\u0627\u0646","\u0626\u0627\u06CC\u0627\u0631","\u062D\u0648\u0632\u06D5\u06CC\u0631\u0627\u0646","\u062A\u06D5\u0645\u0645\u0648\u0648\u0632","\u0626\u0627\u0628","\u0626\u06D5\u06CC\u0644\u0648\u0648\u0644","\u062A\u0634\u0631\u06CC\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645","\u062A\u0634\u0631\u06CC\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645","\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645"],d={name:"ku",months:u,monthsShort:u,weekdays:"\u06CC\u06D5\u06A9\u0634\u06D5\u0645\u0645\u06D5_\u062F\u0648\u0648\u0634\u06D5\u0645\u0645\u06D5_\u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5_\u0686\u0648\u0627\u0631\u0634\u06D5\u0645\u0645\u06D5_\u067E\u06CE\u0646\u062C\u0634\u06D5\u0645\u0645\u06D5_\u0647\u06D5\u06CC\u0646\u06CC_\u0634\u06D5\u0645\u0645\u06D5".split("_"),weekdaysShort:"\u06CC\u06D5\u06A9\u0634\u06D5\u0645_\u062F\u0648\u0648\u0634\u06D5\u0645_\u0633\u06CE\u0634\u06D5\u0645_\u0686\u0648\u0627\u0631\u0634\u06D5\u0645_\u067E\u06CE\u0646\u062C\u0634\u06D5\u0645_\u0647\u06D5\u06CC\u0646\u06CC_\u0634\u06D5\u0645\u0645\u06D5".split("_"),weekStart:6,weekdaysMin:"\u06CC_\u062F_\u0633_\u0686_\u067E_\u0647\u0640_\u0634".split("_"),preparse:function(o){return o.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(a){return r[a]}).replace(/،/g,",")},postformat:function(o){return o.replace(/\d/g,function(a){return e[a]}).replace(/,/g,"\u060C")},ordinal:function(o){return o},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiem:function(o){return o<12?"\u067E.\u0646":"\u062F.\u0646"},relativeTime:{future:"\u0644\u06D5 %s",past:"\u0644\u06D5\u0645\u06D5\u0648\u067E\u06CE\u0634 %s",s:"\u0686\u06D5\u0646\u062F \u0686\u0631\u06A9\u06D5\u06CC\u06D5\u06A9",m:"\u06CC\u06D5\u06A9 \u062E\u0648\u0644\u06D5\u06A9",mm:"%d \u062E\u0648\u0644\u06D5\u06A9",h:"\u06CC\u06D5\u06A9 \u06A9\u0627\u062A\u0698\u0645\u06CE\u0631",hh:"%d \u06A9\u0627\u062A\u0698\u0645\u06CE\u0631",d:"\u06CC\u06D5\u06A9 \u0695\u06C6\u0698",dd:"%d \u0695\u06C6\u0698",M:"\u06CC\u06D5\u06A9 \u0645\u0627\u0646\u06AF",MM:"%d \u0645\u0627\u0646\u06AF",y:"\u06CC\u06D5\u06A9 \u0633\u0627\u06B5",yy:"%d \u0633\u0627\u06B5"}};i.default.locale(d,null,!0),n.default=d,n.englishToArabicNumbersMap=e,Object.defineProperty(n,"__esModule",{value:!0})})});var In=b((Re,Ge)=>{(function(n,t){typeof Re=="object"&&typeof Ge<"u"?Ge.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_cs=t(n.dayjs)})(Re,function(n){"use strict";function t(u){return u&&typeof u=="object"&&"default"in u?u:{default:u}}var s=t(n);function i(u){return u>1&&u<5&&~~(u/10)!=1}function e(u,d,o,a){var _=u+" ";switch(o){case"s":return d||a?"p\xE1r sekund":"p\xE1r sekundami";case"m":return d?"minuta":a?"minutu":"minutou";case"mm":return d||a?_+(i(u)?"minuty":"minut"):_+"minutami";case"h":return d?"hodina":a?"hodinu":"hodinou";case"hh":return d||a?_+(i(u)?"hodiny":"hodin"):_+"hodinami";case"d":return d||a?"den":"dnem";case"dd":return d||a?_+(i(u)?"dny":"dn\xED"):_+"dny";case"M":return d||a?"m\u011Bs\xEDc":"m\u011Bs\xEDcem";case"MM":return d||a?_+(i(u)?"m\u011Bs\xEDce":"m\u011Bs\xEDc\u016F"):_+"m\u011Bs\xEDci";case"y":return d||a?"rok":"rokem";case"yy":return d||a?_+(i(u)?"roky":"let"):_+"lety"}}var r={name:"cs",weekdays:"ned\u011Ble_pond\u011Bl\xED_\xFAter\xFD_st\u0159eda_\u010Dtvrtek_p\xE1tek_sobota".split("_"),weekdaysShort:"ne_po_\xFAt_st_\u010Dt_p\xE1_so".split("_"),weekdaysMin:"ne_po_\xFAt_st_\u010Dt_p\xE1_so".split("_"),months:"leden_\xFAnor_b\u0159ezen_duben_kv\u011Bten_\u010Derven_\u010Dervenec_srpen_z\xE1\u0159\xED_\u0159\xEDjen_listopad_prosinec".split("_"),monthsShort:"led_\xFAno_b\u0159e_dub_kv\u011B_\u010Dvn_\u010Dvc_srp_z\xE1\u0159_\u0159\xEDj_lis_pro".split("_"),weekStart:1,yearStart:4,ordinal:function(u){return u+"."},formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm",l:"D. M. YYYY"},relativeTime:{future:"za %s",past:"p\u0159ed %s",s:e,m:e,mm:e,h:e,hh:e,d:e,dd:e,M:e,MM:e,y:e,yy:e}};return s.default.locale(r,null,!0),r})});var qn=b((Ze,Ve)=>{(function(n,t){typeof Ze=="object"&&typeof Ve<"u"?Ve.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_cy=t(n.dayjs)})(Ze,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"cy",weekdays:"Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"),months:"Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"),weekStart:1,weekdaysShort:"Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"),monthsShort:"Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"),weekdaysMin:"Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},relativeTime:{future:"mewn %s",past:"%s yn \xF4l",s:"ychydig eiliadau",m:"munud",mm:"%d munud",h:"awr",hh:"%d awr",d:"diwrnod",dd:"%d diwrnod",M:"mis",MM:"%d mis",y:"blwyddyn",yy:"%d flynedd"}};return s.default.locale(i,null,!0),i})});var xn=b((Ke,Qe)=>{(function(n,t){typeof Ke=="object"&&typeof Qe<"u"?Qe.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_da=t(n.dayjs)})(Ke,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"da",weekdays:"s\xF8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xF8rdag".split("_"),weekdaysShort:"s\xF8n._man._tirs._ons._tors._fre._l\xF8r.".split("_"),weekdaysMin:"s\xF8._ma._ti._on._to._fr._l\xF8.".split("_"),months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj_juni_juli_aug._sept._okt._nov._dec.".split("_"),weekStart:1,yearStart:4,ordinal:function(e){return e+"."},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd [d.] D. MMMM YYYY [kl.] HH:mm"},relativeTime:{future:"om %s",past:"%s siden",s:"f\xE5 sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en m\xE5ned",MM:"%d m\xE5neder",y:"et \xE5r",yy:"%d \xE5r"}};return s.default.locale(i,null,!0),i})});var Nn=b((Xe,Be)=>{(function(n,t){typeof Xe=="object"&&typeof Be<"u"?Be.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_de=t(n.dayjs)})(Xe,function(n){"use strict";function t(u){return u&&typeof u=="object"&&"default"in u?u:{default:u}}var s=t(n),i={s:"ein paar Sekunden",m:["eine Minute","einer Minute"],mm:"%d Minuten",h:["eine Stunde","einer Stunde"],hh:"%d Stunden",d:["ein Tag","einem Tag"],dd:["%d Tage","%d Tagen"],M:["ein Monat","einem Monat"],MM:["%d Monate","%d Monaten"],y:["ein Jahr","einem Jahr"],yy:["%d Jahre","%d Jahren"]};function e(u,d,o){var a=i[o];return Array.isArray(a)&&(a=a[d?0:1]),a.replace("%d",u)}var r={name:"de",weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),months:"Januar_Februar_M\xE4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xE4rz_Apr._Mai_Juni_Juli_Aug._Sept._Okt._Nov._Dez.".split("_"),ordinal:function(u){return u+"."},weekStart:1,yearStart:4,formats:{LTS:"HH:mm:ss",LT:"HH:mm",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},relativeTime:{future:"in %s",past:"vor %s",s:e,m:e,mm:e,h:e,hh:e,d:e,dd:e,M:e,MM:e,y:e,yy:e}};return s.default.locale(r,null,!0),r})});var En=b((et,tt)=>{(function(n,t){typeof et=="object"&&typeof tt<"u"?tt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_el=t(n.dayjs)})(et,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"el",weekdays:"\u039A\u03C5\u03C1\u03B9\u03B1\u03BA\u03AE_\u0394\u03B5\u03C5\u03C4\u03AD\u03C1\u03B1_\u03A4\u03C1\u03AF\u03C4\u03B7_\u03A4\u03B5\u03C4\u03AC\u03C1\u03C4\u03B7_\u03A0\u03AD\u03BC\u03C0\u03C4\u03B7_\u03A0\u03B1\u03C1\u03B1\u03C3\u03BA\u03B5\u03C5\u03AE_\u03A3\u03AC\u03B2\u03B2\u03B1\u03C4\u03BF".split("_"),weekdaysShort:"\u039A\u03C5\u03C1_\u0394\u03B5\u03C5_\u03A4\u03C1\u03B9_\u03A4\u03B5\u03C4_\u03A0\u03B5\u03BC_\u03A0\u03B1\u03C1_\u03A3\u03B1\u03B2".split("_"),weekdaysMin:"\u039A\u03C5_\u0394\u03B5_\u03A4\u03C1_\u03A4\u03B5_\u03A0\u03B5_\u03A0\u03B1_\u03A3\u03B1".split("_"),months:"\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2_\u03A6\u03B5\u03B2\u03C1\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2_\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2_\u0391\u03C0\u03C1\u03AF\u03BB\u03B9\u03BF\u03C2_\u039C\u03AC\u03B9\u03BF\u03C2_\u0399\u03BF\u03CD\u03BD\u03B9\u03BF\u03C2_\u0399\u03BF\u03CD\u03BB\u03B9\u03BF\u03C2_\u0391\u03CD\u03B3\u03BF\u03C5\u03C3\u03C4\u03BF\u03C2_\u03A3\u03B5\u03C0\u03C4\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2_\u039F\u03BA\u03C4\u03CE\u03B2\u03C1\u03B9\u03BF\u03C2_\u039D\u03BF\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2_\u0394\u03B5\u03BA\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2".split("_"),monthsShort:"\u0399\u03B1\u03BD_\u03A6\u03B5\u03B2_\u039C\u03B1\u03C1_\u0391\u03C0\u03C1_\u039C\u03B1\u03B9_\u0399\u03BF\u03C5\u03BD_\u0399\u03BF\u03C5\u03BB_\u0391\u03C5\u03B3_\u03A3\u03B5\u03C0\u03C4_\u039F\u03BA\u03C4_\u039D\u03BF\u03B5_\u0394\u03B5\u03BA".split("_"),ordinal:function(e){return e},weekStart:1,relativeTime:{future:"\u03C3\u03B5 %s",past:"\u03C0\u03C1\u03B9\u03BD %s",s:"\u03BC\u03B5\u03C1\u03B9\u03BA\u03AC \u03B4\u03B5\u03C5\u03C4\u03B5\u03C1\u03CC\u03BB\u03B5\u03C0\u03C4\u03B1",m:"\u03AD\u03BD\u03B1 \u03BB\u03B5\u03C0\u03C4\u03CC",mm:"%d \u03BB\u03B5\u03C0\u03C4\u03AC",h:"\u03BC\u03AF\u03B1 \u03CE\u03C1\u03B1",hh:"%d \u03CE\u03C1\u03B5\u03C2",d:"\u03BC\u03AF\u03B1 \u03BC\u03AD\u03C1\u03B1",dd:"%d \u03BC\u03AD\u03C1\u03B5\u03C2",M:"\u03AD\u03BD\u03B1 \u03BC\u03AE\u03BD\u03B1",MM:"%d \u03BC\u03AE\u03BD\u03B5\u03C2",y:"\u03AD\u03BD\u03B1 \u03C7\u03C1\u03CC\u03BD\u03BF",yy:"%d \u03C7\u03C1\u03CC\u03BD\u03B9\u03B1"},formats:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"}};return s.default.locale(i,null,!0),i})});var Fn=b((nt,it)=>{(function(n,t){typeof nt=="object"&&typeof it<"u"?it.exports=t():typeof define=="function"&&define.amd?define(t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_en=t()})(nt,function(){"use strict";return{name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(n){var t=["th","st","nd","rd"],s=n%100;return"["+n+(t[(s-20)%10]||t[s]||t[0])+"]"}}})});var Jn=b((st,rt)=>{(function(n,t){typeof st=="object"&&typeof rt<"u"?rt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_es=t(n.dayjs)})(st,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"es",monthsShort:"ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),weekdays:"domingo_lunes_martes_mi\xE9rcoles_jueves_viernes_s\xE1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xE9._jue._vie._s\xE1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xE1".split("_"),months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xEDa",dd:"%d d\xEDas",M:"un mes",MM:"%d meses",y:"un a\xF1o",yy:"%d a\xF1os"},ordinal:function(e){return e+"\xBA"}};return s.default.locale(i,null,!0),i})});var Wn=b((at,ut)=>{(function(n,t){typeof at=="object"&&typeof ut<"u"?ut.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_et=t(n.dayjs)})(at,function(n){"use strict";function t(r){return r&&typeof r=="object"&&"default"in r?r:{default:r}}var s=t(n);function i(r,u,d,o){var a={s:["m\xF5ne sekundi","m\xF5ni sekund","paar sekundit"],m:["\xFChe minuti","\xFCks minut"],mm:["%d minuti","%d minutit"],h:["\xFChe tunni","tund aega","\xFCks tund"],hh:["%d tunni","%d tundi"],d:["\xFChe p\xE4eva","\xFCks p\xE4ev"],M:["kuu aja","kuu aega","\xFCks kuu"],MM:["%d kuu","%d kuud"],y:["\xFChe aasta","aasta","\xFCks aasta"],yy:["%d aasta","%d aastat"]};return u?(a[d][2]?a[d][2]:a[d][1]).replace("%d",r):(o?a[d][0]:a[d][1]).replace("%d",r)}var e={name:"et",weekdays:"p\xFChap\xE4ev_esmasp\xE4ev_teisip\xE4ev_kolmap\xE4ev_neljap\xE4ev_reede_laup\xE4ev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),months:"jaanuar_veebruar_m\xE4rts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_m\xE4rts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),ordinal:function(r){return r+"."},weekStart:1,relativeTime:{future:"%s p\xE4rast",past:"%s tagasi",s:i,m:i,mm:i,h:i,hh:i,d:i,dd:"%d p\xE4eva",M:i,MM:i,y:i,yy:i},formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"}};return s.default.locale(e,null,!0),e})});var Un=b((ot,dt)=>{(function(n,t){typeof ot=="object"&&typeof dt<"u"?dt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_fa=t(n.dayjs)})(ot,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"fa",weekdays:"\u06CC\u06A9\u200C\u0634\u0646\u0628\u0647_\u062F\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200C\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067E\u0646\u062C\u200C\u0634\u0646\u0628\u0647_\u062C\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysShort:"\u06CC\u06A9\u200C\u0634\u0646\u0628\u0647_\u062F\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200C\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067E\u0646\u062C\u200C\u0634\u0646\u0628\u0647_\u062C\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysMin:"\u06CC_\u062F_\u0633_\u0686_\u067E_\u062C_\u0634".split("_"),weekStart:6,months:"\u0698\u0627\u0646\u0648\u06CC\u0647_\u0641\u0648\u0631\u06CC\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06CC\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06CC\u0647_\u0627\u0648\u062A_\u0633\u067E\u062A\u0627\u0645\u0628\u0631_\u0627\u06A9\u062A\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062F\u0633\u0627\u0645\u0628\u0631".split("_"),monthsShort:"\u0698\u0627\u0646\u0648\u06CC\u0647_\u0641\u0648\u0631\u06CC\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06CC\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06CC\u0647_\u0627\u0648\u062A_\u0633\u067E\u062A\u0627\u0645\u0628\u0631_\u0627\u06A9\u062A\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062F\u0633\u0627\u0645\u0628\u0631".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},relativeTime:{future:"\u062F\u0631 %s",past:"%s \u067E\u06CC\u0634",s:"\u0686\u0646\u062F \u062B\u0627\u0646\u06CC\u0647",m:"\u06CC\u06A9 \u062F\u0642\u06CC\u0642\u0647",mm:"%d \u062F\u0642\u06CC\u0642\u0647",h:"\u06CC\u06A9 \u0633\u0627\u0639\u062A",hh:"%d \u0633\u0627\u0639\u062A",d:"\u06CC\u06A9 \u0631\u0648\u0632",dd:"%d \u0631\u0648\u0632",M:"\u06CC\u06A9 \u0645\u0627\u0647",MM:"%d \u0645\u0627\u0647",y:"\u06CC\u06A9 \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"}};return s.default.locale(i,null,!0),i})});var Pn=b((_t,ft)=>{(function(n,t){typeof _t=="object"&&typeof ft<"u"?ft.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_fi=t(n.dayjs)})(_t,function(n){"use strict";function t(r){return r&&typeof r=="object"&&"default"in r?r:{default:r}}var s=t(n);function i(r,u,d,o){var a={s:"muutama sekunti",m:"minuutti",mm:"%d minuuttia",h:"tunti",hh:"%d tuntia",d:"p\xE4iv\xE4",dd:"%d p\xE4iv\xE4\xE4",M:"kuukausi",MM:"%d kuukautta",y:"vuosi",yy:"%d vuotta",numbers:"nolla_yksi_kaksi_kolme_nelj\xE4_viisi_kuusi_seitsem\xE4n_kahdeksan_yhdeks\xE4n".split("_")},_={s:"muutaman sekunnin",m:"minuutin",mm:"%d minuutin",h:"tunnin",hh:"%d tunnin",d:"p\xE4iv\xE4n",dd:"%d p\xE4iv\xE4n",M:"kuukauden",MM:"%d kuukauden",y:"vuoden",yy:"%d vuoden",numbers:"nollan_yhden_kahden_kolmen_nelj\xE4n_viiden_kuuden_seitsem\xE4n_kahdeksan_yhdeks\xE4n".split("_")},y=o&&!u?_:a,f=y[d];return r<10?f.replace("%d",y.numbers[r]):f.replace("%d",r)}var e={name:"fi",weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kes\xE4kuu_hein\xE4kuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kes\xE4_hein\xE4_elo_syys_loka_marras_joulu".split("_"),ordinal:function(r){return r+"."},weekStart:1,yearStart:4,relativeTime:{future:"%s p\xE4\xE4st\xE4",past:"%s sitten",s:i,m:i,mm:i,h:i,hh:i,d:i,dd:i,M:i,MM:i,y:i,yy:i},formats:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM[ta] YYYY",LLL:"D. MMMM[ta] YYYY, [klo] HH.mm",LLLL:"dddd, D. MMMM[ta] YYYY, [klo] HH.mm",l:"D.M.YYYY",ll:"D. MMM YYYY",lll:"D. MMM YYYY, [klo] HH.mm",llll:"ddd, D. MMM YYYY, [klo] HH.mm"}};return s.default.locale(e,null,!0),e})});var Rn=b((lt,mt)=>{(function(n,t){typeof lt=="object"&&typeof mt<"u"?mt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_fr=t(n.dayjs)})(lt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"fr",weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),months:"janvier_f\xE9vrier_mars_avril_mai_juin_juillet_ao\xFBt_septembre_octobre_novembre_d\xE9cembre".split("_"),monthsShort:"janv._f\xE9vr._mars_avr._mai_juin_juil._ao\xFBt_sept._oct._nov._d\xE9c.".split("_"),weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return""+e+(e===1?"er":"")}};return s.default.locale(i,null,!0),i})});var Gn=b((ct,ht)=>{(function(n,t){typeof ct=="object"&&typeof ht<"u"?ht.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_hi=t(n.dayjs)})(ct,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"hi",weekdays:"\u0930\u0935\u093F\u0935\u093E\u0930_\u0938\u094B\u092E\u0935\u093E\u0930_\u092E\u0902\u0917\u0932\u0935\u093E\u0930_\u092C\u0941\u0927\u0935\u093E\u0930_\u0917\u0941\u0930\u0942\u0935\u093E\u0930_\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930_\u0936\u0928\u093F\u0935\u093E\u0930".split("_"),months:"\u091C\u0928\u0935\u0930\u0940_\u092B\u093C\u0930\u0935\u0930\u0940_\u092E\u093E\u0930\u094D\u091A_\u0905\u092A\u094D\u0930\u0948\u0932_\u092E\u0908_\u091C\u0942\u0928_\u091C\u0941\u0932\u093E\u0908_\u0905\u0917\u0938\u094D\u0924_\u0938\u093F\u0924\u092E\u094D\u092C\u0930_\u0905\u0915\u094D\u091F\u0942\u092C\u0930_\u0928\u0935\u092E\u094D\u092C\u0930_\u0926\u093F\u0938\u092E\u094D\u092C\u0930".split("_"),weekdaysShort:"\u0930\u0935\u093F_\u0938\u094B\u092E_\u092E\u0902\u0917\u0932_\u092C\u0941\u0927_\u0917\u0941\u0930\u0942_\u0936\u0941\u0915\u094D\u0930_\u0936\u0928\u093F".split("_"),monthsShort:"\u091C\u0928._\u092B\u093C\u0930._\u092E\u093E\u0930\u094D\u091A_\u0905\u092A\u094D\u0930\u0948._\u092E\u0908_\u091C\u0942\u0928_\u091C\u0941\u0932._\u0905\u0917._\u0938\u093F\u0924._\u0905\u0915\u094D\u091F\u0942._\u0928\u0935._\u0926\u093F\u0938.".split("_"),weekdaysMin:"\u0930_\u0938\u094B_\u092E\u0902_\u092C\u0941_\u0917\u0941_\u0936\u0941_\u0936".split("_"),ordinal:function(e){return e},formats:{LT:"A h:mm \u092C\u091C\u0947",LTS:"A h:mm:ss \u092C\u091C\u0947",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u092C\u091C\u0947",LLLL:"dddd, D MMMM YYYY, A h:mm \u092C\u091C\u0947"},relativeTime:{future:"%s \u092E\u0947\u0902",past:"%s \u092A\u0939\u0932\u0947",s:"\u0915\u0941\u091B \u0939\u0940 \u0915\u094D\u0937\u0923",m:"\u090F\u0915 \u092E\u093F\u0928\u091F",mm:"%d \u092E\u093F\u0928\u091F",h:"\u090F\u0915 \u0918\u0902\u091F\u093E",hh:"%d \u0918\u0902\u091F\u0947",d:"\u090F\u0915 \u0926\u093F\u0928",dd:"%d \u0926\u093F\u0928",M:"\u090F\u0915 \u092E\u0939\u0940\u0928\u0947",MM:"%d \u092E\u0939\u0940\u0928\u0947",y:"\u090F\u0915 \u0935\u0930\u094D\u0937",yy:"%d \u0935\u0930\u094D\u0937"}};return s.default.locale(i,null,!0),i})});var Zn=b((Mt,yt)=>{(function(n,t){typeof Mt=="object"&&typeof yt<"u"?yt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_hu=t(n.dayjs)})(Mt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"hu",weekdays:"vas\xE1rnap_h\xE9tf\u0151_kedd_szerda_cs\xFCt\xF6rt\xF6k_p\xE9ntek_szombat".split("_"),weekdaysShort:"vas_h\xE9t_kedd_sze_cs\xFCt_p\xE9n_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),months:"janu\xE1r_febru\xE1r_m\xE1rcius_\xE1prilis_m\xE1jus_j\xFAnius_j\xFAlius_augusztus_szeptember_okt\xF3ber_november_december".split("_"),monthsShort:"jan_feb_m\xE1rc_\xE1pr_m\xE1j_j\xFAn_j\xFAl_aug_szept_okt_nov_dec".split("_"),ordinal:function(e){return e+"."},weekStart:1,relativeTime:{future:"%s m\xFAlva",past:"%s",s:function(e,r,u,d){return"n\xE9h\xE1ny m\xE1sodperc"+(d||r?"":"e")},m:function(e,r,u,d){return"egy perc"+(d||r?"":"e")},mm:function(e,r,u,d){return e+" perc"+(d||r?"":"e")},h:function(e,r,u,d){return"egy "+(d||r?"\xF3ra":"\xF3r\xE1ja")},hh:function(e,r,u,d){return e+" "+(d||r?"\xF3ra":"\xF3r\xE1ja")},d:function(e,r,u,d){return"egy "+(d||r?"nap":"napja")},dd:function(e,r,u,d){return e+" "+(d||r?"nap":"napja")},M:function(e,r,u,d){return"egy "+(d||r?"h\xF3nap":"h\xF3napja")},MM:function(e,r,u,d){return e+" "+(d||r?"h\xF3nap":"h\xF3napja")},y:function(e,r,u,d){return"egy "+(d||r?"\xE9v":"\xE9ve")},yy:function(e,r,u,d){return e+" "+(d||r?"\xE9v":"\xE9ve")}},formats:{LT:"H:mm",LTS:"H:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D. H:mm",LLLL:"YYYY. MMMM D., dddd H:mm"}};return s.default.locale(i,null,!0),i})});var Vn=b((Yt,pt)=>{(function(n,t){typeof Yt=="object"&&typeof pt<"u"?pt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_hy_am=t(n.dayjs)})(Yt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"hy-am",weekdays:"\u056F\u056B\u0580\u0561\u056F\u056B_\u0565\u0580\u056F\u0578\u0582\u0577\u0561\u0562\u0569\u056B_\u0565\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B_\u0579\u0578\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B_\u0570\u056B\u0576\u0563\u0577\u0561\u0562\u0569\u056B_\u0578\u0582\u0580\u0562\u0561\u0569_\u0577\u0561\u0562\u0561\u0569".split("_"),months:"\u0570\u0578\u0582\u0576\u057E\u0561\u0580\u056B_\u0583\u0565\u057F\u0580\u057E\u0561\u0580\u056B_\u0574\u0561\u0580\u057F\u056B_\u0561\u057A\u0580\u056B\u056C\u056B_\u0574\u0561\u0575\u056B\u057D\u056B_\u0570\u0578\u0582\u0576\u056B\u057D\u056B_\u0570\u0578\u0582\u056C\u056B\u057D\u056B_\u0585\u0563\u0578\u057D\u057F\u0578\u057D\u056B_\u057D\u0565\u057A\u057F\u0565\u0574\u0562\u0565\u0580\u056B_\u0570\u0578\u056F\u057F\u0565\u0574\u0562\u0565\u0580\u056B_\u0576\u0578\u0575\u0565\u0574\u0562\u0565\u0580\u056B_\u0564\u0565\u056F\u057F\u0565\u0574\u0562\u0565\u0580\u056B".split("_"),weekStart:1,weekdaysShort:"\u056F\u0580\u056F_\u0565\u0580\u056F_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),monthsShort:"\u0570\u0576\u057E_\u0583\u057F\u0580_\u0574\u0580\u057F_\u0561\u057A\u0580_\u0574\u0575\u057D_\u0570\u0576\u057D_\u0570\u056C\u057D_\u0585\u0563\u057D_\u057D\u057A\u057F_\u0570\u056F\u057F_\u0576\u0574\u0562_\u0564\u056F\u057F".split("_"),weekdaysMin:"\u056F\u0580\u056F_\u0565\u0580\u056F_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0569.",LLL:"D MMMM YYYY \u0569., HH:mm",LLLL:"dddd, D MMMM YYYY \u0569., HH:mm"},relativeTime:{future:"%s \u0570\u0565\u057F\u0578",past:"%s \u0561\u057C\u0561\u057B",s:"\u0574\u056B \u0584\u0561\u0576\u056B \u057E\u0561\u0575\u0580\u056F\u0575\u0561\u0576",m:"\u0580\u0578\u057A\u0565",mm:"%d \u0580\u0578\u057A\u0565",h:"\u056A\u0561\u0574",hh:"%d \u056A\u0561\u0574",d:"\u0585\u0580",dd:"%d \u0585\u0580",M:"\u0561\u0574\u056B\u057D",MM:"%d \u0561\u0574\u056B\u057D",y:"\u057F\u0561\u0580\u056B",yy:"%d \u057F\u0561\u0580\u056B"}};return s.default.locale(i,null,!0),i})});var Kn=b((Dt,Lt)=>{(function(n,t){typeof Dt=="object"&&typeof Lt<"u"?Lt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_id=t(n.dayjs)})(Dt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"id",weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Agt_Sep_Okt_Nov_Des".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),weekStart:1,formats:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},ordinal:function(e){return e+"."}};return s.default.locale(i,null,!0),i})});var Qn=b((gt,vt)=>{(function(n,t){typeof gt=="object"&&typeof vt<"u"?vt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_it=t(n.dayjs)})(gt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"it",weekdays:"domenica_luned\xEC_marted\xEC_mercoled\xEC_gioved\xEC_venerd\xEC_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),weekStart:1,monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"tra %s",past:"%s fa",s:"qualche secondo",m:"un minuto",mm:"%d minuti",h:"un' ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinal:function(e){return e+"\xBA"}};return s.default.locale(i,null,!0),i})});var Xn=b((St,bt)=>{(function(n,t){typeof St=="object"&&typeof bt<"u"?bt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ja=t(n.dayjs)})(St,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"ja",weekdays:"\u65E5\u66DC\u65E5_\u6708\u66DC\u65E5_\u706B\u66DC\u65E5_\u6C34\u66DC\u65E5_\u6728\u66DC\u65E5_\u91D1\u66DC\u65E5_\u571F\u66DC\u65E5".split("_"),weekdaysShort:"\u65E5_\u6708_\u706B_\u6C34_\u6728_\u91D1_\u571F".split("_"),weekdaysMin:"\u65E5_\u6708_\u706B_\u6C34_\u6728_\u91D1_\u571F".split("_"),months:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),ordinal:function(e){return e+"\u65E5"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5E74M\u6708D\u65E5",LLL:"YYYY\u5E74M\u6708D\u65E5 HH:mm",LLLL:"YYYY\u5E74M\u6708D\u65E5 dddd HH:mm",l:"YYYY/MM/DD",ll:"YYYY\u5E74M\u6708D\u65E5",lll:"YYYY\u5E74M\u6708D\u65E5 HH:mm",llll:"YYYY\u5E74M\u6708D\u65E5(ddd) HH:mm"},meridiem:function(e){return e<12?"\u5348\u524D":"\u5348\u5F8C"},relativeTime:{future:"%s\u5F8C",past:"%s\u524D",s:"\u6570\u79D2",m:"1\u5206",mm:"%d\u5206",h:"1\u6642\u9593",hh:"%d\u6642\u9593",d:"1\u65E5",dd:"%d\u65E5",M:"1\u30F6\u6708",MM:"%d\u30F6\u6708",y:"1\u5E74",yy:"%d\u5E74"}};return s.default.locale(i,null,!0),i})});var Bn=b((kt,jt)=>{(function(n,t){typeof kt=="object"&&typeof jt<"u"?jt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ka=t(n.dayjs)})(kt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"ka",weekdays:"\u10D9\u10D5\u10D8\u10E0\u10D0_\u10DD\u10E0\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8_\u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8_\u10DD\u10D7\u10EE\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8_\u10EE\u10E3\u10D7\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8_\u10DE\u10D0\u10E0\u10D0\u10E1\u10D9\u10D4\u10D5\u10D8_\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8".split("_"),weekdaysShort:"\u10D9\u10D5\u10D8_\u10DD\u10E0\u10E8_\u10E1\u10D0\u10DB_\u10DD\u10D7\u10EE_\u10EE\u10E3\u10D7_\u10DE\u10D0\u10E0_\u10E8\u10D0\u10D1".split("_"),weekdaysMin:"\u10D9\u10D5_\u10DD\u10E0_\u10E1\u10D0_\u10DD\u10D7_\u10EE\u10E3_\u10DE\u10D0_\u10E8\u10D0".split("_"),months:"\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8_\u10D7\u10D4\u10D1\u10D4\u10E0\u10D5\u10D0\u10DA\u10D8_\u10DB\u10D0\u10E0\u10E2\u10D8_\u10D0\u10DE\u10E0\u10D8\u10DA\u10D8_\u10DB\u10D0\u10D8\u10E1\u10D8_\u10D8\u10D5\u10DC\u10D8\u10E1\u10D8_\u10D8\u10D5\u10DA\u10D8\u10E1\u10D8_\u10D0\u10D2\u10D5\u10D8\u10E1\u10E2\u10DD_\u10E1\u10D4\u10E5\u10E2\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8_\u10DD\u10E5\u10E2\u10DD\u10DB\u10D1\u10D4\u10E0\u10D8_\u10DC\u10DD\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8_\u10D3\u10D4\u10D9\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8".split("_"),monthsShort:"\u10D8\u10D0\u10DC_\u10D7\u10D4\u10D1_\u10DB\u10D0\u10E0_\u10D0\u10DE\u10E0_\u10DB\u10D0\u10D8_\u10D8\u10D5\u10DC_\u10D8\u10D5\u10DA_\u10D0\u10D2\u10D5_\u10E1\u10D4\u10E5_\u10DD\u10E5\u10E2_\u10DC\u10DD\u10D4_\u10D3\u10D4\u10D9".split("_"),weekStart:1,formats:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},relativeTime:{future:"%s \u10E8\u10D4\u10DB\u10D3\u10D4\u10D2",past:"%s \u10EC\u10D8\u10DC",s:"\u10EC\u10D0\u10DB\u10D8",m:"\u10EC\u10E3\u10D7\u10D8",mm:"%d \u10EC\u10E3\u10D7\u10D8",h:"\u10E1\u10D0\u10D0\u10D7\u10D8",hh:"%d \u10E1\u10D0\u10D0\u10D7\u10D8\u10E1",d:"\u10D3\u10E6\u10D4\u10E1",dd:"%d \u10D3\u10E6\u10D8\u10E1 \u10D2\u10D0\u10DC\u10DB\u10D0\u10D5\u10DA\u10DD\u10D1\u10D0\u10E8\u10D8",M:"\u10D7\u10D5\u10D8\u10E1",MM:"%d \u10D7\u10D5\u10D8\u10E1",y:"\u10EC\u10D4\u10DA\u10D8",yy:"%d \u10EC\u10DA\u10D8\u10E1"},ordinal:function(e){return e}};return s.default.locale(i,null,!0),i})});var ei=b((Ht,Tt)=>{(function(n,t){typeof Ht=="object"&&typeof Tt<"u"?Tt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_km=t(n.dayjs)})(Ht,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"km",weekdays:"\u17A2\u17B6\u1791\u17B7\u178F\u17D2\u1799_\u1785\u17D0\u1793\u17D2\u1791_\u17A2\u1784\u17D2\u1782\u17B6\u179A_\u1796\u17BB\u1792_\u1796\u17D2\u179A\u17A0\u179F\u17D2\u1794\u178F\u17B7\u17CD_\u179F\u17BB\u1780\u17D2\u179A_\u179F\u17C5\u179A\u17CD".split("_"),months:"\u1798\u1780\u179A\u17B6_\u1780\u17BB\u1798\u17D2\u1797\u17C8_\u1798\u17B8\u1793\u17B6_\u1798\u17C1\u179F\u17B6_\u17A7\u179F\u1797\u17B6_\u1798\u17B7\u1790\u17BB\u1793\u17B6_\u1780\u1780\u17D2\u1780\u178A\u17B6_\u179F\u17B8\u17A0\u17B6_\u1780\u1789\u17D2\u1789\u17B6_\u178F\u17BB\u179B\u17B6_\u179C\u17B7\u1785\u17D2\u1786\u17B7\u1780\u17B6_\u1792\u17D2\u1793\u17BC".split("_"),weekStart:1,weekdaysShort:"\u17A2\u17B6_\u1785_\u17A2_\u1796_\u1796\u17D2\u179A_\u179F\u17BB_\u179F".split("_"),monthsShort:"\u1798\u1780\u179A\u17B6_\u1780\u17BB\u1798\u17D2\u1797\u17C8_\u1798\u17B8\u1793\u17B6_\u1798\u17C1\u179F\u17B6_\u17A7\u179F\u1797\u17B6_\u1798\u17B7\u1790\u17BB\u1793\u17B6_\u1780\u1780\u17D2\u1780\u178A\u17B6_\u179F\u17B8\u17A0\u17B6_\u1780\u1789\u17D2\u1789\u17B6_\u178F\u17BB\u179B\u17B6_\u179C\u17B7\u1785\u17D2\u1786\u17B7\u1780\u17B6_\u1792\u17D2\u1793\u17BC".split("_"),weekdaysMin:"\u17A2\u17B6_\u1785_\u17A2_\u1796_\u1796\u17D2\u179A_\u179F\u17BB_\u179F".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},relativeTime:{future:"%s\u1791\u17C0\u178F",past:"%s\u1798\u17BB\u1793",s:"\u1794\u17C9\u17BB\u1793\u17D2\u1798\u17B6\u1793\u179C\u17B7\u1793\u17B6\u1791\u17B8",m:"\u1798\u17BD\u1799\u1793\u17B6\u1791\u17B8",mm:"%d \u1793\u17B6\u1791\u17B8",h:"\u1798\u17BD\u1799\u1798\u17C9\u17C4\u1784",hh:"%d \u1798\u17C9\u17C4\u1784",d:"\u1798\u17BD\u1799\u1790\u17D2\u1784\u17C3",dd:"%d \u1790\u17D2\u1784\u17C3",M:"\u1798\u17BD\u1799\u1781\u17C2",MM:"%d \u1781\u17C2",y:"\u1798\u17BD\u1799\u1786\u17D2\u1793\u17B6\u17C6",yy:"%d \u1786\u17D2\u1793\u17B6\u17C6"}};return s.default.locale(i,null,!0),i})});var ti=b((wt,$t)=>{(function(n,t){typeof wt=="object"&&typeof $t<"u"?$t.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_lt=t(n.dayjs)})(wt,function(n){"use strict";function t(o){return o&&typeof o=="object"&&"default"in o?o:{default:o}}var s=t(n),i="sausio_vasario_kovo_baland\u017Eio_gegu\u017E\u0117s_bir\u017Eelio_liepos_rugpj\u016B\u010Dio_rugs\u0117jo_spalio_lapkri\u010Dio_gruod\u017Eio".split("_"),e="sausis_vasaris_kovas_balandis_gegu\u017E\u0117_bir\u017Eelis_liepa_rugpj\u016Btis_rugs\u0117jis_spalis_lapkritis_gruodis".split("_"),r=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/,u=function(o,a){return r.test(a)?i[o.month()]:e[o.month()]};u.s=e,u.f=i;var d={name:"lt",weekdays:"sekmadienis_pirmadienis_antradienis_tre\u010Diadienis_ketvirtadienis_penktadienis_\u0161e\u0161tadienis".split("_"),weekdaysShort:"sek_pir_ant_tre_ket_pen_\u0161e\u0161".split("_"),weekdaysMin:"s_p_a_t_k_pn_\u0161".split("_"),months:u,monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),ordinal:function(o){return o+"."},weekStart:1,relativeTime:{future:"u\u017E %s",past:"prie\u0161 %s",s:"kelias sekundes",m:"minut\u0119",mm:"%d minutes",h:"valand\u0105",hh:"%d valandas",d:"dien\u0105",dd:"%d dienas",M:"m\u0117nes\u012F",MM:"%d m\u0117nesius",y:"metus",yy:"%d metus"},format:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], HH:mm [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], HH:mm [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], HH:mm [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], HH:mm [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]"}};return s.default.locale(d,null,!0),d})});var ni=b((Ct,Ot)=>{(function(n,t){typeof Ct=="object"&&typeof Ot<"u"?Ot.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_lv=t(n.dayjs)})(Ct,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"lv",weekdays:"sv\u0113tdiena_pirmdiena_otrdiena_tre\u0161diena_ceturtdiena_piektdiena_sestdiena".split("_"),months:"janv\u0101ris_febru\u0101ris_marts_apr\u012Blis_maijs_j\u016Bnijs_j\u016Blijs_augusts_septembris_oktobris_novembris_decembris".split("_"),weekStart:1,weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),monthsShort:"jan_feb_mar_apr_mai_j\u016Bn_j\u016Bl_aug_sep_okt_nov_dec".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY.",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, HH:mm",LLLL:"YYYY. [gada] D. MMMM, dddd, HH:mm"},relativeTime:{future:"p\u0113c %s",past:"pirms %s",s:"da\u017E\u0101m sekund\u0113m",m:"min\u016Btes",mm:"%d min\u016Bt\u0113m",h:"stundas",hh:"%d stund\u0101m",d:"dienas",dd:"%d dien\u0101m",M:"m\u0113ne\u0161a",MM:"%d m\u0113ne\u0161iem",y:"gada",yy:"%d gadiem"}};return s.default.locale(i,null,!0),i})});var ii=b((zt,At)=>{(function(n,t){typeof zt=="object"&&typeof At<"u"?At.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ms=t(n.dayjs)})(zt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"ms",weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekStart:1,formats:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH.mm",LLLL:"dddd, D MMMM YYYY HH.mm"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},ordinal:function(e){return e+"."}};return s.default.locale(i,null,!0),i})});var si=b((It,qt)=>{(function(n,t){typeof It=="object"&&typeof qt<"u"?qt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_my=t(n.dayjs)})(It,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"my",weekdays:"\u1010\u1014\u1004\u103A\u1039\u1002\u1014\u103D\u1031_\u1010\u1014\u1004\u103A\u1039\u101C\u102C_\u1021\u1004\u103A\u1039\u1002\u102B_\u1017\u102F\u1012\u1039\u1013\u101F\u1030\u1038_\u1000\u103C\u102C\u101E\u1015\u1010\u1031\u1038_\u101E\u1031\u102C\u1000\u103C\u102C_\u1005\u1014\u1031".split("_"),months:"\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E_\u1016\u1031\u1016\u1031\u102C\u103A\u101D\u102B\u101B\u102E_\u1019\u1010\u103A_\u1027\u1015\u103C\u102E_\u1019\u1031_\u1007\u103D\u1014\u103A_\u1007\u1030\u101C\u102D\u102F\u1004\u103A_\u101E\u103C\u1002\u102F\u1010\u103A_\u1005\u1000\u103A\u1010\u1004\u103A\u1018\u102C_\u1021\u1031\u102C\u1000\u103A\u1010\u102D\u102F\u1018\u102C_\u1014\u102D\u102F\u101D\u1004\u103A\u1018\u102C_\u1012\u102E\u1007\u1004\u103A\u1018\u102C".split("_"),weekStart:1,weekdaysShort:"\u1014\u103D\u1031_\u101C\u102C_\u1002\u102B_\u101F\u1030\u1038_\u1000\u103C\u102C_\u101E\u1031\u102C_\u1014\u1031".split("_"),monthsShort:"\u1007\u1014\u103A_\u1016\u1031_\u1019\u1010\u103A_\u1015\u103C\u102E_\u1019\u1031_\u1007\u103D\u1014\u103A_\u101C\u102D\u102F\u1004\u103A_\u101E\u103C_\u1005\u1000\u103A_\u1021\u1031\u102C\u1000\u103A_\u1014\u102D\u102F_\u1012\u102E".split("_"),weekdaysMin:"\u1014\u103D\u1031_\u101C\u102C_\u1002\u102B_\u101F\u1030\u1038_\u1000\u103C\u102C_\u101E\u1031\u102C_\u1014\u1031".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"\u101C\u102C\u1019\u100A\u103A\u1037 %s \u1019\u103E\u102C",past:"\u101C\u103D\u1014\u103A\u1001\u1032\u1037\u101E\u1031\u102C %s \u1000",s:"\u1005\u1000\u1039\u1000\u1014\u103A.\u1021\u1014\u100A\u103A\u1038\u1004\u101A\u103A",m:"\u1010\u1005\u103A\u1019\u102D\u1014\u1005\u103A",mm:"%d \u1019\u102D\u1014\u1005\u103A",h:"\u1010\u1005\u103A\u1014\u102C\u101B\u102E",hh:"%d \u1014\u102C\u101B\u102E",d:"\u1010\u1005\u103A\u101B\u1000\u103A",dd:"%d \u101B\u1000\u103A",M:"\u1010\u1005\u103A\u101C",MM:"%d \u101C",y:"\u1010\u1005\u103A\u1014\u103E\u1005\u103A",yy:"%d \u1014\u103E\u1005\u103A"}};return s.default.locale(i,null,!0),i})});var ri=b((xt,Nt)=>{(function(n,t){typeof xt=="object"&&typeof Nt<"u"?Nt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_nl=t(n.dayjs)})(xt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"nl",weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),ordinal:function(e){return"["+e+(e===1||e===8||e>=20?"ste":"de")+"]"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"een minuut",mm:"%d minuten",h:"een uur",hh:"%d uur",d:"een dag",dd:"%d dagen",M:"een maand",MM:"%d maanden",y:"een jaar",yy:"%d jaar"}};return s.default.locale(i,null,!0),i})});var ai=b((Et,Ft)=>{(function(n,t){typeof Et=="object"&&typeof Ft<"u"?Ft.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_nb=t(n.dayjs)})(Et,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"nb",weekdays:"s\xF8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xF8rdag".split("_"),weekdaysShort:"s\xF8._ma._ti._on._to._fr._l\xF8.".split("_"),weekdaysMin:"s\xF8_ma_ti_on_to_fr_l\xF8".split("_"),months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.".split("_"),ordinal:function(e){return e+"."},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] HH:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},relativeTime:{future:"om %s",past:"%s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en m\xE5ned",MM:"%d m\xE5neder",y:"ett \xE5r",yy:"%d \xE5r"}};return s.default.locale(i,null,!0),i})});var ui=b((Jt,Wt)=>{(function(n,t){typeof Jt=="object"&&typeof Wt<"u"?Wt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_pl=t(n.dayjs)})(Jt,function(n){"use strict";function t(_){return _&&typeof _=="object"&&"default"in _?_:{default:_}}var s=t(n);function i(_){return _%10<5&&_%10>1&&~~(_/10)%10!=1}function e(_,y,f){var l=_+" ";switch(f){case"m":return y?"minuta":"minut\u0119";case"mm":return l+(i(_)?"minuty":"minut");case"h":return y?"godzina":"godzin\u0119";case"hh":return l+(i(_)?"godziny":"godzin");case"MM":return l+(i(_)?"miesi\u0105ce":"miesi\u0119cy");case"yy":return l+(i(_)?"lata":"lat")}}var r="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_wrze\u015Bnia_pa\u017Adziernika_listopada_grudnia".split("_"),u="stycze\u0144_luty_marzec_kwiecie\u0144_maj_czerwiec_lipiec_sierpie\u0144_wrzesie\u0144_pa\u017Adziernik_listopad_grudzie\u0144".split("_"),d=/D MMMM/,o=function(_,y){return d.test(y)?r[_.month()]:u[_.month()]};o.s=u,o.f=r;var a={name:"pl",weekdays:"niedziela_poniedzia\u0142ek_wtorek_\u015Broda_czwartek_pi\u0105tek_sobota".split("_"),weekdaysShort:"ndz_pon_wt_\u015Br_czw_pt_sob".split("_"),weekdaysMin:"Nd_Pn_Wt_\u015Ar_Cz_Pt_So".split("_"),months:o,monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_pa\u017A_lis_gru".split("_"),ordinal:function(_){return _+"."},weekStart:1,yearStart:4,relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:e,mm:e,h:e,hh:e,d:"1 dzie\u0144",dd:"%d dni",M:"miesi\u0105c",MM:e,y:"rok",yy:e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"}};return s.default.locale(a,null,!0),a})});var oi=b((Ut,Pt)=>{(function(n,t){typeof Ut=="object"&&typeof Pt<"u"?Pt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_pt_br=t(n.dayjs)})(Ut,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"pt-br",weekdays:"domingo_segunda-feira_ter\xE7a-feira_quarta-feira_quinta-feira_sexta-feira_s\xE1bado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_s\xE1b".split("_"),weekdaysMin:"Do_2\xAA_3\xAA_4\xAA_5\xAA_6\xAA_S\xE1".split("_"),months:"janeiro_fevereiro_mar\xE7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),ordinal:function(e){return e+"\xBA"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xE0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xE0s] HH:mm"},relativeTime:{future:"em %s",past:"h\xE1 %s",s:"poucos segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xEAs",MM:"%d meses",y:"um ano",yy:"%d anos"}};return s.default.locale(i,null,!0),i})});var di=b((Rt,Gt)=>{(function(n,t){typeof Rt=="object"&&typeof Gt<"u"?Gt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_pt=t(n.dayjs)})(Rt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"pt",weekdays:"domingo_segunda-feira_ter\xE7a-feira_quarta-feira_quinta-feira_sexta-feira_s\xE1bado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sab".split("_"),weekdaysMin:"Do_2\xAA_3\xAA_4\xAA_5\xAA_6\xAA_Sa".split("_"),months:"janeiro_fevereiro_mar\xE7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),ordinal:function(e){return e+"\xBA"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xE0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xE0s] HH:mm"},relativeTime:{future:"em %s",past:"h\xE1 %s",s:"alguns segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xEAs",MM:"%d meses",y:"um ano",yy:"%d anos"}};return s.default.locale(i,null,!0),i})});var _i=b((Zt,Vt)=>{(function(n,t){typeof Zt=="object"&&typeof Vt<"u"?Vt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ro=t(n.dayjs)})(Zt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"ro",weekdays:"Duminic\u0103_Luni_Mar\u021Bi_Miercuri_Joi_Vineri_S\xE2mb\u0103t\u0103".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_S\xE2m".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_S\xE2".split("_"),months:"Ianuarie_Februarie_Martie_Aprilie_Mai_Iunie_Iulie_August_Septembrie_Octombrie_Noiembrie_Decembrie".split("_"),monthsShort:"Ian._Febr._Mart._Apr._Mai_Iun._Iul._Aug._Sept._Oct._Nov._Dec.".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},relativeTime:{future:"peste %s",past:"acum %s",s:"c\xE2teva secunde",m:"un minut",mm:"%d minute",h:"o or\u0103",hh:"%d ore",d:"o zi",dd:"%d zile",M:"o lun\u0103",MM:"%d luni",y:"un an",yy:"%d ani"},ordinal:function(e){return e}};return s.default.locale(i,null,!0),i})});var fi=b((Kt,Qt)=>{(function(n,t){typeof Kt=="object"&&typeof Qt<"u"?Qt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_ru=t(n.dayjs)})(Kt,function(n){"use strict";function t(f){return f&&typeof f=="object"&&"default"in f?f:{default:f}}var s=t(n),i="\u044F\u043D\u0432\u0430\u0440\u044F_\u0444\u0435\u0432\u0440\u0430\u043B\u044F_\u043C\u0430\u0440\u0442\u0430_\u0430\u043F\u0440\u0435\u043B\u044F_\u043C\u0430\u044F_\u0438\u044E\u043D\u044F_\u0438\u044E\u043B\u044F_\u0430\u0432\u0433\u0443\u0441\u0442\u0430_\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044F_\u043E\u043A\u0442\u044F\u0431\u0440\u044F_\u043D\u043E\u044F\u0431\u0440\u044F_\u0434\u0435\u043A\u0430\u0431\u0440\u044F".split("_"),e="\u044F\u043D\u0432\u0430\u0440\u044C_\u0444\u0435\u0432\u0440\u0430\u043B\u044C_\u043C\u0430\u0440\u0442_\u0430\u043F\u0440\u0435\u043B\u044C_\u043C\u0430\u0439_\u0438\u044E\u043D\u044C_\u0438\u044E\u043B\u044C_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C_\u043E\u043A\u0442\u044F\u0431\u0440\u044C_\u043D\u043E\u044F\u0431\u0440\u044C_\u0434\u0435\u043A\u0430\u0431\u0440\u044C".split("_"),r="\u044F\u043D\u0432._\u0444\u0435\u0432\u0440._\u043C\u0430\u0440._\u0430\u043F\u0440._\u043C\u0430\u044F_\u0438\u044E\u043D\u044F_\u0438\u044E\u043B\u044F_\u0430\u0432\u0433._\u0441\u0435\u043D\u0442._\u043E\u043A\u0442._\u043D\u043E\u044F\u0431._\u0434\u0435\u043A.".split("_"),u="\u044F\u043D\u0432._\u0444\u0435\u0432\u0440._\u043C\u0430\u0440\u0442_\u0430\u043F\u0440._\u043C\u0430\u0439_\u0438\u044E\u043D\u044C_\u0438\u044E\u043B\u044C_\u0430\u0432\u0433._\u0441\u0435\u043D\u0442._\u043E\u043A\u0442._\u043D\u043E\u044F\u0431._\u0434\u0435\u043A.".split("_"),d=/D[oD]?(\[[^[\]]*\]|\s)+MMMM?/;function o(f,l,m){var Y,L;return m==="m"?l?"\u043C\u0438\u043D\u0443\u0442\u0430":"\u043C\u0438\u043D\u0443\u0442\u0443":f+" "+(Y=+f,L={mm:l?"\u043C\u0438\u043D\u0443\u0442\u0430_\u043C\u0438\u043D\u0443\u0442\u044B_\u043C\u0438\u043D\u0443\u0442":"\u043C\u0438\u043D\u0443\u0442\u0443_\u043C\u0438\u043D\u0443\u0442\u044B_\u043C\u0438\u043D\u0443\u0442",hh:"\u0447\u0430\u0441_\u0447\u0430\u0441\u0430_\u0447\u0430\u0441\u043E\u0432",dd:"\u0434\u0435\u043D\u044C_\u0434\u043D\u044F_\u0434\u043D\u0435\u0439",MM:"\u043C\u0435\u0441\u044F\u0446_\u043C\u0435\u0441\u044F\u0446\u0430_\u043C\u0435\u0441\u044F\u0446\u0435\u0432",yy:"\u0433\u043E\u0434_\u0433\u043E\u0434\u0430_\u043B\u0435\u0442"}[m].split("_"),Y%10==1&&Y%100!=11?L[0]:Y%10>=2&&Y%10<=4&&(Y%100<10||Y%100>=20)?L[1]:L[2])}var a=function(f,l){return d.test(l)?i[f.month()]:e[f.month()]};a.s=e,a.f=i;var _=function(f,l){return d.test(l)?r[f.month()]:u[f.month()]};_.s=u,_.f=r;var y={name:"ru",weekdays:"\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435_\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A_\u0432\u0442\u043E\u0440\u043D\u0438\u043A_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440\u0433_\u043F\u044F\u0442\u043D\u0438\u0446\u0430_\u0441\u0443\u0431\u0431\u043E\u0442\u0430".split("_"),weekdaysShort:"\u0432\u0441\u043A_\u043F\u043D\u0434_\u0432\u0442\u0440_\u0441\u0440\u0434_\u0447\u0442\u0432_\u043F\u0442\u043D_\u0441\u0431\u0442".split("_"),weekdaysMin:"\u0432\u0441_\u043F\u043D_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043F\u0442_\u0441\u0431".split("_"),months:a,monthsShort:_,weekStart:1,yearStart:4,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0433.",LLL:"D MMMM YYYY \u0433., H:mm",LLLL:"dddd, D MMMM YYYY \u0433., H:mm"},relativeTime:{future:"\u0447\u0435\u0440\u0435\u0437 %s",past:"%s \u043D\u0430\u0437\u0430\u0434",s:"\u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434",m:o,mm:o,h:"\u0447\u0430\u0441",hh:o,d:"\u0434\u0435\u043D\u044C",dd:o,M:"\u043C\u0435\u0441\u044F\u0446",MM:o,y:"\u0433\u043E\u0434",yy:o},ordinal:function(f){return f},meridiem:function(f){return f<4?"\u043D\u043E\u0447\u0438":f<12?"\u0443\u0442\u0440\u0430":f<17?"\u0434\u043D\u044F":"\u0432\u0435\u0447\u0435\u0440\u0430"}};return s.default.locale(y,null,!0),y})});var li=b((Xt,Bt)=>{(function(n,t){typeof Xt=="object"&&typeof Bt<"u"?Bt.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_sv=t(n.dayjs)})(Xt,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"sv",weekdays:"s\xF6ndag_m\xE5ndag_tisdag_onsdag_torsdag_fredag_l\xF6rdag".split("_"),weekdaysShort:"s\xF6n_m\xE5n_tis_ons_tor_fre_l\xF6r".split("_"),weekdaysMin:"s\xF6_m\xE5_ti_on_to_fr_l\xF6".split("_"),months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekStart:1,yearStart:4,ordinal:function(e){var r=e%10;return"["+e+(r===1||r===2?"a":"e")+"]"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [kl.] HH:mm",LLLL:"dddd D MMMM YYYY [kl.] HH:mm",lll:"D MMM YYYY HH:mm",llll:"ddd D MMM YYYY HH:mm"},relativeTime:{future:"om %s",past:"f\xF6r %s sedan",s:"n\xE5gra sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en m\xE5nad",MM:"%d m\xE5nader",y:"ett \xE5r",yy:"%d \xE5r"}};return s.default.locale(i,null,!0),i})});var mi=b((en,tn)=>{(function(n,t){typeof en=="object"&&typeof tn<"u"?tn.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_th=t(n.dayjs)})(en,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"th",weekdays:"\u0E2D\u0E32\u0E17\u0E34\u0E15\u0E22\u0E4C_\u0E08\u0E31\u0E19\u0E17\u0E23\u0E4C_\u0E2D\u0E31\u0E07\u0E04\u0E32\u0E23_\u0E1E\u0E38\u0E18_\u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35_\u0E28\u0E38\u0E01\u0E23\u0E4C_\u0E40\u0E2A\u0E32\u0E23\u0E4C".split("_"),weekdaysShort:"\u0E2D\u0E32\u0E17\u0E34\u0E15\u0E22\u0E4C_\u0E08\u0E31\u0E19\u0E17\u0E23\u0E4C_\u0E2D\u0E31\u0E07\u0E04\u0E32\u0E23_\u0E1E\u0E38\u0E18_\u0E1E\u0E24\u0E2B\u0E31\u0E2A_\u0E28\u0E38\u0E01\u0E23\u0E4C_\u0E40\u0E2A\u0E32\u0E23\u0E4C".split("_"),weekdaysMin:"\u0E2D\u0E32._\u0E08._\u0E2D._\u0E1E._\u0E1E\u0E24._\u0E28._\u0E2A.".split("_"),months:"\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21_\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C_\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21_\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19_\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21_\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19_\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21_\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21_\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19_\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21_\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19_\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21".split("_"),monthsShort:"\u0E21.\u0E04._\u0E01.\u0E1E._\u0E21\u0E35.\u0E04._\u0E40\u0E21.\u0E22._\u0E1E.\u0E04._\u0E21\u0E34.\u0E22._\u0E01.\u0E04._\u0E2A.\u0E04._\u0E01.\u0E22._\u0E15.\u0E04._\u0E1E.\u0E22._\u0E18.\u0E04.".split("_"),formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY \u0E40\u0E27\u0E25\u0E32 H:mm",LLLL:"\u0E27\u0E31\u0E19dddd\u0E17\u0E35\u0E48 D MMMM YYYY \u0E40\u0E27\u0E25\u0E32 H:mm"},relativeTime:{future:"\u0E2D\u0E35\u0E01 %s",past:"%s\u0E17\u0E35\u0E48\u0E41\u0E25\u0E49\u0E27",s:"\u0E44\u0E21\u0E48\u0E01\u0E35\u0E48\u0E27\u0E34\u0E19\u0E32\u0E17\u0E35",m:"1 \u0E19\u0E32\u0E17\u0E35",mm:"%d \u0E19\u0E32\u0E17\u0E35",h:"1 \u0E0A\u0E31\u0E48\u0E27\u0E42\u0E21\u0E07",hh:"%d \u0E0A\u0E31\u0E48\u0E27\u0E42\u0E21\u0E07",d:"1 \u0E27\u0E31\u0E19",dd:"%d \u0E27\u0E31\u0E19",M:"1 \u0E40\u0E14\u0E37\u0E2D\u0E19",MM:"%d \u0E40\u0E14\u0E37\u0E2D\u0E19",y:"1 \u0E1B\u0E35",yy:"%d \u0E1B\u0E35"},ordinal:function(e){return e+"."}};return s.default.locale(i,null,!0),i})});var ci=b((nn,sn)=>{(function(n,t){typeof nn=="object"&&typeof sn<"u"?sn.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_tr=t(n.dayjs)})(nn,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"tr",weekdays:"Pazar_Pazartesi_Sal\u0131_\xC7ar\u015Famba_Per\u015Fembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_\xC7ar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_\xC7a_Pe_Cu_Ct".split("_"),months:"Ocak_\u015Eubat_Mart_Nisan_May\u0131s_Haziran_Temmuz_A\u011Fustos_Eyl\xFCl_Ekim_Kas\u0131m_Aral\u0131k".split("_"),monthsShort:"Oca_\u015Eub_Mar_Nis_May_Haz_Tem_A\u011Fu_Eyl_Eki_Kas_Ara".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},relativeTime:{future:"%s sonra",past:"%s \xF6nce",s:"birka\xE7 saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir g\xFCn",dd:"%d g\xFCn",M:"bir ay",MM:"%d ay",y:"bir y\u0131l",yy:"%d y\u0131l"},ordinal:function(e){return e+"."}};return s.default.locale(i,null,!0),i})});var hi=b((rn,an)=>{(function(n,t){typeof rn=="object"&&typeof an<"u"?an.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_uk=t(n.dayjs)})(rn,function(n){"use strict";function t(a){return a&&typeof a=="object"&&"default"in a?a:{default:a}}var s=t(n),i="\u0441\u0456\u0447\u043D\u044F_\u043B\u044E\u0442\u043E\u0433\u043E_\u0431\u0435\u0440\u0435\u0437\u043D\u044F_\u043A\u0432\u0456\u0442\u043D\u044F_\u0442\u0440\u0430\u0432\u043D\u044F_\u0447\u0435\u0440\u0432\u043D\u044F_\u043B\u0438\u043F\u043D\u044F_\u0441\u0435\u0440\u043F\u043D\u044F_\u0432\u0435\u0440\u0435\u0441\u043D\u044F_\u0436\u043E\u0432\u0442\u043D\u044F_\u043B\u0438\u0441\u0442\u043E\u043F\u0430\u0434\u0430_\u0433\u0440\u0443\u0434\u043D\u044F".split("_"),e="\u0441\u0456\u0447\u0435\u043D\u044C_\u043B\u044E\u0442\u0438\u0439_\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C_\u043A\u0432\u0456\u0442\u0435\u043D\u044C_\u0442\u0440\u0430\u0432\u0435\u043D\u044C_\u0447\u0435\u0440\u0432\u0435\u043D\u044C_\u043B\u0438\u043F\u0435\u043D\u044C_\u0441\u0435\u0440\u043F\u0435\u043D\u044C_\u0432\u0435\u0440\u0435\u0441\u0435\u043D\u044C_\u0436\u043E\u0432\u0442\u0435\u043D\u044C_\u043B\u0438\u0441\u0442\u043E\u043F\u0430\u0434_\u0433\u0440\u0443\u0434\u0435\u043D\u044C".split("_"),r=/D[oD]?(\[[^[\]]*\]|\s)+MMMM?/;function u(a,_,y){var f,l;return y==="m"?_?"\u0445\u0432\u0438\u043B\u0438\u043D\u0430":"\u0445\u0432\u0438\u043B\u0438\u043D\u0443":y==="h"?_?"\u0433\u043E\u0434\u0438\u043D\u0430":"\u0433\u043E\u0434\u0438\u043D\u0443":a+" "+(f=+a,l={ss:_?"\u0441\u0435\u043A\u0443\u043D\u0434\u0430_\u0441\u0435\u043A\u0443\u043D\u0434\u0438_\u0441\u0435\u043A\u0443\u043D\u0434":"\u0441\u0435\u043A\u0443\u043D\u0434\u0443_\u0441\u0435\u043A\u0443\u043D\u0434\u0438_\u0441\u0435\u043A\u0443\u043D\u0434",mm:_?"\u0445\u0432\u0438\u043B\u0438\u043D\u0430_\u0445\u0432\u0438\u043B\u0438\u043D\u0438_\u0445\u0432\u0438\u043B\u0438\u043D":"\u0445\u0432\u0438\u043B\u0438\u043D\u0443_\u0445\u0432\u0438\u043B\u0438\u043D\u0438_\u0445\u0432\u0438\u043B\u0438\u043D",hh:_?"\u0433\u043E\u0434\u0438\u043D\u0430_\u0433\u043E\u0434\u0438\u043D\u0438_\u0433\u043E\u0434\u0438\u043D":"\u0433\u043E\u0434\u0438\u043D\u0443_\u0433\u043E\u0434\u0438\u043D\u0438_\u0433\u043E\u0434\u0438\u043D",dd:"\u0434\u0435\u043D\u044C_\u0434\u043D\u0456_\u0434\u043D\u0456\u0432",MM:"\u043C\u0456\u0441\u044F\u0446\u044C_\u043C\u0456\u0441\u044F\u0446\u0456_\u043C\u0456\u0441\u044F\u0446\u0456\u0432",yy:"\u0440\u0456\u043A_\u0440\u043E\u043A\u0438_\u0440\u043E\u043A\u0456\u0432"}[y].split("_"),f%10==1&&f%100!=11?l[0]:f%10>=2&&f%10<=4&&(f%100<10||f%100>=20)?l[1]:l[2])}var d=function(a,_){return r.test(_)?i[a.month()]:e[a.month()]};d.s=e,d.f=i;var o={name:"uk",weekdays:"\u043D\u0435\u0434\u0456\u043B\u044F_\u043F\u043E\u043D\u0435\u0434\u0456\u043B\u043E\u043A_\u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A_\u0441\u0435\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440_\u043F\u2019\u044F\u0442\u043D\u0438\u0446\u044F_\u0441\u0443\u0431\u043E\u0442\u0430".split("_"),weekdaysShort:"\u043D\u0434\u043B_\u043F\u043D\u0434_\u0432\u0442\u0440_\u0441\u0440\u0434_\u0447\u0442\u0432_\u043F\u0442\u043D_\u0441\u0431\u0442".split("_"),weekdaysMin:"\u043D\u0434_\u043F\u043D_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043F\u0442_\u0441\u0431".split("_"),months:d,monthsShort:"\u0441\u0456\u0447_\u043B\u044E\u0442_\u0431\u0435\u0440_\u043A\u0432\u0456\u0442_\u0442\u0440\u0430\u0432_\u0447\u0435\u0440\u0432_\u043B\u0438\u043F_\u0441\u0435\u0440\u043F_\u0432\u0435\u0440_\u0436\u043E\u0432\u0442_\u043B\u0438\u0441\u0442_\u0433\u0440\u0443\u0434".split("_"),weekStart:1,relativeTime:{future:"\u0437\u0430 %s",past:"%s \u0442\u043E\u043C\u0443",s:"\u0434\u0435\u043A\u0456\u043B\u044C\u043A\u0430 \u0441\u0435\u043A\u0443\u043D\u0434",m:u,mm:u,h:u,hh:u,d:"\u0434\u0435\u043D\u044C",dd:u,M:"\u043C\u0456\u0441\u044F\u0446\u044C",MM:u,y:"\u0440\u0456\u043A",yy:u},ordinal:function(a){return a},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0440.",LLL:"D MMMM YYYY \u0440., HH:mm",LLLL:"dddd, D MMMM YYYY \u0440., HH:mm"}};return s.default.locale(o,null,!0),o})});var Mi=b((un,on)=>{(function(n,t){typeof un=="object"&&typeof on<"u"?on.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_vi=t(n.dayjs)})(un,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"vi",weekdays:"ch\u1EE7 nh\u1EADt_th\u1EE9 hai_th\u1EE9 ba_th\u1EE9 t\u01B0_th\u1EE9 n\u0103m_th\u1EE9 s\xE1u_th\u1EE9 b\u1EA3y".split("_"),months:"th\xE1ng 1_th\xE1ng 2_th\xE1ng 3_th\xE1ng 4_th\xE1ng 5_th\xE1ng 6_th\xE1ng 7_th\xE1ng 8_th\xE1ng 9_th\xE1ng 10_th\xE1ng 11_th\xE1ng 12".split("_"),weekStart:1,weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),monthsShort:"Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),ordinal:function(e){return e},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [n\u0103m] YYYY",LLL:"D MMMM [n\u0103m] YYYY HH:mm",LLLL:"dddd, D MMMM [n\u0103m] YYYY HH:mm",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},relativeTime:{future:"%s t\u1EDBi",past:"%s tr\u01B0\u1EDBc",s:"v\xE0i gi\xE2y",m:"m\u1ED9t ph\xFAt",mm:"%d ph\xFAt",h:"m\u1ED9t gi\u1EDD",hh:"%d gi\u1EDD",d:"m\u1ED9t ng\xE0y",dd:"%d ng\xE0y",M:"m\u1ED9t th\xE1ng",MM:"%d th\xE1ng",y:"m\u1ED9t n\u0103m",yy:"%d n\u0103m"}};return s.default.locale(i,null,!0),i})});var yi=b((dn,_n)=>{(function(n,t){typeof dn=="object"&&typeof _n<"u"?_n.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_zh_cn=t(n.dayjs)})(dn,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"zh-cn",weekdays:"\u661F\u671F\u65E5_\u661F\u671F\u4E00_\u661F\u671F\u4E8C_\u661F\u671F\u4E09_\u661F\u671F\u56DB_\u661F\u671F\u4E94_\u661F\u671F\u516D".split("_"),weekdaysShort:"\u5468\u65E5_\u5468\u4E00_\u5468\u4E8C_\u5468\u4E09_\u5468\u56DB_\u5468\u4E94_\u5468\u516D".split("_"),weekdaysMin:"\u65E5_\u4E00_\u4E8C_\u4E09_\u56DB_\u4E94_\u516D".split("_"),months:"\u4E00\u6708_\u4E8C\u6708_\u4E09\u6708_\u56DB\u6708_\u4E94\u6708_\u516D\u6708_\u4E03\u6708_\u516B\u6708_\u4E5D\u6708_\u5341\u6708_\u5341\u4E00\u6708_\u5341\u4E8C\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),ordinal:function(e,r){return r==="W"?e+"\u5468":e+"\u65E5"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5E74M\u6708D\u65E5",LLL:"YYYY\u5E74M\u6708D\u65E5Ah\u70B9mm\u5206",LLLL:"YYYY\u5E74M\u6708D\u65E5ddddAh\u70B9mm\u5206",l:"YYYY/M/D",ll:"YYYY\u5E74M\u6708D\u65E5",lll:"YYYY\u5E74M\u6708D\u65E5 HH:mm",llll:"YYYY\u5E74M\u6708D\u65E5dddd HH:mm"},relativeTime:{future:"%s\u5185",past:"%s\u524D",s:"\u51E0\u79D2",m:"1 \u5206\u949F",mm:"%d \u5206\u949F",h:"1 \u5C0F\u65F6",hh:"%d \u5C0F\u65F6",d:"1 \u5929",dd:"%d \u5929",M:"1 \u4E2A\u6708",MM:"%d \u4E2A\u6708",y:"1 \u5E74",yy:"%d \u5E74"},meridiem:function(e,r){var u=100*e+r;return u<600?"\u51CC\u6668":u<900?"\u65E9\u4E0A":u<1100?"\u4E0A\u5348":u<1300?"\u4E2D\u5348":u<1800?"\u4E0B\u5348":"\u665A\u4E0A"}};return s.default.locale(i,null,!0),i})});var Yi=b((fn,ln)=>{(function(n,t){typeof fn=="object"&&typeof ln<"u"?ln.exports=t(H()):typeof define=="function"&&define.amd?define(["dayjs"],t):(n=typeof globalThis<"u"?globalThis:n||self).dayjs_locale_zh_tw=t(n.dayjs)})(fn,function(n){"use strict";function t(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var s=t(n),i={name:"zh-tw",weekdays:"\u661F\u671F\u65E5_\u661F\u671F\u4E00_\u661F\u671F\u4E8C_\u661F\u671F\u4E09_\u661F\u671F\u56DB_\u661F\u671F\u4E94_\u661F\u671F\u516D".split("_"),weekdaysShort:"\u9031\u65E5_\u9031\u4E00_\u9031\u4E8C_\u9031\u4E09_\u9031\u56DB_\u9031\u4E94_\u9031\u516D".split("_"),weekdaysMin:"\u65E5_\u4E00_\u4E8C_\u4E09_\u56DB_\u4E94_\u516D".split("_"),months:"\u4E00\u6708_\u4E8C\u6708_\u4E09\u6708_\u56DB\u6708_\u4E94\u6708_\u516D\u6708_\u4E03\u6708_\u516B\u6708_\u4E5D\u6708_\u5341\u6708_\u5341\u4E00\u6708_\u5341\u4E8C\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),ordinal:function(e,r){return r==="W"?e+"\u9031":e+"\u65E5"},formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5E74M\u6708D\u65E5",LLL:"YYYY\u5E74M\u6708D\u65E5 HH:mm",LLLL:"YYYY\u5E74M\u6708D\u65E5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5E74M\u6708D\u65E5",lll:"YYYY\u5E74M\u6708D\u65E5 HH:mm",llll:"YYYY\u5E74M\u6708D\u65E5dddd HH:mm"},relativeTime:{future:"%s\u5167",past:"%s\u524D",s:"\u5E7E\u79D2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5C0F\u6642",hh:"%d \u5C0F\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500B\u6708",MM:"%d \u500B\u6708",y:"1 \u5E74",yy:"%d \u5E74"},meridiem:function(e,r){var u=100*e+r;return u<600?"\u51CC\u6668":u<900?"\u65E9\u4E0A":u<1100?"\u4E0A\u5348":u<1300?"\u4E2D\u5348":u<1800?"\u4E0B\u5348":"\u665A\u4E0A"}};return s.default.locale(i,null,!0),i})});var cn=60,hn=cn*60,Mn=hn*24,$i=Mn*7,ae=1e3,ce=cn*ae,ve=hn*ae,yn=Mn*ae,Yn=$i*ae,_e="millisecond",te="second",ne="minute",ie="hour",V="day",oe="week",R="month",he="quarter",K="year",se="date",pn="YYYY-MM-DDTHH:mm:ssZ",Se="Invalid Date",Dn=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,Ln=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g;var vn={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var s=["th","st","nd","rd"],i=t%100;return"["+t+(s[(i-20)%10]||s[i]||s[0])+"]"}};var be=function(t,s,i){var e=String(t);return!e||e.length>=s?t:""+Array(s+1-e.length).join(i)+t},Ci=function(t){var s=-t.utcOffset(),i=Math.abs(s),e=Math.floor(i/60),r=i%60;return(s<=0?"+":"-")+be(e,2,"0")+":"+be(r,2,"0")},Oi=function n(t,s){if(t.date()1)return n(u[0])}else{var d=t.name;ue[d]=t,e=d}return!i&&e&&(fe=e),e||!i&&fe},F=function(t,s){if(ke(t))return t.clone();var i=typeof s=="object"?s:{};return i.date=t,i.args=arguments,new ye(i)},qi=function(t,s){return F(t,{locale:s.$L,utc:s.$u,x:s.$x,$offset:s.$offset})},z=Sn;z.l=Me;z.i=ke;z.w=qi;var xi=function(t){var s=t.date,i=t.utc;if(s===null)return new Date(NaN);if(z.u(s))return new Date;if(s instanceof Date)return new Date(s);if(typeof s=="string"&&!/Z$/i.test(s)){var e=s.match(Dn);if(e){var r=e[2]-1||0,u=(e[7]||"0").substring(0,3);return i?new Date(Date.UTC(e[1],r,e[3]||1,e[4]||0,e[5]||0,e[6]||0,u)):new Date(e[1],r,e[3]||1,e[4]||0,e[5]||0,e[6]||0,u)}}return new Date(s)},ye=function(){function n(s){this.$L=Me(s.locale,null,!0),this.parse(s),this.$x=this.$x||s.x||{},this[bn]=!0}var t=n.prototype;return t.parse=function(i){this.$d=xi(i),this.init()},t.init=function(){var i=this.$d;this.$y=i.getFullYear(),this.$M=i.getMonth(),this.$D=i.getDate(),this.$W=i.getDay(),this.$H=i.getHours(),this.$m=i.getMinutes(),this.$s=i.getSeconds(),this.$ms=i.getMilliseconds()},t.$utils=function(){return z},t.isValid=function(){return this.$d.toString()!==Se},t.isSame=function(i,e){var r=F(i);return this.startOf(e)<=r&&r<=this.endOf(e)},t.isAfter=function(i,e){return F(i){this.focusedDate??(this.focusedDate=(this.getDefaultFocusedDate()??O()).tz(d)),this.focusedMonth??(this.focusedMonth=this.focusedDate.month()),this.focusedYear??(this.focusedYear=this.focusedDate.year())});let o=this.getSelectedDate()??this.getDefaultFocusedDate()??O().tz(d).hour(0).minute(0).second(0);(this.getMaxDate()!==null&&o.isAfter(this.getMaxDate())||this.getMinDate()!==null&&o.isBefore(this.getMinDate()))&&(o=null),this.hour=o?.hour()??0,this.minute=o?.minute()??0,this.second=o?.second()??0,this.setDisplayText(),this.setMonths(),this.setDayLabels(),i&&this.$nextTick(()=>this.togglePanelVisibility(this.$refs.button)),this.$watch("focusedMonth",()=>{this.focusedMonth=+this.focusedMonth,this.focusedDate.month()!==this.focusedMonth&&(this.focusedDate=this.focusedDate.month(this.focusedMonth))}),this.$watch("focusedYear",()=>{if(this.focusedYear?.length>4&&(this.focusedYear=this.focusedYear.substring(0,4)),!this.focusedYear||this.focusedYear?.length!==4)return;let a=+this.focusedYear;Number.isInteger(a)||(a=O().tz(d).year(),this.focusedYear=a),this.focusedDate.year()!==a&&(this.focusedDate=this.focusedDate.year(a))}),this.$watch("focusedDate",()=>{let a=this.focusedDate.month(),_=this.focusedDate.year();this.focusedMonth!==a&&(this.focusedMonth=a),this.focusedYear!==_&&(this.focusedYear=_),this.setupDaysGrid()}),this.$watch("hour",()=>{let a=+this.hour;if(Number.isInteger(a)?a>23?this.hour=0:a<0?this.hour=23:this.hour=a:this.hour=0,this.isClearingState)return;let _=this.getSelectedDate()??this.focusedDate;this.setState(_.hour(this.hour??0))}),this.$watch("minute",()=>{let a=+this.minute;if(Number.isInteger(a)?a>59?this.minute=0:a<0?this.minute=59:this.minute=a:this.minute=0,this.isClearingState)return;let _=this.getSelectedDate()??this.focusedDate;this.setState(_.minute(this.minute??0))}),this.$watch("second",()=>{let a=+this.second;if(Number.isInteger(a)?a>59?this.second=0:a<0?this.second=59:this.second=a:this.second=0,this.isClearingState)return;let _=this.getSelectedDate()??this.focusedDate;this.setState(_.second(this.second??0))}),this.$watch("state",()=>{if(this.state===void 0)return;let a=this.getSelectedDate();if(a===null){this.clearState();return}this.getMaxDate()!==null&&a?.isAfter(this.getMaxDate())&&(a=null),this.getMinDate()!==null&&a?.isBefore(this.getMinDate())&&(a=null);let _=a?.hour()??0;this.hour!==_&&(this.hour=_);let y=a?.minute()??0;this.minute!==y&&(this.minute=y);let f=a?.second()??0;this.second!==f&&(this.second=f),this.setDisplayText()})},clearState:function(){this.isClearingState=!0,this.setState(null),this.hour=0,this.minute=0,this.second=0,this.$nextTick(()=>this.isClearingState=!1)},dateIsDisabled:function(o){return!!(this.$refs?.disabledDates&&JSON.parse(this.$refs.disabledDates.value??[]).some(a=>(a=O(a),a.isValid()?a.isSame(o,"day"):!1))||this.getMaxDate()&&o.isAfter(this.getMaxDate(),"day")||this.getMinDate()&&o.isBefore(this.getMinDate(),"day"))},dayIsDisabled:function(o){return this.focusedDate??(this.focusedDate=O().tz(d)),this.dateIsDisabled(this.focusedDate.date(o))},dayIsSelected:function(o){let a=this.getSelectedDate();return a===null?!1:(this.focusedDate??(this.focusedDate=O().tz(d)),a.date()===o&&a.month()===this.focusedDate.month()&&a.year()===this.focusedDate.year())},dayIsToday:function(o){let a=O().tz(d);return this.focusedDate??(this.focusedDate=a),a.date()===o&&a.month()===this.focusedDate.month()&&a.year()===this.focusedDate.year()},focusPreviousDay:function(){this.focusedDate??(this.focusedDate=O().tz(d)),this.focusedDate=this.focusedDate.subtract(1,"day")},focusPreviousWeek:function(){this.focusedDate??(this.focusedDate=O().tz(d)),this.focusedDate=this.focusedDate.subtract(1,"week")},focusNextDay:function(){this.focusedDate??(this.focusedDate=O().tz(d)),this.focusedDate=this.focusedDate.add(1,"day")},focusNextWeek:function(){this.focusedDate??(this.focusedDate=O().tz(d)),this.focusedDate=this.focusedDate.add(1,"week")},getDayLabels:function(){let o=O.weekdaysShort();return s===0?o:[...o.slice(s),...o.slice(0,s)]},getMaxDate:function(){let o=O(this.$refs.maxDate?.value);return o.isValid()?o:null},getMinDate:function(){let o=O(this.$refs.minDate?.value);return o.isValid()?o:null},getSelectedDate:function(){if(this.state===void 0||this.state===null)return null;let o=O(this.state);return o.isValid()?o:null},getDefaultFocusedDate:function(){if(this.defaultFocusedDate===null)return null;let o=O(this.defaultFocusedDate);return o.isValid()?o:null},togglePanelVisibility:function(){this.isOpen()||(this.focusedDate=this.getSelectedDate()??this.focusedDate??this.getMinDate()??O().tz(d),this.setupDaysGrid()),this.$refs.panel.toggle(this.$refs.button)},selectDate:function(o=null){o&&this.setFocusedDay(o),this.focusedDate??(this.focusedDate=O().tz(d)),this.setState(this.focusedDate),r&&this.togglePanelVisibility()},setDisplayText:function(){this.displayText=this.getSelectedDate()?this.getSelectedDate().format(t):""},setMonths:function(){this.months=O.months()},setDayLabels:function(){this.dayLabels=this.getDayLabels()},setupDaysGrid:function(){this.focusedDate??(this.focusedDate=O().tz(d)),this.emptyDaysInFocusedMonth=Array.from({length:this.focusedDate.date(8-s).day()},(o,a)=>a+1),this.daysInFocusedMonth=Array.from({length:this.focusedDate.daysInMonth()},(o,a)=>a+1)},setFocusedDay:function(o){this.focusedDate=(this.focusedDate??O().tz(d)).date(o)},setState:function(o){if(o===null){this.state=null,this.setDisplayText();return}this.dateIsDisabled(o)||(this.state=o.hour(this.hour??0).minute(this.minute??0).second(this.second??0).format("YYYY-MM-DD HH:mm:ss"),this.setDisplayText())},isOpen:function(){return this.$refs.panel?.style.display==="block"}}}var pi={ar:Cn(),bs:On(),ca:zn(),ckb:Pe(),cs:In(),cy:qn(),da:xn(),de:Nn(),el:En(),en:Fn(),es:Jn(),et:Wn(),fa:Un(),fi:Pn(),fr:Rn(),hi:Gn(),hu:Zn(),hy:Vn(),id:Kn(),it:Qn(),ja:Xn(),ka:Bn(),km:ei(),ku:Pe(),lt:ti(),lv:ni(),ms:ii(),my:si(),nl:ri(),no:ai(),pl:ui(),pt_BR:oi(),pt_PT:di(),ro:_i(),ru:fi(),sv:li(),th:mi(),tr:ci(),uk:hi(),vi:Mi(),zh_CN:yi(),zh_TW:Yi()};export{Ni as default}; diff --git a/public/js/filament/forms/components/file-upload.js b/public/js/filament/forms/components/file-upload.js deleted file mode 100644 index 44d1790..0000000 --- a/public/js/filament/forms/components/file-upload.js +++ /dev/null @@ -1,123 +0,0 @@ -var mr=Object.defineProperty;var ur=(e,t)=>{for(var i in t)mr(e,i,{get:t[i],enumerable:!0})};var la={};ur(la,{FileOrigin:()=>Ct,FileStatus:()=>Et,OptionTypes:()=>Ui,Status:()=>ll,create:()=>gt,destroy:()=>ft,find:()=>Hi,getOptions:()=>ji,parse:()=>Wi,registerPlugin:()=>ve,setOptions:()=>Ft,supported:()=>Gi});var gr=e=>e instanceof HTMLElement,fr=(e,t=[],i=[])=>{let a={...e},n=[],l=[],o=()=>({...a}),r=()=>{let g=[...n];return n.length=0,g},s=()=>{let g=[...l];l.length=0,g.forEach(({type:f,data:h})=>{p(f,h)})},p=(g,f,h)=>{if(h&&!document.hidden){l.push({type:g,data:f});return}u[g]&&u[g](f),n.push({type:g,data:f})},c=(g,...f)=>m[g]?m[g](...f):null,d={getState:o,processActionQueue:r,processDispatchQueue:s,dispatch:p,query:c},m={};t.forEach(g=>{m={...g(a),...m}});let u={};return i.forEach(g=>{u={...g(p,c,a),...u}}),d},hr=(e,t,i)=>{if(typeof i=="function"){e[t]=i;return}Object.defineProperty(e,t,{...i})},te=(e,t)=>{for(let i in e)e.hasOwnProperty(i)&&t(i,e[i])},We=e=>{let t={};return te(e,i=>{hr(t,i,e[i])}),t},se=(e,t,i=null)=>{if(i===null)return e.getAttribute(t)||e.hasAttribute(t);e.setAttribute(t,i)},br="http://www.w3.org/2000/svg",Er=["svg","path"],za=e=>Er.includes(e),li=(e,t,i={})=>{typeof t=="object"&&(i=t,t=null);let a=za(e)?document.createElementNS(br,e):document.createElement(e);return t&&(za(e)?se(a,"class",t):a.className=t),te(i,(n,l)=>{se(a,n,l)}),a},Tr=e=>(t,i)=>{typeof i<"u"&&e.children[i]?e.insertBefore(t,e.children[i]):e.appendChild(t)},Ir=(e,t)=>(i,a)=>(typeof a<"u"?t.splice(a,0,i):t.push(i),i),vr=(e,t)=>i=>(t.splice(t.indexOf(i),1),i.element.parentNode&&e.removeChild(i.element),i),xr=typeof window<"u"&&typeof window.document<"u",En=()=>xr,yr=En()?li("svg"):{},Rr="children"in yr?e=>e.children.length:e=>e.childNodes.length,Tn=(e,t,i,a)=>{let n=i[0]||e.left,l=i[1]||e.top,o=n+e.width,r=l+e.height*(a[1]||1),s={element:{...e},inner:{left:e.left,top:e.top,right:e.right,bottom:e.bottom},outer:{left:n,top:l,right:o,bottom:r}};return t.filter(p=>!p.isRectIgnored()).map(p=>p.rect).forEach(p=>{Oa(s.inner,{...p.inner}),Oa(s.outer,{...p.outer})}),Fa(s.inner),s.outer.bottom+=s.element.marginBottom,s.outer.right+=s.element.marginRight,Fa(s.outer),s},Oa=(e,t)=>{t.top+=e.top,t.right+=e.left,t.bottom+=e.top,t.left+=e.left,t.bottom>e.bottom&&(e.bottom=t.bottom),t.right>e.right&&(e.right=t.right)},Fa=e=>{e.width=e.right-e.left,e.height=e.bottom-e.top},$e=e=>typeof e=="number",Sr=(e,t,i,a=.001)=>Math.abs(e-t){let a=null,n=null,l=0,o=!1,p=We({interpolate:(c,d)=>{if(o)return;if(!($e(a)&&$e(n))){o=!0,l=0;return}let m=-(n-a)*e;l+=m/i,n+=l,l*=t,Sr(n,a,l)||d?(n=a,l=0,o=!0,p.onupdate(n),p.oncomplete(n)):p.onupdate(n)},target:{set:c=>{if($e(c)&&!$e(n)&&(n=c),a===null&&(a=c,n=c),a=c,n===a||typeof a>"u"){o=!0,l=0,p.onupdate(n),p.oncomplete(n);return}o=!1},get:()=>a},resting:{get:()=>o},onupdate:c=>{},oncomplete:c=>{}});return p};var wr=e=>e<.5?2*e*e:-1+(4-2*e)*e,Lr=({duration:e=500,easing:t=wr,delay:i=0}={})=>{let a=null,n,l,o=!0,r=!1,s=null,c=We({interpolate:(d,m)=>{o||s===null||(a===null&&(a=d),!(d-a=e||m?(n=1,l=r?0:1,c.onupdate(l*s),c.oncomplete(l*s),o=!0):(l=n/e,c.onupdate((n>=0?t(r?1-l:l):0)*s))))},target:{get:()=>r?0:s,set:d=>{if(s===null){s=d,c.onupdate(d),c.oncomplete(d);return}do},onupdate:d=>{},oncomplete:d=>{}});return c},Da={spring:_r,tween:Lr},Mr=(e,t,i)=>{let a=e[t]&&typeof e[t][i]=="object"?e[t][i]:e[t]||e,n=typeof a=="string"?a:a.type,l=typeof a=="object"?{...a}:{};return Da[n]?Da[n](l):null},Yi=(e,t,i,a=!1)=>{t=Array.isArray(t)?t:[t],t.forEach(n=>{e.forEach(l=>{let o=l,r=()=>i[l],s=p=>i[l]=p;typeof l=="object"&&(o=l.key,r=l.getter||r,s=l.setter||s),!(n[o]&&!a)&&(n[o]={get:r,set:s})})})},Ar=({mixinConfig:e,viewProps:t,viewInternalAPI:i,viewExternalAPI:a})=>{let n={...t},l=[];return te(e,(o,r)=>{let s=Mr(r);if(!s)return;s.onupdate=c=>{t[o]=c},s.target=n[o],Yi([{key:o,setter:c=>{s.target!==c&&(s.target=c)},getter:()=>t[o]}],[i,a],t,!0),l.push(s)}),{write:o=>{let r=document.hidden,s=!0;return l.forEach(p=>{p.resting||(s=!1),p.interpolate(o,r)}),s},destroy:()=>{}}},Pr=e=>(t,i)=>{e.addEventListener(t,i)},zr=e=>(t,i)=>{e.removeEventListener(t,i)},Or=({mixinConfig:e,viewProps:t,viewInternalAPI:i,viewExternalAPI:a,viewState:n,view:l})=>{let o=[],r=Pr(l.element),s=zr(l.element);return a.on=(p,c)=>{o.push({type:p,fn:c}),r(p,c)},a.off=(p,c)=>{o.splice(o.findIndex(d=>d.type===p&&d.fn===c),1),s(p,c)},{write:()=>!0,destroy:()=>{o.forEach(p=>{s(p.type,p.fn)})}}},Fr=({mixinConfig:e,viewProps:t,viewExternalAPI:i})=>{Yi(e,i,t)},ue=e=>e!=null,Dr={opacity:1,scaleX:1,scaleY:1,translateX:0,translateY:0,rotateX:0,rotateY:0,rotateZ:0,originX:0,originY:0},Cr=({mixinConfig:e,viewProps:t,viewInternalAPI:i,viewExternalAPI:a,view:n})=>{let l={...t},o={};Yi(e,[i,a],t);let r=()=>[t.translateX||0,t.translateY||0],s=()=>[t.scaleX||0,t.scaleY||0],p=()=>n.rect?Tn(n.rect,n.childViews,r(),s()):null;return i.rect={get:p},a.rect={get:p},e.forEach(c=>{t[c]=typeof l[c]>"u"?Dr[c]:l[c]}),{write:()=>{if(Br(o,t))return Nr(n.element,t),Object.assign(o,{...t}),!0},destroy:()=>{}}},Br=(e,t)=>{if(Object.keys(e).length!==Object.keys(t).length)return!0;for(let i in t)if(t[i]!==e[i])return!0;return!1},Nr=(e,{opacity:t,perspective:i,translateX:a,translateY:n,scaleX:l,scaleY:o,rotateX:r,rotateY:s,rotateZ:p,originX:c,originY:d,width:m,height:u})=>{let g="",f="";(ue(c)||ue(d))&&(f+=`transform-origin: ${c||0}px ${d||0}px;`),ue(i)&&(g+=`perspective(${i}px) `),(ue(a)||ue(n))&&(g+=`translate3d(${a||0}px, ${n||0}px, 0) `),(ue(l)||ue(o))&&(g+=`scale3d(${ue(l)?l:1}, ${ue(o)?o:1}, 1) `),ue(p)&&(g+=`rotateZ(${p}rad) `),ue(r)&&(g+=`rotateX(${r}rad) `),ue(s)&&(g+=`rotateY(${s}rad) `),g.length&&(f+=`transform:${g};`),ue(t)&&(f+=`opacity:${t};`,t===0&&(f+="visibility:hidden;"),t<1&&(f+="pointer-events:none;")),ue(u)&&(f+=`height:${u}px;`),ue(m)&&(f+=`width:${m}px;`);let h=e.elementCurrentStyle||"";(f.length!==h.length||f!==h)&&(e.style.cssText=f,e.elementCurrentStyle=f)},kr={styles:Cr,listeners:Or,animations:Ar,apis:Fr},Ca=(e={},t={},i={})=>(t.layoutCalculated||(e.paddingTop=parseInt(i.paddingTop,10)||0,e.marginTop=parseInt(i.marginTop,10)||0,e.marginRight=parseInt(i.marginRight,10)||0,e.marginBottom=parseInt(i.marginBottom,10)||0,e.marginLeft=parseInt(i.marginLeft,10)||0,t.layoutCalculated=!0),e.left=t.offsetLeft||0,e.top=t.offsetTop||0,e.width=t.offsetWidth||0,e.height=t.offsetHeight||0,e.right=e.left+e.width,e.bottom=e.top+e.height,e.scrollTop=t.scrollTop,e.hidden=t.offsetParent===null,e),ne=({tag:e="div",name:t=null,attributes:i={},read:a=()=>{},write:n=()=>{},create:l=()=>{},destroy:o=()=>{},filterFrameActionsForChild:r=(u,g)=>g,didCreateView:s=()=>{},didWriteView:p=()=>{},ignoreRect:c=!1,ignoreRectUpdate:d=!1,mixins:m=[]}={})=>(u,g={})=>{let f=li(e,`filepond--${t}`,i),h=window.getComputedStyle(f,null),I=Ca(),b=null,T=!1,v=[],y=[],E={},_={},x=[n],R=[a],z=[o],P=()=>f,A=()=>v.concat(),B=()=>E,w=k=>(H,Y)=>H(k,Y),O=()=>b||(b=Tn(I,v,[0,0],[1,1]),b),S=()=>h,L=()=>{b=null,v.forEach(Y=>Y._read()),!(d&&I.width&&I.height)&&Ca(I,f,h);let H={root:K,props:g,rect:I};R.forEach(Y=>Y(H))},D=(k,H,Y)=>{let oe=H.length===0;return x.forEach(ee=>{ee({props:g,root:K,actions:H,timestamp:k,shouldOptimize:Y})===!1&&(oe=!1)}),y.forEach(ee=>{ee.write(k)===!1&&(oe=!1)}),v.filter(ee=>!!ee.element.parentNode).forEach(ee=>{ee._write(k,r(ee,H),Y)||(oe=!1)}),v.forEach((ee,dt)=>{ee.element.parentNode||(K.appendChild(ee.element,dt),ee._read(),ee._write(k,r(ee,H),Y),oe=!1)}),T=oe,p({props:g,root:K,actions:H,timestamp:k}),oe},F=()=>{y.forEach(k=>k.destroy()),z.forEach(k=>{k({root:K,props:g})}),v.forEach(k=>k._destroy())},G={element:{get:P},style:{get:S},childViews:{get:A}},C={...G,rect:{get:O},ref:{get:B},is:k=>t===k,appendChild:Tr(f),createChildView:w(u),linkView:k=>(v.push(k),k),unlinkView:k=>{v.splice(v.indexOf(k),1)},appendChildView:Ir(f,v),removeChildView:vr(f,v),registerWriter:k=>x.push(k),registerReader:k=>R.push(k),registerDestroyer:k=>z.push(k),invalidateLayout:()=>f.layoutCalculated=!1,dispatch:u.dispatch,query:u.query},q={element:{get:P},childViews:{get:A},rect:{get:O},resting:{get:()=>T},isRectIgnored:()=>c,_read:L,_write:D,_destroy:F},X={...G,rect:{get:()=>I}};Object.keys(m).sort((k,H)=>k==="styles"?1:H==="styles"?-1:0).forEach(k=>{let H=kr[k]({mixinConfig:m[k],viewProps:g,viewState:_,viewInternalAPI:C,viewExternalAPI:q,view:We(X)});H&&y.push(H)});let K=We(C);l({root:K,props:g});let pe=Rr(f);return v.forEach((k,H)=>{K.appendChild(k.element,pe+H)}),s(K),We(q)},Vr=(e,t,i=60)=>{let a="__framePainter";if(window[a]){window[a].readers.push(e),window[a].writers.push(t);return}window[a]={readers:[e],writers:[t]};let n=window[a],l=1e3/i,o=null,r=null,s=null,p=null,c=()=>{document.hidden?(s=()=>window.setTimeout(()=>d(performance.now()),l),p=()=>window.clearTimeout(r)):(s=()=>window.requestAnimationFrame(d),p=()=>window.cancelAnimationFrame(r))};document.addEventListener("visibilitychange",()=>{p&&p(),c(),d(performance.now())});let d=m=>{r=s(d),o||(o=m);let u=m-o;u<=l||(o=m-u%l,n.readers.forEach(g=>g()),n.writers.forEach(g=>g(m)))};return c(),d(performance.now()),{pause:()=>{p(r)}}},fe=(e,t)=>({root:i,props:a,actions:n=[],timestamp:l,shouldOptimize:o})=>{n.filter(r=>e[r.type]).forEach(r=>e[r.type]({root:i,props:a,action:r.data,timestamp:l,shouldOptimize:o})),t&&t({root:i,props:a,actions:n,timestamp:l,shouldOptimize:o})},Ba=(e,t)=>t.parentNode.insertBefore(e,t),Na=(e,t)=>t.parentNode.insertBefore(e,t.nextSibling),ci=e=>Array.isArray(e),ke=e=>e==null,Gr=e=>e.trim(),di=e=>""+e,Ur=(e,t=",")=>ke(e)?[]:ci(e)?e:di(e).split(t).map(Gr).filter(i=>i.length),In=e=>typeof e=="boolean",vn=e=>In(e)?e:e==="true",ge=e=>typeof e=="string",xn=e=>$e(e)?e:ge(e)?di(e).replace(/[a-z]+/gi,""):0,ni=e=>parseInt(xn(e),10),ka=e=>parseFloat(xn(e)),bt=e=>$e(e)&&isFinite(e)&&Math.floor(e)===e,Va=(e,t=1e3)=>{if(bt(e))return e;let i=di(e).trim();return/MB$/i.test(i)?(i=i.replace(/MB$i/,"").trim(),ni(i)*t*t):/KB/i.test(i)?(i=i.replace(/KB$i/,"").trim(),ni(i)*t):ni(i)},Xe=e=>typeof e=="function",Wr=e=>{let t=self,i=e.split("."),a=null;for(;a=i.shift();)if(t=t[a],!t)return null;return t},Ga={process:"POST",patch:"PATCH",revert:"DELETE",fetch:"GET",restore:"GET",load:"GET"},Hr=e=>{let t={};return t.url=ge(e)?e:e.url||"",t.timeout=e.timeout?parseInt(e.timeout,10):0,t.headers=e.headers?e.headers:{},te(Ga,i=>{t[i]=jr(i,e[i],Ga[i],t.timeout,t.headers)}),t.process=e.process||ge(e)||e.url?t.process:null,t.remove=e.remove||null,delete t.headers,t},jr=(e,t,i,a,n)=>{if(t===null)return null;if(typeof t=="function")return t;let l={url:i==="GET"||i==="PATCH"?`?${e}=`:"",method:i,headers:n,withCredentials:!1,timeout:a,onload:null,ondata:null,onerror:null};if(ge(t))return l.url=t,l;if(Object.assign(l,t),ge(l.headers)){let o=l.headers.split(/:(.+)/);l.headers={header:o[0],value:o[1]}}return l.withCredentials=vn(l.withCredentials),l},Yr=e=>Hr(e),qr=e=>e===null,ce=e=>typeof e=="object"&&e!==null,$r=e=>ce(e)&&ge(e.url)&&ce(e.process)&&ce(e.revert)&&ce(e.restore)&&ce(e.fetch),Oi=e=>ci(e)?"array":qr(e)?"null":bt(e)?"int":/^[0-9]+ ?(?:GB|MB|KB)$/gi.test(e)?"bytes":$r(e)?"api":typeof e,Xr=e=>e.replace(/{\s*'/g,'{"').replace(/'\s*}/g,'"}').replace(/'\s*:/g,'":').replace(/:\s*'/g,':"').replace(/,\s*'/g,',"').replace(/'\s*,/g,'",'),Kr={array:Ur,boolean:vn,int:e=>Oi(e)==="bytes"?Va(e):ni(e),number:ka,float:ka,bytes:Va,string:e=>Xe(e)?e:di(e),function:e=>Wr(e),serverapi:Yr,object:e=>{try{return JSON.parse(Xr(e))}catch{return null}}},Qr=(e,t)=>Kr[t](e),yn=(e,t,i)=>{if(e===t)return e;let a=Oi(e);if(a!==i){let n=Qr(e,i);if(a=Oi(n),n===null)throw`Trying to assign value with incorrect type to "${option}", allowed type: "${i}"`;e=n}return e},Zr=(e,t)=>{let i=e;return{enumerable:!0,get:()=>i,set:a=>{i=yn(a,e,t)}}},Jr=e=>{let t={};return te(e,i=>{let a=e[i];t[i]=Zr(a[0],a[1])}),We(t)},es=e=>({items:[],listUpdateTimeout:null,itemUpdateTimeout:null,processingQueue:[],options:Jr(e)}),pi=(e,t="-")=>e.split(/(?=[A-Z])/).map(i=>i.toLowerCase()).join(t),ts=(e,t)=>{let i={};return te(t,a=>{i[a]={get:()=>e.getState().options[a],set:n=>{e.dispatch(`SET_${pi(a,"_").toUpperCase()}`,{value:n})}}}),i},is=e=>(t,i,a)=>{let n={};return te(e,l=>{let o=pi(l,"_").toUpperCase();n[`SET_${o}`]=r=>{try{a.options[l]=r.value}catch{}t(`DID_SET_${o}`,{value:a.options[l]})}}),n},as=e=>t=>{let i={};return te(e,a=>{i[`GET_${pi(a,"_").toUpperCase()}`]=n=>t.options[a]}),i},Re={API:1,DROP:2,BROWSE:3,PASTE:4,NONE:5},qi=()=>Math.random().toString(36).substring(2,11),$i=(e,t)=>e.splice(t,1),ns=(e,t)=>{t?e():document.hidden?Promise.resolve(1).then(e):setTimeout(e,0)},mi=()=>{let e=[],t=(a,n)=>{$i(e,e.findIndex(l=>l.event===a&&(l.cb===n||!n)))},i=(a,n,l)=>{e.filter(o=>o.event===a).map(o=>o.cb).forEach(o=>ns(()=>o(...n),l))};return{fireSync:(a,...n)=>{i(a,n,!0)},fire:(a,...n)=>{i(a,n,!1)},on:(a,n)=>{e.push({event:a,cb:n})},onOnce:(a,n)=>{e.push({event:a,cb:(...l)=>{t(a,n),n(...l)}})},off:t}},Rn=(e,t,i)=>{Object.getOwnPropertyNames(e).filter(a=>!i.includes(a)).forEach(a=>Object.defineProperty(t,a,Object.getOwnPropertyDescriptor(e,a)))},ls=["fire","process","revert","load","on","off","onOnce","retryLoad","extend","archive","archived","release","released","requestProcessing","freeze"],he=e=>{let t={};return Rn(e,t,ls),t},os=e=>{e.forEach((t,i)=>{t.released&&$i(e,i)})},U={INIT:1,IDLE:2,PROCESSING_QUEUED:9,PROCESSING:3,PROCESSING_COMPLETE:5,PROCESSING_ERROR:6,PROCESSING_REVERT_ERROR:10,LOADING:7,LOAD_ERROR:8},re={INPUT:1,LIMBO:2,LOCAL:3},Sn=e=>/[^0-9]+/.exec(e),_n=()=>Sn(1.1.toLocaleString())[0],rs=()=>{let e=_n(),t=1e3.toLocaleString();return t!=="1000"?Sn(t)[0]:e==="."?",":"."},M={BOOLEAN:"boolean",INT:"int",NUMBER:"number",STRING:"string",ARRAY:"array",OBJECT:"object",FUNCTION:"function",ACTION:"action",SERVER_API:"serverapi",REGEX:"regex"},Xi=[],Ae=(e,t,i)=>new Promise((a,n)=>{let l=Xi.filter(r=>r.key===e).map(r=>r.cb);if(l.length===0){a(t);return}let o=l.shift();l.reduce((r,s)=>r.then(p=>s(p,i)),o(t,i)).then(r=>a(r)).catch(r=>n(r))}),tt=(e,t,i)=>Xi.filter(a=>a.key===e).map(a=>a.cb(t,i)),ss=(e,t)=>Xi.push({key:e,cb:t}),cs=e=>Object.assign(pt,e),oi=()=>({...pt}),ds=e=>{te(e,(t,i)=>{pt[t]&&(pt[t][0]=yn(i,pt[t][0],pt[t][1]))})},pt={id:[null,M.STRING],name:["filepond",M.STRING],disabled:[!1,M.BOOLEAN],className:[null,M.STRING],required:[!1,M.BOOLEAN],captureMethod:[null,M.STRING],allowSyncAcceptAttribute:[!0,M.BOOLEAN],allowDrop:[!0,M.BOOLEAN],allowBrowse:[!0,M.BOOLEAN],allowPaste:[!0,M.BOOLEAN],allowMultiple:[!1,M.BOOLEAN],allowReplace:[!0,M.BOOLEAN],allowRevert:[!0,M.BOOLEAN],allowRemove:[!0,M.BOOLEAN],allowProcess:[!0,M.BOOLEAN],allowReorder:[!1,M.BOOLEAN],allowDirectoriesOnly:[!1,M.BOOLEAN],storeAsFile:[!1,M.BOOLEAN],forceRevert:[!1,M.BOOLEAN],maxFiles:[null,M.INT],checkValidity:[!1,M.BOOLEAN],itemInsertLocationFreedom:[!0,M.BOOLEAN],itemInsertLocation:["before",M.STRING],itemInsertInterval:[75,M.INT],dropOnPage:[!1,M.BOOLEAN],dropOnElement:[!0,M.BOOLEAN],dropValidation:[!1,M.BOOLEAN],ignoredFiles:[[".ds_store","thumbs.db","desktop.ini"],M.ARRAY],instantUpload:[!0,M.BOOLEAN],maxParallelUploads:[2,M.INT],allowMinimumUploadDuration:[!0,M.BOOLEAN],chunkUploads:[!1,M.BOOLEAN],chunkForce:[!1,M.BOOLEAN],chunkSize:[5e6,M.INT],chunkRetryDelays:[[500,1e3,3e3],M.ARRAY],server:[null,M.SERVER_API],fileSizeBase:[1e3,M.INT],labelFileSizeBytes:["bytes",M.STRING],labelFileSizeKilobytes:["KB",M.STRING],labelFileSizeMegabytes:["MB",M.STRING],labelFileSizeGigabytes:["GB",M.STRING],labelDecimalSeparator:[_n(),M.STRING],labelThousandsSeparator:[rs(),M.STRING],labelIdle:['Drag & Drop your files or Browse',M.STRING],labelInvalidField:["Field contains invalid files",M.STRING],labelFileWaitingForSize:["Waiting for size",M.STRING],labelFileSizeNotAvailable:["Size not available",M.STRING],labelFileCountSingular:["file in list",M.STRING],labelFileCountPlural:["files in list",M.STRING],labelFileLoading:["Loading",M.STRING],labelFileAdded:["Added",M.STRING],labelFileLoadError:["Error during load",M.STRING],labelFileRemoved:["Removed",M.STRING],labelFileRemoveError:["Error during remove",M.STRING],labelFileProcessing:["Uploading",M.STRING],labelFileProcessingComplete:["Upload complete",M.STRING],labelFileProcessingAborted:["Upload cancelled",M.STRING],labelFileProcessingError:["Error during upload",M.STRING],labelFileProcessingRevertError:["Error during revert",M.STRING],labelTapToCancel:["tap to cancel",M.STRING],labelTapToRetry:["tap to retry",M.STRING],labelTapToUndo:["tap to undo",M.STRING],labelButtonRemoveItem:["Remove",M.STRING],labelButtonAbortItemLoad:["Abort",M.STRING],labelButtonRetryItemLoad:["Retry",M.STRING],labelButtonAbortItemProcessing:["Cancel",M.STRING],labelButtonUndoItemProcessing:["Undo",M.STRING],labelButtonRetryItemProcessing:["Retry",M.STRING],labelButtonProcessItem:["Upload",M.STRING],iconRemove:['',M.STRING],iconProcess:['',M.STRING],iconRetry:['',M.STRING],iconUndo:['',M.STRING],iconDone:['',M.STRING],oninit:[null,M.FUNCTION],onwarning:[null,M.FUNCTION],onerror:[null,M.FUNCTION],onactivatefile:[null,M.FUNCTION],oninitfile:[null,M.FUNCTION],onaddfilestart:[null,M.FUNCTION],onaddfileprogress:[null,M.FUNCTION],onaddfile:[null,M.FUNCTION],onprocessfilestart:[null,M.FUNCTION],onprocessfileprogress:[null,M.FUNCTION],onprocessfileabort:[null,M.FUNCTION],onprocessfilerevert:[null,M.FUNCTION],onprocessfile:[null,M.FUNCTION],onprocessfiles:[null,M.FUNCTION],onremovefile:[null,M.FUNCTION],onpreparefile:[null,M.FUNCTION],onupdatefiles:[null,M.FUNCTION],onreorderfiles:[null,M.FUNCTION],beforeDropFile:[null,M.FUNCTION],beforeAddFile:[null,M.FUNCTION],beforeRemoveFile:[null,M.FUNCTION],beforePrepareFile:[null,M.FUNCTION],stylePanelLayout:[null,M.STRING],stylePanelAspectRatio:[null,M.STRING],styleItemPanelAspectRatio:[null,M.STRING],styleButtonRemoveItemPosition:["left",M.STRING],styleButtonProcessItemPosition:["right",M.STRING],styleLoadIndicatorPosition:["right",M.STRING],styleProgressIndicatorPosition:["right",M.STRING],styleButtonRemoveItemAlign:[!1,M.BOOLEAN],files:[[],M.ARRAY],credits:[["https://pqina.nl/","Powered by PQINA"],M.ARRAY]},Ke=(e,t)=>ke(t)?e[0]||null:bt(t)?e[t]||null:(typeof t=="object"&&(t=t.id),e.find(i=>i.id===t)||null),wn=e=>{if(ke(e))return e;if(/:/.test(e)){let t=e.split(":");return t[1]/t[0]}return parseFloat(e)},Pe=e=>e.filter(t=>!t.archived),Ln={EMPTY:0,IDLE:1,ERROR:2,BUSY:3,READY:4},Zt=null,ps=()=>{if(Zt===null)try{let e=new DataTransfer;e.items.add(new File(["hello world"],"This_Works.txt"));let t=document.createElement("input");t.setAttribute("type","file"),t.files=e.files,Zt=t.files.length===1}catch{Zt=!1}return Zt},ms=[U.LOAD_ERROR,U.PROCESSING_ERROR,U.PROCESSING_REVERT_ERROR],us=[U.LOADING,U.PROCESSING,U.PROCESSING_QUEUED,U.INIT],gs=[U.PROCESSING_COMPLETE],fs=e=>ms.includes(e.status),hs=e=>us.includes(e.status),bs=e=>gs.includes(e.status),Ua=e=>ce(e.options.server)&&(ce(e.options.server.process)||Xe(e.options.server.process)),Es=e=>({GET_STATUS:()=>{let t=Pe(e.items),{EMPTY:i,ERROR:a,BUSY:n,IDLE:l,READY:o}=Ln;return t.length===0?i:t.some(fs)?a:t.some(hs)?n:t.some(bs)?o:l},GET_ITEM:t=>Ke(e.items,t),GET_ACTIVE_ITEM:t=>Ke(Pe(e.items),t),GET_ACTIVE_ITEMS:()=>Pe(e.items),GET_ITEMS:()=>e.items,GET_ITEM_NAME:t=>{let i=Ke(e.items,t);return i?i.filename:null},GET_ITEM_SIZE:t=>{let i=Ke(e.items,t);return i?i.fileSize:null},GET_STYLES:()=>Object.keys(e.options).filter(t=>/^style/.test(t)).map(t=>({name:t,value:e.options[t]})),GET_PANEL_ASPECT_RATIO:()=>/circle/.test(e.options.stylePanelLayout)?1:wn(e.options.stylePanelAspectRatio),GET_ITEM_PANEL_ASPECT_RATIO:()=>e.options.styleItemPanelAspectRatio,GET_ITEMS_BY_STATUS:t=>Pe(e.items).filter(i=>i.status===t),GET_TOTAL_ITEMS:()=>Pe(e.items).length,SHOULD_UPDATE_FILE_INPUT:()=>e.options.storeAsFile&&ps()&&!Ua(e),IS_ASYNC:()=>Ua(e),GET_FILE_SIZE_LABELS:t=>({labelBytes:t("GET_LABEL_FILE_SIZE_BYTES")||void 0,labelKilobytes:t("GET_LABEL_FILE_SIZE_KILOBYTES")||void 0,labelMegabytes:t("GET_LABEL_FILE_SIZE_MEGABYTES")||void 0,labelGigabytes:t("GET_LABEL_FILE_SIZE_GIGABYTES")||void 0})}),Ts=e=>{let t=Pe(e.items).length;if(!e.options.allowMultiple)return t===0;let i=e.options.maxFiles;return i===null||tMath.max(Math.min(i,e),t),Is=(e,t,i)=>e.splice(t,0,i),vs=(e,t,i)=>ke(t)?null:typeof i>"u"?(e.push(t),t):(i=Mn(i,0,e.length),Is(e,i,t),t),Fi=e=>/^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i.test(e),Dt=e=>`${e}`.split("/").pop().split("?").shift(),ui=e=>e.split(".").pop(),xs=e=>{if(typeof e!="string")return"";let t=e.split("/").pop();return/svg/.test(t)?"svg":/zip|compressed/.test(t)?"zip":/plain/.test(t)?"txt":/msword/.test(t)?"doc":/[a-z]+/.test(t)?t==="jpeg"?"jpg":t:""},At=(e,t="")=>(t+e).slice(-t.length),An=(e=new Date)=>`${e.getFullYear()}-${At(e.getMonth()+1,"00")}-${At(e.getDate(),"00")}_${At(e.getHours(),"00")}-${At(e.getMinutes(),"00")}-${At(e.getSeconds(),"00")}`,ht=(e,t,i=null,a=null)=>{let n=typeof i=="string"?e.slice(0,e.size,i):e.slice(0,e.size,e.type);return n.lastModifiedDate=new Date,e._relativePath&&(n._relativePath=e._relativePath),ge(t)||(t=An()),t&&a===null&&ui(t)?n.name=t:(a=a||xs(n.type),n.name=t+(a?"."+a:"")),n},ys=()=>window.BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,Pn=(e,t)=>{let i=ys();if(i){let a=new i;return a.append(e),a.getBlob(t)}return new Blob([e],{type:t})},Rs=(e,t)=>{let i=new ArrayBuffer(e.length),a=new Uint8Array(i);for(let n=0;n(/^data:(.+);/.exec(e)||[])[1]||null,Ss=e=>e.split(",")[1].replace(/\s/g,""),_s=e=>atob(Ss(e)),ws=e=>{let t=zn(e),i=_s(e);return Rs(i,t)},Ls=(e,t,i)=>ht(ws(e),t,null,i),Ms=e=>{if(!/^content-disposition:/i.test(e))return null;let t=e.split(/filename=|filename\*=.+''/).splice(1).map(i=>i.trim().replace(/^["']|[;"']{0,2}$/g,"")).filter(i=>i.length);return t.length?decodeURI(t[t.length-1]):null},As=e=>{if(/content-length:/i.test(e)){let t=e.match(/[0-9]+/)[0];return t?parseInt(t,10):null}return null},Ps=e=>/x-content-transfer-id:/i.test(e)&&(e.split(":")[1]||"").trim()||null,Ki=e=>{let t={source:null,name:null,size:null},i=e.split(` -`);for(let a of i){let n=Ms(a);if(n){t.name=n;continue}let l=As(a);if(l){t.size=l;continue}let o=Ps(a);if(o){t.source=o;continue}}return t},zs=e=>{let t={source:null,complete:!1,progress:0,size:null,timestamp:null,duration:0,request:null},i=()=>t.progress,a=()=>{t.request&&t.request.abort&&t.request.abort()},n=()=>{let r=t.source;o.fire("init",r),r instanceof File?o.fire("load",r):r instanceof Blob?o.fire("load",ht(r,r.name)):Fi(r)?o.fire("load",Ls(r)):l(r)},l=r=>{if(!e){o.fire("error",{type:"error",body:"Can't load URL",code:400});return}t.timestamp=Date.now(),t.request=e(r,s=>{t.duration=Date.now()-t.timestamp,t.complete=!0,s instanceof Blob&&(s=ht(s,s.name||Dt(r))),o.fire("load",s instanceof Blob?s:s?s.body:null)},s=>{o.fire("error",typeof s=="string"?{type:"error",code:0,body:s}:s)},(s,p,c)=>{if(c&&(t.size=c),t.duration=Date.now()-t.timestamp,!s){t.progress=null;return}t.progress=p/c,o.fire("progress",t.progress)},()=>{o.fire("abort")},s=>{let p=Ki(typeof s=="string"?s:s.headers);o.fire("meta",{size:t.size||p.size,filename:p.name,source:p.source})})},o={...mi(),setSource:r=>t.source=r,getProgress:i,abort:a,load:n};return o},Wa=e=>/GET|HEAD/.test(e),Qe=(e,t,i)=>{let a={onheaders:()=>{},onprogress:()=>{},onload:()=>{},ontimeout:()=>{},onerror:()=>{},onabort:()=>{},abort:()=>{n=!0,o.abort()}},n=!1,l=!1;i={method:"POST",headers:{},withCredentials:!1,...i},t=encodeURI(t),Wa(i.method)&&e&&(t=`${t}${encodeURIComponent(typeof e=="string"?e:JSON.stringify(e))}`);let o=new XMLHttpRequest,r=Wa(i.method)?o:o.upload;return r.onprogress=s=>{n||a.onprogress(s.lengthComputable,s.loaded,s.total)},o.onreadystatechange=()=>{o.readyState<2||o.readyState===4&&o.status===0||l||(l=!0,a.onheaders(o))},o.onload=()=>{o.status>=200&&o.status<300?a.onload(o):a.onerror(o)},o.onerror=()=>a.onerror(o),o.onabort=()=>{n=!0,a.onabort()},o.ontimeout=()=>a.ontimeout(o),o.open(i.method,t,!0),bt(i.timeout)&&(o.timeout=i.timeout),Object.keys(i.headers).forEach(s=>{let p=unescape(encodeURIComponent(i.headers[s]));o.setRequestHeader(s,p)}),i.responseType&&(o.responseType=i.responseType),i.withCredentials&&(o.withCredentials=!0),o.send(e),a},ie=(e,t,i,a)=>({type:e,code:t,body:i,headers:a}),Ze=e=>t=>{e(ie("error",0,"Timeout",t.getAllResponseHeaders()))},Ha=e=>/\?/.test(e),Ot=(...e)=>{let t="";return e.forEach(i=>{t+=Ha(t)&&Ha(i)?i.replace(/\?/,"&"):i}),t},wi=(e="",t)=>{if(typeof t=="function")return t;if(!t||!ge(t.url))return null;let i=t.onload||(n=>n),a=t.onerror||(n=>null);return(n,l,o,r,s,p)=>{let c=Qe(n,Ot(e,t.url),{...t,responseType:"blob"});return c.onload=d=>{let m=d.getAllResponseHeaders(),u=Ki(m).name||Dt(n);l(ie("load",d.status,t.method==="HEAD"?null:ht(i(d.response),u),m))},c.onerror=d=>{o(ie("error",d.status,a(d.response)||d.statusText,d.getAllResponseHeaders()))},c.onheaders=d=>{p(ie("headers",d.status,null,d.getAllResponseHeaders()))},c.ontimeout=Ze(o),c.onprogress=r,c.onabort=s,c}},xe={QUEUED:0,COMPLETE:1,PROCESSING:2,ERROR:3,WAITING:4},Os=(e,t,i,a,n,l,o,r,s,p,c)=>{let d=[],{chunkTransferId:m,chunkServer:u,chunkSize:g,chunkRetryDelays:f}=c,h={serverId:m,aborted:!1},I=t.ondata||(w=>w),b=t.onload||((w,O)=>O==="HEAD"?w.getResponseHeader("Upload-Offset"):w.response),T=t.onerror||(w=>null),v=w=>{let O=new FormData;ce(n)&&O.append(i,JSON.stringify(n));let S=typeof t.headers=="function"?t.headers(a,n):{...t.headers,"Upload-Length":a.size},L={...t,headers:S},D=Qe(I(O),Ot(e,t.url),L);D.onload=F=>w(b(F,L.method)),D.onerror=F=>o(ie("error",F.status,T(F.response)||F.statusText,F.getAllResponseHeaders())),D.ontimeout=Ze(o)},y=w=>{let O=Ot(e,u.url,h.serverId),L={headers:typeof t.headers=="function"?t.headers(h.serverId):{...t.headers},method:"HEAD"},D=Qe(null,O,L);D.onload=F=>w(b(F,L.method)),D.onerror=F=>o(ie("error",F.status,T(F.response)||F.statusText,F.getAllResponseHeaders())),D.ontimeout=Ze(o)},E=Math.floor(a.size/g);for(let w=0;w<=E;w++){let O=w*g,S=a.slice(O,O+g,"application/offset+octet-stream");d[w]={index:w,size:S.size,offset:O,data:S,file:a,progress:0,retries:[...f],status:xe.QUEUED,error:null,request:null,timeout:null}}let _=()=>l(h.serverId),x=w=>w.status===xe.QUEUED||w.status===xe.ERROR,R=w=>{if(h.aborted)return;if(w=w||d.find(x),!w){d.every(C=>C.status===xe.COMPLETE)&&_();return}w.status=xe.PROCESSING,w.progress=null;let O=u.ondata||(C=>C),S=u.onerror||(C=>null),L=u.onload||(()=>{}),D=Ot(e,u.url,h.serverId),F=typeof u.headers=="function"?u.headers(w):{...u.headers,"Content-Type":"application/offset+octet-stream","Upload-Offset":w.offset,"Upload-Length":a.size,"Upload-Name":a.name},G=w.request=Qe(O(w.data),D,{...u,headers:F});G.onload=C=>{L(C,w.index,d.length),w.status=xe.COMPLETE,w.request=null,A()},G.onprogress=(C,q,X)=>{w.progress=C?q:null,P()},G.onerror=C=>{w.status=xe.ERROR,w.request=null,w.error=S(C.response)||C.statusText,z(w)||o(ie("error",C.status,S(C.response)||C.statusText,C.getAllResponseHeaders()))},G.ontimeout=C=>{w.status=xe.ERROR,w.request=null,z(w)||Ze(o)(C)},G.onabort=()=>{w.status=xe.QUEUED,w.request=null,s()}},z=w=>w.retries.length===0?!1:(w.status=xe.WAITING,clearTimeout(w.timeout),w.timeout=setTimeout(()=>{R(w)},w.retries.shift()),!0),P=()=>{let w=d.reduce((S,L)=>S===null||L.progress===null?null:S+L.progress,0);if(w===null)return r(!1,0,0);let O=d.reduce((S,L)=>S+L.size,0);r(!0,w,O)},A=()=>{d.filter(O=>O.status===xe.PROCESSING).length>=1||R()},B=()=>{d.forEach(w=>{clearTimeout(w.timeout),w.request&&w.request.abort()})};return h.serverId?y(w=>{h.aborted||(d.filter(O=>O.offset{O.status=xe.COMPLETE,O.progress=O.size}),A())}):v(w=>{h.aborted||(p(w),h.serverId=w,A())}),{abort:()=>{h.aborted=!0,B()}}},Fs=(e,t,i,a)=>(n,l,o,r,s,p,c)=>{if(!n)return;let d=a.chunkUploads,m=d&&n.size>a.chunkSize,u=d&&(m||a.chunkForce);if(n instanceof Blob&&u)return Os(e,t,i,n,l,o,r,s,p,c,a);let g=t.ondata||(y=>y),f=t.onload||(y=>y),h=t.onerror||(y=>null),I=typeof t.headers=="function"?t.headers(n,l)||{}:{...t.headers},b={...t,headers:I};var T=new FormData;ce(l)&&T.append(i,JSON.stringify(l)),(n instanceof Blob?[{name:null,file:n}]:n).forEach(y=>{T.append(i,y.file,y.name===null?y.file.name:`${y.name}${y.file.name}`)});let v=Qe(g(T),Ot(e,t.url),b);return v.onload=y=>{o(ie("load",y.status,f(y.response),y.getAllResponseHeaders()))},v.onerror=y=>{r(ie("error",y.status,h(y.response)||y.statusText,y.getAllResponseHeaders()))},v.ontimeout=Ze(r),v.onprogress=s,v.onabort=p,v},Ds=(e="",t,i,a)=>typeof t=="function"?(...n)=>t(i,...n,a):!t||!ge(t.url)?null:Fs(e,t,i,a),Pt=(e="",t)=>{if(typeof t=="function")return t;if(!t||!ge(t.url))return(n,l)=>l();let i=t.onload||(n=>n),a=t.onerror||(n=>null);return(n,l,o)=>{let r=Qe(n,e+t.url,t);return r.onload=s=>{l(ie("load",s.status,i(s.response),s.getAllResponseHeaders()))},r.onerror=s=>{o(ie("error",s.status,a(s.response)||s.statusText,s.getAllResponseHeaders()))},r.ontimeout=Ze(o),r}},On=(e=0,t=1)=>e+Math.random()*(t-e),Cs=(e,t=1e3,i=0,a=25,n=250)=>{let l=null,o=Date.now(),r=()=>{let s=Date.now()-o,p=On(a,n);s+p>t&&(p=s+p-t);let c=s/t;if(c>=1||document.hidden){e(1);return}e(c),l=setTimeout(r,p)};return t>0&&r(),{clear:()=>{clearTimeout(l)}}},Bs=(e,t)=>{let i={complete:!1,perceivedProgress:0,perceivedPerformanceUpdater:null,progress:null,timestamp:null,perceivedDuration:0,duration:0,request:null,response:null},{allowMinimumUploadDuration:a}=t,n=(c,d)=>{let m=()=>{i.duration===0||i.progress===null||p.fire("progress",p.getProgress())},u=()=>{i.complete=!0,p.fire("load-perceived",i.response.body)};p.fire("start"),i.timestamp=Date.now(),i.perceivedPerformanceUpdater=Cs(g=>{i.perceivedProgress=g,i.perceivedDuration=Date.now()-i.timestamp,m(),i.response&&i.perceivedProgress===1&&!i.complete&&u()},a?On(750,1500):0),i.request=e(c,d,g=>{i.response=ce(g)?g:{type:"load",code:200,body:`${g}`,headers:{}},i.duration=Date.now()-i.timestamp,i.progress=1,p.fire("load",i.response.body),(!a||a&&i.perceivedProgress===1)&&u()},g=>{i.perceivedPerformanceUpdater.clear(),p.fire("error",ce(g)?g:{type:"error",code:0,body:`${g}`})},(g,f,h)=>{i.duration=Date.now()-i.timestamp,i.progress=g?f/h:null,m()},()=>{i.perceivedPerformanceUpdater.clear(),p.fire("abort",i.response?i.response.body:null)},g=>{p.fire("transfer",g)})},l=()=>{i.request&&(i.perceivedPerformanceUpdater.clear(),i.request.abort&&i.request.abort(),i.complete=!0)},o=()=>{l(),i.complete=!1,i.perceivedProgress=0,i.progress=0,i.timestamp=null,i.perceivedDuration=0,i.duration=0,i.request=null,i.response=null},r=a?()=>i.progress?Math.min(i.progress,i.perceivedProgress):null:()=>i.progress||null,s=a?()=>Math.min(i.duration,i.perceivedDuration):()=>i.duration,p={...mi(),process:n,abort:l,getProgress:r,getDuration:s,reset:o};return p},Fn=e=>e.substring(0,e.lastIndexOf("."))||e,Ns=e=>{let t=[e.name,e.size,e.type];return e instanceof Blob||Fi(e)?t[0]=e.name||An():Fi(e)?(t[1]=e.length,t[2]=zn(e)):ge(e)&&(t[0]=Dt(e),t[1]=0,t[2]="application/octet-stream"),{name:t[0],size:t[1],type:t[2]}},Je=e=>!!(e instanceof File||e instanceof Blob&&e.name),Dn=e=>{if(!ce(e))return e;let t=ci(e)?[]:{};for(let i in e){if(!e.hasOwnProperty(i))continue;let a=e[i];t[i]=a&&ce(a)?Dn(a):a}return t},ks=(e=null,t=null,i=null)=>{let a=qi(),n={archived:!1,frozen:!1,released:!1,source:null,file:i,serverFileReference:t,transferId:null,processingAborted:!1,status:t?U.PROCESSING_COMPLETE:U.INIT,activeLoader:null,activeProcessor:null},l=null,o={},r=x=>n.status=x,s=(x,...R)=>{n.released||n.frozen||E.fire(x,...R)},p=()=>ui(n.file.name),c=()=>n.file.type,d=()=>n.file.size,m=()=>n.file,u=(x,R,z)=>{if(n.source=x,E.fireSync("init"),n.file){E.fireSync("load-skip");return}n.file=Ns(x),R.on("init",()=>{s("load-init")}),R.on("meta",P=>{n.file.size=P.size,n.file.filename=P.filename,P.source&&(e=re.LIMBO,n.serverFileReference=P.source,n.status=U.PROCESSING_COMPLETE),s("load-meta")}),R.on("progress",P=>{r(U.LOADING),s("load-progress",P)}),R.on("error",P=>{r(U.LOAD_ERROR),s("load-request-error",P)}),R.on("abort",()=>{r(U.INIT),s("load-abort")}),R.on("load",P=>{n.activeLoader=null;let A=w=>{n.file=Je(w)?w:n.file,e===re.LIMBO&&n.serverFileReference?r(U.PROCESSING_COMPLETE):r(U.IDLE),s("load")},B=w=>{n.file=P,s("load-meta"),r(U.LOAD_ERROR),s("load-file-error",w)};if(n.serverFileReference){A(P);return}z(P,A,B)}),R.setSource(x),n.activeLoader=R,R.load()},g=()=>{n.activeLoader&&n.activeLoader.load()},f=()=>{if(n.activeLoader){n.activeLoader.abort();return}r(U.INIT),s("load-abort")},h=(x,R)=>{if(n.processingAborted){n.processingAborted=!1;return}if(r(U.PROCESSING),l=null,!(n.file instanceof Blob)){E.on("load",()=>{h(x,R)});return}x.on("load",A=>{n.transferId=null,n.serverFileReference=A}),x.on("transfer",A=>{n.transferId=A}),x.on("load-perceived",A=>{n.activeProcessor=null,n.transferId=null,n.serverFileReference=A,r(U.PROCESSING_COMPLETE),s("process-complete",A)}),x.on("start",()=>{s("process-start")}),x.on("error",A=>{n.activeProcessor=null,r(U.PROCESSING_ERROR),s("process-error",A)}),x.on("abort",A=>{n.activeProcessor=null,n.serverFileReference=A,r(U.IDLE),s("process-abort"),l&&l()}),x.on("progress",A=>{s("process-progress",A)});let z=A=>{n.archived||x.process(A,{...o})},P=console.error;R(n.file,z,P),n.activeProcessor=x},I=()=>{n.processingAborted=!1,r(U.PROCESSING_QUEUED)},b=()=>new Promise(x=>{if(!n.activeProcessor){n.processingAborted=!0,r(U.IDLE),s("process-abort"),x();return}l=()=>{x()},n.activeProcessor.abort()}),T=(x,R)=>new Promise((z,P)=>{let A=n.serverFileReference!==null?n.serverFileReference:n.transferId;if(A===null){z();return}x(A,()=>{n.serverFileReference=null,n.transferId=null,z()},B=>{if(!R){z();return}r(U.PROCESSING_REVERT_ERROR),s("process-revert-error"),P(B)}),r(U.IDLE),s("process-revert")}),v=(x,R,z)=>{let P=x.split("."),A=P[0],B=P.pop(),w=o;P.forEach(O=>w=w[O]),JSON.stringify(w[B])!==JSON.stringify(R)&&(w[B]=R,s("metadata-update",{key:A,value:o[A],silent:z}))},E={id:{get:()=>a},origin:{get:()=>e,set:x=>e=x},serverId:{get:()=>n.serverFileReference},transferId:{get:()=>n.transferId},status:{get:()=>n.status},filename:{get:()=>n.file.name},filenameWithoutExtension:{get:()=>Fn(n.file.name)},fileExtension:{get:p},fileType:{get:c},fileSize:{get:d},file:{get:m},relativePath:{get:()=>n.file._relativePath},source:{get:()=>n.source},getMetadata:x=>Dn(x?o[x]:o),setMetadata:(x,R,z)=>{if(ce(x)){let P=x;return Object.keys(P).forEach(A=>{v(A,P[A],R)}),x}return v(x,R,z),R},extend:(x,R)=>_[x]=R,abortLoad:f,retryLoad:g,requestProcessing:I,abortProcessing:b,load:u,process:h,revert:T,...mi(),freeze:()=>n.frozen=!0,release:()=>n.released=!0,released:{get:()=>n.released},archive:()=>n.archived=!0,archived:{get:()=>n.archived},setFile:x=>n.file=x},_=We(E);return _},Vs=(e,t)=>ke(t)?0:ge(t)?e.findIndex(i=>i.id===t):-1,ja=(e,t)=>{let i=Vs(e,t);if(!(i<0))return e[i]||null},Ya=(e,t,i,a,n,l)=>{let o=Qe(null,e,{method:"GET",responseType:"blob"});return o.onload=r=>{let s=r.getAllResponseHeaders(),p=Ki(s).name||Dt(e);t(ie("load",r.status,ht(r.response,p),s))},o.onerror=r=>{i(ie("error",r.status,r.statusText,r.getAllResponseHeaders()))},o.onheaders=r=>{l(ie("headers",r.status,null,r.getAllResponseHeaders()))},o.ontimeout=Ze(i),o.onprogress=a,o.onabort=n,o},qa=e=>(e.indexOf("//")===0&&(e=location.protocol+e),e.toLowerCase().replace("blob:","").replace(/([a-z])?:\/\//,"$1").split("/")[0]),Gs=e=>(e.indexOf(":")>-1||e.indexOf("//")>-1)&&qa(location.href)!==qa(e),Jt=e=>(...t)=>Xe(e)?e(...t):e,Us=e=>!Je(e.file),Li=(e,t)=>{clearTimeout(t.listUpdateTimeout),t.listUpdateTimeout=setTimeout(()=>{e("DID_UPDATE_ITEMS",{items:Pe(t.items)})},0)},$a=(e,...t)=>new Promise(i=>{if(!e)return i(!0);let a=e(...t);if(a==null)return i(!0);if(typeof a=="boolean")return i(a);typeof a.then=="function"&&a.then(i)}),Mi=(e,t)=>{e.items.sort((i,a)=>t(he(i),he(a)))},ye=(e,t)=>({query:i,success:a=()=>{},failure:n=()=>{},...l}={})=>{let o=Ke(e.items,i);if(!o){n({error:ie("error",0,"Item not found"),file:null});return}t(o,a,n,l||{})},Ws=(e,t,i)=>({ABORT_ALL:()=>{Pe(i.items).forEach(a=>{a.freeze(),a.abortLoad(),a.abortProcessing()})},DID_SET_FILES:({value:a=[]})=>{let n=a.map(o=>({source:o.source?o.source:o,options:o.options})),l=Pe(i.items);l.forEach(o=>{n.find(r=>r.source===o.source||r.source===o.file)||e("REMOVE_ITEM",{query:o,remove:!1})}),l=Pe(i.items),n.forEach((o,r)=>{l.find(s=>s.source===o.source||s.file===o.source)||e("ADD_ITEM",{...o,interactionMethod:Re.NONE,index:r})})},DID_UPDATE_ITEM_METADATA:({id:a,action:n,change:l})=>{l.silent||(clearTimeout(i.itemUpdateTimeout),i.itemUpdateTimeout=setTimeout(()=>{let o=ja(i.items,a);if(!t("IS_ASYNC")){Ae("SHOULD_PREPARE_OUTPUT",!1,{item:o,query:t,action:n,change:l}).then(c=>{let d=t("GET_BEFORE_PREPARE_FILE");d&&(c=d(o,c)),c&&e("REQUEST_PREPARE_OUTPUT",{query:a,item:o,success:m=>{e("DID_PREPARE_OUTPUT",{id:a,file:m})}},!0)});return}o.origin===re.LOCAL&&e("DID_LOAD_ITEM",{id:o.id,error:null,serverFileReference:o.source});let r=()=>{setTimeout(()=>{e("REQUEST_ITEM_PROCESSING",{query:a})},32)},s=c=>{o.revert(Pt(i.options.server.url,i.options.server.revert),t("GET_FORCE_REVERT")).then(c?r:()=>{}).catch(()=>{})},p=c=>{o.abortProcessing().then(c?r:()=>{})};if(o.status===U.PROCESSING_COMPLETE)return s(i.options.instantUpload);if(o.status===U.PROCESSING)return p(i.options.instantUpload);i.options.instantUpload&&r()},0))},MOVE_ITEM:({query:a,index:n})=>{let l=Ke(i.items,a);if(!l)return;let o=i.items.indexOf(l);n=Mn(n,0,i.items.length-1),o!==n&&i.items.splice(n,0,i.items.splice(o,1)[0])},SORT:({compare:a})=>{Mi(i,a),e("DID_SORT_ITEMS",{items:t("GET_ACTIVE_ITEMS")})},ADD_ITEMS:({items:a,index:n,interactionMethod:l,success:o=()=>{},failure:r=()=>{}})=>{let s=n;if(n===-1||typeof n>"u"){let u=t("GET_ITEM_INSERT_LOCATION"),g=t("GET_TOTAL_ITEMS");s=u==="before"?0:g}let p=t("GET_IGNORED_FILES"),c=u=>Je(u)?!p.includes(u.name.toLowerCase()):!ke(u),m=a.filter(c).map(u=>new Promise((g,f)=>{e("ADD_ITEM",{interactionMethod:l,source:u.source||u,success:g,failure:f,index:s++,options:u.options||{}})}));Promise.all(m).then(o).catch(r)},ADD_ITEM:({source:a,index:n=-1,interactionMethod:l,success:o=()=>{},failure:r=()=>{},options:s={}})=>{if(ke(a)){r({error:ie("error",0,"No source"),file:null});return}if(Je(a)&&i.options.ignoredFiles.includes(a.name.toLowerCase()))return;if(!Ts(i)){if(i.options.allowMultiple||!i.options.allowMultiple&&!i.options.allowReplace){let b=ie("warning",0,"Max files");e("DID_THROW_MAX_FILES",{source:a,error:b}),r({error:b,file:null});return}let I=Pe(i.items)[0];if(I.status===U.PROCESSING_COMPLETE||I.status===U.PROCESSING_REVERT_ERROR){let b=t("GET_FORCE_REVERT");if(I.revert(Pt(i.options.server.url,i.options.server.revert),b).then(()=>{b&&e("ADD_ITEM",{source:a,index:n,interactionMethod:l,success:o,failure:r,options:s})}).catch(()=>{}),b)return}e("REMOVE_ITEM",{query:I.id})}let p=s.type==="local"?re.LOCAL:s.type==="limbo"?re.LIMBO:re.INPUT,c=ks(p,p===re.INPUT?null:a,s.file);Object.keys(s.metadata||{}).forEach(I=>{c.setMetadata(I,s.metadata[I])}),tt("DID_CREATE_ITEM",c,{query:t,dispatch:e});let d=t("GET_ITEM_INSERT_LOCATION");i.options.itemInsertLocationFreedom||(n=d==="before"?-1:i.items.length),vs(i.items,c,n),Xe(d)&&a&&Mi(i,d);let m=c.id;c.on("init",()=>{e("DID_INIT_ITEM",{id:m})}),c.on("load-init",()=>{e("DID_START_ITEM_LOAD",{id:m})}),c.on("load-meta",()=>{e("DID_UPDATE_ITEM_META",{id:m})}),c.on("load-progress",I=>{e("DID_UPDATE_ITEM_LOAD_PROGRESS",{id:m,progress:I})}),c.on("load-request-error",I=>{let b=Jt(i.options.labelFileLoadError)(I);if(I.code>=400&&I.code<500){e("DID_THROW_ITEM_INVALID",{id:m,error:I,status:{main:b,sub:`${I.code} (${I.body})`}}),r({error:I,file:he(c)});return}e("DID_THROW_ITEM_LOAD_ERROR",{id:m,error:I,status:{main:b,sub:i.options.labelTapToRetry}})}),c.on("load-file-error",I=>{e("DID_THROW_ITEM_INVALID",{id:m,error:I.status,status:I.status}),r({error:I.status,file:he(c)})}),c.on("load-abort",()=>{e("REMOVE_ITEM",{query:m})}),c.on("load-skip",()=>{c.on("metadata-update",I=>{Je(c.file)&&e("DID_UPDATE_ITEM_METADATA",{id:m,change:I})}),e("COMPLETE_LOAD_ITEM",{query:m,item:c,data:{source:a,success:o}})}),c.on("load",()=>{let I=b=>{if(!b){e("REMOVE_ITEM",{query:m});return}c.on("metadata-update",T=>{e("DID_UPDATE_ITEM_METADATA",{id:m,change:T})}),Ae("SHOULD_PREPARE_OUTPUT",!1,{item:c,query:t}).then(T=>{let v=t("GET_BEFORE_PREPARE_FILE");v&&(T=v(c,T));let y=()=>{e("COMPLETE_LOAD_ITEM",{query:m,item:c,data:{source:a,success:o}}),Li(e,i)};if(T){e("REQUEST_PREPARE_OUTPUT",{query:m,item:c,success:E=>{e("DID_PREPARE_OUTPUT",{id:m,file:E}),y()}},!0);return}y()})};Ae("DID_LOAD_ITEM",c,{query:t,dispatch:e}).then(()=>{$a(t("GET_BEFORE_ADD_FILE"),he(c)).then(I)}).catch(b=>{if(!b||!b.error||!b.status)return I(!1);e("DID_THROW_ITEM_INVALID",{id:m,error:b.error,status:b.status})})}),c.on("process-start",()=>{e("DID_START_ITEM_PROCESSING",{id:m})}),c.on("process-progress",I=>{e("DID_UPDATE_ITEM_PROCESS_PROGRESS",{id:m,progress:I})}),c.on("process-error",I=>{e("DID_THROW_ITEM_PROCESSING_ERROR",{id:m,error:I,status:{main:Jt(i.options.labelFileProcessingError)(I),sub:i.options.labelTapToRetry}})}),c.on("process-revert-error",I=>{e("DID_THROW_ITEM_PROCESSING_REVERT_ERROR",{id:m,error:I,status:{main:Jt(i.options.labelFileProcessingRevertError)(I),sub:i.options.labelTapToRetry}})}),c.on("process-complete",I=>{e("DID_COMPLETE_ITEM_PROCESSING",{id:m,error:null,serverFileReference:I}),e("DID_DEFINE_VALUE",{id:m,value:I})}),c.on("process-abort",()=>{e("DID_ABORT_ITEM_PROCESSING",{id:m})}),c.on("process-revert",()=>{e("DID_REVERT_ITEM_PROCESSING",{id:m}),e("DID_DEFINE_VALUE",{id:m,value:null})}),e("DID_ADD_ITEM",{id:m,index:n,interactionMethod:l}),Li(e,i);let{url:u,load:g,restore:f,fetch:h}=i.options.server||{};c.load(a,zs(p===re.INPUT?ge(a)&&Gs(a)&&h?wi(u,h):Ya:p===re.LIMBO?wi(u,f):wi(u,g)),(I,b,T)=>{Ae("LOAD_FILE",I,{query:t}).then(b).catch(T)})},REQUEST_PREPARE_OUTPUT:({item:a,success:n,failure:l=()=>{}})=>{let o={error:ie("error",0,"Item not found"),file:null};if(a.archived)return l(o);Ae("PREPARE_OUTPUT",a.file,{query:t,item:a}).then(r=>{Ae("COMPLETE_PREPARE_OUTPUT",r,{query:t,item:a}).then(s=>{if(a.archived)return l(o);n(s)})})},COMPLETE_LOAD_ITEM:({item:a,data:n})=>{let{success:l,source:o}=n,r=t("GET_ITEM_INSERT_LOCATION");if(Xe(r)&&o&&Mi(i,r),e("DID_LOAD_ITEM",{id:a.id,error:null,serverFileReference:a.origin===re.INPUT?null:o}),l(he(a)),a.origin===re.LOCAL){e("DID_LOAD_LOCAL_ITEM",{id:a.id});return}if(a.origin===re.LIMBO){e("DID_COMPLETE_ITEM_PROCESSING",{id:a.id,error:null,serverFileReference:o}),e("DID_DEFINE_VALUE",{id:a.id,value:a.serverId||o});return}t("IS_ASYNC")&&i.options.instantUpload&&e("REQUEST_ITEM_PROCESSING",{query:a.id})},RETRY_ITEM_LOAD:ye(i,a=>{a.retryLoad()}),REQUEST_ITEM_PREPARE:ye(i,(a,n,l)=>{e("REQUEST_PREPARE_OUTPUT",{query:a.id,item:a,success:o=>{e("DID_PREPARE_OUTPUT",{id:a.id,file:o}),n({file:a,output:o})},failure:l},!0)}),REQUEST_ITEM_PROCESSING:ye(i,(a,n,l)=>{if(!(a.status===U.IDLE||a.status===U.PROCESSING_ERROR)){let r=()=>e("REQUEST_ITEM_PROCESSING",{query:a,success:n,failure:l}),s=()=>document.hidden?r():setTimeout(r,32);a.status===U.PROCESSING_COMPLETE||a.status===U.PROCESSING_REVERT_ERROR?a.revert(Pt(i.options.server.url,i.options.server.revert),t("GET_FORCE_REVERT")).then(s).catch(()=>{}):a.status===U.PROCESSING&&a.abortProcessing().then(s);return}a.status!==U.PROCESSING_QUEUED&&(a.requestProcessing(),e("DID_REQUEST_ITEM_PROCESSING",{id:a.id}),e("PROCESS_ITEM",{query:a,success:n,failure:l},!0))}),PROCESS_ITEM:ye(i,(a,n,l)=>{let o=t("GET_MAX_PARALLEL_UPLOADS");if(t("GET_ITEMS_BY_STATUS",U.PROCESSING).length===o){i.processingQueue.push({id:a.id,success:n,failure:l});return}if(a.status===U.PROCESSING)return;let s=()=>{let c=i.processingQueue.shift();if(!c)return;let{id:d,success:m,failure:u}=c,g=Ke(i.items,d);if(!g||g.archived){s();return}e("PROCESS_ITEM",{query:d,success:m,failure:u},!0)};a.onOnce("process-complete",()=>{n(he(a)),s();let c=i.options.server;if(i.options.instantUpload&&a.origin===re.LOCAL&&Xe(c.remove)){let u=()=>{};a.origin=re.LIMBO,i.options.server.remove(a.source,u,u)}t("GET_ITEMS_BY_STATUS",U.PROCESSING_COMPLETE).length===i.items.length&&e("DID_COMPLETE_ITEM_PROCESSING_ALL")}),a.onOnce("process-error",c=>{l({error:c,file:he(a)}),s()});let p=i.options;a.process(Bs(Ds(p.server.url,p.server.process,p.name,{chunkTransferId:a.transferId,chunkServer:p.server.patch,chunkUploads:p.chunkUploads,chunkForce:p.chunkForce,chunkSize:p.chunkSize,chunkRetryDelays:p.chunkRetryDelays}),{allowMinimumUploadDuration:t("GET_ALLOW_MINIMUM_UPLOAD_DURATION")}),(c,d,m)=>{Ae("PREPARE_OUTPUT",c,{query:t,item:a}).then(u=>{e("DID_PREPARE_OUTPUT",{id:a.id,file:u}),d(u)}).catch(m)})}),RETRY_ITEM_PROCESSING:ye(i,a=>{e("REQUEST_ITEM_PROCESSING",{query:a})}),REQUEST_REMOVE_ITEM:ye(i,a=>{$a(t("GET_BEFORE_REMOVE_FILE"),he(a)).then(n=>{n&&e("REMOVE_ITEM",{query:a})})}),RELEASE_ITEM:ye(i,a=>{a.release()}),REMOVE_ITEM:ye(i,(a,n,l,o)=>{let r=()=>{let p=a.id;ja(i.items,p).archive(),e("DID_REMOVE_ITEM",{error:null,id:p,item:a}),Li(e,i),n(he(a))},s=i.options.server;a.origin===re.LOCAL&&s&&Xe(s.remove)&&o.remove!==!1?(e("DID_START_ITEM_REMOVE",{id:a.id}),s.remove(a.source,()=>r(),p=>{e("DID_THROW_ITEM_REMOVE_ERROR",{id:a.id,error:ie("error",0,p,null),status:{main:Jt(i.options.labelFileRemoveError)(p),sub:i.options.labelTapToRetry}})})):((o.revert&&a.origin!==re.LOCAL&&a.serverId!==null||i.options.chunkUploads&&a.file.size>i.options.chunkSize||i.options.chunkUploads&&i.options.chunkForce)&&a.revert(Pt(i.options.server.url,i.options.server.revert),t("GET_FORCE_REVERT")),r())}),ABORT_ITEM_LOAD:ye(i,a=>{a.abortLoad()}),ABORT_ITEM_PROCESSING:ye(i,a=>{if(a.serverId){e("REVERT_ITEM_PROCESSING",{id:a.id});return}a.abortProcessing().then(()=>{i.options.instantUpload&&e("REMOVE_ITEM",{query:a.id})})}),REQUEST_REVERT_ITEM_PROCESSING:ye(i,a=>{if(!i.options.instantUpload){e("REVERT_ITEM_PROCESSING",{query:a});return}let n=r=>{r&&e("REVERT_ITEM_PROCESSING",{query:a})},l=t("GET_BEFORE_REMOVE_FILE");if(!l)return n(!0);let o=l(he(a));if(o==null)return n(!0);if(typeof o=="boolean")return n(o);typeof o.then=="function"&&o.then(n)}),REVERT_ITEM_PROCESSING:ye(i,a=>{a.revert(Pt(i.options.server.url,i.options.server.revert),t("GET_FORCE_REVERT")).then(()=>{(i.options.instantUpload||Us(a))&&e("REMOVE_ITEM",{query:a.id})}).catch(()=>{})}),SET_OPTIONS:({options:a})=>{let n=Object.keys(a),l=Hs.filter(r=>n.includes(r));[...l,...Object.keys(a).filter(r=>!l.includes(r))].forEach(r=>{e(`SET_${pi(r,"_").toUpperCase()}`,{value:a[r]})})}}),Hs=["server"],Qi=e=>e,Ve=e=>document.createElement(e),ae=(e,t)=>{let i=e.childNodes[0];i?t!==i.nodeValue&&(i.nodeValue=t):(i=document.createTextNode(t),e.appendChild(i))},Xa=(e,t,i,a)=>{let n=(a%360-90)*Math.PI/180;return{x:e+i*Math.cos(n),y:t+i*Math.sin(n)}},js=(e,t,i,a,n,l)=>{let o=Xa(e,t,i,n),r=Xa(e,t,i,a);return["M",o.x,o.y,"A",i,i,0,l,0,r.x,r.y].join(" ")},Ys=(e,t,i,a,n)=>{let l=1;return n>a&&n-a<=.5&&(l=0),a>n&&a-n>=.5&&(l=0),js(e,t,i,Math.min(.9999,a)*360,Math.min(.9999,n)*360,l)},qs=({root:e,props:t})=>{t.spin=!1,t.progress=0,t.opacity=0;let i=li("svg");e.ref.path=li("path",{"stroke-width":2,"stroke-linecap":"round"}),i.appendChild(e.ref.path),e.ref.svg=i,e.appendChild(i)},$s=({root:e,props:t})=>{if(t.opacity===0)return;t.align&&(e.element.dataset.align=t.align);let i=parseInt(se(e.ref.path,"stroke-width"),10),a=e.rect.element.width*.5,n=0,l=0;t.spin?(n=0,l=.5):(n=0,l=t.progress);let o=Ys(a,a,a-i,n,l);se(e.ref.path,"d",o),se(e.ref.path,"stroke-opacity",t.spin||t.progress>0?1:0)},Ka=ne({tag:"div",name:"progress-indicator",ignoreRectUpdate:!0,ignoreRect:!0,create:qs,write:$s,mixins:{apis:["progress","spin","align"],styles:["opacity"],animations:{opacity:{type:"tween",duration:500},progress:{type:"spring",stiffness:.95,damping:.65,mass:10}}}}),Xs=({root:e,props:t})=>{e.element.innerHTML=(t.icon||"")+`${t.label}`,t.isDisabled=!1},Ks=({root:e,props:t})=>{let{isDisabled:i}=t,a=e.query("GET_DISABLED")||t.opacity===0;a&&!i?(t.isDisabled=!0,se(e.element,"disabled","disabled")):!a&&i&&(t.isDisabled=!1,e.element.removeAttribute("disabled"))},Cn=ne({tag:"button",attributes:{type:"button"},ignoreRect:!0,ignoreRectUpdate:!0,name:"file-action-button",mixins:{apis:["label"],styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}},listeners:!0},create:Xs,write:Ks}),Bn=(e,t=".",i=1e3,a={})=>{let{labelBytes:n="bytes",labelKilobytes:l="KB",labelMegabytes:o="MB",labelGigabytes:r="GB"}=a;e=Math.round(Math.abs(e));let s=i,p=i*i,c=i*i*i;return ee.toFixed(t).split(".").filter(a=>a!=="0").join(i),Qs=({root:e,props:t})=>{let i=Ve("span");i.className="filepond--file-info-main",se(i,"aria-hidden","true"),e.appendChild(i),e.ref.fileName=i;let a=Ve("span");a.className="filepond--file-info-sub",e.appendChild(a),e.ref.fileSize=a,ae(a,e.query("GET_LABEL_FILE_WAITING_FOR_SIZE")),ae(i,Qi(e.query("GET_ITEM_NAME",t.id)))},Di=({root:e,props:t})=>{ae(e.ref.fileSize,Bn(e.query("GET_ITEM_SIZE",t.id),".",e.query("GET_FILE_SIZE_BASE"),e.query("GET_FILE_SIZE_LABELS",e.query))),ae(e.ref.fileName,Qi(e.query("GET_ITEM_NAME",t.id)))},Za=({root:e,props:t})=>{if(bt(e.query("GET_ITEM_SIZE",t.id))){Di({root:e,props:t});return}ae(e.ref.fileSize,e.query("GET_LABEL_FILE_SIZE_NOT_AVAILABLE"))},Zs=ne({name:"file-info",ignoreRect:!0,ignoreRectUpdate:!0,write:fe({DID_LOAD_ITEM:Di,DID_UPDATE_ITEM_META:Di,DID_THROW_ITEM_LOAD_ERROR:Za,DID_THROW_ITEM_INVALID:Za}),didCreateView:e=>{tt("CREATE_VIEW",{...e,view:e})},create:Qs,mixins:{styles:["translateX","translateY"],animations:{translateX:"spring",translateY:"spring"}}}),Nn=e=>Math.round(e*100),Js=({root:e})=>{let t=Ve("span");t.className="filepond--file-status-main",e.appendChild(t),e.ref.main=t;let i=Ve("span");i.className="filepond--file-status-sub",e.appendChild(i),e.ref.sub=i,kn({root:e,action:{progress:null}})},kn=({root:e,action:t})=>{let i=t.progress===null?e.query("GET_LABEL_FILE_LOADING"):`${e.query("GET_LABEL_FILE_LOADING")} ${Nn(t.progress)}%`;ae(e.ref.main,i),ae(e.ref.sub,e.query("GET_LABEL_TAP_TO_CANCEL"))},ec=({root:e,action:t})=>{let i=t.progress===null?e.query("GET_LABEL_FILE_PROCESSING"):`${e.query("GET_LABEL_FILE_PROCESSING")} ${Nn(t.progress)}%`;ae(e.ref.main,i),ae(e.ref.sub,e.query("GET_LABEL_TAP_TO_CANCEL"))},tc=({root:e})=>{ae(e.ref.main,e.query("GET_LABEL_FILE_PROCESSING")),ae(e.ref.sub,e.query("GET_LABEL_TAP_TO_CANCEL"))},ic=({root:e})=>{ae(e.ref.main,e.query("GET_LABEL_FILE_PROCESSING_ABORTED")),ae(e.ref.sub,e.query("GET_LABEL_TAP_TO_RETRY"))},ac=({root:e})=>{ae(e.ref.main,e.query("GET_LABEL_FILE_PROCESSING_COMPLETE")),ae(e.ref.sub,e.query("GET_LABEL_TAP_TO_UNDO"))},Ja=({root:e})=>{ae(e.ref.main,""),ae(e.ref.sub,"")},zt=({root:e,action:t})=>{ae(e.ref.main,t.status.main),ae(e.ref.sub,t.status.sub)},nc=ne({name:"file-status",ignoreRect:!0,ignoreRectUpdate:!0,write:fe({DID_LOAD_ITEM:Ja,DID_REVERT_ITEM_PROCESSING:Ja,DID_REQUEST_ITEM_PROCESSING:tc,DID_ABORT_ITEM_PROCESSING:ic,DID_COMPLETE_ITEM_PROCESSING:ac,DID_UPDATE_ITEM_PROCESS_PROGRESS:ec,DID_UPDATE_ITEM_LOAD_PROGRESS:kn,DID_THROW_ITEM_LOAD_ERROR:zt,DID_THROW_ITEM_INVALID:zt,DID_THROW_ITEM_PROCESSING_ERROR:zt,DID_THROW_ITEM_PROCESSING_REVERT_ERROR:zt,DID_THROW_ITEM_REMOVE_ERROR:zt}),didCreateView:e=>{tt("CREATE_VIEW",{...e,view:e})},create:Js,mixins:{styles:["translateX","translateY","opacity"],animations:{opacity:{type:"tween",duration:250},translateX:"spring",translateY:"spring"}}}),Ci={AbortItemLoad:{label:"GET_LABEL_BUTTON_ABORT_ITEM_LOAD",action:"ABORT_ITEM_LOAD",className:"filepond--action-abort-item-load",align:"LOAD_INDICATOR_POSITION"},RetryItemLoad:{label:"GET_LABEL_BUTTON_RETRY_ITEM_LOAD",action:"RETRY_ITEM_LOAD",icon:"GET_ICON_RETRY",className:"filepond--action-retry-item-load",align:"BUTTON_PROCESS_ITEM_POSITION"},RemoveItem:{label:"GET_LABEL_BUTTON_REMOVE_ITEM",action:"REQUEST_REMOVE_ITEM",icon:"GET_ICON_REMOVE",className:"filepond--action-remove-item",align:"BUTTON_REMOVE_ITEM_POSITION"},ProcessItem:{label:"GET_LABEL_BUTTON_PROCESS_ITEM",action:"REQUEST_ITEM_PROCESSING",icon:"GET_ICON_PROCESS",className:"filepond--action-process-item",align:"BUTTON_PROCESS_ITEM_POSITION"},AbortItemProcessing:{label:"GET_LABEL_BUTTON_ABORT_ITEM_PROCESSING",action:"ABORT_ITEM_PROCESSING",className:"filepond--action-abort-item-processing",align:"BUTTON_PROCESS_ITEM_POSITION"},RetryItemProcessing:{label:"GET_LABEL_BUTTON_RETRY_ITEM_PROCESSING",action:"RETRY_ITEM_PROCESSING",icon:"GET_ICON_RETRY",className:"filepond--action-retry-item-processing",align:"BUTTON_PROCESS_ITEM_POSITION"},RevertItemProcessing:{label:"GET_LABEL_BUTTON_UNDO_ITEM_PROCESSING",action:"REQUEST_REVERT_ITEM_PROCESSING",icon:"GET_ICON_UNDO",className:"filepond--action-revert-item-processing",align:"BUTTON_PROCESS_ITEM_POSITION"}},Bi=[];te(Ci,e=>{Bi.push(e)});var Ie=e=>{if(Ni(e)==="right")return 0;let t=e.ref.buttonRemoveItem.rect.element;return t.hidden?null:t.width+t.left},lc=e=>e.ref.buttonAbortItemLoad.rect.element.width,ei=e=>Math.floor(e.ref.buttonRemoveItem.rect.element.height/4),oc=e=>Math.floor(e.ref.buttonRemoveItem.rect.element.left/2),rc=e=>e.query("GET_STYLE_LOAD_INDICATOR_POSITION"),sc=e=>e.query("GET_STYLE_PROGRESS_INDICATOR_POSITION"),Ni=e=>e.query("GET_STYLE_BUTTON_REMOVE_ITEM_POSITION"),cc={buttonAbortItemLoad:{opacity:0},buttonRetryItemLoad:{opacity:0},buttonRemoveItem:{opacity:0},buttonProcessItem:{opacity:0},buttonAbortItemProcessing:{opacity:0},buttonRetryItemProcessing:{opacity:0},buttonRevertItemProcessing:{opacity:0},loadProgressIndicator:{opacity:0,align:rc},processProgressIndicator:{opacity:0,align:sc},processingCompleteIndicator:{opacity:0,scaleX:.75,scaleY:.75},info:{translateX:0,translateY:0,opacity:0},status:{translateX:0,translateY:0,opacity:0}},en={buttonRemoveItem:{opacity:1},buttonProcessItem:{opacity:1},info:{translateX:Ie},status:{translateX:Ie}},Ai={buttonAbortItemProcessing:{opacity:1},processProgressIndicator:{opacity:1},status:{opacity:1}},mt={DID_THROW_ITEM_INVALID:{buttonRemoveItem:{opacity:1},info:{translateX:Ie},status:{translateX:Ie,opacity:1}},DID_START_ITEM_LOAD:{buttonAbortItemLoad:{opacity:1},loadProgressIndicator:{opacity:1},status:{opacity:1}},DID_THROW_ITEM_LOAD_ERROR:{buttonRetryItemLoad:{opacity:1},buttonRemoveItem:{opacity:1},info:{translateX:Ie},status:{opacity:1}},DID_START_ITEM_REMOVE:{processProgressIndicator:{opacity:1,align:Ni},info:{translateX:Ie},status:{opacity:0}},DID_THROW_ITEM_REMOVE_ERROR:{processProgressIndicator:{opacity:0,align:Ni},buttonRemoveItem:{opacity:1},info:{translateX:Ie},status:{opacity:1,translateX:Ie}},DID_LOAD_ITEM:en,DID_LOAD_LOCAL_ITEM:{buttonRemoveItem:{opacity:1},info:{translateX:Ie},status:{translateX:Ie}},DID_START_ITEM_PROCESSING:Ai,DID_REQUEST_ITEM_PROCESSING:Ai,DID_UPDATE_ITEM_PROCESS_PROGRESS:Ai,DID_COMPLETE_ITEM_PROCESSING:{buttonRevertItemProcessing:{opacity:1},info:{opacity:1},status:{opacity:1}},DID_THROW_ITEM_PROCESSING_ERROR:{buttonRemoveItem:{opacity:1},buttonRetryItemProcessing:{opacity:1},status:{opacity:1},info:{translateX:Ie}},DID_THROW_ITEM_PROCESSING_REVERT_ERROR:{buttonRevertItemProcessing:{opacity:1},status:{opacity:1},info:{opacity:1}},DID_ABORT_ITEM_PROCESSING:{buttonRemoveItem:{opacity:1},buttonProcessItem:{opacity:1},info:{translateX:Ie},status:{opacity:1}},DID_REVERT_ITEM_PROCESSING:en},dc=ne({create:({root:e})=>{e.element.innerHTML=e.query("GET_ICON_DONE")},name:"processing-complete-indicator",ignoreRect:!0,mixins:{styles:["scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",opacity:{type:"tween",duration:250}}}}),pc=({root:e,props:t})=>{let i=Object.keys(Ci).reduce((g,f)=>(g[f]={...Ci[f]},g),{}),{id:a}=t,n=e.query("GET_ALLOW_REVERT"),l=e.query("GET_ALLOW_REMOVE"),o=e.query("GET_ALLOW_PROCESS"),r=e.query("GET_INSTANT_UPLOAD"),s=e.query("IS_ASYNC"),p=e.query("GET_STYLE_BUTTON_REMOVE_ITEM_ALIGN"),c;s?o&&!n?c=g=>!/RevertItemProcessing/.test(g):!o&&n?c=g=>!/ProcessItem|RetryItemProcessing|AbortItemProcessing/.test(g):!o&&!n&&(c=g=>!/Process/.test(g)):c=g=>!/Process/.test(g);let d=c?Bi.filter(c):Bi.concat();if(r&&n&&(i.RevertItemProcessing.label="GET_LABEL_BUTTON_REMOVE_ITEM",i.RevertItemProcessing.icon="GET_ICON_REMOVE"),s&&!n){let g=mt.DID_COMPLETE_ITEM_PROCESSING;g.info.translateX=oc,g.info.translateY=ei,g.status.translateY=ei,g.processingCompleteIndicator={opacity:1,scaleX:1,scaleY:1}}if(s&&!o&&(["DID_START_ITEM_PROCESSING","DID_REQUEST_ITEM_PROCESSING","DID_UPDATE_ITEM_PROCESS_PROGRESS","DID_THROW_ITEM_PROCESSING_ERROR"].forEach(g=>{mt[g].status.translateY=ei}),mt.DID_THROW_ITEM_PROCESSING_ERROR.status.translateX=lc),p&&n){i.RevertItemProcessing.align="BUTTON_REMOVE_ITEM_POSITION";let g=mt.DID_COMPLETE_ITEM_PROCESSING;g.info.translateX=Ie,g.status.translateY=ei,g.processingCompleteIndicator={opacity:1,scaleX:1,scaleY:1}}l||(i.RemoveItem.disabled=!0),te(i,(g,f)=>{let h=e.createChildView(Cn,{label:e.query(f.label),icon:e.query(f.icon),opacity:0});d.includes(g)&&e.appendChildView(h),f.disabled&&(h.element.setAttribute("disabled","disabled"),h.element.setAttribute("hidden","hidden")),h.element.dataset.align=e.query(`GET_STYLE_${f.align}`),h.element.classList.add(f.className),h.on("click",I=>{I.stopPropagation(),!f.disabled&&e.dispatch(f.action,{query:a})}),e.ref[`button${g}`]=h}),e.ref.processingCompleteIndicator=e.appendChildView(e.createChildView(dc)),e.ref.processingCompleteIndicator.element.dataset.align=e.query("GET_STYLE_BUTTON_PROCESS_ITEM_POSITION"),e.ref.info=e.appendChildView(e.createChildView(Zs,{id:a})),e.ref.status=e.appendChildView(e.createChildView(nc,{id:a}));let m=e.appendChildView(e.createChildView(Ka,{opacity:0,align:e.query("GET_STYLE_LOAD_INDICATOR_POSITION")}));m.element.classList.add("filepond--load-indicator"),e.ref.loadProgressIndicator=m;let u=e.appendChildView(e.createChildView(Ka,{opacity:0,align:e.query("GET_STYLE_PROGRESS_INDICATOR_POSITION")}));u.element.classList.add("filepond--process-indicator"),e.ref.processProgressIndicator=u,e.ref.activeStyles=[]},mc=({root:e,actions:t,props:i})=>{uc({root:e,actions:t,props:i});let a=t.concat().filter(n=>/^DID_/.test(n.type)).reverse().find(n=>mt[n.type]);if(a){e.ref.activeStyles=[];let n=mt[a.type];te(cc,(l,o)=>{let r=e.ref[l];te(o,(s,p)=>{let c=n[l]&&typeof n[l][s]<"u"?n[l][s]:p;e.ref.activeStyles.push({control:r,key:s,value:c})})})}e.ref.activeStyles.forEach(({control:n,key:l,value:o})=>{n[l]=typeof o=="function"?o(e):o})},uc=fe({DID_SET_LABEL_BUTTON_ABORT_ITEM_PROCESSING:({root:e,action:t})=>{e.ref.buttonAbortItemProcessing.label=t.value},DID_SET_LABEL_BUTTON_ABORT_ITEM_LOAD:({root:e,action:t})=>{e.ref.buttonAbortItemLoad.label=t.value},DID_SET_LABEL_BUTTON_ABORT_ITEM_REMOVAL:({root:e,action:t})=>{e.ref.buttonAbortItemRemoval.label=t.value},DID_REQUEST_ITEM_PROCESSING:({root:e})=>{e.ref.processProgressIndicator.spin=!0,e.ref.processProgressIndicator.progress=0},DID_START_ITEM_LOAD:({root:e})=>{e.ref.loadProgressIndicator.spin=!0,e.ref.loadProgressIndicator.progress=0},DID_START_ITEM_REMOVE:({root:e})=>{e.ref.processProgressIndicator.spin=!0,e.ref.processProgressIndicator.progress=0},DID_UPDATE_ITEM_LOAD_PROGRESS:({root:e,action:t})=>{e.ref.loadProgressIndicator.spin=!1,e.ref.loadProgressIndicator.progress=t.progress},DID_UPDATE_ITEM_PROCESS_PROGRESS:({root:e,action:t})=>{e.ref.processProgressIndicator.spin=!1,e.ref.processProgressIndicator.progress=t.progress}}),gc=ne({create:pc,write:mc,didCreateView:e=>{tt("CREATE_VIEW",{...e,view:e})},name:"file"}),fc=({root:e,props:t})=>{e.ref.fileName=Ve("legend"),e.appendChild(e.ref.fileName),e.ref.file=e.appendChildView(e.createChildView(gc,{id:t.id})),e.ref.data=!1},hc=({root:e,props:t})=>{ae(e.ref.fileName,Qi(e.query("GET_ITEM_NAME",t.id)))},bc=ne({create:fc,ignoreRect:!0,write:fe({DID_LOAD_ITEM:hc}),didCreateView:e=>{tt("CREATE_VIEW",{...e,view:e})},tag:"fieldset",name:"file-wrapper"}),tn={type:"spring",damping:.6,mass:7},Ec=({root:e,props:t})=>{[{name:"top"},{name:"center",props:{translateY:null,scaleY:null},mixins:{animations:{scaleY:tn},styles:["translateY","scaleY"]}},{name:"bottom",props:{translateY:null},mixins:{animations:{translateY:tn},styles:["translateY"]}}].forEach(i=>{Tc(e,i,t.name)}),e.element.classList.add(`filepond--${t.name}`),e.ref.scalable=null},Tc=(e,t,i)=>{let a=ne({name:`panel-${t.name} filepond--${i}`,mixins:t.mixins,ignoreRectUpdate:!0}),n=e.createChildView(a,t.props);e.ref[t.name]=e.appendChildView(n)},Ic=({root:e,props:t})=>{if((e.ref.scalable===null||t.scalable!==e.ref.scalable)&&(e.ref.scalable=In(t.scalable)?t.scalable:!0,e.element.dataset.scalable=e.ref.scalable),!t.height)return;let i=e.ref.top.rect.element,a=e.ref.bottom.rect.element,n=Math.max(i.height+a.height,t.height);e.ref.center.translateY=i.height,e.ref.center.scaleY=(n-i.height-a.height)/100,e.ref.bottom.translateY=n-a.height},Vn=ne({name:"panel",read:({root:e,props:t})=>t.heightCurrent=e.ref.bottom.translateY,write:Ic,create:Ec,ignoreRect:!0,mixins:{apis:["height","heightCurrent","scalable"]}}),vc=e=>{let t=e.map(a=>a.id),i;return{setIndex:a=>{i=a},getIndex:()=>i,getItemIndex:a=>t.indexOf(a.id)}},an={type:"spring",stiffness:.75,damping:.45,mass:10},nn="spring",ln={DID_START_ITEM_LOAD:"busy",DID_UPDATE_ITEM_LOAD_PROGRESS:"loading",DID_THROW_ITEM_INVALID:"load-invalid",DID_THROW_ITEM_LOAD_ERROR:"load-error",DID_LOAD_ITEM:"idle",DID_THROW_ITEM_REMOVE_ERROR:"remove-error",DID_START_ITEM_REMOVE:"busy",DID_START_ITEM_PROCESSING:"busy processing",DID_REQUEST_ITEM_PROCESSING:"busy processing",DID_UPDATE_ITEM_PROCESS_PROGRESS:"processing",DID_COMPLETE_ITEM_PROCESSING:"processing-complete",DID_THROW_ITEM_PROCESSING_ERROR:"processing-error",DID_THROW_ITEM_PROCESSING_REVERT_ERROR:"processing-revert-error",DID_ABORT_ITEM_PROCESSING:"cancelled",DID_REVERT_ITEM_PROCESSING:"idle"},xc=({root:e,props:t})=>{if(e.ref.handleClick=a=>e.dispatch("DID_ACTIVATE_ITEM",{id:t.id}),e.element.id=`filepond--item-${t.id}`,e.element.addEventListener("click",e.ref.handleClick),e.ref.container=e.appendChildView(e.createChildView(bc,{id:t.id})),e.ref.panel=e.appendChildView(e.createChildView(Vn,{name:"item-panel"})),e.ref.panel.height=null,t.markedForRemoval=!1,!e.query("GET_ALLOW_REORDER"))return;e.element.dataset.dragState="idle";let i=a=>{if(!a.isPrimary)return;let n=!1,l={x:a.pageX,y:a.pageY};t.dragOrigin={x:e.translateX,y:e.translateY},t.dragCenter={x:a.offsetX,y:a.offsetY};let o=vc(e.query("GET_ACTIVE_ITEMS"));e.dispatch("DID_GRAB_ITEM",{id:t.id,dragState:o});let r=d=>{if(!d.isPrimary)return;d.stopPropagation(),d.preventDefault(),t.dragOffset={x:d.pageX-l.x,y:d.pageY-l.y},t.dragOffset.x*t.dragOffset.x+t.dragOffset.y*t.dragOffset.y>16&&!n&&(n=!0,e.element.removeEventListener("click",e.ref.handleClick)),e.dispatch("DID_DRAG_ITEM",{id:t.id,dragState:o})},s=d=>{d.isPrimary&&(t.dragOffset={x:d.pageX-l.x,y:d.pageY-l.y},c())},p=()=>{c()},c=()=>{document.removeEventListener("pointercancel",p),document.removeEventListener("pointermove",r),document.removeEventListener("pointerup",s),e.dispatch("DID_DROP_ITEM",{id:t.id,dragState:o}),n&&setTimeout(()=>e.element.addEventListener("click",e.ref.handleClick),0)};document.addEventListener("pointercancel",p),document.addEventListener("pointermove",r),document.addEventListener("pointerup",s)};e.element.addEventListener("pointerdown",i)},yc=fe({DID_UPDATE_PANEL_HEIGHT:({root:e,action:t})=>{e.height=t.height}}),Rc=fe({DID_GRAB_ITEM:({root:e,props:t})=>{t.dragOrigin={x:e.translateX,y:e.translateY}},DID_DRAG_ITEM:({root:e})=>{e.element.dataset.dragState="drag"},DID_DROP_ITEM:({root:e,props:t})=>{t.dragOffset=null,t.dragOrigin=null,e.element.dataset.dragState="drop"}},({root:e,actions:t,props:i,shouldOptimize:a})=>{e.element.dataset.dragState==="drop"&&e.scaleX<=1&&(e.element.dataset.dragState="idle");let n=t.concat().filter(o=>/^DID_/.test(o.type)).reverse().find(o=>ln[o.type]);n&&n.type!==i.currentState&&(i.currentState=n.type,e.element.dataset.filepondItemState=ln[i.currentState]||"");let l=e.query("GET_ITEM_PANEL_ASPECT_RATIO")||e.query("GET_PANEL_ASPECT_RATIO");l?a||(e.height=e.rect.element.width*l):(yc({root:e,actions:t,props:i}),!e.height&&e.ref.container.rect.element.height>0&&(e.height=e.ref.container.rect.element.height)),a&&(e.ref.panel.height=null),e.ref.panel.height=e.height}),Sc=ne({create:xc,write:Rc,destroy:({root:e,props:t})=>{e.element.removeEventListener("click",e.ref.handleClick),e.dispatch("RELEASE_ITEM",{query:t.id})},tag:"li",name:"item",mixins:{apis:["id","interactionMethod","markedForRemoval","spawnDate","dragCenter","dragOrigin","dragOffset"],styles:["translateX","translateY","scaleX","scaleY","opacity","height"],animations:{scaleX:nn,scaleY:nn,translateX:an,translateY:an,opacity:{type:"tween",duration:150}}}}),Zi=(e,t)=>Math.max(1,Math.floor((e+1)/t)),Ji=(e,t,i)=>{if(!i)return;let a=e.rect.element.width,n=t.length,l=null;if(n===0||i.topb){if(i.left{se(e.element,"role","list"),e.ref.lastItemSpanwDate=Date.now()},wc=({root:e,action:t})=>{let{id:i,index:a,interactionMethod:n}=t;e.ref.addIndex=a;let l=Date.now(),o=l,r=1;if(n!==Re.NONE){r=0;let s=e.query("GET_ITEM_INSERT_INTERVAL"),p=l-e.ref.lastItemSpanwDate;o=p{e.dragOffset?(e.translateX=null,e.translateY=null,e.translateX=e.dragOrigin.x+e.dragOffset.x,e.translateY=e.dragOrigin.y+e.dragOffset.y,e.scaleX=1.025,e.scaleY=1.025):(e.translateX=t,e.translateY=i,Date.now()>e.spawnDate&&(e.opacity===0&&Lc(e,t,i,a,n),e.scaleX=1,e.scaleY=1,e.opacity=1))},Lc=(e,t,i,a,n)=>{e.interactionMethod===Re.NONE?(e.translateX=null,e.translateX=t,e.translateY=null,e.translateY=i):e.interactionMethod===Re.DROP?(e.translateX=null,e.translateX=t-a*20,e.translateY=null,e.translateY=i-n*10,e.scaleX=.8,e.scaleY=.8):e.interactionMethod===Re.BROWSE?(e.translateY=null,e.translateY=i-30):e.interactionMethod===Re.API&&(e.translateX=null,e.translateX=t-30,e.translateY=null)},Mc=({root:e,action:t})=>{let{id:i}=t,a=e.childViews.find(n=>n.id===i);a&&(a.scaleX=.9,a.scaleY=.9,a.opacity=0,a.markedForRemoval=!0)},Pi=e=>e.rect.element.height+e.rect.element.marginBottom*.5+e.rect.element.marginTop*.5,Ac=e=>e.rect.element.width+e.rect.element.marginLeft*.5+e.rect.element.marginRight*.5,Pc=({root:e,action:t})=>{let{id:i,dragState:a}=t,n=e.query("GET_ITEM",{id:i}),l=e.childViews.find(h=>h.id===i),o=e.childViews.length,r=a.getItemIndex(n);if(!l)return;let s={x:l.dragOrigin.x+l.dragOffset.x+l.dragCenter.x,y:l.dragOrigin.y+l.dragOffset.y+l.dragCenter.y},p=Pi(l),c=Ac(l),d=Math.floor(e.rect.outer.width/c);d>o&&(d=o);let m=Math.floor(o/d+1);ti.setHeight=p*m,ti.setWidth=c*d;var u={y:Math.floor(s.y/p),x:Math.floor(s.x/c),getGridIndex:function(){return s.y>ti.getHeight||s.y<0||s.x>ti.getWidth||s.x<0?r:this.y*d+this.x},getColIndex:function(){let I=e.query("GET_ACTIVE_ITEMS"),b=e.childViews.filter(P=>P.rect.element.height),T=I.map(P=>b.find(A=>A.id===P.id)),v=T.findIndex(P=>P===l),y=Pi(l),E=T.length,_=E,x=0,R=0,z=0;for(let P=0;PP){if(s.y1?u.getGridIndex():u.getColIndex();e.dispatch("MOVE_ITEM",{query:l,index:g});let f=a.getIndex();if(f===void 0||f!==g){if(a.setIndex(g),f===void 0)return;e.dispatch("DID_REORDER_ITEMS",{items:e.query("GET_ACTIVE_ITEMS"),origin:r,target:g})}},zc=fe({DID_ADD_ITEM:wc,DID_REMOVE_ITEM:Mc,DID_DRAG_ITEM:Pc}),Oc=({root:e,props:t,actions:i,shouldOptimize:a})=>{zc({root:e,props:t,actions:i});let{dragCoordinates:n}=t,l=e.rect.element.width,o=e.childViews.filter(T=>T.rect.element.height),r=e.query("GET_ACTIVE_ITEMS").map(T=>o.find(v=>v.id===T.id)).filter(T=>T),s=n?Ji(e,r,n):null,p=e.ref.addIndex||null;e.ref.addIndex=null;let c=0,d=0,m=0;if(r.length===0)return;let u=r[0].rect.element,g=u.marginTop+u.marginBottom,f=u.marginLeft+u.marginRight,h=u.width+f,I=u.height+g,b=Zi(l,h);if(b===1){let T=0,v=0;r.forEach((y,E)=>{if(s){let R=E-s;R===-2?v=-g*.25:R===-1?v=-g*.75:R===0?v=g*.75:R===1?v=g*.25:v=0}a&&(y.translateX=null,y.translateY=null),y.markedForRemoval||on(y,0,T+v);let x=(y.rect.element.height+g)*(y.markedForRemoval?y.opacity:1);T+=x})}else{let T=0,v=0;r.forEach((y,E)=>{E===s&&(c=1),E===p&&(m+=1),y.markedForRemoval&&y.opacity<.5&&(d-=1);let _=E+m+c+d,x=_%b,R=Math.floor(_/b),z=x*h,P=R*I,A=Math.sign(z-T),B=Math.sign(P-v);T=z,v=P,!y.markedForRemoval&&(a&&(y.translateX=null,y.translateY=null),on(y,z,P,A,B))})}},Fc=(e,t)=>t.filter(i=>i.data&&i.data.id?e.id===i.data.id:!0),Dc=ne({create:_c,write:Oc,tag:"ul",name:"list",didWriteView:({root:e})=>{e.childViews.filter(t=>t.markedForRemoval&&t.opacity===0&&t.resting).forEach(t=>{t._destroy(),e.removeChildView(t)})},filterFrameActionsForChild:Fc,mixins:{apis:["dragCoordinates"]}}),Cc=({root:e,props:t})=>{e.ref.list=e.appendChildView(e.createChildView(Dc)),t.dragCoordinates=null,t.overflowing=!1},Bc=({root:e,props:t,action:i})=>{e.query("GET_ITEM_INSERT_LOCATION_FREEDOM")&&(t.dragCoordinates={left:i.position.scopeLeft-e.ref.list.rect.element.left,top:i.position.scopeTop-(e.rect.outer.top+e.rect.element.marginTop+e.rect.element.scrollTop)})},Nc=({props:e})=>{e.dragCoordinates=null},kc=fe({DID_DRAG:Bc,DID_END_DRAG:Nc}),Vc=({root:e,props:t,actions:i})=>{if(kc({root:e,props:t,actions:i}),e.ref.list.dragCoordinates=t.dragCoordinates,t.overflowing&&!t.overflow&&(t.overflowing=!1,e.element.dataset.state="",e.height=null),t.overflow){let a=Math.round(t.overflow);a!==e.height&&(t.overflowing=!0,e.element.dataset.state="overflow",e.height=a)}},Gc=ne({create:Cc,write:Vc,name:"list-scroller",mixins:{apis:["overflow","dragCoordinates"],styles:["height","translateY"],animations:{translateY:"spring"}}}),ze=(e,t,i,a="")=>{i?se(e,t,a):e.removeAttribute(t)},Uc=e=>{if(!(!e||e.value==="")){try{e.value=""}catch{}if(e.value){let t=Ve("form"),i=e.parentNode,a=e.nextSibling;t.appendChild(e),t.reset(),a?i.insertBefore(e,a):i.appendChild(e)}}},Wc=({root:e,props:t})=>{e.element.id=`filepond--browser-${t.id}`,se(e.element,"name",e.query("GET_NAME")),se(e.element,"aria-controls",`filepond--assistant-${t.id}`),se(e.element,"aria-labelledby",`filepond--drop-label-${t.id}`),Gn({root:e,action:{value:e.query("GET_ACCEPTED_FILE_TYPES")}}),Un({root:e,action:{value:e.query("GET_ALLOW_MULTIPLE")}}),Wn({root:e,action:{value:e.query("GET_ALLOW_DIRECTORIES_ONLY")}}),ki({root:e}),Hn({root:e,action:{value:e.query("GET_REQUIRED")}}),jn({root:e,action:{value:e.query("GET_CAPTURE_METHOD")}}),e.ref.handleChange=i=>{if(!e.element.value)return;let a=Array.from(e.element.files).map(n=>(n._relativePath=n.webkitRelativePath,n));setTimeout(()=>{t.onload(a),Uc(e.element)},250)},e.element.addEventListener("change",e.ref.handleChange)},Gn=({root:e,action:t})=>{e.query("GET_ALLOW_SYNC_ACCEPT_ATTRIBUTE")&&ze(e.element,"accept",!!t.value,t.value?t.value.join(","):"")},Un=({root:e,action:t})=>{ze(e.element,"multiple",t.value)},Wn=({root:e,action:t})=>{ze(e.element,"webkitdirectory",t.value)},ki=({root:e})=>{let t=e.query("GET_DISABLED"),i=e.query("GET_ALLOW_BROWSE"),a=t||!i;ze(e.element,"disabled",a)},Hn=({root:e,action:t})=>{t.value?e.query("GET_TOTAL_ITEMS")===0&&ze(e.element,"required",!0):ze(e.element,"required",!1)},jn=({root:e,action:t})=>{ze(e.element,"capture",!!t.value,t.value===!0?"":t.value)},rn=({root:e})=>{let{element:t}=e;if(e.query("GET_TOTAL_ITEMS")>0){ze(t,"required",!1),ze(t,"name",!1);let i=e.query("GET_ACTIVE_ITEMS"),a=!1;for(let n=0;n{e.query("GET_CHECK_VALIDITY")&&e.element.setCustomValidity(e.query("GET_LABEL_INVALID_FIELD"))},jc=ne({tag:"input",name:"browser",ignoreRect:!0,ignoreRectUpdate:!0,attributes:{type:"file"},create:Wc,destroy:({root:e})=>{e.element.removeEventListener("change",e.ref.handleChange)},write:fe({DID_LOAD_ITEM:rn,DID_REMOVE_ITEM:rn,DID_THROW_ITEM_INVALID:Hc,DID_SET_DISABLED:ki,DID_SET_ALLOW_BROWSE:ki,DID_SET_ALLOW_DIRECTORIES_ONLY:Wn,DID_SET_ALLOW_MULTIPLE:Un,DID_SET_ACCEPTED_FILE_TYPES:Gn,DID_SET_CAPTURE_METHOD:jn,DID_SET_REQUIRED:Hn})}),sn={ENTER:13,SPACE:32},Yc=({root:e,props:t})=>{let i=Ve("label");se(i,"for",`filepond--browser-${t.id}`),se(i,"id",`filepond--drop-label-${t.id}`),e.ref.handleKeyDown=a=>{(a.keyCode===sn.ENTER||a.keyCode===sn.SPACE)&&(a.preventDefault(),e.ref.label.click())},e.ref.handleClick=a=>{a.target===i||i.contains(a.target)||e.ref.label.click()},i.addEventListener("keydown",e.ref.handleKeyDown),e.element.addEventListener("click",e.ref.handleClick),Yn(i,t.caption),e.appendChild(i),e.ref.label=i},Yn=(e,t)=>{e.innerHTML=t;let i=e.querySelector(".filepond--label-action");return i&&se(i,"tabindex","0"),t},qc=ne({name:"drop-label",ignoreRect:!0,create:Yc,destroy:({root:e})=>{e.ref.label.addEventListener("keydown",e.ref.handleKeyDown),e.element.removeEventListener("click",e.ref.handleClick)},write:fe({DID_SET_LABEL_IDLE:({root:e,action:t})=>{Yn(e.ref.label,t.value)}}),mixins:{styles:["opacity","translateX","translateY"],animations:{opacity:{type:"tween",duration:150},translateX:"spring",translateY:"spring"}}}),$c=ne({name:"drip-blob",ignoreRect:!0,mixins:{styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}}}}),Xc=({root:e})=>{let t=e.rect.element.width*.5,i=e.rect.element.height*.5;e.ref.blob=e.appendChildView(e.createChildView($c,{opacity:0,scaleX:2.5,scaleY:2.5,translateX:t,translateY:i}))},Kc=({root:e,action:t})=>{if(!e.ref.blob){Xc({root:e});return}e.ref.blob.translateX=t.position.scopeLeft,e.ref.blob.translateY=t.position.scopeTop,e.ref.blob.scaleX=1,e.ref.blob.scaleY=1,e.ref.blob.opacity=1},Qc=({root:e})=>{e.ref.blob&&(e.ref.blob.opacity=0)},Zc=({root:e})=>{e.ref.blob&&(e.ref.blob.scaleX=2.5,e.ref.blob.scaleY=2.5,e.ref.blob.opacity=0)},Jc=({root:e,props:t,actions:i})=>{ed({root:e,props:t,actions:i});let{blob:a}=e.ref;i.length===0&&a&&a.opacity===0&&(e.removeChildView(a),e.ref.blob=null)},ed=fe({DID_DRAG:Kc,DID_DROP:Zc,DID_END_DRAG:Qc}),td=ne({ignoreRect:!0,ignoreRectUpdate:!0,name:"drip",write:Jc}),qn=(e,t)=>{try{let i=new DataTransfer;t.forEach(a=>{a instanceof File?i.items.add(a):i.items.add(new File([a],a.name,{type:a.type}))}),e.files=i.files}catch{return!1}return!0},id=({root:e})=>{e.ref.fields={};let t=document.createElement("legend");t.textContent="Files",e.element.appendChild(t)},gi=(e,t)=>e.ref.fields[t],ea=e=>{e.query("GET_ACTIVE_ITEMS").forEach(t=>{e.ref.fields[t.id]&&e.element.appendChild(e.ref.fields[t.id])})},cn=({root:e})=>ea(e),ad=({root:e,action:t})=>{let n=!(e.query("GET_ITEM",t.id).origin===re.LOCAL)&&e.query("SHOULD_UPDATE_FILE_INPUT"),l=Ve("input");l.type=n?"file":"hidden",l.name=e.query("GET_NAME"),e.ref.fields[t.id]=l,ea(e)},nd=({root:e,action:t})=>{let i=gi(e,t.id);if(!i||(t.serverFileReference!==null&&(i.value=t.serverFileReference),!e.query("SHOULD_UPDATE_FILE_INPUT")))return;let a=e.query("GET_ITEM",t.id);qn(i,[a.file])},ld=({root:e,action:t})=>{e.query("SHOULD_UPDATE_FILE_INPUT")&&setTimeout(()=>{let i=gi(e,t.id);i&&qn(i,[t.file])},0)},od=({root:e})=>{e.element.disabled=e.query("GET_DISABLED")},rd=({root:e,action:t})=>{let i=gi(e,t.id);i&&(i.parentNode&&i.parentNode.removeChild(i),delete e.ref.fields[t.id])},sd=({root:e,action:t})=>{let i=gi(e,t.id);i&&(t.value===null?i.removeAttribute("value"):i.type!="file"&&(i.value=t.value),ea(e))},cd=fe({DID_SET_DISABLED:od,DID_ADD_ITEM:ad,DID_LOAD_ITEM:nd,DID_REMOVE_ITEM:rd,DID_DEFINE_VALUE:sd,DID_PREPARE_OUTPUT:ld,DID_REORDER_ITEMS:cn,DID_SORT_ITEMS:cn}),dd=ne({tag:"fieldset",name:"data",create:id,write:cd,ignoreRect:!0}),pd=e=>"getRootNode"in e?e.getRootNode():document,md=["jpg","jpeg","png","gif","bmp","webp","svg","tiff"],ud=["css","csv","html","txt"],gd={zip:"zip|compressed",epub:"application/epub+zip"},$n=(e="")=>(e=e.toLowerCase(),md.includes(e)?"image/"+(e==="jpg"?"jpeg":e==="svg"?"svg+xml":e):ud.includes(e)?"text/"+e:gd[e]||""),ta=e=>new Promise((t,i)=>{let a=xd(e);if(a.length&&!fd(e))return t(a);hd(e).then(t)}),fd=e=>e.files?e.files.length>0:!1,hd=e=>new Promise((t,i)=>{let a=(e.items?Array.from(e.items):[]).filter(n=>bd(n)).map(n=>Ed(n));if(!a.length){t(e.files?Array.from(e.files):[]);return}Promise.all(a).then(n=>{let l=[];n.forEach(o=>{l.push.apply(l,o)}),t(l.filter(o=>o).map(o=>(o._relativePath||(o._relativePath=o.webkitRelativePath),o)))}).catch(console.error)}),bd=e=>{if(Xn(e)){let t=ia(e);if(t)return t.isFile||t.isDirectory}return e.kind==="file"},Ed=e=>new Promise((t,i)=>{if(vd(e)){Td(ia(e)).then(t).catch(i);return}t([e.getAsFile()])}),Td=e=>new Promise((t,i)=>{let a=[],n=0,l=0,o=()=>{l===0&&n===0&&t(a)},r=s=>{n++;let p=s.createReader(),c=()=>{p.readEntries(d=>{if(d.length===0){n--,o();return}d.forEach(m=>{m.isDirectory?r(m):(l++,m.file(u=>{let g=Id(u);m.fullPath&&(g._relativePath=m.fullPath),a.push(g),l--,o()}))}),c()},i)};c()};r(e)}),Id=e=>{if(e.type.length)return e;let t=e.lastModifiedDate,i=e.name,a=$n(ui(e.name));return a.length&&(e=e.slice(0,e.size,a),e.name=i,e.lastModifiedDate=t),e},vd=e=>Xn(e)&&(ia(e)||{}).isDirectory,Xn=e=>"webkitGetAsEntry"in e,ia=e=>e.webkitGetAsEntry(),xd=e=>{let t=[];try{if(t=Rd(e),t.length)return t;t=yd(e)}catch{}return t},yd=e=>{let t=e.getData("url");return typeof t=="string"&&t.length?[t]:[]},Rd=e=>{let t=e.getData("text/html");if(typeof t=="string"&&t.length){let i=t.match(/src\s*=\s*"(.+?)"/);if(i)return[i[1]]}return[]},ri=[],et=e=>({pageLeft:e.pageX,pageTop:e.pageY,scopeLeft:e.offsetX||e.layerX,scopeTop:e.offsetY||e.layerY}),Sd=(e,t,i)=>{let a=_d(t),n={element:e,filterElement:i,state:null,ondrop:()=>{},onenter:()=>{},ondrag:()=>{},onexit:()=>{},onload:()=>{},allowdrop:()=>{}};return n.destroy=a.addListener(n),n},_d=e=>{let t=ri.find(a=>a.element===e);if(t)return t;let i=wd(e);return ri.push(i),i},wd=e=>{let t=[],i={dragenter:Md,dragover:Ad,dragleave:zd,drop:Pd},a={};te(i,(l,o)=>{a[l]=o(e,t),e.addEventListener(l,a[l],!1)});let n={element:e,addListener:l=>(t.push(l),()=>{t.splice(t.indexOf(l),1),t.length===0&&(ri.splice(ri.indexOf(n),1),te(i,o=>{e.removeEventListener(o,a[o],!1)}))})};return n},Ld=(e,t)=>("elementFromPoint"in e||(e=document),e.elementFromPoint(t.x,t.y)),aa=(e,t)=>{let i=pd(t),a=Ld(i,{x:e.pageX-window.pageXOffset,y:e.pageY-window.pageYOffset});return a===t||t.contains(a)},Kn=null,ii=(e,t)=>{try{e.dropEffect=t}catch{}},Md=(e,t)=>i=>{i.preventDefault(),Kn=i.target,t.forEach(a=>{let{element:n,onenter:l}=a;aa(i,n)&&(a.state="enter",l(et(i)))})},Ad=(e,t)=>i=>{i.preventDefault();let a=i.dataTransfer;ta(a).then(n=>{let l=!1;t.some(o=>{let{filterElement:r,element:s,onenter:p,onexit:c,ondrag:d,allowdrop:m}=o;ii(a,"copy");let u=m(n);if(!u){ii(a,"none");return}if(aa(i,s)){if(l=!0,o.state===null){o.state="enter",p(et(i));return}if(o.state="over",r&&!u){ii(a,"none");return}d(et(i))}else r&&!l&&ii(a,"none"),o.state&&(o.state=null,c(et(i)))})})},Pd=(e,t)=>i=>{i.preventDefault();let a=i.dataTransfer;ta(a).then(n=>{t.forEach(l=>{let{filterElement:o,element:r,ondrop:s,onexit:p,allowdrop:c}=l;if(l.state=null,!(o&&!aa(i,r))){if(!c(n))return p(et(i));s(et(i),n)}})})},zd=(e,t)=>i=>{Kn===i.target&&t.forEach(a=>{let{onexit:n}=a;a.state=null,n(et(i))})},Od=(e,t,i)=>{e.classList.add("filepond--hopper");let{catchesDropsOnPage:a,requiresDropOnElement:n,filterItems:l=c=>c}=i,o=Sd(e,a?document.documentElement:e,n),r="",s="";o.allowdrop=c=>t(l(c)),o.ondrop=(c,d)=>{let m=l(d);if(!t(m)){p.ondragend(c);return}s="drag-drop",p.onload(m,c)},o.ondrag=c=>{p.ondrag(c)},o.onenter=c=>{s="drag-over",p.ondragstart(c)},o.onexit=c=>{s="drag-exit",p.ondragend(c)};let p={updateHopperState:()=>{r!==s&&(e.dataset.hopperState=s,r=s)},onload:()=>{},ondragstart:()=>{},ondrag:()=>{},ondragend:()=>{},destroy:()=>{o.destroy()}};return p},Vi=!1,ut=[],Qn=e=>{let t=document.activeElement;if(t&&(/textarea|input/i.test(t.nodeName)||t.getAttribute("contenteditable")==="true"||t.getAttribute("contenteditable")==="")){let a=!1,n=t;for(;n!==document.body;){if(n.classList.contains("filepond--root")){a=!0;break}n=n.parentNode}if(!a)return}ta(e.clipboardData).then(a=>{a.length&&ut.forEach(n=>n(a))})},Fd=e=>{ut.includes(e)||(ut.push(e),!Vi&&(Vi=!0,document.addEventListener("paste",Qn)))},Dd=e=>{$i(ut,ut.indexOf(e)),ut.length===0&&(document.removeEventListener("paste",Qn),Vi=!1)},Cd=()=>{let e=i=>{t.onload(i)},t={destroy:()=>{Dd(e)},onload:()=>{}};return Fd(e),t},Bd=({root:e,props:t})=>{e.element.id=`filepond--assistant-${t.id}`,se(e.element,"role","alert"),se(e.element,"aria-live","polite"),se(e.element,"aria-relevant","additions")},dn=null,pn=null,zi=[],fi=(e,t)=>{e.element.textContent=t},Nd=e=>{e.element.textContent=""},Zn=(e,t,i)=>{let a=e.query("GET_TOTAL_ITEMS");fi(e,`${i} ${t}, ${a} ${a===1?e.query("GET_LABEL_FILE_COUNT_SINGULAR"):e.query("GET_LABEL_FILE_COUNT_PLURAL")}`),clearTimeout(pn),pn=setTimeout(()=>{Nd(e)},1500)},Jn=e=>e.element.parentNode.contains(document.activeElement),kd=({root:e,action:t})=>{if(!Jn(e))return;e.element.textContent="";let i=e.query("GET_ITEM",t.id);zi.push(i.filename),clearTimeout(dn),dn=setTimeout(()=>{Zn(e,zi.join(", "),e.query("GET_LABEL_FILE_ADDED")),zi.length=0},750)},Vd=({root:e,action:t})=>{if(!Jn(e))return;let i=t.item;Zn(e,i.filename,e.query("GET_LABEL_FILE_REMOVED"))},Gd=({root:e,action:t})=>{let a=e.query("GET_ITEM",t.id).filename,n=e.query("GET_LABEL_FILE_PROCESSING_COMPLETE");fi(e,`${a} ${n}`)},mn=({root:e,action:t})=>{let a=e.query("GET_ITEM",t.id).filename,n=e.query("GET_LABEL_FILE_PROCESSING_ABORTED");fi(e,`${a} ${n}`)},ai=({root:e,action:t})=>{let a=e.query("GET_ITEM",t.id).filename;fi(e,`${t.status.main} ${a} ${t.status.sub}`)},Ud=ne({create:Bd,ignoreRect:!0,ignoreRectUpdate:!0,write:fe({DID_LOAD_ITEM:kd,DID_REMOVE_ITEM:Vd,DID_COMPLETE_ITEM_PROCESSING:Gd,DID_ABORT_ITEM_PROCESSING:mn,DID_REVERT_ITEM_PROCESSING:mn,DID_THROW_ITEM_REMOVE_ERROR:ai,DID_THROW_ITEM_LOAD_ERROR:ai,DID_THROW_ITEM_INVALID:ai,DID_THROW_ITEM_PROCESSING_ERROR:ai}),tag:"span",name:"assistant"}),el=(e,t="-")=>e.replace(new RegExp(`${t}.`,"g"),i=>i.charAt(1).toUpperCase()),tl=(e,t=16,i=!0)=>{let a=Date.now(),n=null;return(...l)=>{clearTimeout(n);let o=Date.now()-a,r=()=>{a=Date.now(),e(...l)};oe.preventDefault(),Hd=({root:e,props:t})=>{let i=e.query("GET_ID");i&&(e.element.id=i);let a=e.query("GET_CLASS_NAME");a&&a.split(" ").filter(s=>s.length).forEach(s=>{e.element.classList.add(s)}),e.ref.label=e.appendChildView(e.createChildView(qc,{...t,translateY:null,caption:e.query("GET_LABEL_IDLE")})),e.ref.list=e.appendChildView(e.createChildView(Gc,{translateY:null})),e.ref.panel=e.appendChildView(e.createChildView(Vn,{name:"panel-root"})),e.ref.assistant=e.appendChildView(e.createChildView(Ud,{...t})),e.ref.data=e.appendChildView(e.createChildView(dd,{...t})),e.ref.measure=Ve("div"),e.ref.measure.style.height="100%",e.element.appendChild(e.ref.measure),e.ref.bounds=null,e.query("GET_STYLES").filter(s=>!ke(s.value)).map(({name:s,value:p})=>{e.element.dataset[s]=p}),e.ref.widthPrevious=null,e.ref.widthUpdated=tl(()=>{e.ref.updateHistory=[],e.dispatch("DID_RESIZE_ROOT")},250),e.ref.previousAspectRatio=null,e.ref.updateHistory=[];let n=window.matchMedia("(pointer: fine) and (hover: hover)").matches,l="PointerEvent"in window;e.query("GET_ALLOW_REORDER")&&l&&!n&&(e.element.addEventListener("touchmove",si,{passive:!1}),e.element.addEventListener("gesturestart",si));let o=e.query("GET_CREDITS");if(o.length===2){let s=document.createElement("a");s.className="filepond--credits",s.href=o[0],s.tabIndex=-1,s.target="_blank",s.rel="noopener noreferrer nofollow",s.textContent=o[1],e.element.appendChild(s),e.ref.credits=s}},jd=({root:e,props:t,actions:i})=>{if(Kd({root:e,props:t,actions:i}),i.filter(E=>/^DID_SET_STYLE_/.test(E.type)).filter(E=>!ke(E.data.value)).map(({type:E,data:_})=>{let x=el(E.substring(8).toLowerCase(),"_");e.element.dataset[x]=_.value,e.invalidateLayout()}),e.rect.element.hidden)return;e.rect.element.width!==e.ref.widthPrevious&&(e.ref.widthPrevious=e.rect.element.width,e.ref.widthUpdated());let a=e.ref.bounds;a||(a=e.ref.bounds=$d(e),e.element.removeChild(e.ref.measure),e.ref.measure=null);let{hopper:n,label:l,list:o,panel:r}=e.ref;n&&n.updateHopperState();let s=e.query("GET_PANEL_ASPECT_RATIO"),p=e.query("GET_ALLOW_MULTIPLE"),c=e.query("GET_TOTAL_ITEMS"),d=p?e.query("GET_MAX_FILES")||Wd:1,m=c===d,u=i.find(E=>E.type==="DID_ADD_ITEM");if(m&&u){let E=u.data.interactionMethod;l.opacity=0,p?l.translateY=-40:E===Re.API?l.translateX=40:E===Re.BROWSE?l.translateY=40:l.translateY=30}else m||(l.opacity=1,l.translateX=0,l.translateY=0);let g=Yd(e),f=qd(e),h=l.rect.element.height,I=!p||m?0:h,b=m?o.rect.element.marginTop:0,T=c===0?0:o.rect.element.marginBottom,v=I+b+f.visual+T,y=I+b+f.bounds+T;if(o.translateY=Math.max(0,I-o.rect.element.marginTop)-g.top,s){let E=e.rect.element.width,_=E*s;s!==e.ref.previousAspectRatio&&(e.ref.previousAspectRatio=s,e.ref.updateHistory=[]);let x=e.ref.updateHistory;x.push(E);let R=2;if(x.length>R*2){let P=x.length,A=P-10,B=0;for(let w=P;w>=A;w--)if(x[w]===x[w-2]&&B++,B>=R)return}r.scalable=!1,r.height=_;let z=_-I-(T-g.bottom)-(m?b:0);f.visual>z?o.overflow=z:o.overflow=null,e.height=_}else if(a.fixedHeight){r.scalable=!1;let E=a.fixedHeight-I-(T-g.bottom)-(m?b:0);f.visual>E?o.overflow=E:o.overflow=null}else if(a.cappedHeight){let E=v>=a.cappedHeight,_=Math.min(a.cappedHeight,v);r.scalable=!0,r.height=E?_:_-g.top-g.bottom;let x=_-I-(T-g.bottom)-(m?b:0);v>a.cappedHeight&&f.visual>x?o.overflow=x:o.overflow=null,e.height=Math.min(a.cappedHeight,y-g.top-g.bottom)}else{let E=c>0?g.top+g.bottom:0;r.scalable=!0,r.height=Math.max(h,v-E),e.height=Math.max(h,y-E)}e.ref.credits&&r.heightCurrent&&(e.ref.credits.style.transform=`translateY(${r.heightCurrent}px)`)},Yd=e=>{let t=e.ref.list.childViews[0].childViews[0];return t?{top:t.rect.element.marginTop,bottom:t.rect.element.marginBottom}:{top:0,bottom:0}},qd=e=>{let t=0,i=0,a=e.ref.list,n=a.childViews[0],l=n.childViews.filter(b=>b.rect.element.height),o=e.query("GET_ACTIVE_ITEMS").map(b=>l.find(T=>T.id===b.id)).filter(b=>b);if(o.length===0)return{visual:t,bounds:i};let r=n.rect.element.width,s=Ji(n,o,a.dragCoordinates),p=o[0].rect.element,c=p.marginTop+p.marginBottom,d=p.marginLeft+p.marginRight,m=p.width+d,u=p.height+c,g=typeof s<"u"&&s>=0?1:0,f=o.find(b=>b.markedForRemoval&&b.opacity<.45)?-1:0,h=o.length+g+f,I=Zi(r,m);return I===1?o.forEach(b=>{let T=b.rect.element.height+c;i+=T,t+=T*b.opacity}):(i=Math.ceil(h/I)*u,t=i),{visual:t,bounds:i}},$d=e=>{let t=e.ref.measureHeight||null;return{cappedHeight:parseInt(e.style.maxHeight,10)||null,fixedHeight:t===0?null:t}},na=(e,t)=>{let i=e.query("GET_ALLOW_REPLACE"),a=e.query("GET_ALLOW_MULTIPLE"),n=e.query("GET_TOTAL_ITEMS"),l=e.query("GET_MAX_FILES"),o=t.length;return!a&&o>1?(e.dispatch("DID_THROW_MAX_FILES",{source:t,error:ie("warning",0,"Max files")}),!0):(l=a?l:1,!a&&i?!1:bt(l)&&n+o>l?(e.dispatch("DID_THROW_MAX_FILES",{source:t,error:ie("warning",0,"Max files")}),!0):!1)},Xd=(e,t,i)=>{let a=e.childViews[0];return Ji(a,t,{left:i.scopeLeft-a.rect.element.left,top:i.scopeTop-(e.rect.outer.top+e.rect.element.marginTop+e.rect.element.scrollTop)})},un=e=>{let t=e.query("GET_ALLOW_DROP"),i=e.query("GET_DISABLED"),a=t&&!i;if(a&&!e.ref.hopper){let n=Od(e.element,l=>{let o=e.query("GET_BEFORE_DROP_FILE")||(()=>!0);return e.query("GET_DROP_VALIDATION")?l.every(s=>tt("ALLOW_HOPPER_ITEM",s,{query:e.query}).every(p=>p===!0)&&o(s)):!0},{filterItems:l=>{let o=e.query("GET_IGNORED_FILES");return l.filter(r=>Je(r)?!o.includes(r.name.toLowerCase()):!0)},catchesDropsOnPage:e.query("GET_DROP_ON_PAGE"),requiresDropOnElement:e.query("GET_DROP_ON_ELEMENT")});n.onload=(l,o)=>{let s=e.ref.list.childViews[0].childViews.filter(c=>c.rect.element.height),p=e.query("GET_ACTIVE_ITEMS").map(c=>s.find(d=>d.id===c.id)).filter(c=>c);Ae("ADD_ITEMS",l,{dispatch:e.dispatch}).then(c=>{if(na(e,c))return!1;e.dispatch("ADD_ITEMS",{items:c,index:Xd(e.ref.list,p,o),interactionMethod:Re.DROP})}),e.dispatch("DID_DROP",{position:o}),e.dispatch("DID_END_DRAG",{position:o})},n.ondragstart=l=>{e.dispatch("DID_START_DRAG",{position:l})},n.ondrag=tl(l=>{e.dispatch("DID_DRAG",{position:l})}),n.ondragend=l=>{e.dispatch("DID_END_DRAG",{position:l})},e.ref.hopper=n,e.ref.drip=e.appendChildView(e.createChildView(td))}else!a&&e.ref.hopper&&(e.ref.hopper.destroy(),e.ref.hopper=null,e.removeChildView(e.ref.drip))},gn=(e,t)=>{let i=e.query("GET_ALLOW_BROWSE"),a=e.query("GET_DISABLED"),n=i&&!a;n&&!e.ref.browser?e.ref.browser=e.appendChildView(e.createChildView(jc,{...t,onload:l=>{Ae("ADD_ITEMS",l,{dispatch:e.dispatch}).then(o=>{if(na(e,o))return!1;e.dispatch("ADD_ITEMS",{items:o,index:-1,interactionMethod:Re.BROWSE})})}}),0):!n&&e.ref.browser&&(e.removeChildView(e.ref.browser),e.ref.browser=null)},fn=e=>{let t=e.query("GET_ALLOW_PASTE"),i=e.query("GET_DISABLED"),a=t&&!i;a&&!e.ref.paster?(e.ref.paster=Cd(),e.ref.paster.onload=n=>{Ae("ADD_ITEMS",n,{dispatch:e.dispatch}).then(l=>{if(na(e,l))return!1;e.dispatch("ADD_ITEMS",{items:l,index:-1,interactionMethod:Re.PASTE})})}):!a&&e.ref.paster&&(e.ref.paster.destroy(),e.ref.paster=null)},Kd=fe({DID_SET_ALLOW_BROWSE:({root:e,props:t})=>{gn(e,t)},DID_SET_ALLOW_DROP:({root:e})=>{un(e)},DID_SET_ALLOW_PASTE:({root:e})=>{fn(e)},DID_SET_DISABLED:({root:e,props:t})=>{un(e),fn(e),gn(e,t),e.query("GET_DISABLED")?e.element.dataset.disabled="disabled":e.element.removeAttribute("data-disabled")}}),Qd=ne({name:"root",read:({root:e})=>{e.ref.measure&&(e.ref.measureHeight=e.ref.measure.offsetHeight)},create:Hd,write:jd,destroy:({root:e})=>{e.ref.paster&&e.ref.paster.destroy(),e.ref.hopper&&e.ref.hopper.destroy(),e.element.removeEventListener("touchmove",si),e.element.removeEventListener("gesturestart",si)},mixins:{styles:["height"]}}),Zd=(e={})=>{let t=null,i=oi(),a=fr(es(i),[Es,as(i)],[Ws,is(i)]);a.dispatch("SET_OPTIONS",{options:e});let n=()=>{document.hidden||a.dispatch("KICK")};document.addEventListener("visibilitychange",n);let l=null,o=!1,r=!1,s=null,p=null,c=()=>{o||(o=!0),clearTimeout(l),l=setTimeout(()=>{o=!1,s=null,p=null,r&&(r=!1,a.dispatch("DID_STOP_RESIZE"))},500)};window.addEventListener("resize",c);let d=Qd(a,{id:qi()}),m=!1,u=!1,g={_read:()=>{o&&(p=window.innerWidth,s||(s=p),!r&&p!==s&&(a.dispatch("DID_START_RESIZE"),r=!0)),u&&m&&(m=d.element.offsetParent===null),!m&&(d._read(),u=d.rect.element.hidden)},_write:S=>{let L=a.processActionQueue().filter(D=>!/^SET_/.test(D.type));m&&!L.length||(b(L),m=d._write(S,L,r),os(a.query("GET_ITEMS")),m&&a.processDispatchQueue())}},f=S=>L=>{let D={type:S};if(!L)return D;if(L.hasOwnProperty("error")&&(D.error=L.error?{...L.error}:null),L.status&&(D.status={...L.status}),L.file&&(D.output=L.file),L.source)D.file=L.source;else if(L.item||L.id){let F=L.item?L.item:a.query("GET_ITEM",L.id);D.file=F?he(F):null}return L.items&&(D.items=L.items.map(he)),/progress/.test(S)&&(D.progress=L.progress),L.hasOwnProperty("origin")&&L.hasOwnProperty("target")&&(D.origin=L.origin,D.target=L.target),D},h={DID_DESTROY:f("destroy"),DID_INIT:f("init"),DID_THROW_MAX_FILES:f("warning"),DID_INIT_ITEM:f("initfile"),DID_START_ITEM_LOAD:f("addfilestart"),DID_UPDATE_ITEM_LOAD_PROGRESS:f("addfileprogress"),DID_LOAD_ITEM:f("addfile"),DID_THROW_ITEM_INVALID:[f("error"),f("addfile")],DID_THROW_ITEM_LOAD_ERROR:[f("error"),f("addfile")],DID_THROW_ITEM_REMOVE_ERROR:[f("error"),f("removefile")],DID_PREPARE_OUTPUT:f("preparefile"),DID_START_ITEM_PROCESSING:f("processfilestart"),DID_UPDATE_ITEM_PROCESS_PROGRESS:f("processfileprogress"),DID_ABORT_ITEM_PROCESSING:f("processfileabort"),DID_COMPLETE_ITEM_PROCESSING:f("processfile"),DID_COMPLETE_ITEM_PROCESSING_ALL:f("processfiles"),DID_REVERT_ITEM_PROCESSING:f("processfilerevert"),DID_THROW_ITEM_PROCESSING_ERROR:[f("error"),f("processfile")],DID_REMOVE_ITEM:f("removefile"),DID_UPDATE_ITEMS:f("updatefiles"),DID_ACTIVATE_ITEM:f("activatefile"),DID_REORDER_ITEMS:f("reorderfiles")},I=S=>{let L={pond:O,...S};delete L.type,d.element.dispatchEvent(new CustomEvent(`FilePond:${S.type}`,{detail:L,bubbles:!0,cancelable:!0,composed:!0}));let D=[];S.hasOwnProperty("error")&&D.push(S.error),S.hasOwnProperty("file")&&D.push(S.file);let F=["type","error","file"];Object.keys(S).filter(C=>!F.includes(C)).forEach(C=>D.push(S[C])),O.fire(S.type,...D);let G=a.query(`GET_ON${S.type.toUpperCase()}`);G&&G(...D)},b=S=>{S.length&&S.filter(L=>h[L.type]).forEach(L=>{let D=h[L.type];(Array.isArray(D)?D:[D]).forEach(F=>{L.type==="DID_INIT_ITEM"?I(F(L.data)):setTimeout(()=>{I(F(L.data))},0)})})},T=S=>a.dispatch("SET_OPTIONS",{options:S}),v=S=>a.query("GET_ACTIVE_ITEM",S),y=S=>new Promise((L,D)=>{a.dispatch("REQUEST_ITEM_PREPARE",{query:S,success:F=>{L(F)},failure:F=>{D(F)}})}),E=(S,L={})=>new Promise((D,F)=>{R([{source:S,options:L}],{index:L.index}).then(G=>D(G&&G[0])).catch(F)}),_=S=>S.file&&S.id,x=(S,L)=>(typeof S=="object"&&!_(S)&&!L&&(L=S,S=void 0),a.dispatch("REMOVE_ITEM",{...L,query:S}),a.query("GET_ACTIVE_ITEM",S)===null),R=(...S)=>new Promise((L,D)=>{let F=[],G={};if(ci(S[0]))F.push.apply(F,S[0]),Object.assign(G,S[1]||{});else{let C=S[S.length-1];typeof C=="object"&&!(C instanceof Blob)&&Object.assign(G,S.pop()),F.push(...S)}a.dispatch("ADD_ITEMS",{items:F,index:G.index,interactionMethod:Re.API,success:L,failure:D})}),z=()=>a.query("GET_ACTIVE_ITEMS"),P=S=>new Promise((L,D)=>{a.dispatch("REQUEST_ITEM_PROCESSING",{query:S,success:F=>{L(F)},failure:F=>{D(F)}})}),A=(...S)=>{let L=Array.isArray(S[0])?S[0]:S,D=L.length?L:z();return Promise.all(D.map(y))},B=(...S)=>{let L=Array.isArray(S[0])?S[0]:S;if(!L.length){let D=z().filter(F=>!(F.status===U.IDLE&&F.origin===re.LOCAL)&&F.status!==U.PROCESSING&&F.status!==U.PROCESSING_COMPLETE&&F.status!==U.PROCESSING_REVERT_ERROR);return Promise.all(D.map(P))}return Promise.all(L.map(P))},w=(...S)=>{let L=Array.isArray(S[0])?S[0]:S,D;typeof L[L.length-1]=="object"?D=L.pop():Array.isArray(S[0])&&(D=S[1]);let F=z();return L.length?L.map(C=>$e(C)?F[C]?F[C].id:null:C).filter(C=>C).map(C=>x(C,D)):Promise.all(F.map(C=>x(C,D)))},O={...mi(),...g,...ts(a,i),setOptions:T,addFile:E,addFiles:R,getFile:v,processFile:P,prepareFile:y,removeFile:x,moveFile:(S,L)=>a.dispatch("MOVE_ITEM",{query:S,index:L}),getFiles:z,processFiles:B,removeFiles:w,prepareFiles:A,sort:S=>a.dispatch("SORT",{compare:S}),browse:()=>{var S=d.element.querySelector("input[type=file]");S&&S.click()},destroy:()=>{O.fire("destroy",d.element),a.dispatch("ABORT_ALL"),d._destroy(),window.removeEventListener("resize",c),document.removeEventListener("visibilitychange",n),a.dispatch("DID_DESTROY")},insertBefore:S=>Ba(d.element,S),insertAfter:S=>Na(d.element,S),appendTo:S=>S.appendChild(d.element),replaceElement:S=>{Ba(d.element,S),S.parentNode.removeChild(S),t=S},restoreElement:()=>{t&&(Na(t,d.element),d.element.parentNode.removeChild(d.element),t=null)},isAttachedTo:S=>d.element===S||t===S,element:{get:()=>d.element},status:{get:()=>a.query("GET_STATUS")}};return a.dispatch("DID_INIT"),We(O)},il=(e={})=>{let t={};return te(oi(),(a,n)=>{t[a]=n[0]}),Zd({...t,...e})},Jd=e=>e.charAt(0).toLowerCase()+e.slice(1),ep=e=>el(e.replace(/^data-/,"")),al=(e,t)=>{te(t,(i,a)=>{te(e,(n,l)=>{let o=new RegExp(i);if(!o.test(n)||(delete e[n],a===!1))return;if(ge(a)){e[a]=l;return}let s=a.group;ce(a)&&!e[s]&&(e[s]={}),e[s][Jd(n.replace(o,""))]=l}),a.mapping&&al(e[a.group],a.mapping)})},tp=(e,t={})=>{let i=[];te(e.attributes,n=>{i.push(e.attributes[n])});let a=i.filter(n=>n.name).reduce((n,l)=>{let o=se(e,l.name);return n[ep(l.name)]=o===l.name?!0:o,n},{});return al(a,t),a},ip=(e,t={})=>{let i={"^class$":"className","^multiple$":"allowMultiple","^capture$":"captureMethod","^webkitdirectory$":"allowDirectoriesOnly","^server":{group:"server",mapping:{"^process":{group:"process"},"^revert":{group:"revert"},"^fetch":{group:"fetch"},"^restore":{group:"restore"},"^load":{group:"load"}}},"^type$":!1,"^files$":!1};tt("SET_ATTRIBUTE_TO_OPTION_MAP",i);let a={...t},n=tp(e.nodeName==="FIELDSET"?e.querySelector("input[type=file]"):e,i);Object.keys(n).forEach(o=>{ce(n[o])?(ce(a[o])||(a[o]={}),Object.assign(a[o],n[o])):a[o]=n[o]}),a.files=(t.files||[]).concat(Array.from(e.querySelectorAll("input:not([type=file])")).map(o=>({source:o.value,options:{type:o.dataset.type}})));let l=il(a);return e.files&&Array.from(e.files).forEach(o=>{l.addFile(o)}),l.replaceElement(e),l},ap=(...e)=>gr(e[0])?ip(...e):il(...e),np=["fire","_read","_write"],hn=e=>{let t={};return Rn(e,t,np),t},lp=(e,t)=>e.replace(/(?:{([a-zA-Z]+)})/g,(i,a)=>t[a]),op=e=>{let t=new Blob(["(",e.toString(),")()"],{type:"application/javascript"}),i=URL.createObjectURL(t),a=new Worker(i);return{transfer:(n,l)=>{},post:(n,l,o)=>{let r=qi();a.onmessage=s=>{s.data.id===r&&l(s.data.message)},a.postMessage({id:r,message:n},o)},terminate:()=>{a.terminate(),URL.revokeObjectURL(i)}}},rp=e=>new Promise((t,i)=>{let a=new Image;a.onload=()=>{t(a)},a.onerror=n=>{i(n)},a.src=e}),nl=(e,t)=>{let i=e.slice(0,e.size,e.type);return i.lastModifiedDate=e.lastModifiedDate,i.name=t,i},sp=e=>nl(e,e.name),bn=[],cp=e=>{if(bn.includes(e))return;bn.push(e);let t=e({addFilter:ss,utils:{Type:M,forin:te,isString:ge,isFile:Je,toNaturalFileSize:Bn,replaceInString:lp,getExtensionFromFilename:ui,getFilenameWithoutExtension:Fn,guesstimateMimeType:$n,getFileFromBlob:ht,getFilenameFromURL:Dt,createRoute:fe,createWorker:op,createView:ne,createItemAPI:he,loadImage:rp,copyFile:sp,renameFile:nl,createBlob:Pn,applyFilterChain:Ae,text:ae,getNumericAspectRatioFromString:wn},views:{fileActionButton:Cn}});cs(t.options)},dp=()=>Object.prototype.toString.call(window.operamini)==="[object OperaMini]",pp=()=>"Promise"in window,mp=()=>"slice"in Blob.prototype,up=()=>"URL"in window&&"createObjectURL"in window.URL,gp=()=>"visibilityState"in document,fp=()=>"performance"in window,hp=()=>"supports"in(window.CSS||{}),bp=()=>/MSIE|Trident/.test(window.navigator.userAgent),Gi=(()=>{let e=En()&&!dp()&&gp()&&pp()&&mp()&&up()&&fp()&&(hp()||bp());return()=>e})(),Ue={apps:[]},Ep="filepond",it=()=>{},ll={},Et={},Ct={},Ui={},gt=it,ft=it,Wi=it,Hi=it,ve=it,ji=it,Ft=it;if(Gi()){Vr(()=>{Ue.apps.forEach(i=>i._read())},i=>{Ue.apps.forEach(a=>a._write(i))});let e=()=>{document.dispatchEvent(new CustomEvent("FilePond:loaded",{detail:{supported:Gi,create:gt,destroy:ft,parse:Wi,find:Hi,registerPlugin:ve,setOptions:Ft}})),document.removeEventListener("DOMContentLoaded",e)};document.readyState!=="loading"?setTimeout(()=>e(),0):document.addEventListener("DOMContentLoaded",e);let t=()=>te(oi(),(i,a)=>{Ui[i]=a[1]});ll={...Ln},Ct={...re},Et={...U},Ui={},t(),gt=(...i)=>{let a=ap(...i);return a.on("destroy",ft),Ue.apps.push(a),hn(a)},ft=i=>{let a=Ue.apps.findIndex(n=>n.isAttachedTo(i));return a>=0?(Ue.apps.splice(a,1)[0].restoreElement(),!0):!1},Wi=i=>Array.from(i.querySelectorAll(`.${Ep}`)).filter(l=>!Ue.apps.find(o=>o.isAttachedTo(l))).map(l=>gt(l)),Hi=i=>{let a=Ue.apps.find(n=>n.isAttachedTo(i));return a?hn(a):null},ve=(...i)=>{i.forEach(cp),t()},ji=()=>{let i={};return te(oi(),(a,n)=>{i[a]=n[0]}),i},Ft=i=>(ce(i)&&(Ue.apps.forEach(a=>{a.setOptions(i)}),ds(i)),ji())}function ol(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable})),i.push.apply(i,a)}return i}function xl(e){for(var t=1;te.length)&&(t=e.length);for(var i=0,a=new Array(t);i
',Cp=Number.isNaN||De.isNaN;function j(e){return typeof e=="number"&&!Cp(e)}var Tl=function(t){return t>0&&t<1/0};function oa(e){return typeof e>"u"}function lt(e){return sa(e)==="object"&&e!==null}var Bp=Object.prototype.hasOwnProperty;function It(e){if(!lt(e))return!1;try{var t=e.constructor,i=t.prototype;return t&&i&&Bp.call(i,"isPrototypeOf")}catch{return!1}}function be(e){return typeof e=="function"}var Np=Array.prototype.slice;function zl(e){return Array.from?Array.from(e):Np.call(e)}function le(e,t){return e&&be(t)&&(Array.isArray(e)||j(e.length)?zl(e).forEach(function(i,a){t.call(e,i,a,e)}):lt(e)&&Object.keys(e).forEach(function(i){t.call(e,e[i],i,e)})),e}var J=Object.assign||function(t){for(var i=arguments.length,a=new Array(i>1?i-1:0),n=1;n0&&a.forEach(function(l){lt(l)&&Object.keys(l).forEach(function(o){t[o]=l[o]})}),t},kp=/\.\d*(?:0|9){12}\d*$/;function xt(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:1e11;return kp.test(e)?Math.round(e*t)/t:e}var Vp=/^width|height|left|top|marginLeft|marginTop$/;function je(e,t){var i=e.style;le(t,function(a,n){Vp.test(n)&&j(a)&&(a="".concat(a,"px")),i[n]=a})}function Gp(e,t){return e.classList?e.classList.contains(t):e.className.indexOf(t)>-1}function de(e,t){if(t){if(j(e.length)){le(e,function(a){de(a,t)});return}if(e.classList){e.classList.add(t);return}var i=e.className.trim();i?i.indexOf(t)<0&&(e.className="".concat(i," ").concat(t)):e.className=t}}function Fe(e,t){if(t){if(j(e.length)){le(e,function(i){Fe(i,t)});return}if(e.classList){e.classList.remove(t);return}e.className.indexOf(t)>=0&&(e.className=e.className.replace(t,""))}}function vt(e,t,i){if(t){if(j(e.length)){le(e,function(a){vt(a,t,i)});return}i?de(e,t):Fe(e,t)}}var Up=/([a-z\d])([A-Z])/g;function xa(e){return e.replace(Up,"$1-$2").toLowerCase()}function ba(e,t){return lt(e[t])?e[t]:e.dataset?e.dataset[t]:e.getAttribute("data-".concat(xa(t)))}function Wt(e,t,i){lt(i)?e[t]=i:e.dataset?e.dataset[t]=i:e.setAttribute("data-".concat(xa(t)),i)}function Wp(e,t){if(lt(e[t]))try{delete e[t]}catch{e[t]=void 0}else if(e.dataset)try{delete e.dataset[t]}catch{e.dataset[t]=void 0}else e.removeAttribute("data-".concat(xa(t)))}var Ol=/\s\s*/,Fl=function(){var e=!1;if(Ti){var t=!1,i=function(){},a=Object.defineProperty({},"once",{get:function(){return e=!0,t},set:function(l){t=l}});De.addEventListener("test",i,a),De.removeEventListener("test",i,a)}return e}();function Oe(e,t,i){var a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},n=i;t.trim().split(Ol).forEach(function(l){if(!Fl){var o=e.listeners;o&&o[l]&&o[l][i]&&(n=o[l][i],delete o[l][i],Object.keys(o[l]).length===0&&delete o[l],Object.keys(o).length===0&&delete e.listeners)}e.removeEventListener(l,n,a)})}function Se(e,t,i){var a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},n=i;t.trim().split(Ol).forEach(function(l){if(a.once&&!Fl){var o=e.listeners,r=o===void 0?{}:o;n=function(){delete r[l][i],e.removeEventListener(l,n,a);for(var p=arguments.length,c=new Array(p),d=0;dMath.abs(i)&&(i=m)})}),i}function bi(e,t){var i=e.pageX,a=e.pageY,n={endX:i,endY:a};return t?n:xl({startX:i,startY:a},n)}function Yp(e){var t=0,i=0,a=0;return le(e,function(n){var l=n.startX,o=n.startY;t+=l,i+=o,a+=1}),t/=a,i/=a,{pageX:t,pageY:i}}function Ye(e){var t=e.aspectRatio,i=e.height,a=e.width,n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"contain",l=Tl(a),o=Tl(i);if(l&&o){var r=i*t;n==="contain"&&r>a||n==="cover"&&r90?{width:s,height:r}:{width:r,height:s}}function $p(e,t,i,a){var n=t.aspectRatio,l=t.naturalWidth,o=t.naturalHeight,r=t.rotate,s=r===void 0?0:r,p=t.scaleX,c=p===void 0?1:p,d=t.scaleY,m=d===void 0?1:d,u=i.aspectRatio,g=i.naturalWidth,f=i.naturalHeight,h=a.fillColor,I=h===void 0?"transparent":h,b=a.imageSmoothingEnabled,T=b===void 0?!0:b,v=a.imageSmoothingQuality,y=v===void 0?"low":v,E=a.maxWidth,_=E===void 0?1/0:E,x=a.maxHeight,R=x===void 0?1/0:x,z=a.minWidth,P=z===void 0?0:z,A=a.minHeight,B=A===void 0?0:A,w=document.createElement("canvas"),O=w.getContext("2d"),S=Ye({aspectRatio:u,width:_,height:R}),L=Ye({aspectRatio:u,width:P,height:B},"cover"),D=Math.min(S.width,Math.max(L.width,g)),F=Math.min(S.height,Math.max(L.height,f)),G=Ye({aspectRatio:n,width:_,height:R}),C=Ye({aspectRatio:n,width:P,height:B},"cover"),q=Math.min(G.width,Math.max(C.width,l)),X=Math.min(G.height,Math.max(C.height,o)),K=[-q/2,-X/2,q,X];return w.width=xt(D),w.height=xt(F),O.fillStyle=I,O.fillRect(0,0,D,F),O.save(),O.translate(D/2,F/2),O.rotate(s*Math.PI/180),O.scale(c,m),O.imageSmoothingEnabled=T,O.imageSmoothingQuality=y,O.drawImage.apply(O,[e].concat(Rl(K.map(function(pe){return Math.floor(xt(pe))})))),O.restore(),w}var Cl=String.fromCharCode;function Xp(e,t,i){var a="";i+=t;for(var n=t;n0;)i.push(Cl.apply(null,zl(n.subarray(0,a)))),n=n.subarray(a);return"data:".concat(t,";base64,").concat(btoa(i.join("")))}function Jp(e){var t=new DataView(e),i;try{var a,n,l;if(t.getUint8(0)===255&&t.getUint8(1)===216)for(var o=t.byteLength,r=2;r+1=8&&(l=p+d)}}}if(l){var m=t.getUint16(l,a),u,g;for(g=0;g=0?l:Al),height:Math.max(a.offsetHeight,o>=0?o:Pl)};this.containerData=r,je(n,{width:r.width,height:r.height}),de(t,Ee),Fe(n,Ee)},initCanvas:function(){var t=this.containerData,i=this.imageData,a=this.options.viewMode,n=Math.abs(i.rotate)%180===90,l=n?i.naturalHeight:i.naturalWidth,o=n?i.naturalWidth:i.naturalHeight,r=l/o,s=t.width,p=t.height;t.height*r>t.width?a===3?s=t.height*r:p=t.width/r:a===3?p=t.width/r:s=t.height*r;var c={aspectRatio:r,naturalWidth:l,naturalHeight:o,width:s,height:p};this.canvasData=c,this.limited=a===1||a===2,this.limitCanvas(!0,!0),c.width=Math.min(Math.max(c.width,c.minWidth),c.maxWidth),c.height=Math.min(Math.max(c.height,c.minHeight),c.maxHeight),c.left=(t.width-c.width)/2,c.top=(t.height-c.height)/2,c.oldLeft=c.left,c.oldTop=c.top,this.initialCanvasData=J({},c)},limitCanvas:function(t,i){var a=this.options,n=this.containerData,l=this.canvasData,o=this.cropBoxData,r=a.viewMode,s=l.aspectRatio,p=this.cropped&&o;if(t){var c=Number(a.minCanvasWidth)||0,d=Number(a.minCanvasHeight)||0;r>1?(c=Math.max(c,n.width),d=Math.max(d,n.height),r===3&&(d*s>c?c=d*s:d=c/s)):r>0&&(c?c=Math.max(c,p?o.width:0):d?d=Math.max(d,p?o.height:0):p&&(c=o.width,d=o.height,d*s>c?c=d*s:d=c/s));var m=Ye({aspectRatio:s,width:c,height:d});c=m.width,d=m.height,l.minWidth=c,l.minHeight=d,l.maxWidth=1/0,l.maxHeight=1/0}if(i)if(r>(p?0:1)){var u=n.width-l.width,g=n.height-l.height;l.minLeft=Math.min(0,u),l.minTop=Math.min(0,g),l.maxLeft=Math.max(0,u),l.maxTop=Math.max(0,g),p&&this.limited&&(l.minLeft=Math.min(o.left,o.left+(o.width-l.width)),l.minTop=Math.min(o.top,o.top+(o.height-l.height)),l.maxLeft=o.left,l.maxTop=o.top,r===2&&(l.width>=n.width&&(l.minLeft=Math.min(0,u),l.maxLeft=Math.max(0,u)),l.height>=n.height&&(l.minTop=Math.min(0,g),l.maxTop=Math.max(0,g))))}else l.minLeft=-l.width,l.minTop=-l.height,l.maxLeft=n.width,l.maxTop=n.height},renderCanvas:function(t,i){var a=this.canvasData,n=this.imageData;if(i){var l=qp({width:n.naturalWidth*Math.abs(n.scaleX||1),height:n.naturalHeight*Math.abs(n.scaleY||1),degree:n.rotate||0}),o=l.width,r=l.height,s=a.width*(o/a.naturalWidth),p=a.height*(r/a.naturalHeight);a.left-=(s-a.width)/2,a.top-=(p-a.height)/2,a.width=s,a.height=p,a.aspectRatio=o/r,a.naturalWidth=o,a.naturalHeight=r,this.limitCanvas(!0,!1)}(a.width>a.maxWidth||a.widtha.maxHeight||a.heighti.width?l.height=l.width/a:l.width=l.height*a),this.cropBoxData=l,this.limitCropBox(!0,!0),l.width=Math.min(Math.max(l.width,l.minWidth),l.maxWidth),l.height=Math.min(Math.max(l.height,l.minHeight),l.maxHeight),l.width=Math.max(l.minWidth,l.width*n),l.height=Math.max(l.minHeight,l.height*n),l.left=i.left+(i.width-l.width)/2,l.top=i.top+(i.height-l.height)/2,l.oldLeft=l.left,l.oldTop=l.top,this.initialCropBoxData=J({},l)},limitCropBox:function(t,i){var a=this.options,n=this.containerData,l=this.canvasData,o=this.cropBoxData,r=this.limited,s=a.aspectRatio;if(t){var p=Number(a.minCropBoxWidth)||0,c=Number(a.minCropBoxHeight)||0,d=r?Math.min(n.width,l.width,l.width+l.left,n.width-l.left):n.width,m=r?Math.min(n.height,l.height,l.height+l.top,n.height-l.top):n.height;p=Math.min(p,n.width),c=Math.min(c,n.height),s&&(p&&c?c*s>p?c=p/s:p=c*s:p?c=p/s:c&&(p=c*s),m*s>d?m=d/s:d=m*s),o.minWidth=Math.min(p,d),o.minHeight=Math.min(c,m),o.maxWidth=d,o.maxHeight=m}i&&(r?(o.minLeft=Math.max(0,l.left),o.minTop=Math.max(0,l.top),o.maxLeft=Math.min(n.width,l.left+l.width)-o.width,o.maxTop=Math.min(n.height,l.top+l.height)-o.height):(o.minLeft=0,o.minTop=0,o.maxLeft=n.width-o.width,o.maxTop=n.height-o.height))},renderCropBox:function(){var t=this.options,i=this.containerData,a=this.cropBoxData;(a.width>a.maxWidth||a.widtha.maxHeight||a.height=i.width&&a.height>=i.height?_l:Ia),je(this.cropBox,J({width:a.width,height:a.height},Gt({translateX:a.left,translateY:a.top}))),this.cropped&&this.limited&&this.limitCanvas(!0,!0),this.disabled||this.output()},output:function(){this.preview(),yt(this.element,ma,this.getData())}},im={initPreview:function(){var t=this.element,i=this.crossOrigin,a=this.options.preview,n=i?this.crossOriginUrl:this.url,l=t.alt||"The image to preview",o=document.createElement("img");if(i&&(o.crossOrigin=i),o.src=n,o.alt=l,this.viewBox.appendChild(o),this.viewBoxImage=o,!!a){var r=a;typeof a=="string"?r=t.ownerDocument.querySelectorAll(a):a.querySelector&&(r=[a]),this.previews=r,le(r,function(s){var p=document.createElement("img");Wt(s,hi,{width:s.offsetWidth,height:s.offsetHeight,html:s.innerHTML}),i&&(p.crossOrigin=i),p.src=n,p.alt=l,p.style.cssText='display:block;width:100%;height:auto;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation:0deg!important;"',s.innerHTML="",s.appendChild(p)})}},resetPreview:function(){le(this.previews,function(t){var i=ba(t,hi);je(t,{width:i.width,height:i.height}),t.innerHTML=i.html,Wp(t,hi)})},preview:function(){var t=this.imageData,i=this.canvasData,a=this.cropBoxData,n=a.width,l=a.height,o=t.width,r=t.height,s=a.left-i.left-t.left,p=a.top-i.top-t.top;!this.cropped||this.disabled||(je(this.viewBoxImage,J({width:o,height:r},Gt(J({translateX:-s,translateY:-p},t)))),le(this.previews,function(c){var d=ba(c,hi),m=d.width,u=d.height,g=m,f=u,h=1;n&&(h=m/n,f=l*h),l&&f>u&&(h=u/l,g=n*h,f=u),je(c,{width:g,height:f}),je(c.getElementsByTagName("img")[0],J({width:o*h,height:r*h},Gt(J({translateX:-s*h,translateY:-p*h},t))))}))}},am={bind:function(){var t=this.element,i=this.options,a=this.cropper;be(i.cropstart)&&Se(t,fa,i.cropstart),be(i.cropmove)&&Se(t,ga,i.cropmove),be(i.cropend)&&Se(t,ua,i.cropend),be(i.crop)&&Se(t,ma,i.crop),be(i.zoom)&&Se(t,ha,i.zoom),Se(a,pl,this.onCropStart=this.cropStart.bind(this)),i.zoomable&&i.zoomOnWheel&&Se(a,hl,this.onWheel=this.wheel.bind(this),{passive:!1,capture:!0}),i.toggleDragModeOnDblclick&&Se(a,dl,this.onDblclick=this.dblclick.bind(this)),Se(t.ownerDocument,ml,this.onCropMove=this.cropMove.bind(this)),Se(t.ownerDocument,ul,this.onCropEnd=this.cropEnd.bind(this)),i.responsive&&Se(window,fl,this.onResize=this.resize.bind(this))},unbind:function(){var t=this.element,i=this.options,a=this.cropper;be(i.cropstart)&&Oe(t,fa,i.cropstart),be(i.cropmove)&&Oe(t,ga,i.cropmove),be(i.cropend)&&Oe(t,ua,i.cropend),be(i.crop)&&Oe(t,ma,i.crop),be(i.zoom)&&Oe(t,ha,i.zoom),Oe(a,pl,this.onCropStart),i.zoomable&&i.zoomOnWheel&&Oe(a,hl,this.onWheel,{passive:!1,capture:!0}),i.toggleDragModeOnDblclick&&Oe(a,dl,this.onDblclick),Oe(t.ownerDocument,ml,this.onCropMove),Oe(t.ownerDocument,ul,this.onCropEnd),i.responsive&&Oe(window,fl,this.onResize)}},nm={resize:function(){if(!this.disabled){var t=this.options,i=this.container,a=this.containerData,n=i.offsetWidth/a.width,l=i.offsetHeight/a.height,o=Math.abs(n-1)>Math.abs(l-1)?n:l;if(o!==1){var r,s;t.restore&&(r=this.getCanvasData(),s=this.getCropBoxData()),this.render(),t.restore&&(this.setCanvasData(le(r,function(p,c){r[c]=p*o})),this.setCropBoxData(le(s,function(p,c){s[c]=p*o})))}}},dblclick:function(){this.disabled||this.options.dragMode===Ml||this.setDragMode(Gp(this.dragBox,da)?Ll:va)},wheel:function(t){var i=this,a=Number(this.options.wheelZoomRatio)||.1,n=1;this.disabled||(t.preventDefault(),!this.wheeling&&(this.wheeling=!0,setTimeout(function(){i.wheeling=!1},50),t.deltaY?n=t.deltaY>0?1:-1:t.wheelDelta?n=-t.wheelDelta/120:t.detail&&(n=t.detail>0?1:-1),this.zoom(-n*a,t)))},cropStart:function(t){var i=t.buttons,a=t.button;if(!(this.disabled||(t.type==="mousedown"||t.type==="pointerdown"&&t.pointerType==="mouse")&&(j(i)&&i!==1||j(a)&&a!==0||t.ctrlKey))){var n=this.options,l=this.pointers,o;t.changedTouches?le(t.changedTouches,function(r){l[r.identifier]=bi(r)}):l[t.pointerId||0]=bi(t),Object.keys(l).length>1&&n.zoomable&&n.zoomOnTouch?o=wl:o=ba(t.target,Ut),Pp.test(o)&&yt(this.element,fa,{originalEvent:t,action:o})!==!1&&(t.preventDefault(),this.action=o,this.cropping=!1,o===Sl&&(this.cropping=!0,de(this.dragBox,Ei)))}},cropMove:function(t){var i=this.action;if(!(this.disabled||!i)){var a=this.pointers;t.preventDefault(),yt(this.element,ga,{originalEvent:t,action:i})!==!1&&(t.changedTouches?le(t.changedTouches,function(n){J(a[n.identifier]||{},bi(n,!0))}):J(a[t.pointerId||0]||{},bi(t,!0)),this.change(t))}},cropEnd:function(t){if(!this.disabled){var i=this.action,a=this.pointers;t.changedTouches?le(t.changedTouches,function(n){delete a[n.identifier]}):delete a[t.pointerId||0],i&&(t.preventDefault(),Object.keys(a).length||(this.action=""),this.cropping&&(this.cropping=!1,vt(this.dragBox,Ei,this.cropped&&this.options.modal)),yt(this.element,ua,{originalEvent:t,action:i}))}}},lm={change:function(t){var i=this.options,a=this.canvasData,n=this.containerData,l=this.cropBoxData,o=this.pointers,r=this.action,s=i.aspectRatio,p=l.left,c=l.top,d=l.width,m=l.height,u=p+d,g=c+m,f=0,h=0,I=n.width,b=n.height,T=!0,v;!s&&t.shiftKey&&(s=d&&m?d/m:1),this.limited&&(f=l.minLeft,h=l.minTop,I=f+Math.min(n.width,a.width,a.left+a.width),b=h+Math.min(n.height,a.height,a.top+a.height));var y=o[Object.keys(o)[0]],E={x:y.endX-y.startX,y:y.endY-y.startY},_=function(R){switch(R){case at:u+E.x>I&&(E.x=I-u);break;case nt:p+E.xb&&(E.y=b-g);break}};switch(r){case Ia:p+=E.x,c+=E.y;break;case at:if(E.x>=0&&(u>=I||s&&(c<=h||g>=b))){T=!1;break}_(at),d+=E.x,d<0&&(r=nt,d=-d,p-=d),s&&(m=d/s,c+=(l.height-m)/2);break;case He:if(E.y<=0&&(c<=h||s&&(p<=f||u>=I))){T=!1;break}_(He),m-=E.y,c+=E.y,m<0&&(r=Tt,m=-m,c-=m),s&&(d=m*s,p+=(l.width-d)/2);break;case nt:if(E.x<=0&&(p<=f||s&&(c<=h||g>=b))){T=!1;break}_(nt),d-=E.x,p+=E.x,d<0&&(r=at,d=-d,p-=d),s&&(m=d/s,c+=(l.height-m)/2);break;case Tt:if(E.y>=0&&(g>=b||s&&(p<=f||u>=I))){T=!1;break}_(Tt),m+=E.y,m<0&&(r=He,m=-m,c-=m),s&&(d=m*s,p+=(l.width-d)/2);break;case Bt:if(s){if(E.y<=0&&(c<=h||u>=I)){T=!1;break}_(He),m-=E.y,c+=E.y,d=m*s}else _(He),_(at),E.x>=0?uh&&(m-=E.y,c+=E.y):(m-=E.y,c+=E.y);d<0&&m<0?(r=Vt,m=-m,d=-d,c-=m,p-=d):d<0?(r=Nt,d=-d,p-=d):m<0&&(r=kt,m=-m,c-=m);break;case Nt:if(s){if(E.y<=0&&(c<=h||p<=f)){T=!1;break}_(He),m-=E.y,c+=E.y,d=m*s,p+=l.width-d}else _(He),_(nt),E.x<=0?p>f?(d-=E.x,p+=E.x):E.y<=0&&c<=h&&(T=!1):(d-=E.x,p+=E.x),E.y<=0?c>h&&(m-=E.y,c+=E.y):(m-=E.y,c+=E.y);d<0&&m<0?(r=kt,m=-m,d=-d,c-=m,p-=d):d<0?(r=Bt,d=-d,p-=d):m<0&&(r=Vt,m=-m,c-=m);break;case Vt:if(s){if(E.x<=0&&(p<=f||g>=b)){T=!1;break}_(nt),d-=E.x,p+=E.x,m=d/s}else _(Tt),_(nt),E.x<=0?p>f?(d-=E.x,p+=E.x):E.y>=0&&g>=b&&(T=!1):(d-=E.x,p+=E.x),E.y>=0?g=0&&(u>=I||g>=b)){T=!1;break}_(at),d+=E.x,m=d/s}else _(Tt),_(at),E.x>=0?u=0&&g>=b&&(T=!1):d+=E.x,E.y>=0?g0?r=E.y>0?kt:Bt:E.x<0&&(p-=d,r=E.y>0?Vt:Nt),E.y<0&&(c-=m),this.cropped||(Fe(this.cropBox,Ee),this.cropped=!0,this.limited&&this.limitCropBox(!0,!0));break}T&&(l.width=d,l.height=m,l.left=p,l.top=c,this.action=r,this.renderCropBox()),le(o,function(x){x.startX=x.endX,x.startY=x.endY})}},om={crop:function(){return this.ready&&!this.cropped&&!this.disabled&&(this.cropped=!0,this.limitCropBox(!0,!0),this.options.modal&&de(this.dragBox,Ei),Fe(this.cropBox,Ee),this.setCropBoxData(this.initialCropBoxData)),this},reset:function(){return this.ready&&!this.disabled&&(this.imageData=J({},this.initialImageData),this.canvasData=J({},this.initialCanvasData),this.cropBoxData=J({},this.initialCropBoxData),this.renderCanvas(),this.cropped&&this.renderCropBox()),this},clear:function(){return this.cropped&&!this.disabled&&(J(this.cropBoxData,{left:0,top:0,width:0,height:0}),this.cropped=!1,this.renderCropBox(),this.limitCanvas(!0,!0),this.renderCanvas(),Fe(this.dragBox,Ei),de(this.cropBox,Ee)),this},replace:function(t){var i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;return!this.disabled&&t&&(this.isImg&&(this.element.src=t),i?(this.url=t,this.image.src=t,this.ready&&(this.viewBoxImage.src=t,le(this.previews,function(a){a.getElementsByTagName("img")[0].src=t}))):(this.isImg&&(this.replaced=!0),this.options.data=null,this.uncreate(),this.load(t))),this},enable:function(){return this.ready&&this.disabled&&(this.disabled=!1,Fe(this.cropper,sl)),this},disable:function(){return this.ready&&!this.disabled&&(this.disabled=!0,de(this.cropper,sl)),this},destroy:function(){var t=this.element;return t[Z]?(t[Z]=void 0,this.isImg&&this.replaced&&(t.src=this.originalUrl),this.uncreate(),this):this},move:function(t){var i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:t,a=this.canvasData,n=a.left,l=a.top;return this.moveTo(oa(t)?t:n+Number(t),oa(i)?i:l+Number(i))},moveTo:function(t){var i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:t,a=this.canvasData,n=!1;return t=Number(t),i=Number(i),this.ready&&!this.disabled&&this.options.movable&&(j(t)&&(a.left=t,n=!0),j(i)&&(a.top=i,n=!0),n&&this.renderCanvas(!0)),this},zoom:function(t,i){var a=this.canvasData;return t=Number(t),t<0?t=1/(1-t):t=1+t,this.zoomTo(a.width*t/a.naturalWidth,null,i)},zoomTo:function(t,i,a){var n=this.options,l=this.canvasData,o=l.width,r=l.height,s=l.naturalWidth,p=l.naturalHeight;if(t=Number(t),t>=0&&this.ready&&!this.disabled&&n.zoomable){var c=s*t,d=p*t;if(yt(this.element,ha,{ratio:t,oldRatio:o/s,originalEvent:a})===!1)return this;if(a){var m=this.pointers,u=Dl(this.cropper),g=m&&Object.keys(m).length?Yp(m):{pageX:a.pageX,pageY:a.pageY};l.left-=(c-o)*((g.pageX-u.left-l.left)/o),l.top-=(d-r)*((g.pageY-u.top-l.top)/r)}else It(i)&&j(i.x)&&j(i.y)?(l.left-=(c-o)*((i.x-l.left)/o),l.top-=(d-r)*((i.y-l.top)/r)):(l.left-=(c-o)/2,l.top-=(d-r)/2);l.width=c,l.height=d,this.renderCanvas(!0)}return this},rotate:function(t){return this.rotateTo((this.imageData.rotate||0)+Number(t))},rotateTo:function(t){return t=Number(t),j(t)&&this.ready&&!this.disabled&&this.options.rotatable&&(this.imageData.rotate=t%360,this.renderCanvas(!0,!0)),this},scaleX:function(t){var i=this.imageData.scaleY;return this.scale(t,j(i)?i:1)},scaleY:function(t){var i=this.imageData.scaleX;return this.scale(j(i)?i:1,t)},scale:function(t){var i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:t,a=this.imageData,n=!1;return t=Number(t),i=Number(i),this.ready&&!this.disabled&&this.options.scalable&&(j(t)&&(a.scaleX=t,n=!0),j(i)&&(a.scaleY=i,n=!0),n&&this.renderCanvas(!0,!0)),this},getData:function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,i=this.options,a=this.imageData,n=this.canvasData,l=this.cropBoxData,o;if(this.ready&&this.cropped){o={x:l.left-n.left,y:l.top-n.top,width:l.width,height:l.height};var r=a.width/a.naturalWidth;if(le(o,function(c,d){o[d]=c/r}),t){var s=Math.round(o.y+o.height),p=Math.round(o.x+o.width);o.x=Math.round(o.x),o.y=Math.round(o.y),o.width=p-o.x,o.height=s-o.y}}else o={x:0,y:0,width:0,height:0};return i.rotatable&&(o.rotate=a.rotate||0),i.scalable&&(o.scaleX=a.scaleX||1,o.scaleY=a.scaleY||1),o},setData:function(t){var i=this.options,a=this.imageData,n=this.canvasData,l={};if(this.ready&&!this.disabled&&It(t)){var o=!1;i.rotatable&&j(t.rotate)&&t.rotate!==a.rotate&&(a.rotate=t.rotate,o=!0),i.scalable&&(j(t.scaleX)&&t.scaleX!==a.scaleX&&(a.scaleX=t.scaleX,o=!0),j(t.scaleY)&&t.scaleY!==a.scaleY&&(a.scaleY=t.scaleY,o=!0)),o&&this.renderCanvas(!0,!0);var r=a.width/a.naturalWidth;j(t.x)&&(l.left=t.x*r+n.left),j(t.y)&&(l.top=t.y*r+n.top),j(t.width)&&(l.width=t.width*r),j(t.height)&&(l.height=t.height*r),this.setCropBoxData(l)}return this},getContainerData:function(){return this.ready?J({},this.containerData):{}},getImageData:function(){return this.sized?J({},this.imageData):{}},getCanvasData:function(){var t=this.canvasData,i={};return this.ready&&le(["left","top","width","height","naturalWidth","naturalHeight"],function(a){i[a]=t[a]}),i},setCanvasData:function(t){var i=this.canvasData,a=i.aspectRatio;return this.ready&&!this.disabled&&It(t)&&(j(t.left)&&(i.left=t.left),j(t.top)&&(i.top=t.top),j(t.width)?(i.width=t.width,i.height=t.width/a):j(t.height)&&(i.height=t.height,i.width=t.height*a),this.renderCanvas(!0)),this},getCropBoxData:function(){var t=this.cropBoxData,i;return this.ready&&this.cropped&&(i={left:t.left,top:t.top,width:t.width,height:t.height}),i||{}},setCropBoxData:function(t){var i=this.cropBoxData,a=this.options.aspectRatio,n,l;return this.ready&&this.cropped&&!this.disabled&&It(t)&&(j(t.left)&&(i.left=t.left),j(t.top)&&(i.top=t.top),j(t.width)&&t.width!==i.width&&(n=!0,i.width=t.width),j(t.height)&&t.height!==i.height&&(l=!0,i.height=t.height),a&&(n?i.height=i.width/a:l&&(i.width=i.height*a)),this.renderCropBox()),this},getCroppedCanvas:function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!this.ready||!window.HTMLCanvasElement)return null;var i=this.canvasData,a=$p(this.image,this.imageData,i,t);if(!this.cropped)return a;var n=this.getData(t.rounded),l=n.x,o=n.y,r=n.width,s=n.height,p=a.width/Math.floor(i.naturalWidth);p!==1&&(l*=p,o*=p,r*=p,s*=p);var c=r/s,d=Ye({aspectRatio:c,width:t.maxWidth||1/0,height:t.maxHeight||1/0}),m=Ye({aspectRatio:c,width:t.minWidth||0,height:t.minHeight||0},"cover"),u=Ye({aspectRatio:c,width:t.width||(p!==1?a.width:r),height:t.height||(p!==1?a.height:s)}),g=u.width,f=u.height;g=Math.min(d.width,Math.max(m.width,g)),f=Math.min(d.height,Math.max(m.height,f));var h=document.createElement("canvas"),I=h.getContext("2d");h.width=xt(g),h.height=xt(f),I.fillStyle=t.fillColor||"transparent",I.fillRect(0,0,g,f);var b=t.imageSmoothingEnabled,T=b===void 0?!0:b,v=t.imageSmoothingQuality;I.imageSmoothingEnabled=T,v&&(I.imageSmoothingQuality=v);var y=a.width,E=a.height,_=l,x=o,R,z,P,A,B,w;_<=-r||_>y?(_=0,R=0,P=0,B=0):_<=0?(P=-_,_=0,R=Math.min(y,r+_),B=R):_<=y&&(P=0,R=Math.min(r,y-_),B=R),R<=0||x<=-s||x>E?(x=0,z=0,A=0,w=0):x<=0?(A=-x,x=0,z=Math.min(E,s+x),w=z):x<=E&&(A=0,z=Math.min(s,E-x),w=z);var O=[_,x,R,z];if(B>0&&w>0){var S=g/r;O.push(P*S,A*S,B*S,w*S)}return I.drawImage.apply(I,[a].concat(Rl(O.map(function(L){return Math.floor(xt(L))})))),h},setAspectRatio:function(t){var i=this.options;return!this.disabled&&!oa(t)&&(i.aspectRatio=Math.max(0,t)||NaN,this.ready&&(this.initCropBox(),this.cropped&&this.renderCropBox())),this},setDragMode:function(t){var i=this.options,a=this.dragBox,n=this.face;if(this.ready&&!this.disabled){var l=t===va,o=i.movable&&t===Ll;t=l||o?t:Ml,i.dragMode=t,Wt(a,Ut,t),vt(a,da,l),vt(a,pa,o),i.cropBoxMovable||(Wt(n,Ut,t),vt(n,da,l),vt(n,pa,o))}return this}},rm=De.Cropper,ya=function(){function e(t){var i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(Ip(this,e),!t||!Fp.test(t.tagName))throw new Error("The first argument is required and must be an or element.");this.element=t,this.options=J({},El,It(i)&&i),this.cropped=!1,this.disabled=!1,this.pointers={},this.ready=!1,this.reloading=!1,this.replaced=!1,this.sized=!1,this.sizing=!1,this.init()}return vp(e,[{key:"init",value:function(){var i=this.element,a=i.tagName.toLowerCase(),n;if(!i[Z]){if(i[Z]=this,a==="img"){if(this.isImg=!0,n=i.getAttribute("src")||"",this.originalUrl=n,!n)return;n=i.src}else a==="canvas"&&window.HTMLCanvasElement&&(n=i.toDataURL());this.load(n)}}},{key:"load",value:function(i){var a=this;if(i){this.url=i,this.imageData={};var n=this.element,l=this.options;if(!l.rotatable&&!l.scalable&&(l.checkOrientation=!1),!l.checkOrientation||!window.ArrayBuffer){this.clone();return}if(zp.test(i)){Op.test(i)?this.read(Qp(i)):this.clone();return}var o=new XMLHttpRequest,r=this.clone.bind(this);this.reloading=!0,this.xhr=o,o.onabort=r,o.onerror=r,o.ontimeout=r,o.onprogress=function(){o.getResponseHeader("content-type")!==bl&&o.abort()},o.onload=function(){a.read(o.response)},o.onloadend=function(){a.reloading=!1,a.xhr=null},l.checkCrossOrigin&&Il(i)&&n.crossOrigin&&(i=vl(i)),o.open("GET",i,!0),o.responseType="arraybuffer",o.withCredentials=n.crossOrigin==="use-credentials",o.send()}}},{key:"read",value:function(i){var a=this.options,n=this.imageData,l=Jp(i),o=0,r=1,s=1;if(l>1){this.url=Zp(i,bl);var p=em(l);o=p.rotate,r=p.scaleX,s=p.scaleY}a.rotatable&&(n.rotate=o),a.scalable&&(n.scaleX=r,n.scaleY=s),this.clone()}},{key:"clone",value:function(){var i=this.element,a=this.url,n=i.crossOrigin,l=a;this.options.checkCrossOrigin&&Il(a)&&(n||(n="anonymous"),l=vl(a)),this.crossOrigin=n,this.crossOriginUrl=l;var o=document.createElement("img");n&&(o.crossOrigin=n),o.src=l||a,o.alt=i.alt||"The image to crop",this.image=o,o.onload=this.start.bind(this),o.onerror=this.stop.bind(this),de(o,cl),i.parentNode.insertBefore(o,i.nextSibling)}},{key:"start",value:function(){var i=this,a=this.image;a.onload=null,a.onerror=null,this.sizing=!0;var n=De.navigator&&/(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(De.navigator.userAgent),l=function(p,c){J(i.imageData,{naturalWidth:p,naturalHeight:c,aspectRatio:p/c}),i.initialImageData=J({},i.imageData),i.sizing=!1,i.sized=!0,i.build()};if(a.naturalWidth&&!n){l(a.naturalWidth,a.naturalHeight);return}var o=document.createElement("img"),r=document.body||document.documentElement;this.sizingImage=o,o.onload=function(){l(o.width,o.height),n||r.removeChild(o)},o.src=a.src,n||(o.style.cssText="left:0;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;opacity:0;position:absolute;top:0;z-index:-1;",r.appendChild(o))}},{key:"stop",value:function(){var i=this.image;i.onload=null,i.onerror=null,i.parentNode.removeChild(i),this.image=null}},{key:"build",value:function(){if(!(!this.sized||this.ready)){var i=this.element,a=this.options,n=this.image,l=i.parentNode,o=document.createElement("div");o.innerHTML=Dp;var r=o.querySelector(".".concat(Z,"-container")),s=r.querySelector(".".concat(Z,"-canvas")),p=r.querySelector(".".concat(Z,"-drag-box")),c=r.querySelector(".".concat(Z,"-crop-box")),d=c.querySelector(".".concat(Z,"-face"));this.container=l,this.cropper=r,this.canvas=s,this.dragBox=p,this.cropBox=c,this.viewBox=r.querySelector(".".concat(Z,"-view-box")),this.face=d,s.appendChild(n),de(i,Ee),l.insertBefore(r,i.nextSibling),Fe(n,cl),this.initPreview(),this.bind(),a.initialAspectRatio=Math.max(0,a.initialAspectRatio)||NaN,a.aspectRatio=Math.max(0,a.aspectRatio)||NaN,a.viewMode=Math.max(0,Math.min(3,Math.round(a.viewMode)))||0,de(c,Ee),a.guides||de(c.getElementsByClassName("".concat(Z,"-dashed")),Ee),a.center||de(c.getElementsByClassName("".concat(Z,"-center")),Ee),a.background&&de(r,"".concat(Z,"-bg")),a.highlight||de(d,wp),a.cropBoxMovable&&(de(d,pa),Wt(d,Ut,Ia)),a.cropBoxResizable||(de(c.getElementsByClassName("".concat(Z,"-line")),Ee),de(c.getElementsByClassName("".concat(Z,"-point")),Ee)),this.render(),this.ready=!0,this.setDragMode(a.dragMode),a.autoCrop&&this.crop(),this.setData(a.data),be(a.ready)&&Se(i,gl,a.ready,{once:!0}),yt(i,gl)}}},{key:"unbuild",value:function(){if(this.ready){this.ready=!1,this.unbind(),this.resetPreview();var i=this.cropper.parentNode;i&&i.removeChild(this.cropper),Fe(this.element,Ee)}}},{key:"uncreate",value:function(){this.ready?(this.unbuild(),this.ready=!1,this.cropped=!1):this.sizing?(this.sizingImage.onload=null,this.sizing=!1,this.sized=!1):this.reloading?(this.xhr.onabort=null,this.xhr.abort()):this.image&&this.stop()}}],[{key:"noConflict",value:function(){return window.Cropper=rm,e}},{key:"setDefaults",value:function(i){J(El,It(i)&&i)}}])}();J(ya.prototype,tm,im,am,nm,lm,om);var Bl={"application/prs.cww":["cww"],"application/prs.xsf+xml":["xsf"],"application/vnd.1000minds.decision-model+xml":["1km"],"application/vnd.3gpp.pic-bw-large":["plb"],"application/vnd.3gpp.pic-bw-small":["psb"],"application/vnd.3gpp.pic-bw-var":["pvb"],"application/vnd.3gpp2.tcap":["tcap"],"application/vnd.3m.post-it-notes":["pwn"],"application/vnd.accpac.simply.aso":["aso"],"application/vnd.accpac.simply.imp":["imp"],"application/vnd.acucobol":["acu"],"application/vnd.acucorp":["atc","acutc"],"application/vnd.adobe.air-application-installer-package+zip":["air"],"application/vnd.adobe.formscentral.fcdt":["fcdt"],"application/vnd.adobe.fxp":["fxp","fxpl"],"application/vnd.adobe.xdp+xml":["xdp"],"application/vnd.adobe.xfdf":["*xfdf"],"application/vnd.age":["age"],"application/vnd.ahead.space":["ahead"],"application/vnd.airzip.filesecure.azf":["azf"],"application/vnd.airzip.filesecure.azs":["azs"],"application/vnd.amazon.ebook":["azw"],"application/vnd.americandynamics.acc":["acc"],"application/vnd.amiga.ami":["ami"],"application/vnd.android.package-archive":["apk"],"application/vnd.anser-web-certificate-issue-initiation":["cii"],"application/vnd.anser-web-funds-transfer-initiation":["fti"],"application/vnd.antix.game-component":["atx"],"application/vnd.apple.installer+xml":["mpkg"],"application/vnd.apple.keynote":["key"],"application/vnd.apple.mpegurl":["m3u8"],"application/vnd.apple.numbers":["numbers"],"application/vnd.apple.pages":["pages"],"application/vnd.apple.pkpass":["pkpass"],"application/vnd.aristanetworks.swi":["swi"],"application/vnd.astraea-software.iota":["iota"],"application/vnd.audiograph":["aep"],"application/vnd.autodesk.fbx":["fbx"],"application/vnd.balsamiq.bmml+xml":["bmml"],"application/vnd.blueice.multipass":["mpm"],"application/vnd.bmi":["bmi"],"application/vnd.businessobjects":["rep"],"application/vnd.chemdraw+xml":["cdxml"],"application/vnd.chipnuts.karaoke-mmd":["mmd"],"application/vnd.cinderella":["cdy"],"application/vnd.citationstyles.style+xml":["csl"],"application/vnd.claymore":["cla"],"application/vnd.cloanto.rp9":["rp9"],"application/vnd.clonk.c4group":["c4g","c4d","c4f","c4p","c4u"],"application/vnd.cluetrust.cartomobile-config":["c11amc"],"application/vnd.cluetrust.cartomobile-config-pkg":["c11amz"],"application/vnd.commonspace":["csp"],"application/vnd.contact.cmsg":["cdbcmsg"],"application/vnd.cosmocaller":["cmc"],"application/vnd.crick.clicker":["clkx"],"application/vnd.crick.clicker.keyboard":["clkk"],"application/vnd.crick.clicker.palette":["clkp"],"application/vnd.crick.clicker.template":["clkt"],"application/vnd.crick.clicker.wordbank":["clkw"],"application/vnd.criticaltools.wbs+xml":["wbs"],"application/vnd.ctc-posml":["pml"],"application/vnd.cups-ppd":["ppd"],"application/vnd.curl.car":["car"],"application/vnd.curl.pcurl":["pcurl"],"application/vnd.dart":["dart"],"application/vnd.data-vision.rdz":["rdz"],"application/vnd.dbf":["dbf"],"application/vnd.dcmp+xml":["dcmp"],"application/vnd.dece.data":["uvf","uvvf","uvd","uvvd"],"application/vnd.dece.ttml+xml":["uvt","uvvt"],"application/vnd.dece.unspecified":["uvx","uvvx"],"application/vnd.dece.zip":["uvz","uvvz"],"application/vnd.denovo.fcselayout-link":["fe_launch"],"application/vnd.dna":["dna"],"application/vnd.dolby.mlp":["mlp"],"application/vnd.dpgraph":["dpg"],"application/vnd.dreamfactory":["dfac"],"application/vnd.ds-keypoint":["kpxx"],"application/vnd.dvb.ait":["ait"],"application/vnd.dvb.service":["svc"],"application/vnd.dynageo":["geo"],"application/vnd.ecowin.chart":["mag"],"application/vnd.enliven":["nml"],"application/vnd.epson.esf":["esf"],"application/vnd.epson.msf":["msf"],"application/vnd.epson.quickanime":["qam"],"application/vnd.epson.salt":["slt"],"application/vnd.epson.ssf":["ssf"],"application/vnd.eszigno3+xml":["es3","et3"],"application/vnd.ezpix-album":["ez2"],"application/vnd.ezpix-package":["ez3"],"application/vnd.fdf":["*fdf"],"application/vnd.fdsn.mseed":["mseed"],"application/vnd.fdsn.seed":["seed","dataless"],"application/vnd.flographit":["gph"],"application/vnd.fluxtime.clip":["ftc"],"application/vnd.framemaker":["fm","frame","maker","book"],"application/vnd.frogans.fnc":["fnc"],"application/vnd.frogans.ltf":["ltf"],"application/vnd.fsc.weblaunch":["fsc"],"application/vnd.fujitsu.oasys":["oas"],"application/vnd.fujitsu.oasys2":["oa2"],"application/vnd.fujitsu.oasys3":["oa3"],"application/vnd.fujitsu.oasysgp":["fg5"],"application/vnd.fujitsu.oasysprs":["bh2"],"application/vnd.fujixerox.ddd":["ddd"],"application/vnd.fujixerox.docuworks":["xdw"],"application/vnd.fujixerox.docuworks.binder":["xbd"],"application/vnd.fuzzysheet":["fzs"],"application/vnd.genomatix.tuxedo":["txd"],"application/vnd.geogebra.file":["ggb"],"application/vnd.geogebra.slides":["ggs"],"application/vnd.geogebra.tool":["ggt"],"application/vnd.geometry-explorer":["gex","gre"],"application/vnd.geonext":["gxt"],"application/vnd.geoplan":["g2w"],"application/vnd.geospace":["g3w"],"application/vnd.gmx":["gmx"],"application/vnd.google-apps.document":["gdoc"],"application/vnd.google-apps.drawing":["gdraw"],"application/vnd.google-apps.form":["gform"],"application/vnd.google-apps.jam":["gjam"],"application/vnd.google-apps.map":["gmap"],"application/vnd.google-apps.presentation":["gslides"],"application/vnd.google-apps.script":["gscript"],"application/vnd.google-apps.site":["gsite"],"application/vnd.google-apps.spreadsheet":["gsheet"],"application/vnd.google-earth.kml+xml":["kml"],"application/vnd.google-earth.kmz":["kmz"],"application/vnd.gov.sk.xmldatacontainer+xml":["xdcf"],"application/vnd.grafeq":["gqf","gqs"],"application/vnd.groove-account":["gac"],"application/vnd.groove-help":["ghf"],"application/vnd.groove-identity-message":["gim"],"application/vnd.groove-injector":["grv"],"application/vnd.groove-tool-message":["gtm"],"application/vnd.groove-tool-template":["tpl"],"application/vnd.groove-vcard":["vcg"],"application/vnd.hal+xml":["hal"],"application/vnd.handheld-entertainment+xml":["zmm"],"application/vnd.hbci":["hbci"],"application/vnd.hhe.lesson-player":["les"],"application/vnd.hp-hpgl":["hpgl"],"application/vnd.hp-hpid":["hpid"],"application/vnd.hp-hps":["hps"],"application/vnd.hp-jlyt":["jlt"],"application/vnd.hp-pcl":["pcl"],"application/vnd.hp-pclxl":["pclxl"],"application/vnd.hydrostatix.sof-data":["sfd-hdstx"],"application/vnd.ibm.minipay":["mpy"],"application/vnd.ibm.modcap":["afp","listafp","list3820"],"application/vnd.ibm.rights-management":["irm"],"application/vnd.ibm.secure-container":["sc"],"application/vnd.iccprofile":["icc","icm"],"application/vnd.igloader":["igl"],"application/vnd.immervision-ivp":["ivp"],"application/vnd.immervision-ivu":["ivu"],"application/vnd.insors.igm":["igm"],"application/vnd.intercon.formnet":["xpw","xpx"],"application/vnd.intergeo":["i2g"],"application/vnd.intu.qbo":["qbo"],"application/vnd.intu.qfx":["qfx"],"application/vnd.ipunplugged.rcprofile":["rcprofile"],"application/vnd.irepository.package+xml":["irp"],"application/vnd.is-xpr":["xpr"],"application/vnd.isac.fcs":["fcs"],"application/vnd.jam":["jam"],"application/vnd.jcp.javame.midlet-rms":["rms"],"application/vnd.jisp":["jisp"],"application/vnd.joost.joda-archive":["joda"],"application/vnd.kahootz":["ktz","ktr"],"application/vnd.kde.karbon":["karbon"],"application/vnd.kde.kchart":["chrt"],"application/vnd.kde.kformula":["kfo"],"application/vnd.kde.kivio":["flw"],"application/vnd.kde.kontour":["kon"],"application/vnd.kde.kpresenter":["kpr","kpt"],"application/vnd.kde.kspread":["ksp"],"application/vnd.kde.kword":["kwd","kwt"],"application/vnd.kenameaapp":["htke"],"application/vnd.kidspiration":["kia"],"application/vnd.kinar":["kne","knp"],"application/vnd.koan":["skp","skd","skt","skm"],"application/vnd.kodak-descriptor":["sse"],"application/vnd.las.las+xml":["lasxml"],"application/vnd.llamagraphics.life-balance.desktop":["lbd"],"application/vnd.llamagraphics.life-balance.exchange+xml":["lbe"],"application/vnd.lotus-1-2-3":["123"],"application/vnd.lotus-approach":["apr"],"application/vnd.lotus-freelance":["pre"],"application/vnd.lotus-notes":["nsf"],"application/vnd.lotus-organizer":["org"],"application/vnd.lotus-screencam":["scm"],"application/vnd.lotus-wordpro":["lwp"],"application/vnd.macports.portpkg":["portpkg"],"application/vnd.mapbox-vector-tile":["mvt"],"application/vnd.mcd":["mcd"],"application/vnd.medcalcdata":["mc1"],"application/vnd.mediastation.cdkey":["cdkey"],"application/vnd.mfer":["mwf"],"application/vnd.mfmp":["mfm"],"application/vnd.micrografx.flo":["flo"],"application/vnd.micrografx.igx":["igx"],"application/vnd.mif":["mif"],"application/vnd.mobius.daf":["daf"],"application/vnd.mobius.dis":["dis"],"application/vnd.mobius.mbk":["mbk"],"application/vnd.mobius.mqy":["mqy"],"application/vnd.mobius.msl":["msl"],"application/vnd.mobius.plc":["plc"],"application/vnd.mobius.txf":["txf"],"application/vnd.mophun.application":["mpn"],"application/vnd.mophun.certificate":["mpc"],"application/vnd.mozilla.xul+xml":["xul"],"application/vnd.ms-artgalry":["cil"],"application/vnd.ms-cab-compressed":["cab"],"application/vnd.ms-excel":["xls","xlm","xla","xlc","xlt","xlw"],"application/vnd.ms-excel.addin.macroenabled.12":["xlam"],"application/vnd.ms-excel.sheet.binary.macroenabled.12":["xlsb"],"application/vnd.ms-excel.sheet.macroenabled.12":["xlsm"],"application/vnd.ms-excel.template.macroenabled.12":["xltm"],"application/vnd.ms-fontobject":["eot"],"application/vnd.ms-htmlhelp":["chm"],"application/vnd.ms-ims":["ims"],"application/vnd.ms-lrm":["lrm"],"application/vnd.ms-officetheme":["thmx"],"application/vnd.ms-outlook":["msg"],"application/vnd.ms-pki.seccat":["cat"],"application/vnd.ms-pki.stl":["*stl"],"application/vnd.ms-powerpoint":["ppt","pps","pot"],"application/vnd.ms-powerpoint.addin.macroenabled.12":["ppam"],"application/vnd.ms-powerpoint.presentation.macroenabled.12":["pptm"],"application/vnd.ms-powerpoint.slide.macroenabled.12":["sldm"],"application/vnd.ms-powerpoint.slideshow.macroenabled.12":["ppsm"],"application/vnd.ms-powerpoint.template.macroenabled.12":["potm"],"application/vnd.ms-project":["*mpp","mpt"],"application/vnd.ms-visio.viewer":["vdx"],"application/vnd.ms-word.document.macroenabled.12":["docm"],"application/vnd.ms-word.template.macroenabled.12":["dotm"],"application/vnd.ms-works":["wps","wks","wcm","wdb"],"application/vnd.ms-wpl":["wpl"],"application/vnd.ms-xpsdocument":["xps"],"application/vnd.mseq":["mseq"],"application/vnd.musician":["mus"],"application/vnd.muvee.style":["msty"],"application/vnd.mynfc":["taglet"],"application/vnd.nato.bindingdataobject+xml":["bdo"],"application/vnd.neurolanguage.nlu":["nlu"],"application/vnd.nitf":["ntf","nitf"],"application/vnd.noblenet-directory":["nnd"],"application/vnd.noblenet-sealer":["nns"],"application/vnd.noblenet-web":["nnw"],"application/vnd.nokia.n-gage.ac+xml":["*ac"],"application/vnd.nokia.n-gage.data":["ngdat"],"application/vnd.nokia.n-gage.symbian.install":["n-gage"],"application/vnd.nokia.radio-preset":["rpst"],"application/vnd.nokia.radio-presets":["rpss"],"application/vnd.novadigm.edm":["edm"],"application/vnd.novadigm.edx":["edx"],"application/vnd.novadigm.ext":["ext"],"application/vnd.oasis.opendocument.chart":["odc"],"application/vnd.oasis.opendocument.chart-template":["otc"],"application/vnd.oasis.opendocument.database":["odb"],"application/vnd.oasis.opendocument.formula":["odf"],"application/vnd.oasis.opendocument.formula-template":["odft"],"application/vnd.oasis.opendocument.graphics":["odg"],"application/vnd.oasis.opendocument.graphics-template":["otg"],"application/vnd.oasis.opendocument.image":["odi"],"application/vnd.oasis.opendocument.image-template":["oti"],"application/vnd.oasis.opendocument.presentation":["odp"],"application/vnd.oasis.opendocument.presentation-template":["otp"],"application/vnd.oasis.opendocument.spreadsheet":["ods"],"application/vnd.oasis.opendocument.spreadsheet-template":["ots"],"application/vnd.oasis.opendocument.text":["odt"],"application/vnd.oasis.opendocument.text-master":["odm"],"application/vnd.oasis.opendocument.text-template":["ott"],"application/vnd.oasis.opendocument.text-web":["oth"],"application/vnd.olpc-sugar":["xo"],"application/vnd.oma.dd2+xml":["dd2"],"application/vnd.openblox.game+xml":["obgx"],"application/vnd.openofficeorg.extension":["oxt"],"application/vnd.openstreetmap.data+xml":["osm"],"application/vnd.openxmlformats-officedocument.presentationml.presentation":["pptx"],"application/vnd.openxmlformats-officedocument.presentationml.slide":["sldx"],"application/vnd.openxmlformats-officedocument.presentationml.slideshow":["ppsx"],"application/vnd.openxmlformats-officedocument.presentationml.template":["potx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":["xlsx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.template":["xltx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.document":["docx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.template":["dotx"],"application/vnd.osgeo.mapguide.package":["mgp"],"application/vnd.osgi.dp":["dp"],"application/vnd.osgi.subsystem":["esa"],"application/vnd.palm":["pdb","pqa","oprc"],"application/vnd.pawaafile":["paw"],"application/vnd.pg.format":["str"],"application/vnd.pg.osasli":["ei6"],"application/vnd.picsel":["efif"],"application/vnd.pmi.widget":["wg"],"application/vnd.pocketlearn":["plf"],"application/vnd.powerbuilder6":["pbd"],"application/vnd.previewsystems.box":["box"],"application/vnd.procrate.brushset":["brushset"],"application/vnd.procreate.brush":["brush"],"application/vnd.procreate.dream":["drm"],"application/vnd.proteus.magazine":["mgz"],"application/vnd.publishare-delta-tree":["qps"],"application/vnd.pvi.ptid1":["ptid"],"application/vnd.pwg-xhtml-print+xml":["xhtm"],"application/vnd.quark.quarkxpress":["qxd","qxt","qwd","qwt","qxl","qxb"],"application/vnd.rar":["rar"],"application/vnd.realvnc.bed":["bed"],"application/vnd.recordare.musicxml":["mxl"],"application/vnd.recordare.musicxml+xml":["musicxml"],"application/vnd.rig.cryptonote":["cryptonote"],"application/vnd.rim.cod":["cod"],"application/vnd.rn-realmedia":["rm"],"application/vnd.rn-realmedia-vbr":["rmvb"],"application/vnd.route66.link66+xml":["link66"],"application/vnd.sailingtracker.track":["st"],"application/vnd.seemail":["see"],"application/vnd.sema":["sema"],"application/vnd.semd":["semd"],"application/vnd.semf":["semf"],"application/vnd.shana.informed.formdata":["ifm"],"application/vnd.shana.informed.formtemplate":["itp"],"application/vnd.shana.informed.interchange":["iif"],"application/vnd.shana.informed.package":["ipk"],"application/vnd.simtech-mindmapper":["twd","twds"],"application/vnd.smaf":["mmf"],"application/vnd.smart.teacher":["teacher"],"application/vnd.software602.filler.form+xml":["fo"],"application/vnd.solent.sdkm+xml":["sdkm","sdkd"],"application/vnd.spotfire.dxp":["dxp"],"application/vnd.spotfire.sfs":["sfs"],"application/vnd.stardivision.calc":["sdc"],"application/vnd.stardivision.draw":["sda"],"application/vnd.stardivision.impress":["sdd"],"application/vnd.stardivision.math":["smf"],"application/vnd.stardivision.writer":["sdw","vor"],"application/vnd.stardivision.writer-global":["sgl"],"application/vnd.stepmania.package":["smzip"],"application/vnd.stepmania.stepchart":["sm"],"application/vnd.sun.wadl+xml":["wadl"],"application/vnd.sun.xml.calc":["sxc"],"application/vnd.sun.xml.calc.template":["stc"],"application/vnd.sun.xml.draw":["sxd"],"application/vnd.sun.xml.draw.template":["std"],"application/vnd.sun.xml.impress":["sxi"],"application/vnd.sun.xml.impress.template":["sti"],"application/vnd.sun.xml.math":["sxm"],"application/vnd.sun.xml.writer":["sxw"],"application/vnd.sun.xml.writer.global":["sxg"],"application/vnd.sun.xml.writer.template":["stw"],"application/vnd.sus-calendar":["sus","susp"],"application/vnd.svd":["svd"],"application/vnd.symbian.install":["sis","sisx"],"application/vnd.syncml+xml":["xsm"],"application/vnd.syncml.dm+wbxml":["bdm"],"application/vnd.syncml.dm+xml":["xdm"],"application/vnd.syncml.dmddf+xml":["ddf"],"application/vnd.tao.intent-module-archive":["tao"],"application/vnd.tcpdump.pcap":["pcap","cap","dmp"],"application/vnd.tmobile-livetv":["tmo"],"application/vnd.trid.tpt":["tpt"],"application/vnd.triscape.mxs":["mxs"],"application/vnd.trueapp":["tra"],"application/vnd.ufdl":["ufd","ufdl"],"application/vnd.uiq.theme":["utz"],"application/vnd.umajin":["umj"],"application/vnd.unity":["unityweb"],"application/vnd.uoml+xml":["uoml","uo"],"application/vnd.vcx":["vcx"],"application/vnd.visio":["vsd","vst","vss","vsw","vsdx","vtx"],"application/vnd.visionary":["vis"],"application/vnd.vsf":["vsf"],"application/vnd.wap.wbxml":["wbxml"],"application/vnd.wap.wmlc":["wmlc"],"application/vnd.wap.wmlscriptc":["wmlsc"],"application/vnd.webturbo":["wtb"],"application/vnd.wolfram.player":["nbp"],"application/vnd.wordperfect":["wpd"],"application/vnd.wqd":["wqd"],"application/vnd.wt.stf":["stf"],"application/vnd.xara":["xar"],"application/vnd.xfdl":["xfdl"],"application/vnd.yamaha.hv-dic":["hvd"],"application/vnd.yamaha.hv-script":["hvs"],"application/vnd.yamaha.hv-voice":["hvp"],"application/vnd.yamaha.openscoreformat":["osf"],"application/vnd.yamaha.openscoreformat.osfpvg+xml":["osfpvg"],"application/vnd.yamaha.smaf-audio":["saf"],"application/vnd.yamaha.smaf-phrase":["spf"],"application/vnd.yellowriver-custom-menu":["cmp"],"application/vnd.zul":["zir","zirz"],"application/vnd.zzazz.deck+xml":["zaz"],"application/x-7z-compressed":["7z"],"application/x-abiword":["abw"],"application/x-ace-compressed":["ace"],"application/x-apple-diskimage":["*dmg"],"application/x-arj":["arj"],"application/x-authorware-bin":["aab","x32","u32","vox"],"application/x-authorware-map":["aam"],"application/x-authorware-seg":["aas"],"application/x-bcpio":["bcpio"],"application/x-bdoc":["*bdoc"],"application/x-bittorrent":["torrent"],"application/x-blender":["blend"],"application/x-blorb":["blb","blorb"],"application/x-bzip":["bz"],"application/x-bzip2":["bz2","boz"],"application/x-cbr":["cbr","cba","cbt","cbz","cb7"],"application/x-cdlink":["vcd"],"application/x-cfs-compressed":["cfs"],"application/x-chat":["chat"],"application/x-chess-pgn":["pgn"],"application/x-chrome-extension":["crx"],"application/x-cocoa":["cco"],"application/x-compressed":["*rar"],"application/x-conference":["nsc"],"application/x-cpio":["cpio"],"application/x-csh":["csh"],"application/x-debian-package":["*deb","udeb"],"application/x-dgc-compressed":["dgc"],"application/x-director":["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"],"application/x-doom":["wad"],"application/x-dtbncx+xml":["ncx"],"application/x-dtbook+xml":["dtb"],"application/x-dtbresource+xml":["res"],"application/x-dvi":["dvi"],"application/x-envoy":["evy"],"application/x-eva":["eva"],"application/x-font-bdf":["bdf"],"application/x-font-ghostscript":["gsf"],"application/x-font-linux-psf":["psf"],"application/x-font-pcf":["pcf"],"application/x-font-snf":["snf"],"application/x-font-type1":["pfa","pfb","pfm","afm"],"application/x-freearc":["arc"],"application/x-futuresplash":["spl"],"application/x-gca-compressed":["gca"],"application/x-glulx":["ulx"],"application/x-gnumeric":["gnumeric"],"application/x-gramps-xml":["gramps"],"application/x-gtar":["gtar"],"application/x-hdf":["hdf"],"application/x-httpd-php":["php"],"application/x-install-instructions":["install"],"application/x-ipynb+json":["ipynb"],"application/x-iso9660-image":["*iso"],"application/x-iwork-keynote-sffkey":["*key"],"application/x-iwork-numbers-sffnumbers":["*numbers"],"application/x-iwork-pages-sffpages":["*pages"],"application/x-java-archive-diff":["jardiff"],"application/x-java-jnlp-file":["jnlp"],"application/x-keepass2":["kdbx"],"application/x-latex":["latex"],"application/x-lua-bytecode":["luac"],"application/x-lzh-compressed":["lzh","lha"],"application/x-makeself":["run"],"application/x-mie":["mie"],"application/x-mobipocket-ebook":["*prc","mobi"],"application/x-ms-application":["application"],"application/x-ms-shortcut":["lnk"],"application/x-ms-wmd":["wmd"],"application/x-ms-wmz":["wmz"],"application/x-ms-xbap":["xbap"],"application/x-msaccess":["mdb"],"application/x-msbinder":["obd"],"application/x-mscardfile":["crd"],"application/x-msclip":["clp"],"application/x-msdos-program":["*exe"],"application/x-msdownload":["*exe","*dll","com","bat","*msi"],"application/x-msmediaview":["mvb","m13","m14"],"application/x-msmetafile":["*wmf","*wmz","*emf","emz"],"application/x-msmoney":["mny"],"application/x-mspublisher":["pub"],"application/x-msschedule":["scd"],"application/x-msterminal":["trm"],"application/x-mswrite":["wri"],"application/x-netcdf":["nc","cdf"],"application/x-ns-proxy-autoconfig":["pac"],"application/x-nzb":["nzb"],"application/x-perl":["pl","pm"],"application/x-pilot":["*prc","*pdb"],"application/x-pkcs12":["p12","pfx"],"application/x-pkcs7-certificates":["p7b","spc"],"application/x-pkcs7-certreqresp":["p7r"],"application/x-rar-compressed":["*rar"],"application/x-redhat-package-manager":["rpm"],"application/x-research-info-systems":["ris"],"application/x-sea":["sea"],"application/x-sh":["sh"],"application/x-shar":["shar"],"application/x-shockwave-flash":["swf"],"application/x-silverlight-app":["xap"],"application/x-sql":["*sql"],"application/x-stuffit":["sit"],"application/x-stuffitx":["sitx"],"application/x-subrip":["srt"],"application/x-sv4cpio":["sv4cpio"],"application/x-sv4crc":["sv4crc"],"application/x-t3vm-image":["t3"],"application/x-tads":["gam"],"application/x-tar":["tar"],"application/x-tcl":["tcl","tk"],"application/x-tex":["tex"],"application/x-tex-tfm":["tfm"],"application/x-texinfo":["texinfo","texi"],"application/x-tgif":["*obj"],"application/x-ustar":["ustar"],"application/x-virtualbox-hdd":["hdd"],"application/x-virtualbox-ova":["ova"],"application/x-virtualbox-ovf":["ovf"],"application/x-virtualbox-vbox":["vbox"],"application/x-virtualbox-vbox-extpack":["vbox-extpack"],"application/x-virtualbox-vdi":["vdi"],"application/x-virtualbox-vhd":["vhd"],"application/x-virtualbox-vmdk":["vmdk"],"application/x-wais-source":["src"],"application/x-web-app-manifest+json":["webapp"],"application/x-x509-ca-cert":["der","crt","pem"],"application/x-xfig":["fig"],"application/x-xliff+xml":["*xlf"],"application/x-xpinstall":["xpi"],"application/x-xz":["xz"],"application/x-zip-compressed":["*zip"],"application/x-zmachine":["z1","z2","z3","z4","z5","z6","z7","z8"],"audio/vnd.dece.audio":["uva","uvva"],"audio/vnd.digital-winds":["eol"],"audio/vnd.dra":["dra"],"audio/vnd.dts":["dts"],"audio/vnd.dts.hd":["dtshd"],"audio/vnd.lucent.voice":["lvp"],"audio/vnd.ms-playready.media.pya":["pya"],"audio/vnd.nuera.ecelp4800":["ecelp4800"],"audio/vnd.nuera.ecelp7470":["ecelp7470"],"audio/vnd.nuera.ecelp9600":["ecelp9600"],"audio/vnd.rip":["rip"],"audio/x-aac":["*aac"],"audio/x-aiff":["aif","aiff","aifc"],"audio/x-caf":["caf"],"audio/x-flac":["flac"],"audio/x-m4a":["*m4a"],"audio/x-matroska":["mka"],"audio/x-mpegurl":["m3u"],"audio/x-ms-wax":["wax"],"audio/x-ms-wma":["wma"],"audio/x-pn-realaudio":["ram","ra"],"audio/x-pn-realaudio-plugin":["rmp"],"audio/x-realaudio":["*ra"],"audio/x-wav":["*wav"],"chemical/x-cdx":["cdx"],"chemical/x-cif":["cif"],"chemical/x-cmdf":["cmdf"],"chemical/x-cml":["cml"],"chemical/x-csml":["csml"],"chemical/x-xyz":["xyz"],"image/prs.btif":["btif","btf"],"image/prs.pti":["pti"],"image/vnd.adobe.photoshop":["psd"],"image/vnd.airzip.accelerator.azv":["azv"],"image/vnd.dece.graphic":["uvi","uvvi","uvg","uvvg"],"image/vnd.djvu":["djvu","djv"],"image/vnd.dvb.subtitle":["*sub"],"image/vnd.dwg":["dwg"],"image/vnd.dxf":["dxf"],"image/vnd.fastbidsheet":["fbs"],"image/vnd.fpx":["fpx"],"image/vnd.fst":["fst"],"image/vnd.fujixerox.edmics-mmr":["mmr"],"image/vnd.fujixerox.edmics-rlc":["rlc"],"image/vnd.microsoft.icon":["ico"],"image/vnd.ms-dds":["dds"],"image/vnd.ms-modi":["mdi"],"image/vnd.ms-photo":["wdp"],"image/vnd.net-fpx":["npx"],"image/vnd.pco.b16":["b16"],"image/vnd.tencent.tap":["tap"],"image/vnd.valve.source.texture":["vtf"],"image/vnd.wap.wbmp":["wbmp"],"image/vnd.xiff":["xif"],"image/vnd.zbrush.pcx":["pcx"],"image/x-3ds":["3ds"],"image/x-adobe-dng":["dng"],"image/x-cmu-raster":["ras"],"image/x-cmx":["cmx"],"image/x-freehand":["fh","fhc","fh4","fh5","fh7"],"image/x-icon":["*ico"],"image/x-jng":["jng"],"image/x-mrsid-image":["sid"],"image/x-ms-bmp":["*bmp"],"image/x-pcx":["*pcx"],"image/x-pict":["pic","pct"],"image/x-portable-anymap":["pnm"],"image/x-portable-bitmap":["pbm"],"image/x-portable-graymap":["pgm"],"image/x-portable-pixmap":["ppm"],"image/x-rgb":["rgb"],"image/x-tga":["tga"],"image/x-xbitmap":["xbm"],"image/x-xpixmap":["xpm"],"image/x-xwindowdump":["xwd"],"message/vnd.wfa.wsc":["wsc"],"model/vnd.bary":["bary"],"model/vnd.cld":["cld"],"model/vnd.collada+xml":["dae"],"model/vnd.dwf":["dwf"],"model/vnd.gdl":["gdl"],"model/vnd.gtw":["gtw"],"model/vnd.mts":["*mts"],"model/vnd.opengex":["ogex"],"model/vnd.parasolid.transmit.binary":["x_b"],"model/vnd.parasolid.transmit.text":["x_t"],"model/vnd.pytha.pyox":["pyo","pyox"],"model/vnd.sap.vds":["vds"],"model/vnd.usda":["usda"],"model/vnd.usdz+zip":["usdz"],"model/vnd.valve.source.compiled-map":["bsp"],"model/vnd.vtu":["vtu"],"text/prs.lines.tag":["dsc"],"text/vnd.curl":["curl"],"text/vnd.curl.dcurl":["dcurl"],"text/vnd.curl.mcurl":["mcurl"],"text/vnd.curl.scurl":["scurl"],"text/vnd.dvb.subtitle":["sub"],"text/vnd.familysearch.gedcom":["ged"],"text/vnd.fly":["fly"],"text/vnd.fmi.flexstor":["flx"],"text/vnd.graphviz":["gv"],"text/vnd.in3d.3dml":["3dml"],"text/vnd.in3d.spot":["spot"],"text/vnd.sun.j2me.app-descriptor":["jad"],"text/vnd.wap.wml":["wml"],"text/vnd.wap.wmlscript":["wmls"],"text/x-asm":["s","asm"],"text/x-c":["c","cc","cxx","cpp","h","hh","dic"],"text/x-component":["htc"],"text/x-fortran":["f","for","f77","f90"],"text/x-handlebars-template":["hbs"],"text/x-java-source":["java"],"text/x-lua":["lua"],"text/x-markdown":["mkd"],"text/x-nfo":["nfo"],"text/x-opml":["opml"],"text/x-org":["*org"],"text/x-pascal":["p","pas"],"text/x-processing":["pde"],"text/x-sass":["sass"],"text/x-scss":["scss"],"text/x-setext":["etx"],"text/x-sfv":["sfv"],"text/x-suse-ymp":["ymp"],"text/x-uuencode":["uu"],"text/x-vcalendar":["vcs"],"text/x-vcard":["vcf"],"video/vnd.dece.hd":["uvh","uvvh"],"video/vnd.dece.mobile":["uvm","uvvm"],"video/vnd.dece.pd":["uvp","uvvp"],"video/vnd.dece.sd":["uvs","uvvs"],"video/vnd.dece.video":["uvv","uvvv"],"video/vnd.dvb.file":["dvb"],"video/vnd.fvt":["fvt"],"video/vnd.mpegurl":["mxu","m4u"],"video/vnd.ms-playready.media.pyv":["pyv"],"video/vnd.uvvu.mp4":["uvu","uvvu"],"video/vnd.vivo":["viv"],"video/x-f4v":["f4v"],"video/x-fli":["fli"],"video/x-flv":["flv"],"video/x-m4v":["m4v"],"video/x-matroska":["mkv","mk3d","mks"],"video/x-mng":["mng"],"video/x-ms-asf":["asf","asx"],"video/x-ms-vob":["vob"],"video/x-ms-wm":["wm"],"video/x-ms-wmv":["wmv"],"video/x-ms-wmx":["wmx"],"video/x-ms-wvx":["wvx"],"video/x-msvideo":["avi"],"video/x-sgi-movie":["movie"],"video/x-smv":["smv"],"x-conference/x-cooltalk":["ice"]};Object.freeze(Bl);var Nl=Bl;var kl={"application/andrew-inset":["ez"],"application/appinstaller":["appinstaller"],"application/applixware":["aw"],"application/appx":["appx"],"application/appxbundle":["appxbundle"],"application/atom+xml":["atom"],"application/atomcat+xml":["atomcat"],"application/atomdeleted+xml":["atomdeleted"],"application/atomsvc+xml":["atomsvc"],"application/atsc-dwd+xml":["dwd"],"application/atsc-held+xml":["held"],"application/atsc-rsat+xml":["rsat"],"application/automationml-aml+xml":["aml"],"application/automationml-amlx+zip":["amlx"],"application/bdoc":["bdoc"],"application/calendar+xml":["xcs"],"application/ccxml+xml":["ccxml"],"application/cdfx+xml":["cdfx"],"application/cdmi-capability":["cdmia"],"application/cdmi-container":["cdmic"],"application/cdmi-domain":["cdmid"],"application/cdmi-object":["cdmio"],"application/cdmi-queue":["cdmiq"],"application/cpl+xml":["cpl"],"application/cu-seeme":["cu"],"application/cwl":["cwl"],"application/dash+xml":["mpd"],"application/dash-patch+xml":["mpp"],"application/davmount+xml":["davmount"],"application/dicom":["dcm"],"application/docbook+xml":["dbk"],"application/dssc+der":["dssc"],"application/dssc+xml":["xdssc"],"application/ecmascript":["ecma"],"application/emma+xml":["emma"],"application/emotionml+xml":["emotionml"],"application/epub+zip":["epub"],"application/exi":["exi"],"application/express":["exp"],"application/fdf":["fdf"],"application/fdt+xml":["fdt"],"application/font-tdpfr":["pfr"],"application/geo+json":["geojson"],"application/gml+xml":["gml"],"application/gpx+xml":["gpx"],"application/gxf":["gxf"],"application/gzip":["gz"],"application/hjson":["hjson"],"application/hyperstudio":["stk"],"application/inkml+xml":["ink","inkml"],"application/ipfix":["ipfix"],"application/its+xml":["its"],"application/java-archive":["jar","war","ear"],"application/java-serialized-object":["ser"],"application/java-vm":["class"],"application/javascript":["*js"],"application/json":["json","map"],"application/json5":["json5"],"application/jsonml+json":["jsonml"],"application/ld+json":["jsonld"],"application/lgr+xml":["lgr"],"application/lost+xml":["lostxml"],"application/mac-binhex40":["hqx"],"application/mac-compactpro":["cpt"],"application/mads+xml":["mads"],"application/manifest+json":["webmanifest"],"application/marc":["mrc"],"application/marcxml+xml":["mrcx"],"application/mathematica":["ma","nb","mb"],"application/mathml+xml":["mathml"],"application/mbox":["mbox"],"application/media-policy-dataset+xml":["mpf"],"application/mediaservercontrol+xml":["mscml"],"application/metalink+xml":["metalink"],"application/metalink4+xml":["meta4"],"application/mets+xml":["mets"],"application/mmt-aei+xml":["maei"],"application/mmt-usd+xml":["musd"],"application/mods+xml":["mods"],"application/mp21":["m21","mp21"],"application/mp4":["*mp4","*mpg4","mp4s","m4p"],"application/msix":["msix"],"application/msixbundle":["msixbundle"],"application/msword":["doc","dot"],"application/mxf":["mxf"],"application/n-quads":["nq"],"application/n-triples":["nt"],"application/node":["cjs"],"application/octet-stream":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"],"application/oda":["oda"],"application/oebps-package+xml":["opf"],"application/ogg":["ogx"],"application/omdoc+xml":["omdoc"],"application/onenote":["onetoc","onetoc2","onetmp","onepkg","one","onea"],"application/oxps":["oxps"],"application/p2p-overlay+xml":["relo"],"application/patch-ops-error+xml":["xer"],"application/pdf":["pdf"],"application/pgp-encrypted":["pgp"],"application/pgp-keys":["asc"],"application/pgp-signature":["sig","*asc"],"application/pics-rules":["prf"],"application/pkcs10":["p10"],"application/pkcs7-mime":["p7m","p7c"],"application/pkcs7-signature":["p7s"],"application/pkcs8":["p8"],"application/pkix-attr-cert":["ac"],"application/pkix-cert":["cer"],"application/pkix-crl":["crl"],"application/pkix-pkipath":["pkipath"],"application/pkixcmp":["pki"],"application/pls+xml":["pls"],"application/postscript":["ai","eps","ps"],"application/provenance+xml":["provx"],"application/pskc+xml":["pskcxml"],"application/raml+yaml":["raml"],"application/rdf+xml":["rdf","owl"],"application/reginfo+xml":["rif"],"application/relax-ng-compact-syntax":["rnc"],"application/resource-lists+xml":["rl"],"application/resource-lists-diff+xml":["rld"],"application/rls-services+xml":["rs"],"application/route-apd+xml":["rapd"],"application/route-s-tsid+xml":["sls"],"application/route-usd+xml":["rusd"],"application/rpki-ghostbusters":["gbr"],"application/rpki-manifest":["mft"],"application/rpki-roa":["roa"],"application/rsd+xml":["rsd"],"application/rss+xml":["rss"],"application/rtf":["rtf"],"application/sbml+xml":["sbml"],"application/scvp-cv-request":["scq"],"application/scvp-cv-response":["scs"],"application/scvp-vp-request":["spq"],"application/scvp-vp-response":["spp"],"application/sdp":["sdp"],"application/senml+xml":["senmlx"],"application/sensml+xml":["sensmlx"],"application/set-payment-initiation":["setpay"],"application/set-registration-initiation":["setreg"],"application/shf+xml":["shf"],"application/sieve":["siv","sieve"],"application/smil+xml":["smi","smil"],"application/sparql-query":["rq"],"application/sparql-results+xml":["srx"],"application/sql":["sql"],"application/srgs":["gram"],"application/srgs+xml":["grxml"],"application/sru+xml":["sru"],"application/ssdl+xml":["ssdl"],"application/ssml+xml":["ssml"],"application/swid+xml":["swidtag"],"application/tei+xml":["tei","teicorpus"],"application/thraud+xml":["tfi"],"application/timestamped-data":["tsd"],"application/toml":["toml"],"application/trig":["trig"],"application/ttml+xml":["ttml"],"application/ubjson":["ubj"],"application/urc-ressheet+xml":["rsheet"],"application/urc-targetdesc+xml":["td"],"application/voicexml+xml":["vxml"],"application/wasm":["wasm"],"application/watcherinfo+xml":["wif"],"application/widget":["wgt"],"application/winhlp":["hlp"],"application/wsdl+xml":["wsdl"],"application/wspolicy+xml":["wspolicy"],"application/xaml+xml":["xaml"],"application/xcap-att+xml":["xav"],"application/xcap-caps+xml":["xca"],"application/xcap-diff+xml":["xdf"],"application/xcap-el+xml":["xel"],"application/xcap-ns+xml":["xns"],"application/xenc+xml":["xenc"],"application/xfdf":["xfdf"],"application/xhtml+xml":["xhtml","xht"],"application/xliff+xml":["xlf"],"application/xml":["xml","xsl","xsd","rng"],"application/xml-dtd":["dtd"],"application/xop+xml":["xop"],"application/xproc+xml":["xpl"],"application/xslt+xml":["*xsl","xslt"],"application/xspf+xml":["xspf"],"application/xv+xml":["mxml","xhvml","xvml","xvm"],"application/yang":["yang"],"application/yin+xml":["yin"],"application/zip":["zip"],"application/zip+dotlottie":["lottie"],"audio/3gpp":["*3gpp"],"audio/aac":["adts","aac"],"audio/adpcm":["adp"],"audio/amr":["amr"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mobile-xmf":["mxmf"],"audio/mp3":["*mp3"],"audio/mp4":["m4a","mp4a","m4b"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx","opus"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/wav":["wav"],"audio/wave":["*wav"],"audio/webm":["weba"],"audio/xm":["xm"],"font/collection":["ttc"],"font/otf":["otf"],"font/ttf":["ttf"],"font/woff":["woff"],"font/woff2":["woff2"],"image/aces":["exr"],"image/apng":["apng"],"image/avci":["avci"],"image/avcs":["avcs"],"image/avif":["avif"],"image/bmp":["bmp","dib"],"image/cgm":["cgm"],"image/dicom-rle":["drle"],"image/dpx":["dpx"],"image/emf":["emf"],"image/fits":["fits"],"image/g3fax":["g3"],"image/gif":["gif"],"image/heic":["heic"],"image/heic-sequence":["heics"],"image/heif":["heif"],"image/heif-sequence":["heifs"],"image/hej2k":["hej2"],"image/ief":["ief"],"image/jaii":["jaii"],"image/jais":["jais"],"image/jls":["jls"],"image/jp2":["jp2","jpg2"],"image/jpeg":["jpg","jpeg","jpe"],"image/jph":["jph"],"image/jphc":["jhc"],"image/jpm":["jpm","jpgm"],"image/jpx":["jpx","jpf"],"image/jxl":["jxl"],"image/jxr":["jxr"],"image/jxra":["jxra"],"image/jxrs":["jxrs"],"image/jxs":["jxs"],"image/jxsc":["jxsc"],"image/jxsi":["jxsi"],"image/jxss":["jxss"],"image/ktx":["ktx"],"image/ktx2":["ktx2"],"image/pjpeg":["jfif"],"image/png":["png"],"image/sgi":["sgi"],"image/svg+xml":["svg","svgz"],"image/t38":["t38"],"image/tiff":["tif","tiff"],"image/tiff-fx":["tfx"],"image/webp":["webp"],"image/wmf":["wmf"],"message/disposition-notification":["disposition-notification"],"message/global":["u8msg"],"message/global-delivery-status":["u8dsn"],"message/global-disposition-notification":["u8mdn"],"message/global-headers":["u8hdr"],"message/rfc822":["eml","mime","mht","mhtml"],"model/3mf":["3mf"],"model/gltf+json":["gltf"],"model/gltf-binary":["glb"],"model/iges":["igs","iges"],"model/jt":["jt"],"model/mesh":["msh","mesh","silo"],"model/mtl":["mtl"],"model/obj":["obj"],"model/prc":["prc"],"model/step":["step","stp","stpnc","p21","210"],"model/step+xml":["stpx"],"model/step+zip":["stpz"],"model/step-xml+zip":["stpxz"],"model/stl":["stl"],"model/u3d":["u3d"],"model/vrml":["wrl","vrml"],"model/x3d+binary":["*x3db","x3dbz"],"model/x3d+fastinfoset":["x3db"],"model/x3d+vrml":["*x3dv","x3dvz"],"model/x3d+xml":["x3d","x3dz"],"model/x3d-vrml":["x3dv"],"text/cache-manifest":["appcache","manifest"],"text/calendar":["ics","ifb"],"text/coffeescript":["coffee","litcoffee"],"text/css":["css"],"text/csv":["csv"],"text/html":["html","htm","shtml"],"text/jade":["jade"],"text/javascript":["js","mjs"],"text/jsx":["jsx"],"text/less":["less"],"text/markdown":["md","markdown"],"text/mathml":["mml"],"text/mdx":["mdx"],"text/n3":["n3"],"text/plain":["txt","text","conf","def","list","log","in","ini"],"text/richtext":["rtx"],"text/rtf":["*rtf"],"text/sgml":["sgml","sgm"],"text/shex":["shex"],"text/slim":["slim","slm"],"text/spdx":["spdx"],"text/stylus":["stylus","styl"],"text/tab-separated-values":["tsv"],"text/troff":["t","tr","roff","man","me","ms"],"text/turtle":["ttl"],"text/uri-list":["uri","uris","urls"],"text/vcard":["vcard"],"text/vtt":["vtt"],"text/wgsl":["wgsl"],"text/xml":["*xml"],"text/yaml":["yaml","yml"],"video/3gpp":["3gp","3gpp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/iso.segment":["m4s"],"video/jpeg":["jpgv"],"video/jpm":["*jpm","*jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts","m2t","m2ts","mts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/webm":["webm"]};Object.freeze(kl);var Vl=kl;var _e=function(e,t,i,a){if(i==="a"&&!a)throw new TypeError("Private accessor was defined without a getter");if(typeof t=="function"?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return i==="m"?a:i==="a"?a.call(e):a?a.value:t.get(e)},Rt,Ht,ot,Ra=class{constructor(...t){Rt.set(this,new Map),Ht.set(this,new Map),ot.set(this,new Map);for(let i of t)this.define(i)}define(t,i=!1){for(let[a,n]of Object.entries(t)){a=a.toLowerCase(),n=n.map(r=>r.toLowerCase()),_e(this,ot,"f").has(a)||_e(this,ot,"f").set(a,new Set);let l=_e(this,ot,"f").get(a),o=!0;for(let r of n){let s=r.startsWith("*");if(r=s?r.slice(1):r,l?.add(r),o&&_e(this,Ht,"f").set(a,r),o=!1,s)continue;let p=_e(this,Rt,"f").get(r);if(p&&p!=a&&!i)throw new Error(`"${a} -> ${r}" conflicts with "${p} -> ${r}". Pass \`force=true\` to override this definition.`);_e(this,Rt,"f").set(r,a)}}return this}getType(t){if(typeof t!="string")return null;let i=t.replace(/^.*[/\\]/s,"").toLowerCase(),a=i.replace(/^.*\./s,"").toLowerCase(),n=i.length{throw new Error("define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances")},Object.freeze(this);for(let t of _e(this,ot,"f").values())Object.freeze(t);return this}_getTestState(){return{types:_e(this,Rt,"f"),extensions:_e(this,Ht,"f")}}};Rt=new WeakMap,Ht=new WeakMap,ot=new WeakMap;var Sa=Ra;var Gl=new Sa(Vl,Nl)._freeze();var Ul=({addFilter:e,utils:t})=>{let{Type:i,replaceInString:a,toNaturalFileSize:n}=t;return e("ALLOW_HOPPER_ITEM",(l,{query:o})=>{if(!o("GET_ALLOW_FILE_SIZE_VALIDATION"))return!0;let r=o("GET_MAX_FILE_SIZE");if(r!==null&&l.size>r)return!1;let s=o("GET_MIN_FILE_SIZE");return!(s!==null&&l.sizenew Promise((r,s)=>{if(!o("GET_ALLOW_FILE_SIZE_VALIDATION"))return r(l);let p=o("GET_FILE_VALIDATE_SIZE_FILTER");if(p&&!p(l))return r(l);let c=o("GET_MAX_FILE_SIZE");if(c!==null&&l.size>c){s({status:{main:o("GET_LABEL_MAX_FILE_SIZE_EXCEEDED"),sub:a(o("GET_LABEL_MAX_FILE_SIZE"),{filesize:n(c,".",o("GET_FILE_SIZE_BASE"),o("GET_FILE_SIZE_LABELS",o))})}});return}let d=o("GET_MIN_FILE_SIZE");if(d!==null&&l.sizeg+f.fileSize,0)>m){s({status:{main:o("GET_LABEL_MAX_TOTAL_FILE_SIZE_EXCEEDED"),sub:a(o("GET_LABEL_MAX_TOTAL_FILE_SIZE"),{filesize:n(m,".",o("GET_FILE_SIZE_BASE"),o("GET_FILE_SIZE_LABELS",o))})}});return}r(l)})),{options:{allowFileSizeValidation:[!0,i.BOOLEAN],maxFileSize:[null,i.INT],minFileSize:[null,i.INT],maxTotalFileSize:[null,i.INT],fileValidateSizeFilter:[null,i.FUNCTION],labelMinFileSizeExceeded:["File is too small",i.STRING],labelMinFileSize:["Minimum file size is {filesize}",i.STRING],labelMaxFileSizeExceeded:["File is too large",i.STRING],labelMaxFileSize:["Maximum file size is {filesize}",i.STRING],labelMaxTotalFileSizeExceeded:["Maximum total size exceeded",i.STRING],labelMaxTotalFileSize:["Maximum total file size is {filesize}",i.STRING]}}},sm=typeof window<"u"&&typeof window.document<"u";sm&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:Ul}));var Wl=Ul;var Hl=({addFilter:e,utils:t})=>{let{Type:i,isString:a,replaceInString:n,guesstimateMimeType:l,getExtensionFromFilename:o,getFilenameFromURL:r}=t,s=(u,g)=>{let f=(/^[^/]+/.exec(u)||[]).pop(),h=g.slice(0,-2);return f===h},p=(u,g)=>u.some(f=>/\*$/.test(f)?s(g,f):f===g),c=u=>{let g="";if(a(u)){let f=r(u),h=o(f);h&&(g=l(h))}else g=u.type;return g},d=(u,g,f)=>{if(g.length===0)return!0;let h=c(u);return f?new Promise((I,b)=>{f(u,h).then(T=>{p(g,T)?I():b()}).catch(b)}):p(g,h)},m=u=>g=>u[g]===null?!1:u[g]||g;return e("SET_ATTRIBUTE_TO_OPTION_MAP",u=>Object.assign(u,{accept:"acceptedFileTypes"})),e("ALLOW_HOPPER_ITEM",(u,{query:g})=>g("GET_ALLOW_FILE_TYPE_VALIDATION")?d(u,g("GET_ACCEPTED_FILE_TYPES")):!0),e("LOAD_FILE",(u,{query:g})=>new Promise((f,h)=>{if(!g("GET_ALLOW_FILE_TYPE_VALIDATION")){f(u);return}let I=g("GET_ACCEPTED_FILE_TYPES"),b=g("GET_FILE_VALIDATE_TYPE_DETECT_TYPE"),T=d(u,I,b),v=()=>{let y=I.map(m(g("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES_MAP"))).filter(_=>_!==!1),E=y.filter((_,x)=>y.indexOf(_)===x);h({status:{main:g("GET_LABEL_FILE_TYPE_NOT_ALLOWED"),sub:n(g("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES"),{allTypes:E.join(", "),allButLastType:E.slice(0,-1).join(", "),lastType:E[E.length-1]})}})};if(typeof T=="boolean")return T?f(u):v();T.then(()=>{f(u)}).catch(v)})),{options:{allowFileTypeValidation:[!0,i.BOOLEAN],acceptedFileTypes:[[],i.ARRAY],labelFileTypeNotAllowed:["File is of invalid type",i.STRING],fileValidateTypeLabelExpectedTypes:["Expects {allButLastType} or {lastType}",i.STRING],fileValidateTypeLabelExpectedTypesMap:[{},i.OBJECT],fileValidateTypeDetectType:[null,i.FUNCTION]}}},cm=typeof window<"u"&&typeof window.document<"u";cm&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:Hl}));var jl=Hl;var Yl=e=>/^image/.test(e.type),ql=({addFilter:e,utils:t})=>{let{Type:i,isFile:a,getNumericAspectRatioFromString:n}=t,l=(p,c)=>!(!Yl(p.file)||!c("GET_ALLOW_IMAGE_CROP")),o=p=>typeof p=="object",r=p=>typeof p=="number",s=(p,c)=>p.setMetadata("crop",Object.assign({},p.getMetadata("crop"),c));return e("DID_CREATE_ITEM",(p,{query:c})=>{p.extend("setImageCrop",d=>{if(!(!l(p,c)||!o(center)))return p.setMetadata("crop",d),d}),p.extend("setImageCropCenter",d=>{if(!(!l(p,c)||!o(d)))return s(p,{center:d})}),p.extend("setImageCropZoom",d=>{if(!(!l(p,c)||!r(d)))return s(p,{zoom:Math.max(1,d)})}),p.extend("setImageCropRotation",d=>{if(!(!l(p,c)||!r(d)))return s(p,{rotation:d})}),p.extend("setImageCropFlip",d=>{if(!(!l(p,c)||!o(d)))return s(p,{flip:d})}),p.extend("setImageCropAspectRatio",d=>{if(!l(p,c)||typeof d>"u")return;let m=p.getMetadata("crop"),u=n(d),g={center:{x:.5,y:.5},flip:m?Object.assign({},m.flip):{horizontal:!1,vertical:!1},rotation:0,zoom:1,aspectRatio:u};return p.setMetadata("crop",g),g})}),e("DID_LOAD_ITEM",(p,{query:c})=>new Promise((d,m)=>{let u=p.file;if(!a(u)||!Yl(u)||!c("GET_ALLOW_IMAGE_CROP")||p.getMetadata("crop"))return d(p);let f=c("GET_IMAGE_CROP_ASPECT_RATIO");p.setMetadata("crop",{center:{x:.5,y:.5},flip:{horizontal:!1,vertical:!1},rotation:0,zoom:1,aspectRatio:f?n(f):null}),d(p)})),{options:{allowImageCrop:[!0,i.BOOLEAN],imageCropAspectRatio:[null,i.STRING]}}},dm=typeof window<"u"&&typeof window.document<"u";dm&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:ql}));var $l=ql;var _a=e=>/^image/.test(e.type),Xl=e=>{let{addFilter:t,utils:i,views:a}=e,{Type:n,createRoute:l,createItemAPI:o=c=>c}=i,{fileActionButton:r}=a;t("SHOULD_REMOVE_ON_REVERT",(c,{item:d,query:m})=>new Promise(u=>{let{file:g}=d,f=m("GET_ALLOW_IMAGE_EDIT")&&m("GET_IMAGE_EDIT_ALLOW_EDIT")&&_a(g);u(!f)})),t("DID_LOAD_ITEM",(c,{query:d,dispatch:m})=>new Promise((u,g)=>{if(c.origin>1){u(c);return}let{file:f}=c;if(!d("GET_ALLOW_IMAGE_EDIT")||!d("GET_IMAGE_EDIT_INSTANT_EDIT")){u(c);return}if(!_a(f)){u(c);return}let h=(b,T,v)=>y=>{s.shift(),y?T(b):v(b),m("KICK"),I()},I=()=>{if(!s.length)return;let{item:b,resolve:T,reject:v}=s[0];m("EDIT_ITEM",{id:b.id,handleEditorResponse:h(b,T,v)})};p({item:c,resolve:u,reject:g}),s.length===1&&I()})),t("DID_CREATE_ITEM",(c,{query:d,dispatch:m})=>{c.extend("edit",()=>{m("EDIT_ITEM",{id:c.id})})});let s=[],p=c=>(s.push(c),c);return t("CREATE_VIEW",c=>{let{is:d,view:m,query:u}=c;if(!u("GET_ALLOW_IMAGE_EDIT"))return;let g=u("GET_ALLOW_IMAGE_PREVIEW");if(!(d("file-info")&&!g||d("file")&&g))return;let h=u("GET_IMAGE_EDIT_EDITOR");if(!h)return;h.filepondCallbackBridge||(h.outputData=!0,h.outputFile=!1,h.filepondCallbackBridge={onconfirm:h.onconfirm||(()=>{}),oncancel:h.oncancel||(()=>{})});let I=({root:v,props:y,action:E})=>{let{id:_}=y,{handleEditorResponse:x}=E;h.cropAspectRatio=v.query("GET_IMAGE_CROP_ASPECT_RATIO")||h.cropAspectRatio,h.outputCanvasBackgroundColor=v.query("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR")||h.outputCanvasBackgroundColor;let R=v.query("GET_ITEM",_);if(!R)return;let z=R.file,P=R.getMetadata("crop"),A={center:{x:.5,y:.5},flip:{horizontal:!1,vertical:!1},zoom:1,rotation:0,aspectRatio:null},B=R.getMetadata("resize"),w=R.getMetadata("filter")||null,O=R.getMetadata("filters")||null,S=R.getMetadata("colors")||null,L=R.getMetadata("markup")||null,D={crop:P||A,size:B?{upscale:B.upscale,mode:B.mode,width:B.size.width,height:B.size.height}:null,filter:O?O.id||O.matrix:v.query("GET_ALLOW_IMAGE_FILTER")&&v.query("GET_IMAGE_FILTER_COLOR_MATRIX")&&!S?w:null,color:S,markup:L};h.onconfirm=({data:F})=>{let{crop:G,size:C,filter:q,color:X,colorMatrix:K,markup:pe}=F,k={};if(G&&(k.crop=G),C){let H=(R.getMetadata("resize")||{}).size,Y={width:C.width,height:C.height};!(Y.width&&Y.height)&&H&&(Y.width=H.width,Y.height=H.height),(Y.width||Y.height)&&(k.resize={upscale:C.upscale,mode:C.mode,size:Y})}pe&&(k.markup=pe),k.colors=X,k.filters=q,k.filter=K,R.setMetadata(k),h.filepondCallbackBridge.onconfirm(F,o(R)),x&&(h.onclose=()=>{x(!0),h.onclose=null})},h.oncancel=()=>{h.filepondCallbackBridge.oncancel(o(R)),x&&(h.onclose=()=>{x(!1),h.onclose=null})},h.open(z,D)},b=({root:v,props:y})=>{if(!u("GET_IMAGE_EDIT_ALLOW_EDIT"))return;let{id:E}=y,_=u("GET_ITEM",E);if(!_)return;let x=_.file;if(_a(x))if(v.ref.handleEdit=R=>{R.stopPropagation(),v.dispatch("EDIT_ITEM",{id:E})},g){let R=m.createChildView(r,{label:"edit",icon:u("GET_IMAGE_EDIT_ICON_EDIT"),opacity:0});R.element.classList.add("filepond--action-edit-item"),R.element.dataset.align=u("GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION"),R.on("click",v.ref.handleEdit),v.ref.buttonEditItem=m.appendChildView(R)}else{let R=m.element.querySelector(".filepond--file-info-main"),z=document.createElement("button");z.className="filepond--action-edit-item-alt",z.innerHTML=u("GET_IMAGE_EDIT_ICON_EDIT")+"edit",z.addEventListener("click",v.ref.handleEdit),R.appendChild(z),v.ref.editButton=z}};m.registerDestroyer(({root:v})=>{v.ref.buttonEditItem&&v.ref.buttonEditItem.off("click",v.ref.handleEdit),v.ref.editButton&&v.ref.editButton.removeEventListener("click",v.ref.handleEdit)});let T={EDIT_ITEM:I,DID_LOAD_ITEM:b};if(g){let v=({root:y})=>{y.ref.buttonEditItem&&(y.ref.buttonEditItem.opacity=1)};T.DID_IMAGE_PREVIEW_SHOW=v}m.registerWriter(l(T))}),{options:{allowImageEdit:[!0,n.BOOLEAN],styleImageEditButtonEditItemPosition:["bottom center",n.STRING],imageEditInstantEdit:[!1,n.BOOLEAN],imageEditAllowEdit:[!0,n.BOOLEAN],imageEditIconEdit:['',n.STRING],imageEditEditor:[null,n.OBJECT]}}},pm=typeof window<"u"&&typeof window.document<"u";pm&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:Xl}));var Kl=Xl;var mm=e=>/^image\/jpeg/.test(e.type),rt={JPEG:65496,APP1:65505,EXIF:1165519206,TIFF:18761,Orientation:274,Unknown:65280},st=(e,t,i=!1)=>e.getUint16(t,i),Ql=(e,t,i=!1)=>e.getUint32(t,i),um=e=>new Promise((t,i)=>{let a=new FileReader;a.onload=function(n){let l=new DataView(n.target.result);if(st(l,0)!==rt.JPEG){t(-1);return}let o=l.byteLength,r=2;for(;rgm,hm="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QA6RXhpZgAATU0AKgAAAAgAAwESAAMAAAABAAYAAAEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAIBASIA/8QAJgABAAAAAAAAAAAAAAAAAAAAAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwBH/9k=",Zl,Ii=fm()?new Image:{};Ii.onload=()=>Zl=Ii.naturalWidth>Ii.naturalHeight;Ii.src=hm;var bm=()=>Zl,Jl=({addFilter:e,utils:t})=>{let{Type:i,isFile:a}=t;return e("DID_LOAD_ITEM",(n,{query:l})=>new Promise((o,r)=>{let s=n.file;if(!a(s)||!mm(s)||!l("GET_ALLOW_IMAGE_EXIF_ORIENTATION")||!bm())return o(n);um(s).then(p=>{n.setMetadata("exif",{orientation:p}),o(n)})})),{options:{allowImageExifOrientation:[!0,i.BOOLEAN]}}},Em=typeof window<"u"&&typeof window.document<"u";Em&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:Jl}));var eo=Jl;var Tm=e=>/^image/.test(e.type),to=(e,t)=>Yt(e.x*t,e.y*t),io=(e,t)=>Yt(e.x+t.x,e.y+t.y),Im=e=>{let t=Math.sqrt(e.x*e.x+e.y*e.y);return t===0?{x:0,y:0}:Yt(e.x/t,e.y/t)},vi=(e,t,i)=>{let a=Math.cos(t),n=Math.sin(t),l=Yt(e.x-i.x,e.y-i.y);return Yt(i.x+a*l.x-n*l.y,i.y+n*l.x+a*l.y)},Yt=(e=0,t=0)=>({x:e,y:t}),Te=(e,t,i=1,a)=>{if(typeof e=="string")return parseFloat(e)*i;if(typeof e=="number")return e*(a?t[a]:Math.min(t.width,t.height))},vm=(e,t,i)=>{let a=e.borderStyle||e.lineStyle||"solid",n=e.backgroundColor||e.fontColor||"transparent",l=e.borderColor||e.lineColor||"transparent",o=Te(e.borderWidth||e.lineWidth,t,i),r=e.lineCap||"round",s=e.lineJoin||"round",p=typeof a=="string"?"":a.map(d=>Te(d,t,i)).join(","),c=e.opacity||1;return{"stroke-linecap":r,"stroke-linejoin":s,"stroke-width":o||0,"stroke-dasharray":p,stroke:l,fill:n,opacity:c}},we=e=>e!=null,xm=(e,t,i=1)=>{let a=Te(e.x,t,i,"width")||Te(e.left,t,i,"width"),n=Te(e.y,t,i,"height")||Te(e.top,t,i,"height"),l=Te(e.width,t,i,"width"),o=Te(e.height,t,i,"height"),r=Te(e.right,t,i,"width"),s=Te(e.bottom,t,i,"height");return we(n)||(we(o)&&we(s)?n=t.height-o-s:n=s),we(a)||(we(l)&&we(r)?a=t.width-l-r:a=r),we(l)||(we(a)&&we(r)?l=t.width-a-r:l=0),we(o)||(we(n)&&we(s)?o=t.height-n-s:o=0),{x:a||0,y:n||0,width:l||0,height:o||0}},ym=e=>e.map((t,i)=>`${i===0?"M":"L"} ${t.x} ${t.y}`).join(" "),Be=(e,t)=>Object.keys(t).forEach(i=>e.setAttribute(i,t[i])),Rm="http://www.w3.org/2000/svg",St=(e,t)=>{let i=document.createElementNS(Rm,e);return t&&Be(i,t),i},Sm=e=>Be(e,{...e.rect,...e.styles}),_m=e=>{let t=e.rect.x+e.rect.width*.5,i=e.rect.y+e.rect.height*.5,a=e.rect.width*.5,n=e.rect.height*.5;return Be(e,{cx:t,cy:i,rx:a,ry:n,...e.styles})},wm={contain:"xMidYMid meet",cover:"xMidYMid slice"},Lm=(e,t)=>{Be(e,{...e.rect,...e.styles,preserveAspectRatio:wm[t.fit]||"none"})},Mm={left:"start",center:"middle",right:"end"},Am=(e,t,i,a)=>{let n=Te(t.fontSize,i,a),l=t.fontFamily||"sans-serif",o=t.fontWeight||"normal",r=Mm[t.textAlign]||"start";Be(e,{...e.rect,...e.styles,"stroke-width":0,"font-weight":o,"font-size":n,"font-family":l,"text-anchor":r}),e.text!==t.text&&(e.text=t.text,e.textContent=t.text.length?t.text:" ")},Pm=(e,t,i,a)=>{Be(e,{...e.rect,...e.styles,fill:"none"});let n=e.childNodes[0],l=e.childNodes[1],o=e.childNodes[2],r=e.rect,s={x:e.rect.x+e.rect.width,y:e.rect.y+e.rect.height};if(Be(n,{x1:r.x,y1:r.y,x2:s.x,y2:s.y}),!t.lineDecoration)return;l.style.display="none",o.style.display="none";let p=Im({x:s.x-r.x,y:s.y-r.y}),c=Te(.05,i,a);if(t.lineDecoration.indexOf("arrow-begin")!==-1){let d=to(p,c),m=io(r,d),u=vi(r,2,m),g=vi(r,-2,m);Be(l,{style:"display:block;",d:`M${u.x},${u.y} L${r.x},${r.y} L${g.x},${g.y}`})}if(t.lineDecoration.indexOf("arrow-end")!==-1){let d=to(p,-c),m=io(s,d),u=vi(s,2,m),g=vi(s,-2,m);Be(o,{style:"display:block;",d:`M${u.x},${u.y} L${s.x},${s.y} L${g.x},${g.y}`})}},zm=(e,t,i,a)=>{Be(e,{...e.styles,fill:"none",d:ym(t.points.map(n=>({x:Te(n.x,i,a,"width"),y:Te(n.y,i,a,"height")})))})},xi=e=>t=>St(e,{id:t.id}),Om=e=>{let t=St("image",{id:e.id,"stroke-linecap":"round","stroke-linejoin":"round",opacity:"0"});return t.onload=()=>{t.setAttribute("opacity",e.opacity||1)},t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",e.src),t},Fm=e=>{let t=St("g",{id:e.id,"stroke-linecap":"round","stroke-linejoin":"round"}),i=St("line");t.appendChild(i);let a=St("path");t.appendChild(a);let n=St("path");return t.appendChild(n),t},Dm={image:Om,rect:xi("rect"),ellipse:xi("ellipse"),text:xi("text"),path:xi("path"),line:Fm},Cm={rect:Sm,ellipse:_m,image:Lm,text:Am,path:zm,line:Pm},Bm=(e,t)=>Dm[e](t),Nm=(e,t,i,a,n)=>{t!=="path"&&(e.rect=xm(i,a,n)),e.styles=vm(i,a,n),Cm[t](e,i,a,n)},km=["x","y","left","top","right","bottom","width","height"],Vm=e=>typeof e=="string"&&/%/.test(e)?parseFloat(e)/100:e,Gm=e=>{let[t,i]=e,a=i.points?{}:km.reduce((n,l)=>(n[l]=Vm(i[l]),n),{});return[t,{zIndex:0,...i,...a}]},Um=(e,t)=>e[1].zIndex>t[1].zIndex?1:e[1].zIndexe.utils.createView({name:"image-preview-markup",tag:"svg",ignoreRect:!0,mixins:{apis:["width","height","crop","markup","resize","dirty"]},write:({root:t,props:i})=>{if(!i.dirty)return;let{crop:a,resize:n,markup:l}=i,o=i.width,r=i.height,s=a.width,p=a.height;if(n){let{size:u}=n,g=u&&u.width,f=u&&u.height,h=n.mode,I=n.upscale;g&&!f&&(f=g),f&&!g&&(g=f);let b=s{let[g,f]=u,h=Bm(g,f);Nm(h,g,f,c,d),t.element.appendChild(h)})}}),jt=(e,t)=>({x:e,y:t}),Hm=(e,t)=>e.x*t.x+e.y*t.y,ao=(e,t)=>jt(e.x-t.x,e.y-t.y),jm=(e,t)=>Hm(ao(e,t),ao(e,t)),no=(e,t)=>Math.sqrt(jm(e,t)),lo=(e,t)=>{let i=e,a=1.5707963267948966,n=t,l=1.5707963267948966-t,o=Math.sin(a),r=Math.sin(n),s=Math.sin(l),p=Math.cos(l),c=i/o,d=c*r,m=c*s;return jt(p*d,p*m)},Ym=(e,t)=>{let i=e.width,a=e.height,n=lo(i,t),l=lo(a,t),o=jt(e.x+Math.abs(n.x),e.y-Math.abs(n.y)),r=jt(e.x+e.width+Math.abs(l.y),e.y+Math.abs(l.x)),s=jt(e.x-Math.abs(l.y),e.y+e.height-Math.abs(l.x));return{width:no(o,r),height:no(o,s)}},qm=(e,t,i=1)=>{let a=e.height/e.width,n=1,l=t,o=1,r=a;r>l&&(r=l,o=r/a);let s=Math.max(n/o,l/r),p=e.width/(i*s*o),c=p*t;return{width:p,height:c}},ro=(e,t,i,a)=>{let n=a.x>.5?1-a.x:a.x,l=a.y>.5?1-a.y:a.y,o=n*2*e.width,r=l*2*e.height,s=Ym(t,i);return Math.max(s.width/o,s.height/r)},so=(e,t)=>{let i=e.width,a=i*t;a>e.height&&(a=e.height,i=a/t);let n=(e.width-i)*.5,l=(e.height-a)*.5;return{x:n,y:l,width:i,height:a}},$m=(e,t={})=>{let{zoom:i,rotation:a,center:n,aspectRatio:l}=t;l||(l=e.height/e.width);let o=qm(e,l,i),r={x:o.width*.5,y:o.height*.5},s={x:0,y:0,width:o.width,height:o.height,center:r},p=typeof t.scaleToFit>"u"||t.scaleToFit,c=ro(e,so(s,l),a,p?n:{x:.5,y:.5}),d=i*c;return{widthFloat:o.width/d,heightFloat:o.height/d,width:Math.round(o.width/d),height:Math.round(o.height/d)}},Ce={type:"spring",stiffness:.5,damping:.45,mass:10},Xm=e=>e.utils.createView({name:"image-bitmap",ignoreRect:!0,mixins:{styles:["scaleX","scaleY"]},create:({root:t,props:i})=>{t.appendChild(i.image)}}),Km=e=>e.utils.createView({name:"image-canvas-wrapper",tag:"div",ignoreRect:!0,mixins:{apis:["crop","width","height"],styles:["originX","originY","translateX","translateY","scaleX","scaleY","rotateZ"],animations:{originX:Ce,originY:Ce,scaleX:Ce,scaleY:Ce,translateX:Ce,translateY:Ce,rotateZ:Ce}},create:({root:t,props:i})=>{i.width=i.image.width,i.height=i.image.height,t.ref.bitmap=t.appendChildView(t.createChildView(Xm(e),{image:i.image}))},write:({root:t,props:i})=>{let{flip:a}=i.crop,{bitmap:n}=t.ref;n.scaleX=a.horizontal?-1:1,n.scaleY=a.vertical?-1:1}}),Qm=e=>e.utils.createView({name:"image-clip",tag:"div",ignoreRect:!0,mixins:{apis:["crop","markup","resize","width","height","dirty","background"],styles:["width","height","opacity"],animations:{opacity:{type:"tween",duration:250}}},didWriteView:function({root:t,props:i}){i.background&&(t.element.style.backgroundColor=i.background)},create:({root:t,props:i})=>{t.ref.image=t.appendChildView(t.createChildView(Km(e),Object.assign({},i))),t.ref.createMarkup=()=>{t.ref.markup||(t.ref.markup=t.appendChildView(t.createChildView(Wm(e),Object.assign({},i))))},t.ref.destroyMarkup=()=>{t.ref.markup&&(t.removeChildView(t.ref.markup),t.ref.markup=null)};let a=t.query("GET_IMAGE_PREVIEW_TRANSPARENCY_INDICATOR");a!==null&&(a==="grid"?t.element.dataset.transparencyIndicator=a:t.element.dataset.transparencyIndicator="color")},write:({root:t,props:i,shouldOptimize:a})=>{let{crop:n,markup:l,resize:o,dirty:r,width:s,height:p}=i;t.ref.image.crop=n;let c={x:0,y:0,width:s,height:p,center:{x:s*.5,y:p*.5}},d={width:t.ref.image.width,height:t.ref.image.height},m={x:n.center.x*d.width,y:n.center.y*d.height},u={x:c.center.x-d.width*n.center.x,y:c.center.y-d.height*n.center.y},g=Math.PI*2+n.rotation%(Math.PI*2),f=n.aspectRatio||d.height/d.width,h=typeof n.scaleToFit>"u"||n.scaleToFit,I=ro(d,so(c,f),g,h?n.center:{x:.5,y:.5}),b=n.zoom*I;l&&l.length?(t.ref.createMarkup(),t.ref.markup.width=s,t.ref.markup.height=p,t.ref.markup.resize=o,t.ref.markup.dirty=r,t.ref.markup.markup=l,t.ref.markup.crop=$m(d,n)):t.ref.markup&&t.ref.destroyMarkup();let T=t.ref.image;if(a){T.originX=null,T.originY=null,T.translateX=null,T.translateY=null,T.rotateZ=null,T.scaleX=null,T.scaleY=null;return}T.originX=m.x,T.originY=m.y,T.translateX=u.x,T.translateY=u.y,T.rotateZ=g,T.scaleX=b,T.scaleY=b}}),Zm=e=>e.utils.createView({name:"image-preview",tag:"div",ignoreRect:!0,mixins:{apis:["image","crop","markup","resize","dirty","background"],styles:["translateY","scaleX","scaleY","opacity"],animations:{scaleX:Ce,scaleY:Ce,translateY:Ce,opacity:{type:"tween",duration:400}}},create:({root:t,props:i})=>{t.ref.clip=t.appendChildView(t.createChildView(Qm(e),{id:i.id,image:i.image,crop:i.crop,markup:i.markup,resize:i.resize,dirty:i.dirty,background:i.background}))},write:({root:t,props:i,shouldOptimize:a})=>{let{clip:n}=t.ref,{image:l,crop:o,markup:r,resize:s,dirty:p}=i;if(n.crop=o,n.markup=r,n.resize=s,n.dirty=p,n.opacity=a?0:1,a||t.rect.element.hidden)return;let c=l.height/l.width,d=o.aspectRatio||c,m=t.rect.inner.width,u=t.rect.inner.height,g=t.query("GET_IMAGE_PREVIEW_HEIGHT"),f=t.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),h=t.query("GET_IMAGE_PREVIEW_MAX_HEIGHT"),I=t.query("GET_PANEL_ASPECT_RATIO"),b=t.query("GET_ALLOW_MULTIPLE");I&&!b&&(g=m*I,d=I);let T=g!==null?g:Math.max(f,Math.min(m*d,h)),v=T/d;v>m&&(v=m,T=v*d),T>u&&(T=u,v=u/d),n.width=v,n.height=T}}),Jm=` - - - - - - - - - - - - - - - - - -`,oo=0,eu=e=>e.utils.createView({name:"image-preview-overlay",tag:"div",ignoreRect:!0,create:({root:t,props:i})=>{let a=Jm;if(document.querySelector("base")){let n=new URL(window.location.href.replace(window.location.hash,"")).href;a=a.replace(/url\(\#/g,"url("+n+"#")}oo++,t.element.classList.add(`filepond--image-preview-overlay-${i.status}`),t.element.innerHTML=a.replace(/__UID__/g,oo)},mixins:{styles:["opacity"],animations:{opacity:{type:"spring",mass:25}}}}),tu=function(){self.onmessage=e=>{createImageBitmap(e.data.message.file).then(t=>{self.postMessage({id:e.data.id,message:t},[t])})}},iu=function(){self.onmessage=e=>{let t=e.data.message.imageData,i=e.data.message.colorMatrix,a=t.data,n=a.length,l=i[0],o=i[1],r=i[2],s=i[3],p=i[4],c=i[5],d=i[6],m=i[7],u=i[8],g=i[9],f=i[10],h=i[11],I=i[12],b=i[13],T=i[14],v=i[15],y=i[16],E=i[17],_=i[18],x=i[19],R=0,z=0,P=0,A=0,B=0;for(;R{let i=new Image;i.onload=()=>{let a=i.naturalWidth,n=i.naturalHeight;i=null,t(a,n)},i.src=e},nu={1:()=>[1,0,0,1,0,0],2:e=>[-1,0,0,1,e,0],3:(e,t)=>[-1,0,0,-1,e,t],4:(e,t)=>[1,0,0,-1,0,t],5:()=>[0,1,1,0,0,0],6:(e,t)=>[0,1,-1,0,t,0],7:(e,t)=>[0,-1,-1,0,t,e],8:e=>[0,-1,1,0,0,e]},lu=(e,t,i,a)=>{a!==-1&&e.transform.apply(e,nu[a](t,i))},ou=(e,t,i,a)=>{t=Math.round(t),i=Math.round(i);let n=document.createElement("canvas");n.width=t,n.height=i;let l=n.getContext("2d");return a>=5&&a<=8&&([t,i]=[i,t]),lu(l,t,i,a),l.drawImage(e,0,0,t,i),n},co=e=>/^image/.test(e.type)&&!/svg/.test(e.type),ru=10,su=10,cu=e=>{let t=Math.min(ru/e.width,su/e.height),i=document.createElement("canvas"),a=i.getContext("2d"),n=i.width=Math.ceil(e.width*t),l=i.height=Math.ceil(e.height*t);a.drawImage(e,0,0,n,l);let o=null;try{o=a.getImageData(0,0,n,l).data}catch{return null}let r=o.length,s=0,p=0,c=0,d=0;for(;dMath.floor(Math.sqrt(e/(t/4))),du=(e,t)=>(t=t||document.createElement("canvas"),t.width=e.width,t.height=e.height,t.getContext("2d").drawImage(e,0,0),t),pu=e=>{let t;try{t=new ImageData(e.width,e.height)}catch{t=document.createElement("canvas").getContext("2d").createImageData(e.width,e.height)}return t.data.set(new Uint8ClampedArray(e.data)),t},mu=e=>new Promise((t,i)=>{let a=new Image;a.crossOrigin="Anonymous",a.onload=()=>{t(a)},a.onerror=n=>{i(n)},a.src=e}),uu=e=>{let t=eu(e),i=Zm(e),{createWorker:a}=e.utils,n=(b,T,v)=>new Promise(y=>{b.ref.imageData||(b.ref.imageData=v.getContext("2d").getImageData(0,0,v.width,v.height));let E=pu(b.ref.imageData);if(!T||T.length!==20)return v.getContext("2d").putImageData(E,0,0),y();let _=a(iu);_.post({imageData:E,colorMatrix:T},x=>{v.getContext("2d").putImageData(x,0,0),_.terminate(),y()},[E.data.buffer])}),l=(b,T)=>{b.removeChildView(T),T.image.width=1,T.image.height=1,T._destroy()},o=({root:b})=>{let T=b.ref.images.shift();return T.opacity=0,T.translateY=-15,b.ref.imageViewBin.push(T),T},r=({root:b,props:T,image:v})=>{let y=T.id,E=b.query("GET_ITEM",{id:y});if(!E)return;let _=E.getMetadata("crop")||{center:{x:.5,y:.5},flip:{horizontal:!1,vertical:!1},zoom:1,rotation:0,aspectRatio:null},x=b.query("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR"),R,z,P=!1;b.query("GET_IMAGE_PREVIEW_MARKUP_SHOW")&&(R=E.getMetadata("markup")||[],z=E.getMetadata("resize"),P=!0);let A=b.appendChildView(b.createChildView(i,{id:y,image:v,crop:_,resize:z,markup:R,dirty:P,background:x,opacity:0,scaleX:1.15,scaleY:1.15,translateY:15}),b.childViews.length);b.ref.images.push(A),A.opacity=1,A.scaleX=1,A.scaleY=1,A.translateY=0,setTimeout(()=>{b.dispatch("DID_IMAGE_PREVIEW_SHOW",{id:y})},250)},s=({root:b,props:T})=>{let v=b.query("GET_ITEM",{id:T.id});if(!v)return;let y=b.ref.images[b.ref.images.length-1];y.crop=v.getMetadata("crop"),y.background=b.query("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR"),b.query("GET_IMAGE_PREVIEW_MARKUP_SHOW")&&(y.dirty=!0,y.resize=v.getMetadata("resize"),y.markup=v.getMetadata("markup"))},p=({root:b,props:T,action:v})=>{if(!/crop|filter|markup|resize/.test(v.change.key)||!b.ref.images.length)return;let y=b.query("GET_ITEM",{id:T.id});if(y){if(/filter/.test(v.change.key)){let E=b.ref.images[b.ref.images.length-1];n(b,v.change.value,E.image);return}if(/crop|markup|resize/.test(v.change.key)){let E=y.getMetadata("crop"),_=b.ref.images[b.ref.images.length-1];if(E&&E.aspectRatio&&_.crop&&_.crop.aspectRatio&&Math.abs(E.aspectRatio-_.crop.aspectRatio)>1e-5){let x=o({root:b});r({root:b,props:T,image:du(x.image)})}else s({root:b,props:T})}}},c=b=>{let v=window.navigator.userAgent.match(/Firefox\/([0-9]+)\./),y=v?parseInt(v[1]):null;return y!==null&&y<=58?!1:"createImageBitmap"in window&&co(b)},d=({root:b,props:T})=>{let{id:v}=T,y=b.query("GET_ITEM",v);if(!y)return;let E=URL.createObjectURL(y.file);au(E,(_,x)=>{b.dispatch("DID_IMAGE_PREVIEW_CALCULATE_SIZE",{id:v,width:_,height:x})})},m=({root:b,props:T})=>{let{id:v}=T,y=b.query("GET_ITEM",v);if(!y)return;let E=URL.createObjectURL(y.file),_=()=>{mu(E).then(x)},x=R=>{URL.revokeObjectURL(E);let P=(y.getMetadata("exif")||{}).orientation||-1,{width:A,height:B}=R;if(!A||!B)return;P>=5&&P<=8&&([A,B]=[B,A]);let w=Math.max(1,window.devicePixelRatio*.75),S=b.query("GET_IMAGE_PREVIEW_ZOOM_FACTOR")*w,L=B/A,D=b.rect.element.width,F=b.rect.element.height,G=D,C=G*L;L>1?(G=Math.min(A,D*S),C=G*L):(C=Math.min(B,F*S),G=C/L);let q=ou(R,G,C,P),X=()=>{let pe=b.query("GET_IMAGE_PREVIEW_CALCULATE_AVERAGE_IMAGE_COLOR")?cu(data):null;y.setMetadata("color",pe,!0),"close"in R&&R.close(),b.ref.overlayShadow.opacity=1,r({root:b,props:T,image:q})},K=y.getMetadata("filter");K?n(b,K,q).then(X):X()};if(c(y.file)){let R=a(tu);R.post({file:y.file},z=>{if(R.terminate(),!z){_();return}x(z)})}else _()},u=({root:b})=>{let T=b.ref.images[b.ref.images.length-1];T.translateY=0,T.scaleX=1,T.scaleY=1,T.opacity=1},g=({root:b})=>{b.ref.overlayShadow.opacity=1,b.ref.overlayError.opacity=0,b.ref.overlaySuccess.opacity=0},f=({root:b})=>{b.ref.overlayShadow.opacity=.25,b.ref.overlayError.opacity=1},h=({root:b})=>{b.ref.overlayShadow.opacity=.25,b.ref.overlaySuccess.opacity=1},I=({root:b})=>{b.ref.images=[],b.ref.imageData=null,b.ref.imageViewBin=[],b.ref.overlayShadow=b.appendChildView(b.createChildView(t,{opacity:0,status:"idle"})),b.ref.overlaySuccess=b.appendChildView(b.createChildView(t,{opacity:0,status:"success"})),b.ref.overlayError=b.appendChildView(b.createChildView(t,{opacity:0,status:"failure"}))};return e.utils.createView({name:"image-preview-wrapper",create:I,styles:["height"],apis:["height"],destroy:({root:b})=>{b.ref.images.forEach(T=>{T.image.width=1,T.image.height=1})},didWriteView:({root:b})=>{b.ref.images.forEach(T=>{T.dirty=!1})},write:e.utils.createRoute({DID_IMAGE_PREVIEW_DRAW:u,DID_IMAGE_PREVIEW_CONTAINER_CREATE:d,DID_FINISH_CALCULATE_PREVIEWSIZE:m,DID_UPDATE_ITEM_METADATA:p,DID_THROW_ITEM_LOAD_ERROR:f,DID_THROW_ITEM_PROCESSING_ERROR:f,DID_THROW_ITEM_INVALID:f,DID_COMPLETE_ITEM_PROCESSING:h,DID_START_ITEM_PROCESSING:g,DID_REVERT_ITEM_PROCESSING:g},({root:b})=>{let T=b.ref.imageViewBin.filter(v=>v.opacity===0);b.ref.imageViewBin=b.ref.imageViewBin.filter(v=>v.opacity>0),T.forEach(v=>l(b,v)),T.length=0})})},po=e=>{let{addFilter:t,utils:i}=e,{Type:a,createRoute:n,isFile:l}=i,o=uu(e);return t("CREATE_VIEW",r=>{let{is:s,view:p,query:c}=r;if(!s("file")||!c("GET_ALLOW_IMAGE_PREVIEW"))return;let d=({root:h,props:I})=>{let{id:b}=I,T=c("GET_ITEM",b);if(!T||!l(T.file)||T.archived)return;let v=T.file;if(!Tm(v)||!c("GET_IMAGE_PREVIEW_FILTER_ITEM")(T))return;let y="createImageBitmap"in(window||{}),E=c("GET_IMAGE_PREVIEW_MAX_FILE_SIZE");if(!y&&E&&v.size>E)return;h.ref.imagePreview=p.appendChildView(p.createChildView(o,{id:b}));let _=h.query("GET_IMAGE_PREVIEW_HEIGHT");_&&h.dispatch("DID_UPDATE_PANEL_HEIGHT",{id:T.id,height:_});let x=!y&&v.size>c("GET_IMAGE_PREVIEW_MAX_INSTANT_PREVIEW_FILE_SIZE");h.dispatch("DID_IMAGE_PREVIEW_CONTAINER_CREATE",{id:b},x)},m=(h,I)=>{if(!h.ref.imagePreview)return;let{id:b}=I,T=h.query("GET_ITEM",{id:b});if(!T)return;let v=h.query("GET_PANEL_ASPECT_RATIO"),y=h.query("GET_ITEM_PANEL_ASPECT_RATIO"),E=h.query("GET_IMAGE_PREVIEW_HEIGHT");if(v||y||E)return;let{imageWidth:_,imageHeight:x}=h.ref;if(!_||!x)return;let R=h.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),z=h.query("GET_IMAGE_PREVIEW_MAX_HEIGHT"),A=(T.getMetadata("exif")||{}).orientation||-1;if(A>=5&&A<=8&&([_,x]=[x,_]),!co(T.file)||h.query("GET_IMAGE_PREVIEW_UPSCALE")){let D=2048/_;_*=D,x*=D}let B=x/_,w=(T.getMetadata("crop")||{}).aspectRatio||B,O=Math.max(R,Math.min(x,z)),S=h.rect.element.width,L=Math.min(S*w,O);h.dispatch("DID_UPDATE_PANEL_HEIGHT",{id:T.id,height:L})},u=({root:h})=>{h.ref.shouldRescale=!0},g=({root:h,action:I})=>{I.change.key==="crop"&&(h.ref.shouldRescale=!0)},f=({root:h,action:I})=>{h.ref.imageWidth=I.width,h.ref.imageHeight=I.height,h.ref.shouldRescale=!0,h.ref.shouldDrawPreview=!0,h.dispatch("KICK")};p.registerWriter(n({DID_RESIZE_ROOT:u,DID_STOP_RESIZE:u,DID_LOAD_ITEM:d,DID_IMAGE_PREVIEW_CALCULATE_SIZE:f,DID_UPDATE_ITEM_METADATA:g},({root:h,props:I})=>{h.ref.imagePreview&&(h.rect.element.hidden||(h.ref.shouldRescale&&(m(h,I),h.ref.shouldRescale=!1),h.ref.shouldDrawPreview&&(requestAnimationFrame(()=>{requestAnimationFrame(()=>{h.dispatch("DID_FINISH_CALCULATE_PREVIEWSIZE",{id:I.id})})}),h.ref.shouldDrawPreview=!1)))}))}),{options:{allowImagePreview:[!0,a.BOOLEAN],imagePreviewFilterItem:[()=>!0,a.FUNCTION],imagePreviewHeight:[null,a.INT],imagePreviewMinHeight:[44,a.INT],imagePreviewMaxHeight:[256,a.INT],imagePreviewMaxFileSize:[null,a.INT],imagePreviewZoomFactor:[2,a.INT],imagePreviewUpscale:[!1,a.BOOLEAN],imagePreviewMaxInstantPreviewFileSize:[1e6,a.INT],imagePreviewTransparencyIndicator:[null,a.STRING],imagePreviewCalculateAverageImageColor:[!1,a.BOOLEAN],imagePreviewMarkupShow:[!0,a.BOOLEAN],imagePreviewMarkupFilter:[()=>!0,a.FUNCTION]}}},gu=typeof window<"u"&&typeof window.document<"u";gu&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:po}));var mo=po;var fu=e=>/^image/.test(e.type),hu=(e,t)=>{let i=new Image;i.onload=()=>{let a=i.naturalWidth,n=i.naturalHeight;i=null,t({width:a,height:n})},i.onerror=()=>t(null),i.src=e},uo=({addFilter:e,utils:t})=>{let{Type:i}=t;return e("DID_LOAD_ITEM",(a,{query:n})=>new Promise((l,o)=>{let r=a.file;if(!fu(r)||!n("GET_ALLOW_IMAGE_RESIZE"))return l(a);let s=n("GET_IMAGE_RESIZE_MODE"),p=n("GET_IMAGE_RESIZE_TARGET_WIDTH"),c=n("GET_IMAGE_RESIZE_TARGET_HEIGHT"),d=n("GET_IMAGE_RESIZE_UPSCALE");if(p===null&&c===null)return l(a);let m=p===null?c:p,u=c===null?m:c,g=URL.createObjectURL(r);hu(g,f=>{if(URL.revokeObjectURL(g),!f)return l(a);let{width:h,height:I}=f,b=(a.getMetadata("exif")||{}).orientation||-1;if(b>=5&&b<=8&&([h,I]=[I,h]),h===m&&I===u)return l(a);if(!d){if(s==="cover"){if(h<=m||I<=u)return l(a)}else if(h<=m&&I<=m)return l(a)}a.setMetadata("resize",{mode:s,upscale:d,size:{width:m,height:u}}),l(a)})})),{options:{allowImageResize:[!0,i.BOOLEAN],imageResizeMode:["cover",i.STRING],imageResizeUpscale:[!0,i.BOOLEAN],imageResizeTargetWidth:[null,i.INT],imageResizeTargetHeight:[null,i.INT]}}},bu=typeof window<"u"&&typeof window.document<"u";bu&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:uo}));var go=uo;var Eu=e=>/^image/.test(e.type),Tu=e=>e.substr(0,e.lastIndexOf("."))||e,Iu={jpeg:"jpg","svg+xml":"svg"},vu=(e,t)=>{let i=Tu(e),a=t.split("/")[1],n=Iu[a]||a;return`${i}.${n}`},xu=e=>/jpeg|png|svg\+xml/.test(e)?e:"image/jpeg",yu=e=>/^image/.test(e.type),Ru={1:()=>[1,0,0,1,0,0],2:e=>[-1,0,0,1,e,0],3:(e,t)=>[-1,0,0,-1,e,t],4:(e,t)=>[1,0,0,-1,0,t],5:()=>[0,1,1,0,0,0],6:(e,t)=>[0,1,-1,0,t,0],7:(e,t)=>[0,-1,-1,0,t,e],8:e=>[0,-1,1,0,0,e]},Su=(e,t,i)=>(i===-1&&(i=1),Ru[i](e,t)),qt=(e,t)=>({x:e,y:t}),_u=(e,t)=>e.x*t.x+e.y*t.y,fo=(e,t)=>qt(e.x-t.x,e.y-t.y),wu=(e,t)=>_u(fo(e,t),fo(e,t)),ho=(e,t)=>Math.sqrt(wu(e,t)),bo=(e,t)=>{let i=e,a=1.5707963267948966,n=t,l=1.5707963267948966-t,o=Math.sin(a),r=Math.sin(n),s=Math.sin(l),p=Math.cos(l),c=i/o,d=c*r,m=c*s;return qt(p*d,p*m)},Lu=(e,t)=>{let i=e.width,a=e.height,n=bo(i,t),l=bo(a,t),o=qt(e.x+Math.abs(n.x),e.y-Math.abs(n.y)),r=qt(e.x+e.width+Math.abs(l.y),e.y+Math.abs(l.x)),s=qt(e.x-Math.abs(l.y),e.y+e.height-Math.abs(l.x));return{width:ho(o,r),height:ho(o,s)}},Io=(e,t,i=0,a={x:.5,y:.5})=>{let n=a.x>.5?1-a.x:a.x,l=a.y>.5?1-a.y:a.y,o=n*2*e.width,r=l*2*e.height,s=Lu(t,i);return Math.max(s.width/o,s.height/r)},vo=(e,t)=>{let i=e.width,a=i*t;a>e.height&&(a=e.height,i=a/t);let n=(e.width-i)*.5,l=(e.height-a)*.5;return{x:n,y:l,width:i,height:a}},Eo=(e,t,i=1)=>{let a=e.height/e.width,n=1,l=t,o=1,r=a;r>l&&(r=l,o=r/a);let s=Math.max(n/o,l/r),p=e.width/(i*s*o),c=p*t;return{width:p,height:c}},xo=e=>{e.width=1,e.height=1,e.getContext("2d").clearRect(0,0,1,1)},To=e=>e&&(e.horizontal||e.vertical),Mu=(e,t,i)=>{if(t<=1&&!To(i))return e.width=e.naturalWidth,e.height=e.naturalHeight,e;let a=document.createElement("canvas"),n=e.naturalWidth,l=e.naturalHeight,o=t>=5&&t<=8;o?(a.width=l,a.height=n):(a.width=n,a.height=l);let r=a.getContext("2d");if(t&&r.transform.apply(r,Su(n,l,t)),To(i)){let s=[1,0,0,1,0,0];(!o&&i.horizontal||o&i.vertical)&&(s[0]=-1,s[4]=n),(!o&&i.vertical||o&&i.horizontal)&&(s[3]=-1,s[5]=l),r.transform(...s)}return r.drawImage(e,0,0,n,l),a},Au=(e,t,i={},a={})=>{let{canvasMemoryLimit:n,background:l=null}=a,o=i.zoom||1,r=Mu(e,t,i.flip),s={width:r.width,height:r.height},p=i.aspectRatio||s.height/s.width,c=Eo(s,p,o);if(n){let T=c.width*c.height;if(T>n){let v=Math.sqrt(n)/Math.sqrt(T);s.width=Math.floor(s.width*v),s.height=Math.floor(s.height*v),c=Eo(s,p,o)}}let d=document.createElement("canvas"),m={x:c.width*.5,y:c.height*.5},u={x:0,y:0,width:c.width,height:c.height,center:m},g=typeof i.scaleToFit>"u"||i.scaleToFit,f=o*Io(s,vo(u,p),i.rotation,g?i.center:{x:.5,y:.5});d.width=Math.round(c.width/f),d.height=Math.round(c.height/f),m.x/=f,m.y/=f;let h={x:m.x-s.width*(i.center?i.center.x:.5),y:m.y-s.height*(i.center?i.center.y:.5)},I=d.getContext("2d");l&&(I.fillStyle=l,I.fillRect(0,0,d.width,d.height)),I.translate(m.x,m.y),I.rotate(i.rotation||0),I.drawImage(r,h.x-m.x,h.y-m.y,s.width,s.height);let b=I.getImageData(0,0,d.width,d.height);return xo(d),b},Pu=typeof window<"u"&&typeof window.document<"u";Pu&&(HTMLCanvasElement.prototype.toBlob||Object.defineProperty(HTMLCanvasElement.prototype,"toBlob",{value:function(e,t,i){var a=this.toDataURL(t,i).split(",")[1];setTimeout(function(){for(var n=atob(a),l=n.length,o=new Uint8Array(l),r=0;rnew Promise(a=>{let n=i?i(e):e;Promise.resolve(n).then(l=>{l.toBlob(a,t.type,t.quality)})}),Ri=(e,t)=>$t(e.x*t,e.y*t),Si=(e,t)=>$t(e.x+t.x,e.y+t.y),yo=e=>{let t=Math.sqrt(e.x*e.x+e.y*e.y);return t===0?{x:0,y:0}:$t(e.x/t,e.y/t)},qe=(e,t,i)=>{let a=Math.cos(t),n=Math.sin(t),l=$t(e.x-i.x,e.y-i.y);return $t(i.x+a*l.x-n*l.y,i.y+n*l.x+a*l.y)},$t=(e=0,t=0)=>({x:e,y:t}),me=(e,t,i=1,a)=>{if(typeof e=="string")return parseFloat(e)*i;if(typeof e=="number")return e*(a?t[a]:Math.min(t.width,t.height))},ct=(e,t,i)=>{let a=e.borderStyle||e.lineStyle||"solid",n=e.backgroundColor||e.fontColor||"transparent",l=e.borderColor||e.lineColor||"transparent",o=me(e.borderWidth||e.lineWidth,t,i),r=e.lineCap||"round",s=e.lineJoin||"round",p=typeof a=="string"?"":a.map(d=>me(d,t,i)).join(","),c=e.opacity||1;return{"stroke-linecap":r,"stroke-linejoin":s,"stroke-width":o||0,"stroke-dasharray":p,stroke:l,fill:n,opacity:c}},Le=e=>e!=null,wt=(e,t,i=1)=>{let a=me(e.x,t,i,"width")||me(e.left,t,i,"width"),n=me(e.y,t,i,"height")||me(e.top,t,i,"height"),l=me(e.width,t,i,"width"),o=me(e.height,t,i,"height"),r=me(e.right,t,i,"width"),s=me(e.bottom,t,i,"height");return Le(n)||(Le(o)&&Le(s)?n=t.height-o-s:n=s),Le(a)||(Le(l)&&Le(r)?a=t.width-l-r:a=r),Le(l)||(Le(a)&&Le(r)?l=t.width-a-r:l=0),Le(o)||(Le(n)&&Le(s)?o=t.height-n-s:o=0),{x:a||0,y:n||0,width:l||0,height:o||0}},Ou=e=>e.map((t,i)=>`${i===0?"M":"L"} ${t.x} ${t.y}`).join(" "),Ne=(e,t)=>Object.keys(t).forEach(i=>e.setAttribute(i,t[i])),Fu="http://www.w3.org/2000/svg",_t=(e,t)=>{let i=document.createElementNS(Fu,e);return t&&Ne(i,t),i},Du=e=>Ne(e,{...e.rect,...e.styles}),Cu=e=>{let t=e.rect.x+e.rect.width*.5,i=e.rect.y+e.rect.height*.5,a=e.rect.width*.5,n=e.rect.height*.5;return Ne(e,{cx:t,cy:i,rx:a,ry:n,...e.styles})},Bu={contain:"xMidYMid meet",cover:"xMidYMid slice"},Nu=(e,t)=>{Ne(e,{...e.rect,...e.styles,preserveAspectRatio:Bu[t.fit]||"none"})},ku={left:"start",center:"middle",right:"end"},Vu=(e,t,i,a)=>{let n=me(t.fontSize,i,a),l=t.fontFamily||"sans-serif",o=t.fontWeight||"normal",r=ku[t.textAlign]||"start";Ne(e,{...e.rect,...e.styles,"stroke-width":0,"font-weight":o,"font-size":n,"font-family":l,"text-anchor":r}),e.text!==t.text&&(e.text=t.text,e.textContent=t.text.length?t.text:" ")},Gu=(e,t,i,a)=>{Ne(e,{...e.rect,...e.styles,fill:"none"});let n=e.childNodes[0],l=e.childNodes[1],o=e.childNodes[2],r=e.rect,s={x:e.rect.x+e.rect.width,y:e.rect.y+e.rect.height};if(Ne(n,{x1:r.x,y1:r.y,x2:s.x,y2:s.y}),!t.lineDecoration)return;l.style.display="none",o.style.display="none";let p=yo({x:s.x-r.x,y:s.y-r.y}),c=me(.05,i,a);if(t.lineDecoration.indexOf("arrow-begin")!==-1){let d=Ri(p,c),m=Si(r,d),u=qe(r,2,m),g=qe(r,-2,m);Ne(l,{style:"display:block;",d:`M${u.x},${u.y} L${r.x},${r.y} L${g.x},${g.y}`})}if(t.lineDecoration.indexOf("arrow-end")!==-1){let d=Ri(p,-c),m=Si(s,d),u=qe(s,2,m),g=qe(s,-2,m);Ne(o,{style:"display:block;",d:`M${u.x},${u.y} L${s.x},${s.y} L${g.x},${g.y}`})}},Uu=(e,t,i,a)=>{Ne(e,{...e.styles,fill:"none",d:Ou(t.points.map(n=>({x:me(n.x,i,a,"width"),y:me(n.y,i,a,"height")})))})},yi=e=>t=>_t(e,{id:t.id}),Wu=e=>{let t=_t("image",{id:e.id,"stroke-linecap":"round","stroke-linejoin":"round",opacity:"0"});return t.onload=()=>{t.setAttribute("opacity",e.opacity||1)},t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",e.src),t},Hu=e=>{let t=_t("g",{id:e.id,"stroke-linecap":"round","stroke-linejoin":"round"}),i=_t("line");t.appendChild(i);let a=_t("path");t.appendChild(a);let n=_t("path");return t.appendChild(n),t},ju={image:Wu,rect:yi("rect"),ellipse:yi("ellipse"),text:yi("text"),path:yi("path"),line:Hu},Yu={rect:Du,ellipse:Cu,image:Nu,text:Vu,path:Uu,line:Gu},qu=(e,t)=>ju[e](t),$u=(e,t,i,a,n)=>{t!=="path"&&(e.rect=wt(i,a,n)),e.styles=ct(i,a,n),Yu[t](e,i,a,n)},Ro=(e,t)=>e[1].zIndex>t[1].zIndex?1:e[1].zIndexnew Promise(n=>{let{background:l=null}=a,o=new FileReader;o.onloadend=()=>{let r=o.result,s=document.createElement("div");s.style.cssText="position:absolute;pointer-events:none;width:0;height:0;visibility:hidden;",s.innerHTML=r;let p=s.querySelector("svg");document.body.appendChild(s);let c=p.getBBox();s.parentNode.removeChild(s);let d=s.querySelector("title"),m=p.getAttribute("viewBox")||"",u=p.getAttribute("width")||"",g=p.getAttribute("height")||"",f=parseFloat(u)||null,h=parseFloat(g)||null,I=(u.match(/[a-z]+/)||[])[0]||"",b=(g.match(/[a-z]+/)||[])[0]||"",T=m.split(" ").map(parseFloat),v=T.length?{x:T[0],y:T[1],width:T[2],height:T[3]}:c,y=f??v.width,E=h??v.height;p.style.overflow="visible",p.setAttribute("width",y),p.setAttribute("height",E);let _="";if(i&&i.length){let K={width:y,height:E};_=i.sort(Ro).reduce((pe,k)=>{let H=qu(k[0],k[1]);return $u(H,k[0],k[1],K),H.removeAttribute("id"),H.getAttribute("opacity")===1&&H.removeAttribute("opacity"),pe+` -`+H.outerHTML+` -`},""),_=` - -${_.replace(/ /g," ")} - -`}let x=t.aspectRatio||E/y,R=y,z=R*x,P=typeof t.scaleToFit>"u"||t.scaleToFit,A=t.center?t.center.x:.5,B=t.center?t.center.y:.5,w=Io({width:y,height:E},vo({width:R,height:z},x),t.rotation,P?{x:A,y:B}:{x:.5,y:.5}),O=t.zoom*w,S=t.rotation*(180/Math.PI),L={x:R*.5,y:z*.5},D={x:L.x-y*A,y:L.y-E*B},F=[`rotate(${S} ${L.x} ${L.y})`,`translate(${L.x} ${L.y})`,`scale(${O})`,`translate(${-L.x} ${-L.y})`,`translate(${D.x} ${D.y})`],G=t.flip&&t.flip.horizontal,C=t.flip&&t.flip.vertical,q=[`scale(${G?-1:1} ${C?-1:1})`,`translate(${G?-y:0} ${C?-E:0})`],X=` - - -${d?d.textContent:""} - - -${p.outerHTML}${_} - - -`;n(X)},o.readAsText(e)}),Ku=e=>{let t;try{t=new ImageData(e.width,e.height)}catch{t=document.createElement("canvas").getContext("2d").createImageData(e.width,e.height)}return t.data.set(e.data),t},Qu=()=>{let e={resize:c,filter:p},t=(d,m)=>(d.forEach(u=>{m=e[u.type](m,u.data)}),m),i=(d,m)=>{let u=d.transforms,g=null;if(u.forEach(f=>{f.type==="filter"&&(g=f)}),g){let f=null;u.forEach(h=>{h.type==="resize"&&(f=h)}),f&&(f.data.matrix=g.data,u=u.filter(h=>h.type!=="filter"))}m(t(u,d.imageData))};self.onmessage=d=>{i(d.data.message,m=>{self.postMessage({id:d.data.id,message:m},[m.data.buffer])})};let a=1,n=1,l=1;function o(d,m,u){let g=m[d]/255,f=m[d+1]/255,h=m[d+2]/255,I=m[d+3]/255,b=g*u[0]+f*u[1]+h*u[2]+I*u[3]+u[4],T=g*u[5]+f*u[6]+h*u[7]+I*u[8]+u[9],v=g*u[10]+f*u[11]+h*u[12]+I*u[13]+u[14],y=g*u[15]+f*u[16]+h*u[17]+I*u[18]+u[19],E=Math.max(0,b*y)+a*(1-y),_=Math.max(0,T*y)+n*(1-y),x=Math.max(0,v*y)+l*(1-y);m[d]=Math.max(0,Math.min(1,E))*255,m[d+1]=Math.max(0,Math.min(1,_))*255,m[d+2]=Math.max(0,Math.min(1,x))*255}let r=self.JSON.stringify([1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0]);function s(d){return self.JSON.stringify(d||[])===r}function p(d,m){if(!m||s(m))return d;let u=d.data,g=u.length,f=m[0],h=m[1],I=m[2],b=m[3],T=m[4],v=m[5],y=m[6],E=m[7],_=m[8],x=m[9],R=m[10],z=m[11],P=m[12],A=m[13],B=m[14],w=m[15],O=m[16],S=m[17],L=m[18],D=m[19],F=0,G=0,C=0,q=0,X=0,K=0,pe=0,k=0,H=0,Y=0,oe=0,ee=0;for(;F1&&g===!1)return p(d,I);f=d.width*w,h=d.height*w}let b=d.width,T=d.height,v=Math.round(f),y=Math.round(h),E=d.data,_=new Uint8ClampedArray(v*y*4),x=b/v,R=T/y,z=Math.ceil(x*.5),P=Math.ceil(R*.5);for(let A=0;A=-1&&oe<=1&&(O=2*oe*oe*oe-3*oe*oe+1,O>0)){Y=4*(H+X*b);let ee=E[Y+3];C+=O*ee,L+=O,ee<255&&(O=O*ee/250),D+=O*E[Y],F+=O*E[Y+1],G+=O*E[Y+2],S+=O}}}_[w]=D/S,_[w+1]=F/S,_[w+2]=G/S,_[w+3]=C/L,I&&o(w,_,I)}return{data:_,width:v,height:y}}},Zu=(e,t)=>{if(e.getUint32(t+4,!1)!==1165519206)return;t+=4;let i=e.getUint16(t+=6,!1)===18761;t+=e.getUint32(t+4,i);let a=e.getUint16(t,i);t+=2;for(let n=0;n{let t=new DataView(e);if(t.getUint16(0)!==65496)return null;let i=2,a,n,l=!1;for(;i=65504&&a<=65519||a===65534)||(l||(l=Zu(t,i,n)),i+n>t.byteLength)));)i+=n;return e.slice(0,i)},eg=e=>new Promise(t=>{let i=new FileReader;i.onload=()=>t(Ju(i.result)||null),i.readAsArrayBuffer(e.slice(0,256*1024))}),tg=()=>window.BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,ig=(e,t)=>{let i=tg();if(i){let a=new i;return a.append(e),a.getBlob(t)}return new Blob([e],{type:t})},ag=()=>Math.random().toString(36).substr(2,9),ng=e=>{let t=new Blob(["(",e.toString(),")()"],{type:"application/javascript"}),i=URL.createObjectURL(t),a=new Worker(i),n=[];return{transfer:()=>{},post:(l,o,r)=>{let s=ag();n[s]=o,a.onmessage=p=>{let c=n[p.data.id];c&&(c(p.data.message),delete n[p.data.id])},a.postMessage({id:s,message:l},r)},terminate:()=>{a.terminate(),URL.revokeObjectURL(i)}}},lg=e=>new Promise((t,i)=>{let a=new Image;a.onload=()=>{t(a)},a.onerror=n=>{i(n)},a.src=e}),og=e=>e.reduce((t,i)=>t.then(a=>i().then(Array.prototype.concat.bind(a))),Promise.resolve([])),rg=(e,t)=>new Promise(i=>{let a={width:e.width,height:e.height},n=e.getContext("2d"),l=t.sort(Ro).map(o=>()=>new Promise(r=>{gg[o[0]](n,a,o[1],r)&&r()}));og(l).then(()=>i(e))}),Lt=(e,t)=>{e.beginPath(),e.lineCap=t["stroke-linecap"],e.lineJoin=t["stroke-linejoin"],e.lineWidth=t["stroke-width"],t["stroke-dasharray"].length&&e.setLineDash(t["stroke-dasharray"].split(",")),e.fillStyle=t.fill,e.strokeStyle=t.stroke,e.globalAlpha=t.opacity||1},Mt=e=>{e.fill(),e.stroke(),e.globalAlpha=1},sg=(e,t,i)=>{let a=wt(i,t),n=ct(i,t);return Lt(e,n),e.rect(a.x,a.y,a.width,a.height),Mt(e,n),!0},cg=(e,t,i)=>{let a=wt(i,t),n=ct(i,t);Lt(e,n);let l=a.x,o=a.y,r=a.width,s=a.height,p=.5522848,c=r/2*p,d=s/2*p,m=l+r,u=o+s,g=l+r/2,f=o+s/2;return e.moveTo(l,f),e.bezierCurveTo(l,f-d,g-c,o,g,o),e.bezierCurveTo(g+c,o,m,f-d,m,f),e.bezierCurveTo(m,f+d,g+c,u,g,u),e.bezierCurveTo(g-c,u,l,f+d,l,f),Mt(e,n),!0},dg=(e,t,i,a)=>{let n=wt(i,t),l=ct(i,t);Lt(e,l);let o=new Image;new URL(i.src,window.location.href).origin!==window.location.origin&&(o.crossOrigin=""),o.onload=()=>{if(i.fit==="cover"){let s=n.width/n.height,p=s>1?o.width:o.height*s,c=s>1?o.width/s:o.height,d=o.width*.5-p*.5,m=o.height*.5-c*.5;e.drawImage(o,d,m,p,c,n.x,n.y,n.width,n.height)}else if(i.fit==="contain"){let s=Math.min(n.width/o.width,n.height/o.height),p=s*o.width,c=s*o.height,d=n.x+n.width*.5-p*.5,m=n.y+n.height*.5-c*.5;e.drawImage(o,0,0,o.width,o.height,d,m,p,c)}else e.drawImage(o,0,0,o.width,o.height,n.x,n.y,n.width,n.height);Mt(e,l),a()},o.src=i.src},pg=(e,t,i)=>{let a=wt(i,t),n=ct(i,t);Lt(e,n);let l=me(i.fontSize,t),o=i.fontFamily||"sans-serif",r=i.fontWeight||"normal",s=i.textAlign||"left";return e.font=`${r} ${l}px ${o}`,e.textAlign=s,e.fillText(i.text,a.x,a.y),Mt(e,n),!0},mg=(e,t,i)=>{let a=ct(i,t);Lt(e,a),e.beginPath();let n=i.points.map(o=>({x:me(o.x,t,1,"width"),y:me(o.y,t,1,"height")}));e.moveTo(n[0].x,n[0].y);let l=n.length;for(let o=1;o{let a=wt(i,t),n=ct(i,t);Lt(e,n),e.beginPath();let l={x:a.x,y:a.y},o={x:a.x+a.width,y:a.y+a.height};e.moveTo(l.x,l.y),e.lineTo(o.x,o.y);let r=yo({x:o.x-l.x,y:o.y-l.y}),s=.04*Math.min(t.width,t.height);if(i.lineDecoration.indexOf("arrow-begin")!==-1){let p=Ri(r,s),c=Si(l,p),d=qe(l,2,c),m=qe(l,-2,c);e.moveTo(d.x,d.y),e.lineTo(l.x,l.y),e.lineTo(m.x,m.y)}if(i.lineDecoration.indexOf("arrow-end")!==-1){let p=Ri(r,-s),c=Si(o,p),d=qe(o,2,c),m=qe(o,-2,c);e.moveTo(d.x,d.y),e.lineTo(o.x,o.y),e.lineTo(m.x,m.y)}return Mt(e,n),!0},gg={rect:sg,ellipse:cg,image:dg,text:pg,line:ug,path:mg},fg=e=>{let t=document.createElement("canvas");return t.width=e.width,t.height=e.height,t.getContext("2d").putImageData(e,0,0),t},hg=(e,t,i={})=>new Promise((a,n)=>{if(!e||!yu(e))return n({status:"not an image file",file:e});let{stripImageHead:l,beforeCreateBlob:o,afterCreateBlob:r,canvasMemoryLimit:s}=i,{crop:p,size:c,filter:d,markup:m,output:u}=t,g=t.image&&t.image.orientation?Math.max(1,Math.min(8,t.image.orientation)):null,f=u&&u.quality,h=f===null?null:f/100,I=u&&u.type||null,b=u&&u.background||null,T=[];c&&(typeof c.width=="number"||typeof c.height=="number")&&T.push({type:"resize",data:c}),d&&d.length===20&&T.push({type:"filter",data:d});let v=_=>{let x=r?r(_):_;Promise.resolve(x).then(a)},y=(_,x)=>{let R=fg(_),z=m.length?rg(R,m):R;Promise.resolve(z).then(P=>{zu(P,x,o).then(A=>{if(xo(P),l)return v(A);eg(e).then(B=>{B!==null&&(A=new Blob([B,A.slice(20)],{type:A.type})),v(A)})}).catch(n)})};if(/svg/.test(e.type)&&I===null)return Xu(e,p,m,{background:b}).then(_=>{a(ig(_,"image/svg+xml"))});let E=URL.createObjectURL(e);lg(E).then(_=>{URL.revokeObjectURL(E);let x=Au(_,g,p,{canvasMemoryLimit:s,background:b}),R={quality:h,type:I||e.type};if(!T.length)return y(x,R);let z=ng(Qu);z.post({transforms:T,imageData:x},P=>{y(Ku(P),R),z.terminate()},[x.data.buffer])}).catch(n)}),bg=["x","y","left","top","right","bottom","width","height"],Eg=e=>typeof e=="string"&&/%/.test(e)?parseFloat(e)/100:e,Tg=e=>{let[t,i]=e,a=i.points?{}:bg.reduce((n,l)=>(n[l]=Eg(i[l]),n),{});return[t,{zIndex:0,...i,...a}]},Ig=e=>new Promise((t,i)=>{let a=new Image;a.src=URL.createObjectURL(e);let n=()=>{let o=a.naturalWidth,r=a.naturalHeight;o&&r&&(URL.revokeObjectURL(a.src),clearInterval(l),t({width:o,height:r}))};a.onerror=o=>{URL.revokeObjectURL(a.src),clearInterval(l),i(o)};let l=setInterval(n,1);n()});typeof window<"u"&&typeof window.document<"u"&&(HTMLCanvasElement.prototype.toBlob||Object.defineProperty(HTMLCanvasElement.prototype,"toBlob",{value:function(e,t,i){let a=this;setTimeout(()=>{let n=a.toDataURL(t,i).split(",")[1],l=atob(n),o=l.length,r=new Uint8Array(o);for(;o--;)r[o]=l.charCodeAt(o);e(new Blob([r],{type:t||"image/png"}))})}}));var La=typeof window<"u"&&typeof window.document<"u",vg=La&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,So=({addFilter:e,utils:t})=>{let{Type:i,forin:a,getFileFromBlob:n,isFile:l}=t,o=["crop","resize","filter","markup","output"],r=c=>(d,m,u)=>d(m,c?c(u):u),s=c=>c.aspectRatio===null&&c.rotation===0&&c.zoom===1&&c.center&&c.center.x===.5&&c.center.y===.5&&c.flip&&c.flip.horizontal===!1&&c.flip.vertical===!1;e("SHOULD_PREPARE_OUTPUT",(c,{query:d})=>new Promise(m=>{m(!d("IS_ASYNC"))}));let p=(c,d,m)=>new Promise(u=>{if(!c("GET_ALLOW_IMAGE_TRANSFORM")||m.archived||!l(d)||!Eu(d))return u(!1);Ig(d).then(()=>{let g=c("GET_IMAGE_TRANSFORM_IMAGE_FILTER");if(g){let f=g(d);if(f==null)return handleRevert(!0);if(typeof f=="boolean")return u(f);if(typeof f.then=="function")return f.then(u)}u(!0)}).catch(g=>{u(!1)})});return e("DID_CREATE_ITEM",(c,{query:d,dispatch:m})=>{d("GET_ALLOW_IMAGE_TRANSFORM")&&c.extend("requestPrepare",()=>new Promise((u,g)=>{m("REQUEST_PREPARE_OUTPUT",{query:c.id,item:c,success:u,failure:g},!0)}))}),e("PREPARE_OUTPUT",(c,{query:d,item:m})=>new Promise(u=>{p(d,c,m).then(g=>{if(!g)return u(c);let f=[];d("GET_IMAGE_TRANSFORM_VARIANTS_INCLUDE_ORIGINAL")&&f.push(()=>new Promise(x=>{x({name:d("GET_IMAGE_TRANSFORM_VARIANTS_ORIGINAL_NAME"),file:c})})),d("GET_IMAGE_TRANSFORM_VARIANTS_INCLUDE_DEFAULT")&&f.push((x,R,z)=>new Promise(P=>{x(R,z).then(A=>P({name:d("GET_IMAGE_TRANSFORM_VARIANTS_DEFAULT_NAME"),file:A}))}));let h=d("GET_IMAGE_TRANSFORM_VARIANTS")||{};a(h,(x,R)=>{let z=r(R);f.push((P,A,B)=>new Promise(w=>{z(P,A,B).then(O=>w({name:x,file:O}))}))});let I=d("GET_IMAGE_TRANSFORM_OUTPUT_QUALITY"),b=d("GET_IMAGE_TRANSFORM_OUTPUT_QUALITY_MODE"),T=I===null?null:I/100,v=d("GET_IMAGE_TRANSFORM_OUTPUT_MIME_TYPE"),y=d("GET_IMAGE_TRANSFORM_CLIENT_TRANSFORMS")||o;m.setMetadata("output",{type:v,quality:T,client:y},!0);let E=(x,R)=>new Promise((z,P)=>{let A={...R};Object.keys(A).filter(C=>C!=="exif").forEach(C=>{y.indexOf(C)===-1&&delete A[C]});let{resize:B,exif:w,output:O,crop:S,filter:L,markup:D}=A,F={image:{orientation:w?w.orientation:null},output:O&&(O.type||typeof O.quality=="number"||O.background)?{type:O.type,quality:typeof O.quality=="number"?O.quality*100:null,background:O.background||d("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR")||null}:void 0,size:B&&(B.size.width||B.size.height)?{mode:B.mode,upscale:B.upscale,...B.size}:void 0,crop:S&&!s(S)?{...S}:void 0,markup:D&&D.length?D.map(Tg):[],filter:L};if(F.output){let C=O.type?O.type!==x.type:!1,q=/\/jpe?g$/.test(x.type),X=O.quality!==null?q&&b==="always":!1;if(!!!(F.size||F.crop||F.filter||C||X))return z(x)}let G={beforeCreateBlob:d("GET_IMAGE_TRANSFORM_BEFORE_CREATE_BLOB"),afterCreateBlob:d("GET_IMAGE_TRANSFORM_AFTER_CREATE_BLOB"),canvasMemoryLimit:d("GET_IMAGE_TRANSFORM_CANVAS_MEMORY_LIMIT"),stripImageHead:d("GET_IMAGE_TRANSFORM_OUTPUT_STRIP_IMAGE_HEAD")};hg(x,F,G).then(C=>{let q=n(C,vu(x.name,xu(C.type)));z(q)}).catch(P)}),_=f.map(x=>x(E,c,m.getMetadata()));Promise.all(_).then(x=>{u(x.length===1&&x[0].name===null?x[0].file:x)})})})),{options:{allowImageTransform:[!0,i.BOOLEAN],imageTransformImageFilter:[null,i.FUNCTION],imageTransformOutputMimeType:[null,i.STRING],imageTransformOutputQuality:[null,i.INT],imageTransformOutputStripImageHead:[!0,i.BOOLEAN],imageTransformClientTransforms:[null,i.ARRAY],imageTransformOutputQualityMode:["always",i.STRING],imageTransformVariants:[null,i.OBJECT],imageTransformVariantsIncludeDefault:[!0,i.BOOLEAN],imageTransformVariantsDefaultName:[null,i.STRING],imageTransformVariantsIncludeOriginal:[!1,i.BOOLEAN],imageTransformVariantsOriginalName:["original_",i.STRING],imageTransformBeforeCreateBlob:[null,i.FUNCTION],imageTransformAfterCreateBlob:[null,i.FUNCTION],imageTransformCanvasMemoryLimit:[La&&vg?4096*4096:null,i.INT],imageTransformCanvasBackgroundColor:[null,i.STRING]}}};La&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:So}));var _o=So;var Ma=e=>/^video/.test(e.type),Xt=e=>/^audio/.test(e.type),Aa=class{constructor(t,i){this.mediaEl=t,this.audioElems=i,this.onplayhead=!1,this.duration=0,this.timelineWidth=this.audioElems.timeline.offsetWidth-this.audioElems.playhead.offsetWidth,this.moveplayheadFn=this.moveplayhead.bind(this),this.registerListeners()}registerListeners(){this.mediaEl.addEventListener("timeupdate",this.timeUpdate.bind(this),!1),this.mediaEl.addEventListener("canplaythrough",()=>this.duration=this.mediaEl.duration,!1),this.audioElems.timeline.addEventListener("click",this.timelineClicked.bind(this),!1),this.audioElems.button.addEventListener("click",this.play.bind(this)),this.audioElems.playhead.addEventListener("mousedown",this.mouseDown.bind(this),!1),window.addEventListener("mouseup",this.mouseUp.bind(this),!1)}play(){this.mediaEl.paused?this.mediaEl.play():this.mediaEl.pause(),this.audioElems.button.classList.toggle("play"),this.audioElems.button.classList.toggle("pause")}timeUpdate(){let t=this.mediaEl.currentTime/this.duration*100;this.audioElems.playhead.style.marginLeft=t+"%",this.mediaEl.currentTime===this.duration&&(this.audioElems.button.classList.toggle("play"),this.audioElems.button.classList.toggle("pause"))}moveplayhead(t){let i=t.clientX-this.getPosition(this.audioElems.timeline);i>=0&&i<=this.timelineWidth&&(this.audioElems.playhead.style.marginLeft=i+"px"),i<0&&(this.audioElems.playhead.style.marginLeft="0px"),i>this.timelineWidth&&(this.audioElems.playhead.style.marginLeft=this.timelineWidth-4+"px")}timelineClicked(t){this.moveplayhead(t),this.mediaEl.currentTime=this.duration*this.clickPercent(t)}mouseDown(){this.onplayhead=!0,window.addEventListener("mousemove",this.moveplayheadFn,!0),this.mediaEl.removeEventListener("timeupdate",this.timeUpdate.bind(this),!1)}mouseUp(t){window.removeEventListener("mousemove",this.moveplayheadFn,!0),this.onplayhead==!0&&(this.moveplayhead(t),this.mediaEl.currentTime=this.duration*this.clickPercent(t),this.mediaEl.addEventListener("timeupdate",this.timeUpdate.bind(this),!1)),this.onplayhead=!1}clickPercent(t){return(t.clientX-this.getPosition(this.audioElems.timeline))/this.timelineWidth}getPosition(t){return t.getBoundingClientRect().left}},xg=e=>e.utils.createView({name:"media-preview",tag:"div",ignoreRect:!0,create:({root:t,props:i})=>{let{id:a}=i,n=t.query("GET_ITEM",{id:i.id}),l=Xt(n.file)?"audio":"video";if(t.ref.media=document.createElement(l),t.ref.media.setAttribute("controls",!0),t.element.appendChild(t.ref.media),Xt(n.file)){let o=document.createDocumentFragment();t.ref.audio=[],t.ref.audio.container=document.createElement("div"),t.ref.audio.button=document.createElement("span"),t.ref.audio.timeline=document.createElement("div"),t.ref.audio.playhead=document.createElement("div"),t.ref.audio.container.className="audioplayer",t.ref.audio.button.className="playpausebtn play",t.ref.audio.timeline.className="timeline",t.ref.audio.playhead.className="playhead",t.ref.audio.timeline.appendChild(t.ref.audio.playhead),t.ref.audio.container.appendChild(t.ref.audio.button),t.ref.audio.container.appendChild(t.ref.audio.timeline),o.appendChild(t.ref.audio.container),t.element.appendChild(o)}},write:e.utils.createRoute({DID_MEDIA_PREVIEW_LOAD:({root:t,props:i})=>{let{id:a}=i,n=t.query("GET_ITEM",{id:i.id});if(!n)return;let l=window.URL||window.webkitURL,o=new Blob([n.file],{type:n.file.type});t.ref.media.type=n.file.type,t.ref.media.src=n.file.mock&&n.file.url||l.createObjectURL(o),Xt(n.file)&&new Aa(t.ref.media,t.ref.audio),t.ref.media.addEventListener("loadeddata",()=>{let r=75;if(Ma(n.file)){let s=t.ref.media.offsetWidth,p=t.ref.media.videoWidth/s;r=t.ref.media.videoHeight/p}t.dispatch("DID_UPDATE_PANEL_HEIGHT",{id:i.id,height:r})},!1)}})}),yg=e=>{let t=({root:a,props:n})=>{let{id:l}=n;a.query("GET_ITEM",l)&&a.dispatch("DID_MEDIA_PREVIEW_LOAD",{id:l})},i=({root:a,props:n})=>{let l=xg(e);a.ref.media=a.appendChildView(a.createChildView(l,{id:n.id}))};return e.utils.createView({name:"media-preview-wrapper",create:i,write:e.utils.createRoute({DID_MEDIA_PREVIEW_CONTAINER_CREATE:t})})},Pa=e=>{let{addFilter:t,utils:i}=e,{Type:a,createRoute:n}=i,l=yg(e);return t("CREATE_VIEW",o=>{let{is:r,view:s,query:p}=o;if(!r("file"))return;let c=({root:d,props:m})=>{let{id:u}=m,g=p("GET_ITEM",u),f=p("GET_ALLOW_VIDEO_PREVIEW"),h=p("GET_ALLOW_AUDIO_PREVIEW");!g||g.archived||(!Ma(g.file)||!f)&&(!Xt(g.file)||!h)||(d.ref.mediaPreview=s.appendChildView(s.createChildView(l,{id:u})),d.dispatch("DID_MEDIA_PREVIEW_CONTAINER_CREATE",{id:u}))};s.registerWriter(n({DID_LOAD_ITEM:c},({root:d,props:m})=>{let{id:u}=m,g=p("GET_ITEM",u),f=d.query("GET_ALLOW_VIDEO_PREVIEW"),h=d.query("GET_ALLOW_AUDIO_PREVIEW");!g||(!Ma(g.file)||!f)&&(!Xt(g.file)||!h)||d.rect.element.hidden}))}),{options:{allowVideoPreview:[!0,a.BOOLEAN],allowAudioPreview:[!0,a.BOOLEAN]}}},Rg=typeof window<"u"&&typeof window.document<"u";Rg&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:Pa}));var wo={labelIdle:'\u134B\u12ED\u120E\u127D \u1235\u1260\u12CD \u12A5\u12DA\u1205 \u130B\u122D \u12ED\u120D\u1240\u1241\u1275 \u12C8\u12ED\u121D \u134B\u12ED\u1209\u1295 \u12ED\u121D\u1228\u1321 ',labelInvalidField:"\u1218\u1235\u12A9 \u120D\u12AD \u12EB\u120D\u1206\u1291 \u134B\u12ED\u120E\u127D\u1295 \u12ED\u12DF\u120D",labelFileWaitingForSize:"\u12E8\u134B\u12ED\u1209\u1295 \u1218\u1320\u1295 \u1260\u1218\u1320\u1263\u1260\u1245 \u120B\u12ED",labelFileSizeNotAvailable:"\u12E8\u134B\u12ED\u1209\u1295 \u1218\u1320\u1295 \u120A\u1308\u129D \u12A0\u120D\u127B\u1208\u121D",labelFileLoading:"\u1260\u121B\u1295\u1260\u1265 \u120B\u12ED",labelFileLoadError:"\u1260\u121B\u1295\u1260\u1265 \u120B\u12ED \u127D\u130D\u122D \u1270\u1348\u1325\u122F\u120D",labelFileProcessing:"\u134B\u12ED\u1209\u1295 \u1260\u1218\u132B\u1295 \u120B\u12ED",labelFileProcessingComplete:"\u134B\u12ED\u1209\u1295 \u1218\u132B\u1295 \u1270\u1320\u1293\u1245\u124B\u120D",labelFileProcessingAborted:"\u134B\u12ED\u1209\u1295 \u1218\u132B\u1295 \u1270\u124B\u122D\u1327\u120D",labelFileProcessingError:"\u134B\u12ED\u1209\u1295 \u1260\u1218\u132B\u1295 \u120B\u12ED \u127D\u130D\u122D \u1270\u1348\u1325\u122F\u120D",labelFileProcessingRevertError:"\u1348\u12ED\u1209\u1295 \u1260\u1218\u1240\u120D\u1260\u1235 \u120B\u12ED \u127D\u130D\u122D \u1270\u1348\u1325\u122F\u120D",labelFileRemoveError:"\u1260\u121B\u1325\u134B\u1275 \u120B\u12ED \u127D\u130D\u122D \u1270\u1348\u1325\u122F\u120D",labelTapToCancel:"\u1208\u121B\u124B\u1228\u1325 \u1290\u12AB \u12EB\u12F5\u122D\u1309",labelTapToRetry:"\u12F0\u130D\u121E \u1208\u1218\u121E\u12A8\u122D \u1290\u12AB \u12EB\u12F5\u122D\u1309",labelTapToUndo:"\u12C8\u12F0\u1290\u1260\u1228\u1260\u1275 \u1208\u1218\u1218\u1208\u1235 \u1290\u12AB \u12EB\u12F5\u122D\u1309",labelButtonRemoveItem:"\u120B\u1325\u134B",labelButtonAbortItemLoad:"\u120B\u124B\u122D\u1325",labelButtonRetryItemLoad:"\u12F0\u130D\u121C \u120D\u121E\u12AD\u122D",labelButtonAbortItemProcessing:"\u12ED\u1245\u122D",labelButtonUndoItemProcessing:"\u12C8\u12F0\u1290\u1260\u1228\u1260\u1275 \u120D\u1218\u120D\u1235",labelButtonRetryItemProcessing:"\u12F0\u130D\u121C \u120D\u121E\u12AD\u122D",labelButtonProcessItem:"\u120D\u132B\u1295",labelMaxFileSizeExceeded:"\u134B\u12ED\u1209 \u1270\u120D\u124B\u120D",labelMaxFileSize:"\u12E8\u134B\u12ED\u120D \u1218\u1320\u1295 \u12A8 {filesize} \u1218\u1265\u1208\u1325 \u12A0\u12ED\u1348\u1240\u12F5\u121D",labelMaxTotalFileSizeExceeded:"\u12E8\u121A\u1348\u1240\u12F0\u12CD\u1295 \u1320\u1245\u120B\u120B \u12E8\u134B\u12ED\u120D \u1218\u1320\u1295 \u12A0\u120D\u1348\u12CB\u120D",labelMaxTotalFileSize:"\u1320\u1245\u120B\u120B \u12E8\u134B\u12ED\u120D \u1218\u1320\u1295 \u12A8 {filesize} \u1218\u1265\u1208\u1325 \u12A0\u12ED\u1348\u1240\u12F5\u121D",labelFileTypeNotAllowed:"\u12E8\u1270\u1233\u1233\u1270 \u12E8\u134B\u12ED\u120D \u12A0\u12ED\u1290\u1275 \u1290\u12CD",fileValidateTypeLabelExpectedTypes:"\u12E8\u134B\u12ED\u120D \u12A0\u12ED\u1290\u1271 \u1218\u1206\u1295 \u12E8\u121A\u1308\u1263\u12CD {allButLastType} \u12A5\u1293 {lastType} \u1290\u12CD",imageValidateSizeLabelFormatError:"\u12E8\u121D\u1235\u120D \u12A0\u12ED\u1290\u1271 \u1208\u1218\u132B\u1295 \u12A0\u12ED\u1206\u1295\u121D",imageValidateSizeLabelImageSizeTooSmall:"\u121D\u1235\u1209 \u1260\u1323\u121D \u12A0\u1295\u1237\u120D",imageValidateSizeLabelImageSizeTooBig:"\u121D\u1235\u1209 \u1260\u1323\u121D \u1270\u120D\u124B\u120D",imageValidateSizeLabelExpectedMinSize:"\u12DD\u1245\u1270\u129B\u12CD \u12E8\u121D\u1235\u120D \u120D\u12AC\u1275 {minWidth} \xD7 {minHeight} \u1290\u12CD",imageValidateSizeLabelExpectedMaxSize:"\u12A8\u134D\u1270\u129B\u12CD \u12E8\u121D\u1235\u120D \u120D\u12AC\u1275 {maxWidth} \xD7 {maxHeight} \u1290\u12CD",imageValidateSizeLabelImageResolutionTooLow:"\u12E8\u121D\u1235\u1209 \u1325\u122B\u1275 \u1260\u1323\u121D \u12DD\u1245\u1270\u129B \u1290\u12CD",imageValidateSizeLabelImageResolutionTooHigh:"\u12E8\u121D\u1235\u1209 \u1325\u122B\u1275 \u1260\u1323\u121D \u12A8\u134D\u1270\u129B \u1290\u12CD",imageValidateSizeLabelExpectedMinResolution:"\u12DD\u1245\u1270\u129B\u12CD \u12E8\u121D\u1235\u120D \u1325\u122B\u1275 {minResolution} \u1290\u12CD",imageValidateSizeLabelExpectedMaxResolution:"\u12A8\u134D\u1270\u129B\u12CD \u12E8\u121D\u1235\u120D \u1325\u122B\u1275 {maxResolution} \u1290\u12CD"};var Lo={labelIdle:'\u0627\u0633\u062D\u0628 \u0648 \u0627\u062F\u0631\u062C \u0645\u0644\u0641\u0627\u062A\u0643 \u0623\u0648 \u062A\u0635\u0641\u062D ',labelInvalidField:"\u0627\u0644\u062D\u0642\u0644 \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0645\u0644\u0641\u0627\u062A \u063A\u064A\u0631 \u0635\u0627\u0644\u062D\u0629",labelFileWaitingForSize:"\u0628\u0627\u0646\u062A\u0638\u0627\u0631 \u0627\u0644\u062D\u062C\u0645",labelFileSizeNotAvailable:"\u0627\u0644\u062D\u062C\u0645 \u063A\u064A\u0631 \u0645\u062A\u0627\u062D",labelFileLoading:"\u0628\u0627\u0644\u0625\u0646\u062A\u0638\u0627\u0631",labelFileLoadError:"\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u062A\u062D\u0645\u064A\u0644",labelFileProcessing:"\u064A\u062A\u0645 \u0627\u0644\u0631\u0641\u0639",labelFileProcessingComplete:"\u062A\u0645 \u0627\u0644\u0631\u0641\u0639",labelFileProcessingAborted:"\u062A\u0645 \u0625\u0644\u063A\u0627\u0621 \u0627\u0644\u0631\u0641\u0639",labelFileProcessingError:"\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u0631\u0641\u0639",labelFileProcessingRevertError:"\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u062A\u0631\u0627\u062C\u0639",labelFileRemoveError:"\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u062D\u0630\u0641",labelTapToCancel:"\u0627\u0646\u0642\u0631 \u0644\u0644\u0625\u0644\u063A\u0627\u0621",labelTapToRetry:"\u0627\u0646\u0642\u0631 \u0644\u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629",labelTapToUndo:"\u0627\u0646\u0642\u0631 \u0644\u0644\u062A\u0631\u0627\u062C\u0639",labelButtonRemoveItem:"\u0645\u0633\u062D",labelButtonAbortItemLoad:"\u0625\u0644\u063A\u0627\u0621",labelButtonRetryItemLoad:"\u0625\u0639\u0627\u062F\u0629",labelButtonAbortItemProcessing:"\u0625\u0644\u063A\u0627\u0621",labelButtonUndoItemProcessing:"\u062A\u0631\u0627\u062C\u0639",labelButtonRetryItemProcessing:"\u0625\u0639\u0627\u062F\u0629",labelButtonProcessItem:"\u0631\u0641\u0639",labelMaxFileSizeExceeded:"\u0627\u0644\u0645\u0644\u0641 \u0643\u0628\u064A\u0631 \u062C\u062F\u0627",labelMaxFileSize:"\u062D\u062C\u0645 \u0627\u0644\u0645\u0644\u0641 \u0627\u0644\u0623\u0642\u0635\u0649: {filesize}",labelMaxTotalFileSizeExceeded:"\u062A\u0645 \u062A\u062C\u0627\u0648\u0632 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 \u0644\u0644\u062D\u062C\u0645 \u0627\u0644\u0625\u062C\u0645\u0627\u0644\u064A",labelMaxTotalFileSize:"\u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 \u0644\u062D\u062C\u0645 \u0627\u0644\u0645\u0644\u0641: {filesize}",labelFileTypeNotAllowed:"\u0645\u0644\u0641 \u0645\u0646 \u0646\u0648\u0639 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D",fileValidateTypeLabelExpectedTypes:"\u062A\u062A\u0648\u0642\u0639 {allButLastType} \u0645\u0646 {lastType}",imageValidateSizeLabelFormatError:"\u0646\u0648\u0639 \u0627\u0644\u0635\u0648\u0631\u0629 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645",imageValidateSizeLabelImageSizeTooSmall:"\u0627\u0644\u0635\u0648\u0631\u0629 \u0635\u063A\u064A\u0631 \u062C\u062F\u0627",imageValidateSizeLabelImageSizeTooBig:"\u0627\u0644\u0635\u0648\u0631\u0629 \u0643\u0628\u064A\u0631\u0629 \u062C\u062F\u0627",imageValidateSizeLabelExpectedMinSize:"\u0627\u0644\u062D\u062F \u0627\u0644\u0623\u062F\u0646\u0649 \u0644\u0644\u0623\u0628\u0639\u0627\u062F \u0647\u0648: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u0627\u0644\u062D\u062F \u0627\u0644\u0623\u0642\u0635\u0649 \u0644\u0644\u0623\u0628\u0639\u0627\u062F \u0647\u0648: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0627\u0644\u062F\u0642\u0629 \u0636\u0639\u064A\u0641\u0629 \u062C\u062F\u0627",imageValidateSizeLabelImageResolutionTooHigh:"\u0627\u0644\u062F\u0642\u0629 \u0645\u0631\u062A\u0641\u0639\u0629 \u062C\u062F\u0627",imageValidateSizeLabelExpectedMinResolution:"\u0623\u0642\u0644 \u062F\u0642\u0629: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u0623\u0642\u0635\u0649 \u062F\u0642\u0629: {maxResolution}"};var Mo={labelIdle:'Fayl\u0131n\u0131z\u0131 S\xFCr\xFC\u015Fd\xFCr\xFCn & Burax\u0131n ya da Se\xE7in ',labelInvalidField:"Sah\u0259d\u0259 etibars\u0131z fayllar var",labelFileWaitingForSize:"\xD6l\xE7\xFC hesablan\u0131r",labelFileSizeNotAvailable:"\xD6l\xE7\xFC m\xF6vcud deyil",labelFileLoading:"Y\xFCkl\u0259nir",labelFileLoadError:"Y\xFCkl\u0259m\u0259 \u0259snas\u0131nda x\u0259ta ba\u015F verdi",labelFileProcessing:"Y\xFCkl\u0259nir",labelFileProcessingComplete:"Y\xFCkl\u0259m\u0259 tamamland\u0131",labelFileProcessingAborted:"Y\xFCkl\u0259m\u0259 l\u0259\u011Fv edildi",labelFileProcessingError:"Y\xFCk\u0259y\u0259rk\u0259n x\u0259ta ba\u015F verdi",labelFileProcessingRevertError:"Geri \xE7\u0259k\u0259rk\u0259n x\u0259ta ba\u015F verdi",labelFileRemoveError:"\xC7\u0131xarark\u0259n x\u0259ta ba\u015F verdi",labelTapToCancel:"\u0130mtina etm\u0259k \xFC\xE7\xFCn klikl\u0259yin",labelTapToRetry:"T\u0259krar yoxlamaq \xFC\xE7\xFCn klikl\u0259yin",labelTapToUndo:"Geri almaq \xFC\xE7\xFCn klikl\u0259yin",labelButtonRemoveItem:"\xC7\u0131xar",labelButtonAbortItemLoad:"\u0130mtina Et",labelButtonRetryItemLoad:"T\u0259krar yoxla",labelButtonAbortItemProcessing:"\u0130mtina et",labelButtonUndoItemProcessing:"Geri Al",labelButtonRetryItemProcessing:"T\u0259krar yoxla",labelButtonProcessItem:"Y\xFCkl\u0259",labelMaxFileSizeExceeded:"Fayl \xE7ox b\xF6y\xFCkd\xFCr",labelMaxFileSize:"\u018Fn b\xF6y\xFCk fayl \xF6l\xE7\xFCs\xFC: {filesize}",labelMaxTotalFileSizeExceeded:"Maksimum \xF6l\xE7\xFC ke\xE7ildi",labelMaxTotalFileSize:"Maksimum fayl \xF6l\xE7\xFCs\xFC :{filesize}",labelFileTypeNotAllowed:"Etibars\u0131z fayl tipi",fileValidateTypeLabelExpectedTypes:"Bu {allButLastType} ya da bu fayl olmas\u0131 laz\u0131md\u0131r: {lastType}",imageValidateSizeLabelFormatError:"\u015E\u0259kil tipi d\u0259st\u0259kl\u0259nmir",imageValidateSizeLabelImageSizeTooSmall:"\u015E\u0259kil \xE7ox ki\xE7ik",imageValidateSizeLabelImageSizeTooBig:"\u015E\u0259kil \xE7ox b\xF6y\xFCk",imageValidateSizeLabelExpectedMinSize:"Minimum \xF6l\xE7\xFC {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksimum \xF6l\xE7\xFC {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"G\xF6r\xFCnt\xFC imkan\u0131 \xE7ox a\u015Fa\u011F\u0131",imageValidateSizeLabelImageResolutionTooHigh:"G\xF6r\xFCnt\xFC imkan\u0131 \xE7ox y\xFCks\u0259k",imageValidateSizeLabelExpectedMinResolution:"Minimum g\xF6r\xFCnt\xFC imkan\u0131 {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maximum g\xF6r\xFCnt\xFC imkan\u0131 {maxResolution}"};var Ao={labelIdle:'Arrossega i deixa anar els teus fitxers o Navega ',labelInvalidField:"El camp cont\xE9 fitxers inv\xE0lids",labelFileWaitingForSize:"Esperant mida",labelFileSizeNotAvailable:"Mida no disponible",labelFileLoading:"Carregant",labelFileLoadError:"Error durant la c\xE0rrega",labelFileProcessing:"Pujant",labelFileProcessingComplete:"Pujada completada",labelFileProcessingAborted:"Pujada cancel\xB7lada",labelFileProcessingError:"Error durant la pujada",labelFileProcessingRevertError:"Error durant la reversi\xF3",labelFileRemoveError:"Error durant l'eliminaci\xF3",labelTapToCancel:"toca per cancel\xB7lar",labelTapToRetry:"toca per reintentar",labelTapToUndo:"toca per desfer",labelButtonRemoveItem:"Eliminar",labelButtonAbortItemLoad:"Cancel\xB7lar",labelButtonRetryItemLoad:"Reintentar",labelButtonAbortItemProcessing:"Cancel\xB7lar",labelButtonUndoItemProcessing:"Desfer",labelButtonRetryItemProcessing:"Reintentar",labelButtonProcessItem:"Pujar",labelMaxFileSizeExceeded:"El fitxer \xE9s massa gran",labelMaxFileSize:"La mida m\xE0xima del fitxer \xE9s {filesize}",labelMaxTotalFileSizeExceeded:"Mida m\xE0xima total excedida",labelMaxTotalFileSize:"La mida m\xE0xima total del fitxer \xE9s {filesize}",labelFileTypeNotAllowed:"Fitxer de tipus inv\xE0lid",fileValidateTypeLabelExpectedTypes:"Espera {allButLastType} o {lastType}",imageValidateSizeLabelFormatError:"Tipus d'imatge no suportada",imageValidateSizeLabelImageSizeTooSmall:"La imatge \xE9s massa petita",imageValidateSizeLabelImageSizeTooBig:"La imatge \xE9s massa gran",imageValidateSizeLabelExpectedMinSize:"La mida m\xEDnima \xE9s {minWidth} x {minHeight}",imageValidateSizeLabelExpectedMaxSize:"La mida m\xE0xima \xE9s {maxWidth} x {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"La resoluci\xF3 \xE9s massa baixa",imageValidateSizeLabelImageResolutionTooHigh:"La resoluci\xF3 \xE9s massa alta",imageValidateSizeLabelExpectedMinResolution:"La resoluci\xF3 m\xEDnima \xE9s {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"La resoluci\xF3 m\xE0xima \xE9s {maxResolution}"};var Po={labelIdle:'\u067E\u06D5\u0695\u06AF\u06D5\u06A9\u0627\u0646 \u0641\u0695\u06CE \u0628\u062F\u06D5 \u0626\u06CE\u0631\u06D5 \u0628\u06C6 \u0628\u0627\u0631\u06A9\u0631\u062F\u0646 \u06CC\u0627\u0646 \u0647\u06D5\u06B5\u0628\u0698\u06CE\u0631\u06D5 ',labelInvalidField:"\u067E\u06D5\u0695\u06AF\u06D5\u06CC \u0646\u0627\u062F\u0631\u0648\u0633\u062A\u06CC \u062A\u06CE\u062F\u0627\u06CC\u06D5",labelFileWaitingForSize:"\u0686\u0627\u0648\u06D5\u0695\u0648\u0627\u0646\u06CC\u06CC \u0642\u06D5\u0628\u0627\u0631\u06D5",labelFileSizeNotAvailable:"\u0642\u06D5\u0628\u0627\u0631\u06D5 \u0628\u06D5\u0631\u062F\u06D5\u0633\u062A \u0646\u06CC\u06D5",labelFileLoading:"\u0628\u0627\u0631\u06A9\u0631\u062F\u0646",labelFileLoadError:"\u0647\u06D5\u06B5\u06D5 \u0644\u06D5\u0645\u0627\u0648\u06D5\u06CC \u0628\u0627\u0631\u06A9\u0631\u062F\u0646",labelFileProcessing:"\u0628\u0627\u0631\u06A9\u0631\u062F\u0646",labelFileProcessingComplete:"\u0628\u0627\u0631\u06A9\u0631\u062F\u0646 \u062A\u06D5\u0648\u0627\u0648 \u0628\u0648\u0648",labelFileProcessingAborted:"\u0628\u0627\u0631\u06A9\u0631\u062F\u0646 \u0647\u06D5\u06B5\u0648\u06D5\u0634\u0627\u06CC\u06D5\u0648\u06D5",labelFileProcessingError:"\u0647\u06D5\u06B5\u06D5 \u0644\u06D5\u06A9\u0627\u062A\u06CC \u0628\u0627\u0631\u06A9\u0631\u062F\u0646\u062F\u0627",labelFileProcessingRevertError:"\u0647\u06D5\u06B5\u06D5 \u0644\u06D5 \u06A9\u0627\u062A\u06CC \u06AF\u06D5\u0695\u0627\u0646\u06D5\u0648\u06D5",labelFileRemoveError:"\u0647\u06D5\u06B5\u06D5 \u0644\u06D5 \u06A9\u0627\u062A\u06CC \u0633\u0695\u06CC\u0646\u06D5\u0648\u06D5",labelTapToCancel:"\u0628\u06C6 \u0647\u06D5\u06B5\u0648\u06D5\u0634\u0627\u0646\u062F\u0646\u06D5\u0648\u06D5 Tab \u062F\u0627\u0628\u06AF\u0631\u06D5",labelTapToRetry:"tap \u062F\u0627\u0628\u06AF\u0631\u06D5 \u0628\u06C6 \u062F\u0648\u0648\u0628\u0627\u0631\u06D5\u06A9\u0631\u062F\u0646\u06D5\u0648\u06D5",labelTapToUndo:"tap \u062F\u0627\u0628\u06AF\u0631\u06D5 \u0628\u06C6 \u06AF\u06D5\u0695\u0627\u0646\u062F\u0646\u06D5\u0648\u06D5",labelButtonRemoveItem:"\u0633\u0695\u06CC\u0646\u06D5\u0648\u06D5",labelButtonAbortItemLoad:"\u0647\u06D5\u06B5\u0648\u06D5\u0634\u0627\u0646\u062F\u0646\u06D5\u0648\u06D5",labelButtonRetryItemLoad:"\u0647\u06D5\u0648\u06B5\u062F\u0627\u0646\u06D5\u0648\u06D5",labelButtonAbortItemProcessing:"\u067E\u06D5\u0634\u06CC\u0645\u0627\u0646\u0628\u0648\u0648\u0646\u06D5\u0648\u06D5",labelButtonUndoItemProcessing:"\u06AF\u06D5\u0695\u0627\u0646\u062F\u0646\u06D5\u0648\u06D5",labelButtonRetryItemProcessing:"\u0647\u06D5\u0648\u06B5\u062F\u0627\u0646\u06D5\u0648\u06D5",labelButtonProcessItem:"\u0628\u0627\u0631\u06A9\u0631\u062F\u0646",labelMaxFileSizeExceeded:"\u067E\u06D5\u0695\u06AF\u06D5 \u0632\u06C6\u0631 \u06AF\u06D5\u0648\u0631\u06D5\u06CC\u06D5",labelMaxFileSize:"\u0632\u06C6\u0631\u062A\u0631\u06CC\u0646 \u0642\u06D5\u0628\u0627\u0631\u06D5 {filesize}",labelMaxTotalFileSizeExceeded:"\u0632\u06C6\u0631\u062A\u0631\u06CC\u0646 \u0642\u06D5\u0628\u0627\u0631\u06D5\u06CC \u06A9\u06C6\u06CC \u06AF\u0634\u062A\u06CC \u062A\u06CE\u067E\u06D5\u0695\u06CE\u0646\u062F\u0631\u0627",labelMaxTotalFileSize:"\u0632\u06C6\u0631\u062A\u0631\u06CC\u0646 \u0642\u06D5\u0628\u0627\u0631\u06D5\u06CC \u06A9\u06C6\u06CC \u067E\u06D5\u0695\u06AF\u06D5 {filesize}",labelFileTypeNotAllowed:"\u062C\u06C6\u0631\u06CC \u067E\u06D5\u0695\u06AF\u06D5\u06A9\u06D5 \u0646\u0627\u062F\u0631\u0648\u0633\u062A\u06D5",fileValidateTypeLabelExpectedTypes:"\u062C\u06AF\u06D5 \u0644\u06D5 {allButLastType} \u06CC\u0627\u0646 {lastType}",imageValidateSizeLabelFormatError:"\u062C\u06C6\u0631\u06CC \u0648\u06CE\u0646\u06D5 \u067E\u0627\u06B5\u067E\u0634\u062A\u06CC\u06CC \u0646\u06D5\u06A9\u0631\u0627\u0648\u06D5",imageValidateSizeLabelImageSizeTooSmall:"\u0648\u06CE\u0646\u06D5\u06A9\u06D5 \u0632\u06C6\u0631 \u0628\u0686\u0648\u0648\u06A9\u06D5",imageValidateSizeLabelImageSizeTooBig:"\u0648\u06CE\u0646\u06D5\u06A9\u06D5 \u0632\u06C6\u0631 \u06AF\u06D5\u0648\u0631\u06D5\u06CC\u06D5",imageValidateSizeLabelExpectedMinSize:"\u06A9\u06D5\u0645\u062A\u0631\u06CC\u0646 \u0642\u06D5\u0628\u0627\u0631\u06D5 {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u0632\u06C6\u0631\u062A\u0631\u06CC\u0646 \u0642\u06D5\u0628\u0627\u0631\u06D5 {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0648\u0631\u062F\u0628\u06CC\u0646\u06CC\u06CC\u06D5\u06A9\u06D5\u06CC \u0632\u06C6\u0631 \u06A9\u06D5\u0645\u06D5",imageValidateSizeLabelImageResolutionTooHigh:"\u0648\u0631\u062F\u0628\u06CC\u0646\u06CC\u06CC\u06D5\u06A9\u06D5\u06CC \u0632\u06C6\u0631 \u0628\u06D5\u0631\u0632\u06D5",imageValidateSizeLabelExpectedMinResolution:"\u06A9\u06D5\u0645\u062A\u0631\u06CC\u0646 \u0648\u0631\u062F\u0628\u06CC\u0646\u06CC\u06CC {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u0632\u06C6\u0631\u062A\u0631\u06CC\u0646 \u0648\u0631\u062F\u0628\u06CC\u0646\u06CC {maxResolution}"};var zo={labelIdle:'P\u0159et\xE1hn\u011Bte soubor sem (drag&drop) nebo Vyhledat ',labelInvalidField:"Pole obsahuje chybn\xE9 soubory",labelFileWaitingForSize:"Zji\u0161\u0165uje se velikost",labelFileSizeNotAvailable:"Velikost nen\xED zn\xE1m\xE1",labelFileLoading:"P\u0159en\xE1\u0161\xED se",labelFileLoadError:"Chyba p\u0159i p\u0159enosu",labelFileProcessing:"Prob\xEDh\xE1 upload",labelFileProcessingComplete:"Upload dokon\u010Den",labelFileProcessingAborted:"Upload stornov\xE1n",labelFileProcessingError:"Chyba p\u0159i uploadu",labelFileProcessingRevertError:"Chyba p\u0159i obnov\u011B",labelFileRemoveError:"Chyba p\u0159i odstran\u011Bn\xED",labelTapToCancel:"klepn\u011Bte pro storno",labelTapToRetry:"klepn\u011Bte pro opakov\xE1n\xED",labelTapToUndo:"klepn\u011Bte pro vr\xE1cen\xED",labelButtonRemoveItem:"Odstranit",labelButtonAbortItemLoad:"Storno",labelButtonRetryItemLoad:"Opakovat",labelButtonAbortItemProcessing:"Zp\u011Bt",labelButtonUndoItemProcessing:"Vr\xE1tit",labelButtonRetryItemProcessing:"Opakovat",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"Soubor je p\u0159\xEDli\u0161 velk\xFD",labelMaxFileSize:"Nejv\u011Bt\u0161\xED velikost souboru je {filesize}",labelMaxTotalFileSizeExceeded:"P\u0159ekro\u010Dena maxim\xE1ln\xED celkov\xE1 velikost souboru",labelMaxTotalFileSize:"Maxim\xE1ln\xED celkov\xE1 velikost souboru je {filesize}",labelFileTypeNotAllowed:"Soubor je nespr\xE1vn\xE9ho typu",fileValidateTypeLabelExpectedTypes:"O\u010Dek\xE1v\xE1 se {allButLastType} nebo {lastType}",imageValidateSizeLabelFormatError:"Obr\xE1zek tohoto typu nen\xED podporov\xE1n",imageValidateSizeLabelImageSizeTooSmall:"Obr\xE1zek je p\u0159\xEDli\u0161 mal\xFD",imageValidateSizeLabelImageSizeTooBig:"Obr\xE1zek je p\u0159\xEDli\u0161 velk\xFD",imageValidateSizeLabelExpectedMinSize:"Minim\xE1ln\xED rozm\u011Br je {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maxim\xE1ln\xED rozm\u011Br je {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rozli\u0161en\xED je p\u0159\xEDli\u0161 mal\xE9",imageValidateSizeLabelImageResolutionTooHigh:"Rozli\u0161en\xED je p\u0159\xEDli\u0161 velk\xE9",imageValidateSizeLabelExpectedMinResolution:"Minim\xE1ln\xED rozli\u0161en\xED je {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maxim\xE1ln\xED rozli\u0161en\xED je {maxResolution}"};var Oo={labelIdle:'Tr\xE6k & slip filer eller Gennemse ',labelInvalidField:"Felt indeholder ugyldige filer",labelFileWaitingForSize:"Venter p\xE5 st\xF8rrelse",labelFileSizeNotAvailable:"St\xF8rrelse ikke tilg\xE6ngelig",labelFileLoading:"Loader",labelFileLoadError:"Load fejlede",labelFileProcessing:"Uploader",labelFileProcessingComplete:"Upload f\xE6rdig",labelFileProcessingAborted:"Upload annulleret",labelFileProcessingError:"Upload fejlede",labelFileProcessingRevertError:"Fortryd fejlede",labelFileRemoveError:"Fjern fejlede",labelTapToCancel:"tryk for at annullere",labelTapToRetry:"tryk for at pr\xF8ve igen",labelTapToUndo:"tryk for at fortryde",labelButtonRemoveItem:"Fjern",labelButtonAbortItemLoad:"Annuller",labelButtonRetryItemLoad:"Fors\xF8g igen",labelButtonAbortItemProcessing:"Annuller",labelButtonUndoItemProcessing:"Fortryd",labelButtonRetryItemProcessing:"Pr\xF8v igen",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"Filen er for stor",labelMaxFileSize:"Maksimal filst\xF8rrelse er {filesize}",labelMaxTotalFileSizeExceeded:"Maksimal totalst\xF8rrelse overskredet",labelMaxTotalFileSize:"Maksimal total filst\xF8rrelse er {filesize}",labelFileTypeNotAllowed:"Ugyldig filtype",fileValidateTypeLabelExpectedTypes:"Forventer {allButLastType} eller {lastType}",imageValidateSizeLabelFormatError:"Ugyldigt format",imageValidateSizeLabelImageSizeTooSmall:"Billedet er for lille",imageValidateSizeLabelImageSizeTooBig:"Billedet er for stort",imageValidateSizeLabelExpectedMinSize:"Minimum st\xF8rrelse er {minBredde} \xD7 {minH\xF8jde}",imageValidateSizeLabelExpectedMaxSize:"Maksimal st\xF8rrelse er {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"For lav opl\xF8sning",imageValidateSizeLabelImageResolutionTooHigh:"For h\xF8j opl\xF8sning",imageValidateSizeLabelExpectedMinResolution:"Minimum opl\xF8sning er {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksimal opl\xF8sning er {maxResolution}"};var Fo={labelIdle:'Dateien ablegen oder ausw\xE4hlen ',labelInvalidField:"Feld beinhaltet ung\xFCltige Dateien",labelFileWaitingForSize:"Dateigr\xF6\xDFe berechnen",labelFileSizeNotAvailable:"Dateigr\xF6\xDFe nicht verf\xFCgbar",labelFileLoading:"Laden",labelFileLoadError:"Fehler beim Laden",labelFileProcessing:"Upload l\xE4uft",labelFileProcessingComplete:"Upload abgeschlossen",labelFileProcessingAborted:"Upload abgebrochen",labelFileProcessingError:"Fehler beim Upload",labelFileProcessingRevertError:"Fehler beim Wiederherstellen",labelFileRemoveError:"Fehler beim L\xF6schen",labelTapToCancel:"abbrechen",labelTapToRetry:"erneut versuchen",labelTapToUndo:"r\xFCckg\xE4ngig",labelButtonRemoveItem:"Entfernen",labelButtonAbortItemLoad:"Verwerfen",labelButtonRetryItemLoad:"Erneut versuchen",labelButtonAbortItemProcessing:"Abbrechen",labelButtonUndoItemProcessing:"R\xFCckg\xE4ngig",labelButtonRetryItemProcessing:"Erneut versuchen",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"Datei ist zu gro\xDF",labelMaxFileSize:"Maximale Dateigr\xF6\xDFe: {filesize}",labelMaxTotalFileSizeExceeded:"Maximale gesamte Dateigr\xF6\xDFe \xFCberschritten",labelMaxTotalFileSize:"Maximale gesamte Dateigr\xF6\xDFe: {filesize}",labelFileTypeNotAllowed:"Dateityp ung\xFCltig",fileValidateTypeLabelExpectedTypes:"Erwartet {allButLastType} oder {lastType}",imageValidateSizeLabelFormatError:"Bildtyp nicht unterst\xFCtzt",imageValidateSizeLabelImageSizeTooSmall:"Bild ist zu klein",imageValidateSizeLabelImageSizeTooBig:"Bild ist zu gro\xDF",imageValidateSizeLabelExpectedMinSize:"Mindestgr\xF6\xDFe: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximale Gr\xF6\xDFe: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Aufl\xF6sung ist zu niedrig",imageValidateSizeLabelImageResolutionTooHigh:"Aufl\xF6sung ist zu hoch",imageValidateSizeLabelExpectedMinResolution:"Mindestaufl\xF6sung: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maximale Aufl\xF6sung: {maxResolution}"};var Do={labelIdle:'\u03A3\u03CD\u03C1\u03B5\u03C4\u03B5 \u03C4\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03C3\u03B1\u03C2 \u03C3\u03C4\u03BF \u03C0\u03BB\u03B1\u03AF\u03C3\u03B9\u03BF \u03AE \u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 ',labelInvalidField:"\u03A4\u03BF \u03C0\u03B5\u03B4\u03AF\u03BF \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03BC\u03B7 \u03AD\u03B3\u03BA\u03C5\u03C1\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1",labelFileWaitingForSize:"\u03A3\u03B5 \u03B1\u03BD\u03B1\u03BC\u03BF\u03BD\u03AE \u03B3\u03B9\u03B1 \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2",labelFileSizeNotAvailable:"\u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03BC\u03B7 \u03B4\u03B9\u03B1\u03B8\u03AD\u03C3\u03B9\u03BC\u03BF",labelFileLoading:"\u03A6\u03CC\u03C1\u03C4\u03C9\u03C3\u03B7 \u03C3\u03B5 \u03B5\u03BE\u03AD\u03BB\u03B9\u03BE\u03B7",labelFileLoadError:"\u03A3\u03C6\u03AC\u03BB\u03BC\u03B1 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7 \u03C6\u03CC\u03C1\u03C4\u03C9\u03C3\u03B7",labelFileProcessing:"\u0395\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1",labelFileProcessingComplete:"\u0397 \u03B5\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03BF\u03BB\u03BF\u03BA\u03BB\u03B7\u03C1\u03CE\u03B8\u03B7\u03BA\u03B5",labelFileProcessingAborted:"\u0397 \u03B5\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03B1\u03BA\u03C5\u03C1\u03CE\u03B8\u03B7\u03BA\u03B5",labelFileProcessingError:"\u03A3\u03C6\u03AC\u03BB\u03BC\u03B1 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7\u03BD \u03B5\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1",labelFileProcessingRevertError:"\u03A3\u03C6\u03AC\u03BB\u03BC\u03B1 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7\u03BD \u03B5\u03C0\u03B1\u03BD\u03B1\u03C6\u03BF\u03C1\u03AC",labelFileRemoveError:"\u03A3\u03C6\u03AC\u03BB\u03BC\u03B1 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7\u03BD \u03B4\u03B9\u03B1\u03B3\u03C1\u03B1\u03C6\u03AE",labelTapToCancel:"\u03C0\u03B1\u03C4\u03AE\u03C3\u03C4\u03B5 \u03B3\u03B9\u03B1 \u03B1\u03BA\u03CD\u03C1\u03C9\u03C3\u03B7",labelTapToRetry:"\u03C0\u03B1\u03C4\u03AE\u03C3\u03C4\u03B5 \u03B3\u03B9\u03B1 \u03B5\u03C0\u03B1\u03BD\u03AC\u03BB\u03B7\u03C8\u03B7",labelTapToUndo:"\u03C0\u03B1\u03C4\u03AE\u03C3\u03C4\u03B5 \u03B3\u03B9\u03B1 \u03B1\u03BD\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7",labelButtonRemoveItem:"\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7",labelButtonAbortItemLoad:"\u0391\u03BA\u03CD\u03C1\u03C9\u03C3\u03B7",labelButtonRetryItemLoad:"\u0395\u03C0\u03B1\u03BD\u03AC\u03BB\u03B7\u03C8\u03B7",labelButtonAbortItemProcessing:"\u0391\u03BA\u03CD\u03C1\u03C9\u03C3\u03B7",labelButtonUndoItemProcessing:"\u0391\u03BD\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7",labelButtonRetryItemProcessing:"\u0395\u03C0\u03B1\u03BD\u03AC\u03BB\u03B7\u03C8\u03B7",labelButtonProcessItem:"\u039C\u03B5\u03C4\u03B1\u03C6\u03CC\u03C1\u03C4\u03C9\u03C3\u03B7",labelMaxFileSizeExceeded:"\u03A4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03BC\u03B5\u03B3\u03AC\u03BB\u03BF",labelMaxFileSize:"\u03A4\u03BF \u03BC\u03AD\u03B3\u03B9\u03C3\u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03B5\u03AF\u03BD\u03B1\u03B9 {filesize}",labelMaxTotalFileSizeExceeded:"\u03A5\u03C0\u03AD\u03C1\u03B2\u03B1\u03C3\u03B7 \u03C4\u03BF\u03C5 \u03BC\u03AD\u03B3\u03B9\u03C3\u03C4\u03BF\u03C5 \u03C3\u03C5\u03BD\u03BF\u03BB\u03B9\u03BA\u03BF\u03CD \u03BC\u03B5\u03B3\u03AD\u03B8\u03BF\u03C5\u03C2",labelMaxTotalFileSize:"\u03A4\u03BF \u03BC\u03AD\u03B3\u03B9\u03C3\u03C4\u03BF \u03C3\u03C5\u03BD\u03BF\u03BB\u03B9\u03BA\u03CC \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD \u03B5\u03AF\u03BD\u03B1\u03B9 {filesize}",labelFileTypeNotAllowed:"\u039C\u03B7 \u03AD\u03B3\u03BA\u03C5\u03C1\u03BF\u03C2 \u03C4\u03CD\u03C0\u03BF\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5",fileValidateTypeLabelExpectedTypes:"\u03A4\u03B1 \u03B1\u03C0\u03BF\u03B4\u03B5\u03BA\u03C4\u03AC \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 {allButLastType} \u03AE {lastType}",imageValidateSizeLabelFormatError:"\u039F \u03C4\u03CD\u03C0\u03BF\u03C2 \u03C4\u03B7\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03B4\u03B5\u03BD \u03C5\u03C0\u03BF\u03C3\u03C4\u03B7\u03C1\u03AF\u03B6\u03B5\u03C4\u03B1\u03B9",imageValidateSizeLabelImageSizeTooSmall:"\u0397 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03BC\u03B9\u03BA\u03C1\u03AE",imageValidateSizeLabelImageSizeTooBig:"\u0397 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03BC\u03B5\u03B3\u03AC\u03BB\u03B7",imageValidateSizeLabelExpectedMinSize:"\u03A4\u03BF \u03B5\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03BF \u03B1\u03C0\u03BF\u03B4\u03B5\u03BA\u03C4\u03CC \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u03A4\u03BF \u03BC\u03AD\u03B3\u03B9\u03C3\u03C4\u03BF \u03B1\u03C0\u03BF\u03B4\u03B5\u03BA\u03C4\u03CC \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0397 \u03B1\u03BD\u03AC\u03BB\u03C5\u03C3\u03B7 \u03C4\u03B7\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03C7\u03B1\u03BC\u03B7\u03BB\u03AE",imageValidateSizeLabelImageResolutionTooHigh:"\u0397 \u03B1\u03BD\u03AC\u03BB\u03C5\u03C3\u03B7 \u03C4\u03B7\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03C5\u03C8\u03B7\u03BB\u03AE",imageValidateSizeLabelExpectedMinResolution:"\u0397 \u03B5\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03B7 \u03B1\u03C0\u03BF\u03B4\u03B5\u03BA\u03C4\u03AE \u03B1\u03BD\u03AC\u03BB\u03C5\u03C3\u03B7 \u03B5\u03AF\u03BD\u03B1\u03B9 {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u0397 \u03BC\u03AD\u03B3\u03B9\u03C3\u03C4\u03B7 \u03B1\u03C0\u03BF\u03B4\u03B5\u03BA\u03C4\u03AE \u03B1\u03BD\u03AC\u03BB\u03C5\u03C3\u03B7 \u03B5\u03AF\u03BD\u03B1\u03B9 {maxResolution}"};var Co={labelIdle:'Drag & Drop your files or Browse ',labelInvalidField:"Field contains invalid files",labelFileWaitingForSize:"Waiting for size",labelFileSizeNotAvailable:"Size not available",labelFileLoading:"Loading",labelFileLoadError:"Error during load",labelFileProcessing:"Uploading",labelFileProcessingComplete:"Upload complete",labelFileProcessingAborted:"Upload cancelled",labelFileProcessingError:"Error during upload",labelFileProcessingRevertError:"Error during revert",labelFileRemoveError:"Error during remove",labelTapToCancel:"tap to cancel",labelTapToRetry:"tap to retry",labelTapToUndo:"tap to undo",labelButtonRemoveItem:"Remove",labelButtonAbortItemLoad:"Abort",labelButtonRetryItemLoad:"Retry",labelButtonAbortItemProcessing:"Cancel",labelButtonUndoItemProcessing:"Undo",labelButtonRetryItemProcessing:"Retry",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"File is too large",labelMaxFileSize:"Maximum file size is {filesize}",labelMaxTotalFileSizeExceeded:"Maximum total size exceeded",labelMaxTotalFileSize:"Maximum total file size is {filesize}",labelFileTypeNotAllowed:"File of invalid type",fileValidateTypeLabelExpectedTypes:"Expects {allButLastType} or {lastType}",imageValidateSizeLabelFormatError:"Image type not supported",imageValidateSizeLabelImageSizeTooSmall:"Image is too small",imageValidateSizeLabelImageSizeTooBig:"Image is too big",imageValidateSizeLabelExpectedMinSize:"Minimum size is {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximum size is {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Resolution is too low",imageValidateSizeLabelImageResolutionTooHigh:"Resolution is too high",imageValidateSizeLabelExpectedMinResolution:"Minimum resolution is {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maximum resolution is {maxResolution}"};var Bo={labelIdle:'Arrastra y suelta tus archivos o Examina ',labelInvalidField:"El campo contiene archivos inv\xE1lidos",labelFileWaitingForSize:"Esperando tama\xF1o",labelFileSizeNotAvailable:"Tama\xF1o no disponible",labelFileLoading:"Cargando",labelFileLoadError:"Error durante la carga",labelFileProcessing:"Subiendo",labelFileProcessingComplete:"Subida completa",labelFileProcessingAborted:"Subida cancelada",labelFileProcessingError:"Error durante la subida",labelFileProcessingRevertError:"Error durante la reversi\xF3n",labelFileRemoveError:"Error durante la eliminaci\xF3n",labelTapToCancel:"toca para cancelar",labelTapToRetry:"tocar para reintentar",labelTapToUndo:"tocar para deshacer",labelButtonRemoveItem:"Eliminar",labelButtonAbortItemLoad:"Cancelar",labelButtonRetryItemLoad:"Reintentar",labelButtonAbortItemProcessing:"Cancelar",labelButtonUndoItemProcessing:"Deshacer",labelButtonRetryItemProcessing:"Reintentar",labelButtonProcessItem:"Subir",labelMaxFileSizeExceeded:"El archivo es demasiado grande",labelMaxFileSize:"El tama\xF1o m\xE1ximo del archivo es {filesize}",labelMaxTotalFileSizeExceeded:"Tama\xF1o total m\xE1ximo excedido",labelMaxTotalFileSize:"El tama\xF1o total m\xE1ximo del archivo es {filesize}",labelFileTypeNotAllowed:"Archivo de tipo inv\xE1lido",fileValidateTypeLabelExpectedTypes:"Espera {allButLastType} o {lastType}",imageValidateSizeLabelFormatError:"Tipo de imagen no soportada",imageValidateSizeLabelImageSizeTooSmall:"La imagen es demasiado peque\xF1a",imageValidateSizeLabelImageSizeTooBig:"La imagen es demasiado grande",imageValidateSizeLabelExpectedMinSize:"El tama\xF1o m\xEDnimo es {minWidth} x {minHeight}",imageValidateSizeLabelExpectedMaxSize:"El tama\xF1o m\xE1ximo es {maxWidth} x {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"La resoluci\xF3n es demasiado baja",imageValidateSizeLabelImageResolutionTooHigh:"La resoluci\xF3n es demasiado alta",imageValidateSizeLabelExpectedMinResolution:"La resoluci\xF3n m\xEDnima es {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"La resoluci\xF3n m\xE1xima es {maxResolution}"};var No={labelIdle:'\u0641\u0627\u06CC\u0644 \u0631\u0627 \u0627\u06CC\u0646\u062C\u0627 \u0628\u06A9\u0634\u06CC\u062F \u0648 \u0631\u0647\u0627 \u06A9\u0646\u06CC\u062F\u060C \u06CC\u0627 \u062C\u0633\u062A\u062C\u0648 \u06A9\u0646\u06CC\u062F ',labelInvalidField:"\u0641\u06CC\u0644\u062F \u062F\u0627\u0631\u0627\u06CC \u0641\u0627\u06CC\u0644 \u0647\u0627\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",labelFileWaitingForSize:"Waiting for size",labelFileSizeNotAvailable:"\u062D\u062C\u0645 \u0641\u0627\u06CC\u0644 \u0645\u062C\u0627\u0632 \u0646\u06CC\u0633\u062A",labelFileLoading:"\u062F\u0631\u062D\u0627\u0644 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC",labelFileLoadError:"\u062E\u0637\u0627 \u062F\u0631 \u0632\u0645\u0627\u0646 \u0627\u062C\u0631\u0627",labelFileProcessing:"\u062F\u0631\u062D\u0627\u0644 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC",labelFileProcessingComplete:"\u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u06A9\u0627\u0645\u0644 \u0634\u062F",labelFileProcessingAborted:"\u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u0644\u063A\u0648 \u0634\u062F",labelFileProcessingError:"\u062E\u0637\u0627 \u062F\u0631 \u0632\u0645\u0627\u0646 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC",labelFileProcessingRevertError:"\u062E\u0637\u0627 \u062F\u0631 \u0632\u0645\u0627\u0646 \u062D\u0630\u0641",labelFileRemoveError:"\u062E\u0637\u0627 \u062F\u0631 \u0632\u0645\u0627\u0646 \u062D\u0630\u0641",labelTapToCancel:"\u0628\u0631\u0627\u06CC \u0644\u063A\u0648 \u0636\u0631\u0628\u0647 \u0628\u0632\u0646\u06CC\u062F",labelTapToRetry:"\u0628\u0631\u0627\u06CC \u062A\u06A9\u0631\u0627\u0631 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F",labelTapToUndo:"\u0628\u0631\u0627\u06CC \u0628\u0631\u06AF\u0634\u062A \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F",labelButtonRemoveItem:"\u062D\u0630\u0641",labelButtonAbortItemLoad:"\u0644\u063A\u0648",labelButtonRetryItemLoad:"\u062A\u06A9\u0631\u0627\u0631",labelButtonAbortItemProcessing:"\u0644\u063A\u0648",labelButtonUndoItemProcessing:"\u0628\u0631\u06AF\u0634\u062A",labelButtonRetryItemProcessing:"\u062A\u06A9\u0631\u0627\u0631",labelButtonProcessItem:"\u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC",labelMaxFileSizeExceeded:"\u0641\u0627\u06CC\u0644 \u0628\u0633\u06CC\u0627\u0631 \u062D\u062C\u06CC\u0645 \u0627\u0633\u062A",labelMaxFileSize:"\u062D\u062F\u0627\u06A9\u062B\u0631 \u0645\u062C\u0627\u0632 \u0641\u0627\u06CC\u0644 {filesize} \u0627\u0633\u062A",labelMaxTotalFileSizeExceeded:"\u0627\u0632 \u062D\u062F\u0627\u06A9\u062B\u0631 \u062D\u062C\u0645 \u0641\u0627\u06CC\u0644 \u0628\u06CC\u0634\u062A\u0631 \u0634\u062F",labelMaxTotalFileSize:"\u062D\u062F\u0627\u06A9\u062B\u0631 \u062D\u062C\u0645 \u0641\u0627\u06CC\u0644 {filesize} \u0627\u0633\u062A",labelFileTypeNotAllowed:"\u0646\u0648\u0639 \u0641\u0627\u06CC\u0644 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",fileValidateTypeLabelExpectedTypes:"\u062F\u0631 \u0627\u0646\u062A\u0638\u0627\u0631 {allButLastType} \u06CC\u0627 {lastType}",imageValidateSizeLabelFormatError:"\u0641\u0631\u0645\u062A \u062A\u0635\u0648\u06CC\u0631 \u067E\u0634\u062A\u06CC\u0628\u0627\u0646\u06CC \u0646\u0645\u06CC \u0634\u0648\u062F",imageValidateSizeLabelImageSizeTooSmall:"\u062A\u0635\u0648\u06CC\u0631 \u0628\u0633\u06CC\u0627\u0631 \u06A9\u0648\u0686\u06A9 \u0627\u0633\u062A",imageValidateSizeLabelImageSizeTooBig:"\u062A\u0635\u0648\u06CC\u0631 \u0628\u0633\u06CC\u0627\u0631 \u0628\u0632\u0631\u06AF \u0627\u0633\u062A",imageValidateSizeLabelExpectedMinSize:"\u062D\u062F\u0627\u0642\u0644 \u0627\u0646\u062F\u0627\u0632\u0647 {minWidth} \xD7 {minHeight} \u0627\u0633\u062A",imageValidateSizeLabelExpectedMaxSize:"\u062D\u062F\u0627\u06A9\u062B\u0631 \u0627\u0646\u062F\u0627\u0632\u0647 {maxWidth} \xD7 {maxHeight} \u0627\u0633\u062A",imageValidateSizeLabelImageResolutionTooLow:"\u0648\u0636\u0648\u062D \u062A\u0635\u0648\u06CC\u0631 \u0628\u0633\u06CC\u0627\u0631 \u06A9\u0645 \u0627\u0633\u062A",imageValidateSizeLabelImageResolutionTooHigh:"\u0648\u0636\u0648\u0639 \u062A\u0635\u0648\u06CC\u0631 \u0628\u0633\u06CC\u0627\u0631 \u0632\u06CC\u0627\u062F \u0627\u0633\u062A",imageValidateSizeLabelExpectedMinResolution:"\u062D\u062F\u0627\u0642\u0644 \u0648\u0636\u0648\u062D \u062A\u0635\u0648\u06CC\u0631 {minResolution} \u0627\u0633\u062A",imageValidateSizeLabelExpectedMaxResolution:"\u062D\u062F\u0627\u06A9\u062B\u0631 \u0648\u0636\u0648\u062D \u062A\u0635\u0648\u06CC\u0631 {maxResolution} \u0627\u0633\u062A"};var ko={labelIdle:'Ved\xE4 ja pudota tiedostoja tai Selaa ',labelInvalidField:"Kent\xE4ss\xE4 on virheellisi\xE4 tiedostoja",labelFileWaitingForSize:"Odotetaan kokoa",labelFileSizeNotAvailable:"Kokoa ei saatavilla",labelFileLoading:"Ladataan",labelFileLoadError:"Virhe latauksessa",labelFileProcessing:"L\xE4hetet\xE4\xE4n",labelFileProcessingComplete:"L\xE4hetys valmis",labelFileProcessingAborted:"L\xE4hetys peruttu",labelFileProcessingError:"Virhe l\xE4hetyksess\xE4",labelFileProcessingRevertError:"Virhe palautuksessa",labelFileRemoveError:"Virhe poistamisessa",labelTapToCancel:"peruuta napauttamalla",labelTapToRetry:"yrit\xE4 uudelleen napauttamalla",labelTapToUndo:"kumoa napauttamalla",labelButtonRemoveItem:"Poista",labelButtonAbortItemLoad:"Keskeyt\xE4",labelButtonRetryItemLoad:"Yrit\xE4 uudelleen",labelButtonAbortItemProcessing:"Peruuta",labelButtonUndoItemProcessing:"Kumoa",labelButtonRetryItemProcessing:"Yrit\xE4 uudelleen",labelButtonProcessItem:"L\xE4het\xE4",labelMaxFileSizeExceeded:"Tiedoston koko on liian suuri",labelMaxFileSize:"Tiedoston maksimikoko on {filesize}",labelMaxTotalFileSizeExceeded:"Tiedostojen yhdistetty maksimikoko ylitetty",labelMaxTotalFileSize:"Tiedostojen yhdistetty maksimikoko on {filesize}",labelFileTypeNotAllowed:"Tiedostotyyppi\xE4 ei sallita",fileValidateTypeLabelExpectedTypes:"Sallitaan {allButLastType} tai {lastType}",imageValidateSizeLabelFormatError:"Kuvatyyppi\xE4 ei tueta",imageValidateSizeLabelImageSizeTooSmall:"Kuva on liian pieni",imageValidateSizeLabelImageSizeTooBig:"Kuva on liian suuri",imageValidateSizeLabelExpectedMinSize:"Minimikoko on {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksimikoko on {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Resoluutio on liian pieni",imageValidateSizeLabelImageResolutionTooHigh:"Resoluutio on liian suuri",imageValidateSizeLabelExpectedMinResolution:"Minimiresoluutio on {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksimiresoluutio on {maxResolution}"};var Vo={labelIdle:'Faites glisser vos fichiers ou Parcourir ',labelInvalidField:"Le champ contient des fichiers invalides",labelFileWaitingForSize:"En attente de taille",labelFileSizeNotAvailable:"Taille non disponible",labelFileLoading:"Chargement",labelFileLoadError:"Erreur durant le chargement",labelFileProcessing:"Traitement",labelFileProcessingComplete:"Traitement effectu\xE9",labelFileProcessingAborted:"Traitement interrompu",labelFileProcessingError:"Erreur durant le traitement",labelFileProcessingRevertError:"Erreur durant la restauration",labelFileRemoveError:"Erreur durant la suppression",labelTapToCancel:"appuyer pour annuler",labelTapToRetry:"appuyer pour r\xE9essayer",labelTapToUndo:"appuyer pour revenir en arri\xE8re",labelButtonRemoveItem:"Retirer",labelButtonAbortItemLoad:"Annuler",labelButtonRetryItemLoad:"Recommencer",labelButtonAbortItemProcessing:"Annuler",labelButtonUndoItemProcessing:"Revenir en arri\xE8re",labelButtonRetryItemProcessing:"Recommencer",labelButtonProcessItem:"Transf\xE9rer",labelMaxFileSizeExceeded:"Le fichier est trop volumineux",labelMaxFileSize:"La taille maximale de fichier est {filesize}",labelMaxTotalFileSizeExceeded:"Taille totale maximale d\xE9pass\xE9e",labelMaxTotalFileSize:"La taille totale maximale des fichiers est {filesize}",labelFileTypeNotAllowed:"Fichier non valide",fileValidateTypeLabelExpectedTypes:"Attendu {allButLastType} ou {lastType}",imageValidateSizeLabelFormatError:"Type d'image non pris en charge",imageValidateSizeLabelImageSizeTooSmall:"L'image est trop petite",imageValidateSizeLabelImageSizeTooBig:"L'image est trop grande",imageValidateSizeLabelExpectedMinSize:"La taille minimale est {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"La taille maximale est {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"La r\xE9solution est trop faible",imageValidateSizeLabelImageResolutionTooHigh:"La r\xE9solution est trop \xE9lev\xE9e",imageValidateSizeLabelExpectedMinResolution:"La r\xE9solution minimale est {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"La r\xE9solution maximale est {maxResolution}"};var Go={labelIdle:'\u05D2\u05E8\u05D5\u05E8 \u05D5\u05E9\u05D7\u05E8\u05E8 \u05D0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD \u05DB\u05D0\u05DF \u05D0\u05D5 \u05DC\u05D7\u05E5 \u05DB\u05D0\u05DF \u05DC\u05D1\u05D7\u05D9\u05E8\u05D4 ',labelInvalidField:"\u05E7\u05D5\u05D1\u05E5 \u05DC\u05D0 \u05D7\u05D5\u05E7\u05D9",labelFileWaitingForSize:"\u05DE\u05D7\u05E9\u05D1 \u05D0\u05EA \u05D2\u05D5\u05D3\u05DC \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileSizeNotAvailable:"\u05DC\u05D0 \u05E0\u05D9\u05EA\u05DF \u05DC\u05E7\u05D1\u05D5\u05E2 \u05D0\u05EA \u05D2\u05D5\u05D3\u05DC \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileLoading:"\u05D8\u05D5\u05E2\u05DF...",labelFileLoadError:"\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D0\u05E8\u05E2\u05D4 \u05D1\u05E2\u05EA \u05D8\u05E2\u05D9\u05E0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileProcessing:"\u05DE\u05E2\u05DC\u05D4 \u05D0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileProcessingComplete:"\u05D4\u05E2\u05DC\u05D0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD \u05D4\u05E1\u05EA\u05D9\u05D9\u05DE\u05D4",labelFileProcessingAborted:"\u05D4\u05E2\u05DC\u05D0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD \u05D1\u05D5\u05D8\u05DC\u05D4",labelFileProcessingError:"\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D0\u05E8\u05E2\u05D4 \u05D1\u05E2\u05EA \u05D4\u05E2\u05DC\u05D0\u05EA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileProcessingRevertError:"\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D0\u05E8\u05E2\u05D4 \u05D1\u05E2\u05EA \u05E9\u05D7\u05D6\u05D5\u05E8 \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD",labelFileRemoveError:"\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D0\u05E8\u05E2\u05D4 \u05D1\u05E2\u05EA \u05D4\u05E1\u05E8\u05EA \u05D4\u05E7\u05D5\u05D1\u05E5",labelTapToCancel:"\u05D4\u05E7\u05DC\u05E7 \u05DC\u05D1\u05D9\u05D8\u05D5\u05DC",labelTapToRetry:"\u05D4\u05E7\u05DC\u05E7 \u05DC\u05E0\u05E1\u05D5\u05EA \u05E9\u05E0\u05D9\u05EA",labelTapToUndo:"\u05D4\u05E7\u05DC\u05E7 \u05DC\u05E9\u05D7\u05D6\u05E8",labelButtonRemoveItem:"\u05D4\u05E1\u05E8",labelButtonAbortItemLoad:"\u05D1\u05D8\u05DC",labelButtonRetryItemLoad:"\u05D8\u05E2\u05DF \u05E9\u05E0\u05D9\u05EA",labelButtonAbortItemProcessing:"\u05D1\u05D8\u05DC",labelButtonUndoItemProcessing:"\u05E9\u05D7\u05D6\u05E8",labelButtonRetryItemProcessing:"\u05E0\u05E1\u05D4 \u05E9\u05E0\u05D9\u05EA",labelButtonProcessItem:"\u05D4\u05E2\u05DC\u05D4 \u05E7\u05D5\u05D1\u05E5",labelMaxFileSizeExceeded:"\u05D4\u05E7\u05D5\u05D1\u05E5 \u05D2\u05D3\u05D5\u05DC \u05DE\u05D3\u05D9",labelMaxFileSize:"\u05D2\u05D5\u05D3\u05DC \u05D4\u05DE\u05D9\u05E8\u05D1\u05D9 \u05D4\u05DE\u05D5\u05EA\u05E8 \u05D4\u05D5\u05D0: {filesize}",labelMaxTotalFileSizeExceeded:"\u05D2\u05D5\u05D3\u05DC \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD \u05D7\u05D5\u05E8\u05D2 \u05DE\u05D4\u05DB\u05DE\u05D5\u05EA \u05D4\u05DE\u05D5\u05EA\u05E8\u05EA",labelMaxTotalFileSize:"\u05D4\u05D2\u05D5\u05D3\u05DC \u05D4\u05DE\u05D9\u05E8\u05D1\u05D9 \u05E9\u05DC \u05E1\u05DA \u05D4\u05E7\u05D1\u05E6\u05D9\u05DD: {filesize}",labelFileTypeNotAllowed:"\u05E7\u05D5\u05D1\u05E5 \u05DE\u05E1\u05D5\u05D2 \u05D6\u05D4 \u05D0\u05D9\u05E0\u05D5 \u05DE\u05D5\u05EA\u05E8",fileValidateTypeLabelExpectedTypes:"\u05D4\u05E7\u05D1\u05E6\u05D9\u05DD \u05D4\u05DE\u05D5\u05EA\u05E8\u05D9\u05DD \u05D4\u05DD {allButLastType} \u05D0\u05D5 {lastType}",imageValidateSizeLabelFormatError:"\u05EA\u05DE\u05D5\u05E0\u05D4 \u05D1\u05E4\u05D5\u05E8\u05DE\u05D8 \u05D6\u05D4 \u05D0\u05D9\u05E0\u05D4 \u05E0\u05EA\u05DE\u05DB\u05EA",imageValidateSizeLabelImageSizeTooSmall:"\u05EA\u05DE\u05D5\u05E0\u05D4 \u05D6\u05D5 \u05E7\u05D8\u05E0\u05D4 \u05DE\u05D3\u05D9",imageValidateSizeLabelImageSizeTooBig:"\u05EA\u05DE\u05D5\u05E0\u05D4 \u05D6\u05D5 \u05D2\u05D3\u05D5\u05DC\u05D4 \u05DE\u05D3\u05D9",imageValidateSizeLabelExpectedMinSize:"\u05D4\u05D2\u05D5\u05D3\u05DC \u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05E4\u05D7\u05D5\u05EA: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u05D4\u05D2\u05D5\u05D3\u05DC \u05D4\u05DE\u05E8\u05D1\u05D9 \u05D4\u05DE\u05D5\u05EA\u05E8: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u05D4\u05E8\u05D6\u05D5\u05DC\u05D5\u05E6\u05D9\u05D4 \u05E9\u05DC \u05EA\u05DE\u05D5\u05E0\u05D4 \u05D6\u05D5 \u05E0\u05DE\u05D5\u05DB\u05D4 \u05DE\u05D3\u05D9",imageValidateSizeLabelImageResolutionTooHigh:"\u05D4\u05E8\u05D6\u05D5\u05DC\u05D5\u05E6\u05D9\u05D4 \u05E9\u05DC \u05EA\u05DE\u05D5\u05E0\u05D4 \u05D6\u05D5 \u05D2\u05D1\u05D5\u05D4\u05D4 \u05DE\u05D3\u05D9",imageValidateSizeLabelExpectedMinResolution:"\u05D4\u05E8\u05D6\u05D5\u05DC\u05D5\u05E6\u05D9\u05D4 \u05E6\u05E8\u05D9\u05DB\u05D4 \u05DC\u05D4\u05D9\u05D5\u05EA \u05DC\u05E4\u05D7\u05D5\u05EA: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u05D4\u05E8\u05D6\u05D5\u05DC\u05D5\u05E6\u05D9\u05D4 \u05D4\u05DE\u05D9\u05E8\u05D1\u05D9\u05EA \u05D4\u05DE\u05D5\u05EA\u05E8\u05EA \u05D4\u05D9\u05D0: {maxResolution}"};var Uo={labelIdle:'Ovdje "ispusti" datoteku ili Pretra\u017Ei ',labelInvalidField:"Polje sadr\u017Ei neispravne datoteke",labelFileWaitingForSize:"\u010Cekanje na veli\u010Dinu datoteke",labelFileSizeNotAvailable:"Veli\u010Dina datoteke nije dostupna",labelFileLoading:"U\u010Ditavanje",labelFileLoadError:"Gre\u0161ka tijekom u\u010Ditavanja",labelFileProcessing:"Prijenos",labelFileProcessingComplete:"Prijenos zavr\u0161en",labelFileProcessingAborted:"Prijenos otkazan",labelFileProcessingError:"Gre\u0161ka tijekom prijenosa",labelFileProcessingRevertError:"Gre\u0161ka tijekom vra\u0107anja",labelFileRemoveError:"Gre\u0161ka tijekom uklananja datoteke",labelTapToCancel:"Dodirni za prekid",labelTapToRetry:"Dodirni za ponovno",labelTapToUndo:"Dodirni za vra\u0107anje",labelButtonRemoveItem:"Ukloni",labelButtonAbortItemLoad:"Odbaci",labelButtonRetryItemLoad:"Ponovi",labelButtonAbortItemProcessing:"Prekini",labelButtonUndoItemProcessing:"Vrati",labelButtonRetryItemProcessing:"Ponovi",labelButtonProcessItem:"Prijenos",labelMaxFileSizeExceeded:"Datoteka je prevelika",labelMaxFileSize:"Maksimalna veli\u010Dina datoteke je {filesize}",labelMaxTotalFileSizeExceeded:"Maksimalna ukupna veli\u010Dina datoteke prekora\u010Dena",labelMaxTotalFileSize:"Maksimalna ukupna veli\u010Dina datoteke je {filesize}",labelFileTypeNotAllowed:"Tip datoteke nije podr\u017Ean",fileValidateTypeLabelExpectedTypes:"O\u010Dekivan {allButLastType} ili {lastType}",imageValidateSizeLabelFormatError:"Tip slike nije podr\u017Ean",imageValidateSizeLabelImageSizeTooSmall:"Slika je premala",imageValidateSizeLabelImageSizeTooBig:"Slika je prevelika",imageValidateSizeLabelExpectedMinSize:"Minimalna veli\u010Dina je {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksimalna veli\u010Dina je {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rezolucija je preniska",imageValidateSizeLabelImageResolutionTooHigh:"Rezolucija je previsoka",imageValidateSizeLabelExpectedMinResolution:"Minimalna rezolucija je {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksimalna rezolucija je {maxResolution}"};var Wo={labelIdle:'Mozgasd ide a f\xE1jlt a felt\xF6lt\xE9shez, vagy tall\xF3z\xE1s ',labelInvalidField:"A mez\u0151 \xE9rv\xE9nytelen f\xE1jlokat tartalmaz",labelFileWaitingForSize:"F\xE1ljm\xE9ret kisz\xE1mol\xE1sa",labelFileSizeNotAvailable:"A f\xE1jlm\xE9ret nem el\xE9rhet\u0151",labelFileLoading:"T\xF6lt\xE9s",labelFileLoadError:"Hiba a bet\xF6lt\xE9s sor\xE1n",labelFileProcessing:"Felt\xF6lt\xE9s",labelFileProcessingComplete:"Sikeres felt\xF6lt\xE9s",labelFileProcessingAborted:"A felt\xF6lt\xE9s megszak\xEDtva",labelFileProcessingError:"Hiba t\xF6rt\xE9nt a felt\xF6lt\xE9s sor\xE1n",labelFileProcessingRevertError:"Hiba a vissza\xE1ll\xEDt\xE1s sor\xE1n",labelFileRemoveError:"Hiba t\xF6rt\xE9nt az elt\xE1vol\xEDt\xE1s sor\xE1n",labelTapToCancel:"koppints a t\xF6rl\xE9shez",labelTapToRetry:"koppints az \xFAjrakezd\xE9shez",labelTapToUndo:"koppints a visszavon\xE1shoz",labelButtonRemoveItem:"Elt\xE1vol\xEDt\xE1s",labelButtonAbortItemLoad:"Megszak\xEDt\xE1s",labelButtonRetryItemLoad:"\xDAjrapr\xF3b\xE1lkoz\xE1s",labelButtonAbortItemProcessing:"Megszak\xEDt\xE1s",labelButtonUndoItemProcessing:"Visszavon\xE1s",labelButtonRetryItemProcessing:"\xDAjrapr\xF3b\xE1lkoz\xE1s",labelButtonProcessItem:"Felt\xF6lt\xE9s",labelMaxFileSizeExceeded:"A f\xE1jl t\xFAll\xE9pte a maxim\xE1lis m\xE9retet",labelMaxFileSize:"Maxim\xE1lis f\xE1jlm\xE9ret: {filesize}",labelMaxTotalFileSizeExceeded:"T\xFAll\xE9pte a maxim\xE1lis teljes m\xE9retet",labelMaxTotalFileSize:"A maxim\xE1is teljes f\xE1jlm\xE9ret: {filesize}",labelFileTypeNotAllowed:"\xC9rv\xE9nytelen t\xEDpus\xFA f\xE1jl",fileValidateTypeLabelExpectedTypes:"Enged\xE9lyezett t\xEDpusok {allButLastType} vagy {lastType}",imageValidateSizeLabelFormatError:"A k\xE9pt\xEDpus nem t\xE1mogatott",imageValidateSizeLabelImageSizeTooSmall:"A k\xE9p t\xFAl kicsi",imageValidateSizeLabelImageSizeTooBig:"A k\xE9p t\xFAl nagy",imageValidateSizeLabelExpectedMinSize:"Minimum m\xE9ret: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximum m\xE9ret: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"A felbont\xE1s t\xFAl alacsony",imageValidateSizeLabelImageResolutionTooHigh:"A felbont\xE1s t\xFAl magas",imageValidateSizeLabelExpectedMinResolution:"Minim\xE1is felbont\xE1s: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maxim\xE1lis felbont\xE1s: {maxResolution}"};var Ho={labelIdle:'Seret & Jatuhkan berkas Anda atau Jelajahi',labelInvalidField:"Isian berisi berkas yang tidak valid",labelFileWaitingForSize:"Menunggu ukuran berkas",labelFileSizeNotAvailable:"Ukuran berkas tidak tersedia",labelFileLoading:"Memuat",labelFileLoadError:"Kesalahan saat memuat",labelFileProcessing:"Mengunggah",labelFileProcessingComplete:"Pengunggahan selesai",labelFileProcessingAborted:"Pengunggahan dibatalkan",labelFileProcessingError:"Kesalahan saat pengunggahan",labelFileProcessingRevertError:"Kesalahan saat pemulihan",labelFileRemoveError:"Kesalahan saat penghapusan",labelTapToCancel:"ketuk untuk membatalkan",labelTapToRetry:"ketuk untuk mencoba lagi",labelTapToUndo:"ketuk untuk mengurungkan",labelButtonRemoveItem:"Hapus",labelButtonAbortItemLoad:"Batalkan",labelButtonRetryItemLoad:"Coba Kembali",labelButtonAbortItemProcessing:"Batalkan",labelButtonUndoItemProcessing:"Urungkan",labelButtonRetryItemProcessing:"Coba Kembali",labelButtonProcessItem:"Unggah",labelMaxFileSizeExceeded:"Berkas terlalu besar",labelMaxFileSize:"Ukuran berkas maksimum adalah {filesize}",labelMaxTotalFileSizeExceeded:"Jumlah berkas maksimum terlampaui",labelMaxTotalFileSize:"Jumlah berkas maksimum adalah {filesize}",labelFileTypeNotAllowed:"Jenis berkas tidak valid",fileValidateTypeLabelExpectedTypes:"Mengharapkan {allButLastType} atau {lastType}",imageValidateSizeLabelFormatError:"Jenis citra tidak didukung",imageValidateSizeLabelImageSizeTooSmall:"Citra terlalu kecil",imageValidateSizeLabelImageSizeTooBig:"Citra terlalu besar",imageValidateSizeLabelExpectedMinSize:"Ukuran minimum adalah {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Ukuran maksimum adalah {minWidth} \xD7 {minHeight}",imageValidateSizeLabelImageResolutionTooLow:"Resolusi terlalu rendah",imageValidateSizeLabelImageResolutionTooHigh:"Resolusi terlalu tinggi",imageValidateSizeLabelExpectedMinResolution:"Resolusi minimum adalah {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Resolusi maksimum adalah {maxResolution}"};var jo={labelIdle:'Trascina e rilascia i tuoi file oppure Sfoglia ',labelInvalidField:"Il campo contiene dei file non validi",labelFileWaitingForSize:"In attesa della dimensione",labelFileSizeNotAvailable:"Dimensione non disponibile",labelFileLoading:"Caricamento",labelFileLoadError:"Errore durante il caricamento",labelFileProcessing:"Caricamento",labelFileProcessingComplete:"Caricamento completato",labelFileProcessingAborted:"Caricamento cancellato",labelFileProcessingError:"Errore durante il caricamento",labelFileProcessingRevertError:"Errore durante il ripristino",labelFileRemoveError:"Errore durante l'eliminazione",labelTapToCancel:"tocca per cancellare",labelTapToRetry:"tocca per riprovare",labelTapToUndo:"tocca per ripristinare",labelButtonRemoveItem:"Elimina",labelButtonAbortItemLoad:"Cancella",labelButtonRetryItemLoad:"Ritenta",labelButtonAbortItemProcessing:"Cancella",labelButtonUndoItemProcessing:"Indietro",labelButtonRetryItemProcessing:"Ritenta",labelButtonProcessItem:"Carica",labelMaxFileSizeExceeded:"La dimensione del file \xE8 eccessiva",labelMaxFileSize:"La dimensione massima del file \xE8 {filesize}",labelMaxTotalFileSizeExceeded:"Dimensione totale massima superata",labelMaxTotalFileSize:"La dimensione massima totale dei file \xE8 {filesize}",labelFileTypeNotAllowed:"File non supportato",fileValidateTypeLabelExpectedTypes:"Aspetta {allButLastType} o {lastType}",imageValidateSizeLabelFormatError:"Tipo di immagine non supportata",imageValidateSizeLabelImageSizeTooSmall:"L'immagine \xE8 troppo piccola",imageValidateSizeLabelImageSizeTooBig:"L'immagine \xE8 troppo grande",imageValidateSizeLabelExpectedMinSize:"La dimensione minima \xE8 {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"La dimensione massima \xE8 {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"La risoluzione \xE8 troppo bassa",imageValidateSizeLabelImageResolutionTooHigh:"La risoluzione \xE8 troppo alta",imageValidateSizeLabelExpectedMinResolution:"La risoluzione minima \xE8 {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"La risoluzione massima \xE8 {maxResolution}"};var Yo={labelIdle:'\u30D5\u30A1\u30A4\u30EB\u3092\u30C9\u30E9\u30C3\u30B0&\u30C9\u30ED\u30C3\u30D7\u53C8\u306F\u30D5\u30A1\u30A4\u30EB\u9078\u629E',labelInvalidField:"\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3067\u304D\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059",labelFileWaitingForSize:"\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u3092\u5F85\u3063\u3066\u3044\u307E\u3059",labelFileSizeNotAvailable:"\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u304C\u307F\u3064\u304B\u308A\u307E\u305B\u3093",labelFileLoading:"\u8AAD\u8FBC\u4E2D...",labelFileLoadError:"\u8AAD\u8FBC\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F",labelFileProcessing:"\u8AAD\u8FBC\u4E2D...",labelFileProcessingComplete:"\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u5B8C\u4E86",labelFileProcessingAborted:"\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F",labelFileProcessingError:"\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F",labelFileProcessingRevertError:"\u30ED\u30FC\u30EB\u30D0\u30C3\u30AF\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F",labelFileRemoveError:"\u524A\u9664\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F",labelTapToCancel:"\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u30AD\u30E3\u30F3\u30BB\u30EB",labelTapToRetry:"\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u4E0B\u3055\u3044",labelTapToUndo:"\u5143\u306B\u623B\u3059\u306B\u306F\u30BF\u30C3\u30D7\u3057\u307E\u3059",labelButtonRemoveItem:"\u524A\u9664",labelButtonAbortItemLoad:"\u4E2D\u65AD",labelButtonRetryItemLoad:"\u3082\u3046\u4E00\u5EA6\u5B9F\u884C",labelButtonAbortItemProcessing:"\u30AD\u30E3\u30F3\u30BB\u30EB",labelButtonUndoItemProcessing:"\u5143\u306B\u623B\u3059",labelButtonRetryItemProcessing:"\u3082\u3046\u4E00\u5EA6\u5B9F\u884C",labelButtonProcessItem:"\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9",labelMaxFileSizeExceeded:"\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u304C\u5927\u304D\u3059\u304E\u307E\u3059",labelMaxFileSize:"\u6700\u5927\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u306F {filesize} \u3067\u3059",labelMaxTotalFileSizeExceeded:"\u6700\u5927\u5408\u8A08\u30B5\u30A4\u30BA\u3092\u8D85\u3048\u307E\u3057\u305F",labelMaxTotalFileSize:"\u6700\u5927\u5408\u8A08\u30D5\u30A1\u30A4\u30EB\u30B5\u30A4\u30BA\u306F {filesize} \u3067\u3059",labelFileTypeNotAllowed:"\u7121\u52B9\u306A\u30D5\u30A1\u30A4\u30EB\u3067\u3059",fileValidateTypeLabelExpectedTypes:"\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u308B\u30D5\u30A1\u30A4\u30EB\u306F {allButLastType} \u53C8\u306F {lastType} \u3067\u3059",imageValidateSizeLabelFormatError:"\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u306A\u3044\u753B\u50CF\u3067\u3059",imageValidateSizeLabelImageSizeTooSmall:"\u753B\u50CF\u304C\u5C0F\u3055\u3059\u304E\u307E\u3059",imageValidateSizeLabelImageSizeTooBig:"\u753B\u50CF\u304C\u5927\u304D\u3059\u304E\u307E\u3059",imageValidateSizeLabelExpectedMinSize:"\u753B\u50CF\u306E\u6700\u5C0F\u30B5\u30A4\u30BA\u306F{minWidth}\xD7{minHeight}\u3067\u3059",imageValidateSizeLabelExpectedMaxSize:"\u753B\u50CF\u306E\u6700\u5927\u30B5\u30A4\u30BA\u306F{maxWidth} \xD7 {maxHeight}\u3067\u3059",imageValidateSizeLabelImageResolutionTooLow:"\u753B\u50CF\u306E\u89E3\u50CF\u5EA6\u304C\u4F4E\u3059\u304E\u307E\u3059",imageValidateSizeLabelImageResolutionTooHigh:"\u753B\u50CF\u306E\u89E3\u50CF\u5EA6\u304C\u9AD8\u3059\u304E\u307E\u3059",imageValidateSizeLabelExpectedMinResolution:"\u753B\u50CF\u306E\u6700\u5C0F\u89E3\u50CF\u5EA6\u306F{minResolution}\u3067\u3059",imageValidateSizeLabelExpectedMaxResolution:"\u753B\u50CF\u306E\u6700\u5927\u89E3\u50CF\u5EA6\u306F{maxResolution}\u3067\u3059"};var qo={labelIdle:'\u1791\u17B6\u1789&\u178A\u17B6\u1780\u17CB\u17A0\u17D2\u179C\u17B6\u179B\u17CB\u17AF\u1780\u179F\u17B6\u179A\u179A\u1794\u179F\u17CB\u17A2\u17D2\u1793\u1780 \u17AC \u179F\u17D2\u179C\u17C2\u1784\u179A\u1780 ',labelInvalidField:"\u1785\u1793\u17D2\u179B\u17C4\u17C7\u1798\u17B6\u1793\u17AF\u1780\u179F\u17B6\u179A\u1798\u17B7\u1793\u178F\u17D2\u179A\u17B9\u1798\u178F\u17D2\u179A\u17BC\u179C",labelFileWaitingForSize:"\u1780\u17C6\u1796\u17BB\u1784\u179A\u1784\u17CB\u1785\u17B6\u17C6\u1791\u17C6\u17A0\u17C6",labelFileSizeNotAvailable:"\u1791\u17C6\u17A0\u17C6\u1798\u17B7\u1793\u17A2\u17B6\u1785\u1794\u17D2\u179A\u17BE\u1794\u17B6\u1793",labelFileLoading:"\u1780\u17C6\u1796\u17BB\u1784\u178A\u17C6\u178E\u17BE\u179A\u1780\u17B6\u179A",labelFileLoadError:"\u1798\u17B6\u1793\u1794\u1789\u17D2\u17A0\u17B6\u1780\u17C6\u17A1\u17BB\u1784\u1796\u17C1\u179B\u178A\u17C6\u178E\u17BE\u179A\u1780\u17B6\u179A",labelFileProcessing:"\u1780\u17C6\u1796\u17BB\u1784\u1795\u17D2\u1791\u17BB\u1780\u17A1\u17BE\u1784",labelFileProcessingComplete:"\u1780\u17B6\u179A\u1795\u17D2\u1791\u17BB\u1780\u17A1\u17BE\u1784\u1796\u17C1\u1789\u179B\u17C1\u1789",labelFileProcessingAborted:"\u1780\u17B6\u179A\u1794\u1784\u17D2\u17A0\u17C4\u17C7\u178F\u17D2\u179A\u17BC\u179C\u1794\u17B6\u1793\u1794\u17C4\u17C7\u1794\u1784\u17CB",labelFileProcessingError:"\u1798\u17B6\u1793\u1794\u1789\u17D2\u17A0\u17B6\u1780\u17C6\u17A1\u17BB\u1784\u1796\u17C1\u179B\u1780\u17C6\u1796\u17BB\u1784\u1795\u17D2\u1791\u17BB\u1780\u17A1\u17BE\u1784",labelFileProcessingRevertError:"\u1798\u17B6\u1793\u1794\u1789\u17D2\u17A0\u17B6\u1780\u17C6\u17A1\u17BB\u1784\u1796\u17C1\u179B\u178F\u17D2\u179A\u17A1\u1794\u17CB",labelFileRemoveError:"\u1798\u17B6\u1793\u1794\u1789\u17D2\u17A0\u17B6\u1780\u17C6\u17A1\u17BB\u1784\u1796\u17C1\u179B\u178A\u1780\u1785\u17C1\u1789",labelTapToCancel:"\u1785\u17BB\u1785\u178A\u17BE\u1798\u17D2\u1794\u17B8\u1794\u17C4\u17C7\u1794\u1784\u17CB",labelTapToRetry:"\u1785\u17BB\u1785\u178A\u17BE\u1798\u17D2\u1794\u17B8\u1796\u17D2\u1799\u17B6\u1799\u17B6\u1798\u1798\u17D2\u178F\u1784\u1791\u17C0\u178F",labelTapToUndo:"\u1785\u17BB\u1785\u178A\u17BE\u1798\u17D2\u1794\u17B8\u1798\u17B7\u1793\u1792\u17D2\u179C\u17BE\u179C\u17B7\u1789",labelButtonRemoveItem:"\u1799\u1780\u1785\u17C1\u1789",labelButtonAbortItemLoad:"\u1794\u17C4\u17C7\u1794\u1784\u17CB",labelButtonRetryItemLoad:"\u1796\u17D2\u1799\u17B6\u1799\u17B6\u1798\u1798\u17D2\u178F\u1784\u1791\u17C0\u178F",labelButtonAbortItemProcessing:"\u1794\u17C4\u17C7\u1794\u1784\u17CB",labelButtonUndoItemProcessing:"\u1798\u17B7\u1793\u1792\u17D2\u179C\u17BE\u179C\u17B7\u1789",labelButtonRetryItemProcessing:"\u1796\u17D2\u1799\u17B6\u1799\u17B6\u1798\u1798\u17D2\u178F\u1784\u1791\u17C0\u178F",labelButtonProcessItem:"\u1795\u17D2\u1791\u17BB\u1780\u17A1\u17BE\u1784",labelMaxFileSizeExceeded:"\u17AF\u1780\u179F\u17B6\u179A\u1792\u17C6\u1796\u17C1\u1780",labelMaxFileSize:"\u1791\u17C6\u17A0\u17C6\u17AF\u1780\u179F\u17B6\u179A\u17A2\u178F\u17B7\u1794\u179A\u1798\u17B6\u1782\u17BA {filesize}",labelMaxTotalFileSizeExceeded:"\u179B\u17BE\u179F\u1791\u17C6\u17A0\u17C6\u179F\u179A\u17BB\u1794\u17A2\u178F\u17B7\u1794\u179A\u1798\u17B6",labelMaxTotalFileSize:"\u1791\u17C6\u17A0\u17C6\u17AF\u1780\u179F\u17B6\u179A\u179F\u179A\u17BB\u1794\u17A2\u178F\u17B7\u1794\u179A\u1798\u17B6\u1782\u17BA {filesize}",labelFileTypeNotAllowed:"\u1794\u17D2\u179A\u1797\u17C1\u1791\u17AF\u1780\u179F\u17B6\u179A\u1798\u17B7\u1793\u178F\u17D2\u179A\u17B9\u1798\u178F\u17D2\u179A\u17BC\u179C",fileValidateTypeLabelExpectedTypes:"\u179A\u17C6\u1796\u17B9\u1784\u1790\u17B6 {allButLastType} \u17AC {lastType}",imageValidateSizeLabelFormatError:"\u1794\u17D2\u179A\u1797\u17C1\u1791\u179A\u17BC\u1794\u1797\u17B6\u1796\u1798\u17B7\u1793\u178F\u17D2\u179A\u17B9\u1798\u178F\u17D2\u179A\u17BC\u179C",imageValidateSizeLabelImageSizeTooSmall:"\u179A\u17BC\u1794\u1797\u17B6\u1796\u178F\u17BC\u1785\u1796\u17C1\u1780",imageValidateSizeLabelImageSizeTooBig:"\u179A\u17BC\u1794\u1797\u17B6\u1796\u1792\u17C6\u1796\u17C1\u1780",imageValidateSizeLabelExpectedMinSize:"\u1791\u17C6\u17A0\u17C6\u17A2\u1794\u17D2\u1794\u1794\u179A\u1798\u17B6\u1782\u17BA {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u1791\u17C6\u17A0\u17C6\u17A2\u178F\u17B7\u1794\u179A\u1798\u17B6\u1782\u17BA {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u1782\u17BB\u178E\u1797\u17B6\u1796\u1794\u1784\u17D2\u17A0\u17B6\u1789\u1791\u17B6\u1794\u1796\u17C1\u1780",imageValidateSizeLabelImageResolutionTooHigh:"\u1782\u17BB\u178E\u1797\u17B6\u1796\u1794\u1784\u17D2\u17A0\u17B6\u1789\u1781\u17D2\u1796\u179F\u17CB\u1796\u17C1\u1780",imageValidateSizeLabelExpectedMinResolution:"\u1782\u17BB\u178E\u1797\u17B6\u1796\u1794\u1784\u17D2\u17A0\u17B6\u1789\u17A2\u1794\u17D2\u1794\u1794\u179A\u1798\u17B6\u1782\u17BA {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u1782\u17BB\u178E\u1797\u17B6\u1796\u1794\u1784\u17D2\u17A0\u17B6\u1789\u17A2\u178F\u17B7\u1794\u179A\u1798\u17B6\u1782\u17BA {maxResolution}"};var $o={labelIdle:'\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8 \uD558\uAC70\uB098 \uCC3E\uC544\uBCF4\uAE30 ',labelInvalidField:"\uD544\uB4DC\uC5D0 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4.",labelFileWaitingForSize:"\uC6A9\uB7C9 \uD655\uC778\uC911",labelFileSizeNotAvailable:"\uC0AC\uC6A9\uD560 \uC218 \uC5C6\uB294 \uC6A9\uB7C9",labelFileLoading:"\uBD88\uB7EC\uC624\uB294 \uC911",labelFileLoadError:"\uD30C\uC77C \uBD88\uB7EC\uC624\uAE30 \uC2E4\uD328",labelFileProcessing:"\uC5C5\uB85C\uB4DC \uC911",labelFileProcessingComplete:"\uC5C5\uB85C\uB4DC \uC131\uACF5",labelFileProcessingAborted:"\uC5C5\uB85C\uB4DC \uCDE8\uC18C\uB428",labelFileProcessingError:"\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328",labelFileProcessingRevertError:"\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328",labelFileRemoveError:"\uC81C\uAC70 \uC2E4\uD328",labelTapToCancel:"\uD0ED\uD558\uC5EC \uCDE8\uC18C",labelTapToRetry:"\uD0ED\uD558\uC5EC \uC7AC\uC2DC\uC791",labelTapToUndo:"\uD0ED\uD558\uC5EC \uC2E4\uD589 \uCDE8\uC18C",labelButtonRemoveItem:"\uC81C\uAC70",labelButtonAbortItemLoad:"\uC911\uB2E8",labelButtonRetryItemLoad:"\uC7AC\uC2DC\uC791",labelButtonAbortItemProcessing:"\uCDE8\uC18C",labelButtonUndoItemProcessing:"\uC2E4\uD589 \uCDE8\uC18C",labelButtonRetryItemProcessing:"\uC7AC\uC2DC\uC791",labelButtonProcessItem:"\uC5C5\uB85C\uB4DC",labelMaxFileSizeExceeded:"\uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4.",labelMaxFileSize:"\uCD5C\uB300 \uD30C\uC77C \uC6A9\uB7C9\uC740 {filesize} \uC785\uB2C8\uB2E4.",labelMaxTotalFileSizeExceeded:"\uCD5C\uB300 \uC804\uCCB4 \uD30C\uC77C \uC6A9\uB7C9 \uCD08\uACFC\uD558\uC600\uC2B5\uB2C8\uB2E4.",labelMaxTotalFileSize:"\uCD5C\uB300 \uC804\uCCB4 \uD30C\uC77C \uC6A9\uB7C9\uC740 {filesize} \uC785\uB2C8\uB2E4.",labelFileTypeNotAllowed:"\uC798\uBABB\uB41C \uD615\uC2DD\uC758 \uD30C\uC77C",fileValidateTypeLabelExpectedTypes:"{allButLastType} \uB610\uB294 {lastType}",imageValidateSizeLabelFormatError:"\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uC774\uBBF8\uC9C0 \uC720\uD615",imageValidateSizeLabelImageSizeTooSmall:"\uC774\uBBF8\uC9C0\uAC00 \uB108\uBB34 \uC791\uC2B5\uB2C8\uB2E4.",imageValidateSizeLabelImageSizeTooBig:"\uC774\uBBF8\uC9C0\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4.",imageValidateSizeLabelExpectedMinSize:"\uC774\uBBF8\uC9C0 \uCD5C\uC18C \uD06C\uAE30\uB294 {minWidth} \xD7 {minHeight} \uC785\uB2C8\uB2E4",imageValidateSizeLabelExpectedMaxSize:"\uC774\uBBF8\uC9C0 \uCD5C\uB300 \uD06C\uAE30\uB294 {maxWidth} \xD7 {maxHeight} \uC785\uB2C8\uB2E4",imageValidateSizeLabelImageResolutionTooLow:"\uD574\uC0C1\uB3C4\uAC00 \uB108\uBB34 \uB0AE\uC2B5\uB2C8\uB2E4.",imageValidateSizeLabelImageResolutionTooHigh:"\uD574\uC0C1\uB3C4\uAC00 \uB108\uBB34 \uB192\uC2B5\uB2C8\uB2E4.",imageValidateSizeLabelExpectedMinResolution:"\uCD5C\uC18C \uD574\uC0C1\uB3C4\uB294 {minResolution} \uC785\uB2C8\uB2E4.",imageValidateSizeLabelExpectedMaxResolution:"\uCD5C\uB300 \uD574\uC0C1\uB3C4\uB294 {maxResolution} \uC785\uB2C8\uB2E4."};var Xo={labelIdle:'\u012Ed\u0117kite failus \u010Dia arba Ie\u0161kokite ',labelInvalidField:"Laukelis talpina netinkamus failus",labelFileWaitingForSize:"Laukiama dyd\u017Eio",labelFileSizeNotAvailable:"Dydis ne\u017Einomas",labelFileLoading:"Kraunama",labelFileLoadError:"Klaida \u012Fkeliant",labelFileProcessing:"\u012Ekeliama",labelFileProcessingComplete:"\u012Ek\u0117limas s\u0117kmingas",labelFileProcessingAborted:"\u012Ek\u0117limas at\u0161auktas",labelFileProcessingError:"\u012Ekeliant \u012Fvyko klaida",labelFileProcessingRevertError:"At\u0161aukiant \u012Fvyko klaida",labelFileRemoveError:"I\u0161trinant \u012Fvyko klaida",labelTapToCancel:"Palieskite nor\u0117dami at\u0161aukti",labelTapToRetry:"Palieskite nor\u0117dami pakartoti",labelTapToUndo:"Palieskite nor\u0117dami at\u0161aukti",labelButtonRemoveItem:"I\u0161trinti",labelButtonAbortItemLoad:"Sustabdyti",labelButtonRetryItemLoad:"Pakartoti",labelButtonAbortItemProcessing:"At\u0161aukti",labelButtonUndoItemProcessing:"At\u0161aukti",labelButtonRetryItemProcessing:"Pakartoti",labelButtonProcessItem:"\u012Ekelti",labelMaxFileSizeExceeded:"Failas per didelis",labelMaxFileSize:"Maksimalus failo dydis yra {filesize}",labelMaxTotalFileSizeExceeded:"Vir\u0161ijote maksimal\u0173 leistin\u0105 dyd\u012F",labelMaxTotalFileSize:"Maksimalus leistinas dydis yra {filesize}",labelFileTypeNotAllowed:"Netinkamas failas",fileValidateTypeLabelExpectedTypes:"Tikisi {allButLastType} arba {lastType}",imageValidateSizeLabelFormatError:"Nuotraukos formatas nepalaikomas",imageValidateSizeLabelImageSizeTooSmall:"Nuotrauka per ma\u017Ea",imageValidateSizeLabelImageSizeTooBig:"Nuotrauka per didel\u0117",imageValidateSizeLabelExpectedMinSize:"Minimalus dydis yra {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksimalus dydis yra {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rezoliucija per ma\u017Ea",imageValidateSizeLabelImageResolutionTooHigh:"Rezoliucija per didel\u0117",imageValidateSizeLabelExpectedMinResolution:"Minimali rezoliucija yra {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksimali rezoliucija yra {maxResolution}"};var Ko={labelIdle:'Ievelciet savus failus vai p\u0101rl\u016Bkojiet \u0161eit ',labelInvalidField:"Lauks satur neder\u012Bgus failus",labelFileWaitingForSize:"Gaid\u0101m faila izm\u0113ru",labelFileSizeNotAvailable:"Izm\u0113rs nav pieejams",labelFileLoading:"Notiek iel\u0101de",labelFileLoadError:"Notika k\u013C\u016Bda iel\u0101des laik\u0101",labelFileProcessing:"Notiek aug\u0161upiel\u0101de",labelFileProcessingComplete:"Aug\u0161upiel\u0101de pabeigta",labelFileProcessingAborted:"Aug\u0161upiel\u0101de atcelta",labelFileProcessingError:"Notika k\u013C\u016Bda aug\u0161upiel\u0101des laik\u0101",labelFileProcessingRevertError:"Notika k\u013C\u016Bda atgrie\u0161anas laik\u0101",labelFileRemoveError:"Notika k\u013C\u016Bda dz\u0113\u0161anas laik\u0101",labelTapToCancel:"pieskarieties, lai atceltu",labelTapToRetry:"pieskarieties, lai m\u0113\u0123in\u0101tu v\u0113lreiz",labelTapToUndo:"pieskarieties, lai atsauktu",labelButtonRemoveItem:"Dz\u0113st",labelButtonAbortItemLoad:"P\u0101rtraukt",labelButtonRetryItemLoad:"M\u0113\u0123in\u0101t v\u0113lreiz",labelButtonAbortItemProcessing:"P\u0101rtraucam",labelButtonUndoItemProcessing:"Atsaucam",labelButtonRetryItemProcessing:"M\u0113\u0123in\u0101m v\u0113lreiz",labelButtonProcessItem:"Aug\u0161upiel\u0101d\u0113t",labelMaxFileSizeExceeded:"Fails ir p\u0101r\u0101k liels",labelMaxFileSize:"Maksim\u0101lais faila izm\u0113rs ir {filesize}",labelMaxTotalFileSizeExceeded:"P\u0101rsniegts maksim\u0101lais kop\u0113jais failu izm\u0113rs",labelMaxTotalFileSize:"Maksim\u0101lais kop\u0113jais failu izm\u0113rs ir {filesize}",labelFileTypeNotAllowed:"Neder\u012Bgs faila tips",fileValidateTypeLabelExpectedTypes:"Sagaid\u0101m {allButLastType} vai {lastType}",imageValidateSizeLabelFormatError:"Neatbilsto\u0161s att\u0113la tips",imageValidateSizeLabelImageSizeTooSmall:"Att\u0113ls ir p\u0101r\u0101k mazs",imageValidateSizeLabelImageSizeTooBig:"Att\u0113ls ir p\u0101r\u0101k liels",imageValidateSizeLabelExpectedMinSize:"Minim\u0101lais izm\u0113rs ir {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksim\u0101lais izm\u0113rs ir {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Iz\u0161\u0137irtsp\u0113ja ir p\u0101r\u0101k zema",imageValidateSizeLabelImageResolutionTooHigh:"Iz\u0161\u0137irtsp\u0113ja ir p\u0101r\u0101k augsta",imageValidateSizeLabelExpectedMinResolution:"Minim\u0101l\u0101 iz\u0161\u0137irtsp\u0113ja ir {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksim\u0101l\u0101 iz\u0161\u0137irtsp\u0113ja ir {maxResolution}"};var Qo={labelIdle:'Drag & Drop je bestanden of Bladeren ',labelInvalidField:"Veld bevat ongeldige bestanden",labelFileWaitingForSize:"Wachten op grootte",labelFileSizeNotAvailable:"Grootte niet beschikbaar",labelFileLoading:"Laden",labelFileLoadError:"Fout tijdens laden",labelFileProcessing:"Uploaden",labelFileProcessingComplete:"Upload afgerond",labelFileProcessingAborted:"Upload geannuleerd",labelFileProcessingError:"Fout tijdens upload",labelFileProcessingRevertError:"Fout bij herstellen",labelFileRemoveError:"Fout bij verwijderen",labelTapToCancel:"tik om te annuleren",labelTapToRetry:"tik om opnieuw te proberen",labelTapToUndo:"tik om ongedaan te maken",labelButtonRemoveItem:"Verwijderen",labelButtonAbortItemLoad:"Afbreken",labelButtonRetryItemLoad:"Opnieuw proberen",labelButtonAbortItemProcessing:"Annuleren",labelButtonUndoItemProcessing:"Ongedaan maken",labelButtonRetryItemProcessing:"Opnieuw proberen",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"Bestand is te groot",labelMaxFileSize:"Maximale bestandsgrootte is {filesize}",labelMaxTotalFileSizeExceeded:"Maximale totale grootte overschreden",labelMaxTotalFileSize:"Maximale totale bestandsgrootte is {filesize}",labelFileTypeNotAllowed:"Ongeldig bestandstype",fileValidateTypeLabelExpectedTypes:"Verwacht {allButLastType} of {lastType}",imageValidateSizeLabelFormatError:"Afbeeldingstype niet ondersteund",imageValidateSizeLabelImageSizeTooSmall:"Afbeelding is te klein",imageValidateSizeLabelImageSizeTooBig:"Afbeelding is te groot",imageValidateSizeLabelExpectedMinSize:"Minimale afmeting is {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximale afmeting is {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Resolutie is te laag",imageValidateSizeLabelImageResolutionTooHigh:"Resolution is too high",imageValidateSizeLabelExpectedMinResolution:"Minimale resolutie is {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maximale resolutie is {maxResolution}"};var Zo={labelIdle:'Dra og slipp filene dine, eller Bla gjennom... ',labelInvalidField:"Feltet inneholder ugyldige filer",labelFileWaitingForSize:"Venter p\xE5 st\xF8rrelse",labelFileSizeNotAvailable:"St\xF8rrelse ikke tilgjengelig",labelFileLoading:"Laster",labelFileLoadError:"Feil under lasting",labelFileProcessing:"Laster opp",labelFileProcessingComplete:"Opplasting ferdig",labelFileProcessingAborted:"Opplasting avbrutt",labelFileProcessingError:"Feil under opplasting",labelFileProcessingRevertError:"Feil under reversering",labelFileRemoveError:"Feil under flytting",labelTapToCancel:"klikk for \xE5 avbryte",labelTapToRetry:"klikk for \xE5 pr\xF8ve p\xE5 nytt",labelTapToUndo:"klikk for \xE5 angre",labelButtonRemoveItem:"Fjern",labelButtonAbortItemLoad:"Avbryt",labelButtonRetryItemLoad:"Pr\xF8v p\xE5 nytt",labelButtonAbortItemProcessing:"Avbryt",labelButtonUndoItemProcessing:"Angre",labelButtonRetryItemProcessing:"Pr\xF8v p\xE5 nytt",labelButtonProcessItem:"Last opp",labelMaxFileSizeExceeded:"Filen er for stor",labelMaxFileSize:"Maksimal filst\xF8rrelse er {filesize}",labelMaxTotalFileSizeExceeded:"Maksimal total st\xF8rrelse oversteget",labelMaxTotalFileSize:"Maksimal total st\xF8rrelse er {filesize}",labelFileTypeNotAllowed:"Ugyldig filtype",fileValidateTypeLabelExpectedTypes:"Forventer {allButLastType} eller {lastType}",imageValidateSizeLabelFormatError:"Bildeformat ikke st\xF8ttet",imageValidateSizeLabelImageSizeTooSmall:"Bildet er for lite",imageValidateSizeLabelImageSizeTooBig:"Bildet er for stort",imageValidateSizeLabelExpectedMinSize:"Minimumsst\xF8rrelse er {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksimumsst\xF8rrelse er {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Oppl\xF8sningen er for lav",imageValidateSizeLabelImageResolutionTooHigh:"Oppl\xF8sningen er for h\xF8y",imageValidateSizeLabelExpectedMinResolution:"Minimum oppl\xF8sning er {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksimal oppl\xF8sning er {maxResolution}"};var Jo={labelIdle:'Przeci\u0105gnij i upu\u015B\u0107 lub wybierz pliki',labelInvalidField:"Nieprawid\u0142owe pliki",labelFileWaitingForSize:"Pobieranie rozmiaru",labelFileSizeNotAvailable:"Nieznany rozmiar",labelFileLoading:"Wczytywanie",labelFileLoadError:"B\u0142\u0105d wczytywania",labelFileProcessing:"Przesy\u0142anie",labelFileProcessingComplete:"Przes\u0142ano",labelFileProcessingAborted:"Przerwano",labelFileProcessingError:"Przesy\u0142anie nie powiod\u0142o si\u0119",labelFileProcessingRevertError:"Co\u015B posz\u0142o nie tak",labelFileRemoveError:"Nieudane usuni\u0119cie",labelTapToCancel:"Anuluj",labelTapToRetry:"Pon\xF3w",labelTapToUndo:"Cofnij",labelButtonRemoveItem:"Usu\u0144",labelButtonAbortItemLoad:"Przerwij",labelButtonRetryItemLoad:"Pon\xF3w",labelButtonAbortItemProcessing:"Anuluj",labelButtonUndoItemProcessing:"Cofnij",labelButtonRetryItemProcessing:"Pon\xF3w",labelButtonProcessItem:"Prze\u015Blij",labelMaxFileSizeExceeded:"Plik jest zbyt du\u017Cy",labelMaxFileSize:"Dopuszczalna wielko\u015B\u0107 pliku to {filesize}",labelMaxTotalFileSizeExceeded:"Przekroczono \u0142\u0105czny rozmiar plik\xF3w",labelMaxTotalFileSize:"\u0141\u0105czny rozmiar plik\xF3w nie mo\u017Ce przekroczy\u0107 {filesize}",labelFileTypeNotAllowed:"Niedozwolony rodzaj pliku",fileValidateTypeLabelExpectedTypes:"Oczekiwano {allButLastType} lub {lastType}",imageValidateSizeLabelFormatError:"Nieobs\u0142ugiwany format obrazu",imageValidateSizeLabelImageSizeTooSmall:"Obraz jest zbyt ma\u0142y",imageValidateSizeLabelImageSizeTooBig:"Obraz jest zbyt du\u017Cy",imageValidateSizeLabelExpectedMinSize:"Minimalne wymiary obrazu to {minWidth}\xD7{minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maksymalna wymiary obrazu to {maxWidth}\xD7{maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rozdzielczo\u015B\u0107 jest zbyt niska",imageValidateSizeLabelImageResolutionTooHigh:"Rozdzielczo\u015B\u0107 jest zbyt wysoka",imageValidateSizeLabelExpectedMinResolution:"Minimalna rozdzielczo\u015B\u0107 to {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maksymalna rozdzielczo\u015B\u0107 to {maxResolution}"};var _i={labelIdle:'Arraste e solte os arquivos ou Clique aqui ',labelInvalidField:"Arquivos inv\xE1lidos",labelFileWaitingForSize:"Calculando o tamanho do arquivo",labelFileSizeNotAvailable:"Tamanho do arquivo indispon\xEDvel",labelFileLoading:"Carregando",labelFileLoadError:"Erro durante o carregamento",labelFileProcessing:"Enviando",labelFileProcessingComplete:"Envio finalizado",labelFileProcessingAborted:"Envio cancelado",labelFileProcessingError:"Erro durante o envio",labelFileProcessingRevertError:"Erro ao reverter o envio",labelFileRemoveError:"Erro ao remover o arquivo",labelTapToCancel:"clique para cancelar",labelTapToRetry:"clique para reenviar",labelTapToUndo:"clique para desfazer",labelButtonRemoveItem:"Remover",labelButtonAbortItemLoad:"Abortar",labelButtonRetryItemLoad:"Reenviar",labelButtonAbortItemProcessing:"Cancelar",labelButtonUndoItemProcessing:"Desfazer",labelButtonRetryItemProcessing:"Reenviar",labelButtonProcessItem:"Enviar",labelMaxFileSizeExceeded:"Arquivo \xE9 muito grande",labelMaxFileSize:"O tamanho m\xE1ximo permitido: {filesize}",labelMaxTotalFileSizeExceeded:"Tamanho total dos arquivos excedido",labelMaxTotalFileSize:"Tamanho total permitido: {filesize}",labelFileTypeNotAllowed:"Tipo de arquivo inv\xE1lido",fileValidateTypeLabelExpectedTypes:"Tipos de arquivo suportados s\xE3o {allButLastType} ou {lastType}",imageValidateSizeLabelFormatError:"Tipo de imagem inv\xE1lida",imageValidateSizeLabelImageSizeTooSmall:"Imagem muito pequena",imageValidateSizeLabelImageSizeTooBig:"Imagem muito grande",imageValidateSizeLabelExpectedMinSize:"Tamanho m\xEDnimo permitida: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Tamanho m\xE1ximo permitido: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Resolu\xE7\xE3o muito baixa",imageValidateSizeLabelImageResolutionTooHigh:"Resolu\xE7\xE3o muito alta",imageValidateSizeLabelExpectedMinResolution:"Resolu\xE7\xE3o m\xEDnima permitida: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Resolu\xE7\xE3o m\xE1xima permitida: {maxResolution}"};var er={labelIdle:'Trage \u0219i plaseaz\u0103 fi\u0219iere sau Caut\u0103-le ',labelInvalidField:"C\xE2mpul con\u021Bine fi\u0219iere care nu sunt valide",labelFileWaitingForSize:"\xCEn a\u0219teptarea dimensiunii",labelFileSizeNotAvailable:"Dimensiunea nu este diponibil\u0103",labelFileLoading:"Se \xEEncarc\u0103",labelFileLoadError:"Eroare la \xEEnc\u0103rcare",labelFileProcessing:"Se \xEEncarc\u0103",labelFileProcessingComplete:"\xCEnc\u0103rcare finalizat\u0103",labelFileProcessingAborted:"\xCEnc\u0103rcare anulat\u0103",labelFileProcessingError:"Eroare la \xEEnc\u0103rcare",labelFileProcessingRevertError:"Eroare la anulare",labelFileRemoveError:"Eroare la \u015Ftergere",labelTapToCancel:"apas\u0103 pentru a anula",labelTapToRetry:"apas\u0103 pentru a re\xEEncerca",labelTapToUndo:"apas\u0103 pentru a anula",labelButtonRemoveItem:"\u015Eterge",labelButtonAbortItemLoad:"Anuleaz\u0103",labelButtonRetryItemLoad:"Re\xEEncearc\u0103",labelButtonAbortItemProcessing:"Anuleaz\u0103",labelButtonUndoItemProcessing:"Anuleaz\u0103",labelButtonRetryItemProcessing:"Re\xEEncearc\u0103",labelButtonProcessItem:"\xCEncarc\u0103",labelMaxFileSizeExceeded:"Fi\u0219ierul este prea mare",labelMaxFileSize:"Dimensiunea maxim\u0103 a unui fi\u0219ier este de {filesize}",labelMaxTotalFileSizeExceeded:"Dimensiunea total\u0103 maxim\u0103 a fost dep\u0103\u0219it\u0103",labelMaxTotalFileSize:"Dimensiunea total\u0103 maxim\u0103 a fi\u0219ierelor este de {filesize}",labelFileTypeNotAllowed:"Tipul fi\u0219ierului nu este valid",fileValidateTypeLabelExpectedTypes:"Se a\u0219teapt\u0103 {allButLastType} sau {lastType}",imageValidateSizeLabelFormatError:"Formatul imaginii nu este acceptat",imageValidateSizeLabelImageSizeTooSmall:"Imaginea este prea mic\u0103",imageValidateSizeLabelImageSizeTooBig:"Imaginea este prea mare",imageValidateSizeLabelExpectedMinSize:"M\u0103rimea minim\u0103 este de {maxWidth} x {maxHeight}",imageValidateSizeLabelExpectedMaxSize:"M\u0103rimea maxim\u0103 este de {maxWidth} x {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rezolu\u021Bia este prea mic\u0103",imageValidateSizeLabelImageResolutionTooHigh:"Rezolu\u021Bia este prea mare",imageValidateSizeLabelExpectedMinResolution:"Rezolu\u021Bia minim\u0103 este de {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Rezolu\u021Bia maxim\u0103 este de {maxResolution}"};var tr={labelIdle:'\u041F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B \u0438\u043B\u0438 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 ',labelInvalidField:"\u041F\u043E\u043B\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0435 \u0444\u0430\u0439\u043B\u044B",labelFileWaitingForSize:"\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0440\u0430\u0437\u043C\u0435\u0440",labelFileSizeNotAvailable:"\u0420\u0430\u0437\u043C\u0435\u0440 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F",labelFileLoading:"\u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435",labelFileLoadError:"\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0438",labelFileProcessing:"\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430",labelFileProcessingComplete:"\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430",labelFileProcessingAborted:"\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u0430",labelFileProcessingError:"\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0435",labelFileProcessingRevertError:"\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0435",labelFileRemoveError:"\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0438",labelTapToCancel:"\u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u0434\u043B\u044F \u043E\u0442\u043C\u0435\u043D\u044B",labelTapToRetry:"\u043D\u0430\u0436\u043C\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u043F\u043E\u043F\u044B\u0442\u043A\u0443",labelTapToUndo:"\u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u0434\u043B\u044F \u043E\u0442\u043C\u0435\u043D\u044B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F",labelButtonRemoveItem:"\u0423\u0434\u0430\u043B\u0438\u0442\u044C",labelButtonAbortItemLoad:"\u041F\u0440\u0435\u043A\u0440\u0430\u0449\u0435\u043D\u043E",labelButtonRetryItemLoad:"\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0443",labelButtonAbortItemProcessing:"\u041E\u0442\u043C\u0435\u043D\u0430",labelButtonUndoItemProcessing:"\u041E\u0442\u043C\u0435\u043D\u0430 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F",labelButtonRetryItemProcessing:"\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0443",labelButtonProcessItem:"\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430",labelMaxFileSizeExceeded:"\u0424\u0430\u0439\u043B \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043E\u043B\u044C\u0448\u043E\u0439",labelMaxFileSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u0444\u0430\u0439\u043B\u0430: {filesize}",labelMaxTotalFileSizeExceeded:"\u041F\u0440\u0435\u0432\u044B\u0448\u0435\u043D \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440",labelMaxTotalFileSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u0444\u0430\u0439\u043B\u0430: {filesize}",labelFileTypeNotAllowed:"\u0424\u0430\u0439\u043B \u043D\u0435\u0432\u0435\u0440\u043D\u043E\u0433\u043E \u0442\u0438\u043F\u0430",fileValidateTypeLabelExpectedTypes:"\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F {allButLastType} \u0438\u043B\u0438 {lastType}",imageValidateSizeLabelFormatError:"\u0422\u0438\u043F \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F",imageValidateSizeLabelImageSizeTooSmall:"\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u043E\u0435",imageValidateSizeLabelImageSizeTooBig:"\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043E\u043B\u044C\u0448\u043E\u0435",imageValidateSizeLabelExpectedMinSize:"\u041C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043D\u0438\u0437\u043A\u043E\u0435",imageValidateSizeLabelImageResolutionTooHigh:"\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0432\u044B\u0441\u043E\u043A\u043E\u0435",imageValidateSizeLabelExpectedMinResolution:"\u041C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435: {maxResolution}"};var ir={labelIdle:'Natiahn\xFA\u0165 s\xFAbor (drag&drop) alebo Vyh\u013Eada\u0165 ',labelInvalidField:"Pole obsahuje chybn\xE9 s\xFAbory",labelFileWaitingForSize:"Zis\u0165uje sa ve\u013Ekos\u0165",labelFileSizeNotAvailable:"Nezn\xE1ma ve\u013Ekos\u0165",labelFileLoading:"Pren\xE1\u0161a sa",labelFileLoadError:"Chyba pri prenose",labelFileProcessing:"Prebieha upload",labelFileProcessingComplete:"Upload dokon\u010Den\xFD",labelFileProcessingAborted:"Upload stornovan\xFD",labelFileProcessingError:"Chyba pri uploade",labelFileProcessingRevertError:"Chyba pri obnove",labelFileRemoveError:"Chyba pri odstr\xE1nen\xED",labelTapToCancel:"Kliknite pre storno",labelTapToRetry:"Kliknite pre opakovanie",labelTapToUndo:"Kliknite pre vr\xE1tenie",labelButtonRemoveItem:"Odstr\xE1ni\u0165",labelButtonAbortItemLoad:"Storno",labelButtonRetryItemLoad:"Opakova\u0165",labelButtonAbortItemProcessing:"Sp\xE4\u0165",labelButtonUndoItemProcessing:"Vr\xE1ti\u0165",labelButtonRetryItemProcessing:"Opakova\u0165",labelButtonProcessItem:"Upload",labelMaxFileSizeExceeded:"S\xFAbor je pr\xEDli\u0161 ve\u013Ek\xFD",labelMaxFileSize:"Najv\xE4\u010D\u0161ia ve\u013Ekos\u0165 s\xFAboru je {filesize}",labelMaxTotalFileSizeExceeded:"Prekro\u010Den\xE1 maxim\xE1lna celkov\xE1 ve\u013Ekos\u0165 s\xFAboru",labelMaxTotalFileSize:"Maxim\xE1lna celkov\xE1 ve\u013Ekos\u0165 s\xFAboru je {filesize}",labelFileTypeNotAllowed:"S\xFAbor je nespr\xE1vneho typu",fileValidateTypeLabelExpectedTypes:"O\u010Dak\xE1va sa {allButLastType} alebo {lastType}",imageValidateSizeLabelFormatError:"Obr\xE1zok tohto typu nie je podporovan\xFD",imageValidateSizeLabelImageSizeTooSmall:"Obr\xE1zok je pr\xEDli\u0161 mal\xFD",imageValidateSizeLabelImageSizeTooBig:"Obr\xE1zok je pr\xEDli\u0161 ve\u013Ek\xFD",imageValidateSizeLabelExpectedMinSize:"Minim\xE1lny rozmer je {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maxim\xE1lny rozmer je {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Rozl\xED\u0161enie je pr\xEDli\u0161 mal\xE9",imageValidateSizeLabelImageResolutionTooHigh:"Rozli\u0161enie je pr\xEDli\u0161 ve\u013Ek\xE9",imageValidateSizeLabelExpectedMinResolution:"Minim\xE1lne rozl\xED\u0161enie je {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maxim\xE1lne rozl\xED\u0161enie je {maxResolution}"};var ar={labelIdle:'Drag och sl\xE4pp dina filer eller Bl\xE4ddra ',labelInvalidField:"F\xE4ltet inneh\xE5ller felaktiga filer",labelFileWaitingForSize:"V\xE4ntar p\xE5 storlek",labelFileSizeNotAvailable:"Storleken finns inte tillg\xE4nglig",labelFileLoading:"Laddar",labelFileLoadError:"Fel under laddning",labelFileProcessing:"Laddar upp",labelFileProcessingComplete:"Uppladdning klar",labelFileProcessingAborted:"Uppladdning avbruten",labelFileProcessingError:"Fel under uppladdning",labelFileProcessingRevertError:"Fel under \xE5terst\xE4llning",labelFileRemoveError:"Fel under borttagning",labelTapToCancel:"tryck f\xF6r att avbryta",labelTapToRetry:"tryck f\xF6r att f\xF6rs\xF6ka igen",labelTapToUndo:"tryck f\xF6r att \xE5ngra",labelButtonRemoveItem:"Tabort",labelButtonAbortItemLoad:"Avbryt",labelButtonRetryItemLoad:"F\xF6rs\xF6k igen",labelButtonAbortItemProcessing:"Avbryt",labelButtonUndoItemProcessing:"\xC5ngra",labelButtonRetryItemProcessing:"F\xF6rs\xF6k igen",labelButtonProcessItem:"Ladda upp",labelMaxFileSizeExceeded:"Filen \xE4r f\xF6r stor",labelMaxFileSize:"St\xF6rsta till\xE5tna filstorlek \xE4r {filesize}",labelMaxTotalFileSizeExceeded:"Maximal uppladdningsstorlek uppn\xE5d",labelMaxTotalFileSize:"Maximal uppladdningsstorlek \xE4r {filesize}",labelFileTypeNotAllowed:"Felaktig filtyp",fileValidateTypeLabelExpectedTypes:"Godk\xE4nda filtyper {allButLastType} eller {lastType}",imageValidateSizeLabelFormatError:"Bildtypen saknar st\xF6d",imageValidateSizeLabelImageSizeTooSmall:"Bilden \xE4r f\xF6r liten",imageValidateSizeLabelImageSizeTooBig:"Bilden \xE4r f\xF6r stor",imageValidateSizeLabelExpectedMinSize:"Minimal storlek \xE4r {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximal storlek \xE4r {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"Uppl\xF6sningen \xE4r f\xF6r l\xE5g",imageValidateSizeLabelImageResolutionTooHigh:"Uppl\xF6sningen \xE4r f\xF6r h\xF6g",imageValidateSizeLabelExpectedMinResolution:"Minsta till\xE5tna uppl\xF6sning \xE4r {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"H\xF6gsta till\xE5tna uppl\xF6sning \xE4r {maxResolution}"};var nr={labelIdle:'Dosyan\u0131z\u0131 S\xFCr\xFCkleyin & B\u0131rak\u0131n ya da Se\xE7in ',labelInvalidField:"Alan ge\xE7ersiz dosyalar i\xE7eriyor",labelFileWaitingForSize:"Boyut hesaplan\u0131yor",labelFileSizeNotAvailable:"Boyut mevcut de\u011Fil",labelFileLoading:"Y\xFCkleniyor",labelFileLoadError:"Y\xFCkleme s\u0131ras\u0131nda hata olu\u015Ftu",labelFileProcessing:"Y\xFCkleniyor",labelFileProcessingComplete:"Y\xFCkleme tamamland\u0131",labelFileProcessingAborted:"Y\xFCkleme iptal edildi",labelFileProcessingError:"Y\xFCklerken hata olu\u015Ftu",labelFileProcessingRevertError:"Geri \xE7ekerken hata olu\u015Ftu",labelFileRemoveError:"Kald\u0131r\u0131rken hata olu\u015Ftu",labelTapToCancel:"\u0130ptal etmek i\xE7in t\u0131klay\u0131n",labelTapToRetry:"Tekrar denemek i\xE7in t\u0131klay\u0131n",labelTapToUndo:"Geri almak i\xE7in t\u0131klay\u0131n",labelButtonRemoveItem:"Kald\u0131r",labelButtonAbortItemLoad:"\u0130ptal Et",labelButtonRetryItemLoad:"Tekrar dene",labelButtonAbortItemProcessing:"\u0130ptal et",labelButtonUndoItemProcessing:"Geri Al",labelButtonRetryItemProcessing:"Tekrar dene",labelButtonProcessItem:"Y\xFCkle",labelMaxFileSizeExceeded:"Dosya \xE7ok b\xFCy\xFCk",labelMaxFileSize:"En fazla dosya boyutu: {filesize}",labelMaxTotalFileSizeExceeded:"Maximum boyut a\u015F\u0131ld\u0131",labelMaxTotalFileSize:"Maximum dosya boyutu :{filesize}",labelFileTypeNotAllowed:"Ge\xE7ersiz dosya tipi",fileValidateTypeLabelExpectedTypes:"\u015Eu {allButLastType} ya da \u015Fu dosya olmas\u0131 gerekir: {lastType}",imageValidateSizeLabelFormatError:"Resim tipi desteklenmiyor",imageValidateSizeLabelImageSizeTooSmall:"Resim \xE7ok k\xFC\xE7\xFCk",imageValidateSizeLabelImageSizeTooBig:"Resim \xE7ok b\xFCy\xFCk",imageValidateSizeLabelExpectedMinSize:"Minimum boyut {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"Maximum boyut {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\xC7\xF6z\xFCn\xFCrl\xFCk \xE7ok d\xFC\u015F\xFCk",imageValidateSizeLabelImageResolutionTooHigh:"\xC7\xF6z\xFCn\xFCrl\xFCk \xE7ok y\xFCksek",imageValidateSizeLabelExpectedMinResolution:"Minimum \xE7\xF6z\xFCn\xFCrl\xFCk {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"Maximum \xE7\xF6z\xFCn\xFCrl\xFCk {maxResolution}"};var lr={labelIdle:'\u041F\u0435\u0440\u0435\u0442\u044F\u0433\u043D\u0456\u0442\u044C \u0444\u0430\u0439\u043B\u0438 \u0430\u0431\u043E \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044C ',labelInvalidField:"\u041F\u043E\u043B\u0435 \u043C\u0456\u0441\u0442\u0438\u0442\u044C \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u0456 \u0444\u0430\u0439\u043B\u0438",labelFileWaitingForSize:"\u0412\u043A\u0430\u0436\u0456\u0442\u044C \u0440\u043E\u0437\u043C\u0456\u0440",labelFileSizeNotAvailable:"\u0420\u043E\u0437\u043C\u0456\u0440 \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439",labelFileLoading:"\u041E\u0447\u0456\u043A\u0443\u0432\u0430\u043D\u043D\u044F",labelFileLoadError:"\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u043E\u0447\u0456\u043A\u0443\u0432\u0430\u043D\u043D\u0456",labelFileProcessing:"\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F",labelFileProcessingComplete:"\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E",labelFileProcessingAborted:"\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F \u0441\u043A\u0430\u0441\u043E\u0432\u0430\u043D\u043E",labelFileProcessingError:"\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u0456",labelFileProcessingRevertError:"\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0456",labelFileRemoveError:"\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u0432\u0438\u0434\u0430\u043B\u0435\u043D\u043D\u0456",labelTapToCancel:"\u0412\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438",labelTapToRetry:"\u041D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C, \u0449\u043E\u0431 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0438 \u0441\u043F\u0440\u043E\u0431\u0443",labelTapToUndo:"\u041D\u0430\u0442\u0438\u0441\u043D\u0456\u0442\u044C, \u0449\u043E\u0431 \u0432\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438 \u043E\u0441\u0442\u0430\u043D\u043D\u044E \u0434\u0456\u044E",labelButtonRemoveItem:"\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438",labelButtonAbortItemLoad:"\u0412\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438",labelButtonRetryItemLoad:"\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0438 \u0441\u043F\u0440\u043E\u0431\u0443",labelButtonAbortItemProcessing:"\u0412\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438",labelButtonUndoItemProcessing:"\u0412\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438 \u043E\u0441\u0442\u0430\u043D\u043D\u044E \u0434\u0456\u044E",labelButtonRetryItemProcessing:"\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0438 \u0441\u043F\u0440\u043E\u0431\u0443",labelButtonProcessItem:"\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F",labelMaxFileSizeExceeded:"\u0424\u0430\u0439\u043B \u0437\u0430\u043D\u0430\u0434\u0442\u043E \u0432\u0435\u043B\u0438\u043A\u0438\u0439",labelMaxFileSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440 \u0444\u0430\u0439\u043B\u0443: {filesize}",labelMaxTotalFileSizeExceeded:"\u041F\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043D\u043E \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0438\u0439 \u0437\u0430\u0433\u0430\u043B\u044C\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440",labelMaxTotalFileSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0438\u0439 \u0437\u0430\u0433\u0430\u043B\u044C\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440: {filesize}",labelFileTypeNotAllowed:"\u0424\u043E\u0440\u043C\u0430\u0442 \u0444\u0430\u0439\u043B\u0443 \u043D\u0435 \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u0454\u0442\u044C\u0441\u044F",fileValidateTypeLabelExpectedTypes:"\u041E\u0447\u0456\u043A\u0443\u0454\u0442\u044C\u0441\u044F {allButLastType} \u0430\u0431\u043E {lastType}",imageValidateSizeLabelFormatError:"\u0424\u043E\u0440\u043C\u0430\u0442 \u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u043D\u0435 \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u0454\u0442\u044C\u0441\u044F",imageValidateSizeLabelImageSizeTooSmall:"\u0417\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0430\u043D\u0430\u0434\u0442\u043E \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0435",imageValidateSizeLabelImageSizeTooBig:"\u0417\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0430\u043D\u0430\u0434\u0442\u043E \u0432\u0435\u043B\u0438\u043A\u0435",imageValidateSizeLabelExpectedMinSize:"\u041C\u0456\u043D\u0456\u043C\u0430\u043B\u044C\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0438\u0439 \u0440\u043E\u0437\u043C\u0456\u0440: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0420\u043E\u0437\u043C\u0456\u0440\u0438 \u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0430\u043D\u0430\u0434\u0442\u043E \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0456",imageValidateSizeLabelImageResolutionTooHigh:"\u0420\u043E\u0437\u043C\u0456\u0440\u0438 \u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u043D\u044F \u0437\u0430\u043D\u0430\u0434\u0442\u043E \u0432\u0435\u043B\u0438\u043A\u0456",imageValidateSizeLabelExpectedMinResolution:"\u041C\u0456\u043D\u0456\u043C\u0430\u043B\u044C\u043D\u0456 \u0440\u043E\u0437\u043C\u0456\u0440\u0438: {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0456 \u0440\u043E\u0437\u043C\u0456\u0440\u0438: {maxResolution}"};var or={labelIdle:'K\xE9o th\u1EA3 t\u1EC7p c\u1EE7a b\u1EA1n ho\u1EB7c T\xECm ki\u1EBFm ',labelInvalidField:"Tr\u01B0\u1EDDng ch\u1EE9a c\xE1c t\u1EC7p kh\xF4ng h\u1EE3p l\u1EC7",labelFileWaitingForSize:"\u0110ang ch\u1EDD k\xEDch th\u01B0\u1EDBc",labelFileSizeNotAvailable:"K\xEDch th\u01B0\u1EDBc kh\xF4ng c\xF3 s\u1EB5n",labelFileLoading:"\u0110ang t\u1EA3i",labelFileLoadError:"L\u1ED7i khi t\u1EA3i",labelFileProcessing:"\u0110ang t\u1EA3i l\xEAn",labelFileProcessingComplete:"T\u1EA3i l\xEAn th\xE0nh c\xF4ng",labelFileProcessingAborted:"\u0110\xE3 hu\u1EF7 t\u1EA3i l\xEAn",labelFileProcessingError:"L\u1ED7i khi t\u1EA3i l\xEAn",labelFileProcessingRevertError:"L\u1ED7i khi ho\xE0n nguy\xEAn",labelFileRemoveError:"L\u1ED7i khi x\xF3a",labelTapToCancel:"nh\u1EA5n \u0111\u1EC3 h\u1EE7y",labelTapToRetry:"nh\u1EA5n \u0111\u1EC3 th\u1EED l\u1EA1i",labelTapToUndo:"nh\u1EA5n \u0111\u1EC3 ho\xE0n t\xE1c",labelButtonRemoveItem:"Xo\xE1",labelButtonAbortItemLoad:"Hu\u1EF7 b\u1ECF",labelButtonRetryItemLoad:"Th\u1EED l\u1EA1i",labelButtonAbortItemProcessing:"H\u1EE7y b\u1ECF",labelButtonUndoItemProcessing:"Ho\xE0n t\xE1c",labelButtonRetryItemProcessing:"Th\u1EED l\u1EA1i",labelButtonProcessItem:"T\u1EA3i l\xEAn",labelMaxFileSizeExceeded:"T\u1EADp tin qu\xE1 l\u1EDBn",labelMaxFileSize:"K\xEDch th\u01B0\u1EDBc t\u1EC7p t\u1ED1i \u0111a l\xE0 {filesize}",labelMaxTotalFileSizeExceeded:"\u0110\xE3 v\u01B0\u1EE3t qu\xE1 t\u1ED5ng k\xEDch th\u01B0\u1EDBc t\u1ED1i \u0111a",labelMaxTotalFileSize:"T\u1ED5ng k\xEDch th\u01B0\u1EDBc t\u1EC7p t\u1ED1i \u0111a l\xE0 {filesize}",labelFileTypeNotAllowed:"T\u1EC7p thu\u1ED9c lo\u1EA1i kh\xF4ng h\u1EE3p l\u1EC7",fileValidateTypeLabelExpectedTypes:"Ki\u1EC3u t\u1EC7p h\u1EE3p l\u1EC7 l\xE0 {allButLastType} ho\u1EB7c {lastType}",imageValidateSizeLabelFormatError:"Lo\u1EA1i h\xECnh \u1EA3nh kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3",imageValidateSizeLabelImageSizeTooSmall:"H\xECnh \u1EA3nh qu\xE1 nh\u1ECF",imageValidateSizeLabelImageSizeTooBig:"H\xECnh \u1EA3nh qu\xE1 l\u1EDBn",imageValidateSizeLabelExpectedMinSize:"K\xEDch th\u01B0\u1EDBc t\u1ED1i thi\u1EC3u l\xE0 {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"K\xEDch th\u01B0\u1EDBc t\u1ED1i \u0111a l\xE0 {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u0110\u1ED9 ph\xE2n gi\u1EA3i qu\xE1 th\u1EA5p",imageValidateSizeLabelImageResolutionTooHigh:"\u0110\u1ED9 ph\xE2n gi\u1EA3i qu\xE1 cao",imageValidateSizeLabelExpectedMinResolution:"\u0110\u1ED9 ph\xE2n gi\u1EA3i t\u1ED1i thi\u1EC3u l\xE0 {minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u0110\u1ED9 ph\xE2n gi\u1EA3i t\u1ED1i \u0111a l\xE0 {maxResolution}"};var rr={labelIdle:'\u62D6\u653E\u6587\u4EF6\uFF0C\u6216\u8005 \u6D4F\u89C8 ',labelInvalidField:"\u5B57\u6BB5\u5305\u542B\u65E0\u6548\u6587\u4EF6",labelFileWaitingForSize:"\u8BA1\u7B97\u6587\u4EF6\u5927\u5C0F",labelFileSizeNotAvailable:"\u6587\u4EF6\u5927\u5C0F\u4E0D\u53EF\u7528",labelFileLoading:"\u52A0\u8F7D",labelFileLoadError:"\u52A0\u8F7D\u9519\u8BEF",labelFileProcessing:"\u4E0A\u4F20",labelFileProcessingComplete:"\u5DF2\u4E0A\u4F20",labelFileProcessingAborted:"\u4E0A\u4F20\u5DF2\u53D6\u6D88",labelFileProcessingError:"\u4E0A\u4F20\u51FA\u9519",labelFileProcessingRevertError:"\u8FD8\u539F\u51FA\u9519",labelFileRemoveError:"\u5220\u9664\u51FA\u9519",labelTapToCancel:"\u70B9\u51FB\u53D6\u6D88",labelTapToRetry:"\u70B9\u51FB\u91CD\u8BD5",labelTapToUndo:"\u70B9\u51FB\u64A4\u6D88",labelButtonRemoveItem:"\u5220\u9664",labelButtonAbortItemLoad:"\u4E2D\u6B62",labelButtonRetryItemLoad:"\u91CD\u8BD5",labelButtonAbortItemProcessing:"\u53D6\u6D88",labelButtonUndoItemProcessing:"\u64A4\u6D88",labelButtonRetryItemProcessing:"\u91CD\u8BD5",labelButtonProcessItem:"\u4E0A\u4F20",labelMaxFileSizeExceeded:"\u6587\u4EF6\u592A\u5927",labelMaxFileSize:"\u6700\u5927\u503C: {filesize}",labelMaxTotalFileSizeExceeded:"\u8D85\u8FC7\u6700\u5927\u6587\u4EF6\u5927\u5C0F",labelMaxTotalFileSize:"\u6700\u5927\u6587\u4EF6\u5927\u5C0F\uFF1A{filesize}",labelFileTypeNotAllowed:"\u6587\u4EF6\u7C7B\u578B\u65E0\u6548",fileValidateTypeLabelExpectedTypes:"\u5E94\u4E3A {allButLastType} \u6216 {lastType}",imageValidateSizeLabelFormatError:"\u4E0D\u652F\u6301\u56FE\u50CF\u7C7B\u578B",imageValidateSizeLabelImageSizeTooSmall:"\u56FE\u50CF\u592A\u5C0F",imageValidateSizeLabelImageSizeTooBig:"\u56FE\u50CF\u592A\u5927",imageValidateSizeLabelExpectedMinSize:"\u6700\u5C0F\u503C: {minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u6700\u5927\u503C: {maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u5206\u8FA8\u7387\u592A\u4F4E",imageValidateSizeLabelImageResolutionTooHigh:"\u5206\u8FA8\u7387\u592A\u9AD8",imageValidateSizeLabelExpectedMinResolution:"\u6700\u5C0F\u5206\u8FA8\u7387\uFF1A{minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u6700\u5927\u5206\u8FA8\u7387\uFF1A{maxResolution}"};var sr={labelIdle:'\u62D6\u653E\u6A94\u6848\uFF0C\u6216\u8005 \u700F\u89BD ',labelInvalidField:"\u4E0D\u652F\u63F4\u6B64\u6A94\u6848",labelFileWaitingForSize:"\u6B63\u5728\u8A08\u7B97\u6A94\u6848\u5927\u5C0F",labelFileSizeNotAvailable:"\u6A94\u6848\u5927\u5C0F\u4E0D\u7B26",labelFileLoading:"\u8B80\u53D6\u4E2D",labelFileLoadError:"\u8B80\u53D6\u932F\u8AA4",labelFileProcessing:"\u4E0A\u50B3",labelFileProcessingComplete:"\u5DF2\u4E0A\u50B3",labelFileProcessingAborted:"\u4E0A\u50B3\u5DF2\u53D6\u6D88",labelFileProcessingError:"\u4E0A\u50B3\u767C\u751F\u932F\u8AA4",labelFileProcessingRevertError:"\u9084\u539F\u932F\u8AA4",labelFileRemoveError:"\u522A\u9664\u932F\u8AA4",labelTapToCancel:"\u9EDE\u64CA\u53D6\u6D88",labelTapToRetry:"\u9EDE\u64CA\u91CD\u8A66",labelTapToUndo:"\u9EDE\u64CA\u9084\u539F",labelButtonRemoveItem:"\u522A\u9664",labelButtonAbortItemLoad:"\u505C\u6B62",labelButtonRetryItemLoad:"\u91CD\u8A66",labelButtonAbortItemProcessing:"\u53D6\u6D88",labelButtonUndoItemProcessing:"\u53D6\u6D88",labelButtonRetryItemProcessing:"\u91CD\u8A66",labelButtonProcessItem:"\u4E0A\u50B3",labelMaxFileSizeExceeded:"\u6A94\u6848\u904E\u5927",labelMaxFileSize:"\u6700\u5927\u503C\uFF1A{filesize}",labelMaxTotalFileSizeExceeded:"\u8D85\u904E\u6700\u5927\u53EF\u4E0A\u50B3\u5927\u5C0F",labelMaxTotalFileSize:"\u6700\u5927\u53EF\u4E0A\u50B3\u5927\u5C0F\uFF1A{filesize}",labelFileTypeNotAllowed:"\u4E0D\u652F\u63F4\u6B64\u985E\u578B\u6A94\u6848",fileValidateTypeLabelExpectedTypes:"\u61C9\u70BA {allButLastType} \u6216 {lastType}",imageValidateSizeLabelFormatError:"\u4E0D\u652F\u6301\u6B64\u985E\u5716\u7247\u985E\u578B",imageValidateSizeLabelImageSizeTooSmall:"\u5716\u7247\u904E\u5C0F",imageValidateSizeLabelImageSizeTooBig:"\u5716\u7247\u904E\u5927",imageValidateSizeLabelExpectedMinSize:"\u6700\u5C0F\u5C3A\u5BF8\uFF1A{minWidth} \xD7 {minHeight}",imageValidateSizeLabelExpectedMaxSize:"\u6700\u5927\u5C3A\u5BF8\uFF1A{maxWidth} \xD7 {maxHeight}",imageValidateSizeLabelImageResolutionTooLow:"\u89E3\u6790\u5EA6\u904E\u4F4E",imageValidateSizeLabelImageResolutionTooHigh:"\u89E3\u6790\u5EA6\u904E\u9AD8",imageValidateSizeLabelExpectedMinResolution:"\u6700\u4F4E\u89E3\u6790\u5EA6\uFF1A{minResolution}",imageValidateSizeLabelExpectedMaxResolution:"\u6700\u9AD8\u89E3\u6790\u5EA6\uFF1A{maxResolution}"};ve(Wl);ve(jl);ve($l);ve(Kl);ve(eo);ve(mo);ve(go);ve(_o);ve(Pa);window.FilePond=la;function Sg({acceptedFileTypes:e,imageEditorEmptyFillColor:t,imageEditorMode:i,imageEditorViewportHeight:a,imageEditorViewportWidth:n,deleteUploadedFileUsing:l,isDeletable:o,isDisabled:r,getUploadedFilesUsing:s,imageCropAspectRatio:p,imagePreviewHeight:c,imageResizeMode:d,imageResizeTargetHeight:m,imageResizeTargetWidth:u,imageResizeUpscale:g,isAvatar:f,hasImageEditor:h,hasCircleCropper:I,canEditSvgs:b,isSvgEditingConfirmed:T,confirmSvgEditingMessage:v,disabledSvgEditingMessage:y,isDownloadable:E,isMultiple:_,isOpenable:x,isPasteable:R,isPreviewable:z,isReorderable:P,itemPanelAspectRatio:A,loadingIndicatorPosition:B,locale:w,maxFiles:O,maxSize:S,minSize:L,maxParallelUploads:D,mimeTypeMap:F,panelAspectRatio:G,panelLayout:C,placeholder:q,removeUploadedFileButtonPosition:X,removeUploadedFileUsing:K,reorderUploadedFilesUsing:pe,shouldAppendFiles:k,shouldOrientImageFromExif:H,shouldTransformImage:Y,state:oe,uploadButtonPosition:ee,uploadingMessage:dt,uploadProgressIndicatorPosition:dr,uploadUsing:pr}){return{fileKeyIndex:{},pond:null,shouldUpdateState:!0,state:oe,lastState:null,error:null,uploadedFileIndex:{},isEditorOpen:!1,editingFile:{},currentRatio:"",editor:{},init:async function(){Ft(cr[w]??cr.en),this.pond=gt(this.$refs.input,{acceptedFileTypes:e,allowImageExifOrientation:H,allowPaste:R,allowRemove:o,allowReorder:P,allowImagePreview:z,allowVideoPreview:z,allowAudioPreview:z,allowImageTransform:Y,credits:!1,files:await this.getFiles(),imageCropAspectRatio:p,imagePreviewHeight:c,imageResizeTargetHeight:m,imageResizeTargetWidth:u,imageResizeMode:d,imageResizeUpscale:g,imageTransformOutputStripImageHead:!1,itemInsertLocation:k?"after":"before",...q&&{labelIdle:q},maxFiles:O,maxFileSize:S,minFileSize:L,...D&&{maxParallelUploads:D},styleButtonProcessItemPosition:ee,styleButtonRemoveItemPosition:X,styleItemPanelAspectRatio:A,styleLoadIndicatorPosition:B,stylePanelAspectRatio:G,stylePanelLayout:C,styleProgressIndicatorPosition:dr,server:{load:async(N,W)=>{let Q=await(await fetch(N,{cache:"no-store"})).blob();W(Q)},process:(N,W,$,Q,Ge,Me)=>{this.shouldUpdateState=!1;let Kt=("10000000-1000-4000-8000"+-1e11).replace(/[018]/g,Qt=>(Qt^crypto.getRandomValues(new Uint8Array(1))[0]&15>>Qt/4).toString(16));pr(Kt,W,Qt=>{this.shouldUpdateState=!0,Q(Qt)},Ge,Me)},remove:async(N,W)=>{let $=this.uploadedFileIndex[N]??null;$&&(await l($),W())},revert:async(N,W)=>{await K(N),W()}},allowImageEdit:h,imageEditEditor:{open:N=>this.loadEditor(N),onconfirm:()=>{},oncancel:()=>this.closeEditor(),onclose:()=>this.closeEditor()},fileValidateTypeDetectType:(N,W)=>new Promise(($,Q)=>{let Ge=N.name.split(".").pop().toLowerCase(),Me=F[Ge]||W||Gl.getType(Ge);Me?$(Me):Q()})}),this.$watch("state",async()=>{if(this.pond&&this.shouldUpdateState&&this.state!==void 0){if(this.state!==null&&Object.values(this.state).filter(N=>N.startsWith("livewire-file:")).length){this.lastState=null;return}JSON.stringify(this.state)!==this.lastState&&(this.lastState=JSON.stringify(this.state),this.pond.files=await this.getFiles())}}),this.pond.on("reorderfiles",async N=>{let W=N.map($=>$.source instanceof File?$.serverId:this.uploadedFileIndex[$.source]??null).filter($=>$);await pe(k?W:W.reverse())}),this.pond.on("initfile",async N=>{E&&(f||this.insertDownloadLink(N))}),this.pond.on("initfile",async N=>{x&&(f||this.insertOpenLink(N))}),this.pond.on("addfilestart",async N=>{N.status===Et.PROCESSING_QUEUED&&this.dispatchFormEvent("form-processing-started",{message:dt})});let V=async()=>{this.pond.getFiles().filter(N=>N.status===Et.PROCESSING||N.status===Et.PROCESSING_QUEUED).length||this.dispatchFormEvent("form-processing-finished")};this.pond.on("processfile",V),this.pond.on("processfileabort",V),this.pond.on("processfilerevert",V),C==="compact circle"&&(this.pond.on("error",N=>{this.error=`${N.main}: ${N.sub}`.replace("Expects or","Expects")}),this.pond.on("removefile",()=>this.error=null))},destroy:function(){this.destroyEditor(),ft(this.$refs.input),this.pond=null},dispatchFormEvent:function(V,N={}){this.$el.closest("form")?.dispatchEvent(new CustomEvent(V,{composed:!0,cancelable:!0,detail:N}))},getUploadedFiles:async function(){let V=await s();this.fileKeyIndex=V??{},this.uploadedFileIndex=Object.entries(this.fileKeyIndex).filter(([N,W])=>W?.url).reduce((N,[W,$])=>(N[$.url]=W,N),{})},getFiles:async function(){await this.getUploadedFiles();let V=[];for(let N of Object.values(this.fileKeyIndex))N&&V.push({source:N.url,options:{type:"local",...!N.type||z&&(/^audio/.test(N.type)||/^image/.test(N.type)||/^video/.test(N.type))?{}:{file:{name:N.name,size:N.size,type:N.type}}}});return k?V:V.reverse()},insertDownloadLink:function(V){if(V.origin!==Ct.LOCAL)return;let N=this.getDownloadLink(V);N&&document.getElementById(`filepond--item-${V.id}`).querySelector(".filepond--file-info-main").prepend(N)},insertOpenLink:function(V){if(V.origin!==Ct.LOCAL)return;let N=this.getOpenLink(V);N&&document.getElementById(`filepond--item-${V.id}`).querySelector(".filepond--file-info-main").prepend(N)},getDownloadLink:function(V){let N=V.source;if(!N)return;let W=document.createElement("a");return W.className="filepond--download-icon",W.href=N,W.download=V.file.name,W},getOpenLink:function(V){let N=V.source;if(!N)return;let W=document.createElement("a");return W.className="filepond--open-icon",W.href=N,W.target="_blank",W},initEditor:function(){r||h&&(this.editor=new ya(this.$refs.editor,{aspectRatio:n/a,autoCropArea:1,center:!0,crop:V=>{this.$refs.xPositionInput.value=Math.round(V.detail.x),this.$refs.yPositionInput.value=Math.round(V.detail.y),this.$refs.heightInput.value=Math.round(V.detail.height),this.$refs.widthInput.value=Math.round(V.detail.width),this.$refs.rotationInput.value=V.detail.rotate},cropBoxResizable:!0,guides:!0,highlight:!0,responsive:!0,toggleDragModeOnDblclick:!0,viewMode:i,wheelZoomRatio:.02}))},closeEditor:function(){this.editingFile={},this.isEditorOpen=!1,this.destroyEditor()},fixImageDimensions:function(V,N){if(V.type!=="image/svg+xml")return N(V);let W=new FileReader;W.onload=$=>{let Q=new DOMParser().parseFromString($.target.result,"image/svg+xml")?.querySelector("svg");if(!Q)return N(V);let Ge=["viewBox","ViewBox","viewbox"].find(Kt=>Q.hasAttribute(Kt));if(!Ge)return N(V);let Me=Q.getAttribute(Ge).split(" ");return!Me||Me.length!==4?N(V):(Q.setAttribute("width",parseFloat(Me[2])+"pt"),Q.setAttribute("height",parseFloat(Me[3])+"pt"),N(new File([new Blob([new XMLSerializer().serializeToString(Q)],{type:"image/svg+xml"})],V.name,{type:"image/svg+xml",_relativePath:""})))},W.readAsText(V)},loadEditor:function(V){if(r||!h||!V)return;let N=V.type==="image/svg+xml";if(!b&&N){alert(y);return}T&&N&&!confirm(v)||this.fixImageDimensions(V,W=>{this.editingFile=W,this.initEditor();let $=new FileReader;$.onload=Q=>{this.isEditorOpen=!0,setTimeout(()=>this.editor.replace(Q.target.result),200)},$.readAsDataURL(V)})},getRoundedCanvas:function(V){let N=V.width,W=V.height,$=document.createElement("canvas");$.width=N,$.height=W;let Q=$.getContext("2d");return Q.imageSmoothingEnabled=!0,Q.drawImage(V,0,0,N,W),Q.globalCompositeOperation="destination-in",Q.beginPath(),Q.ellipse(N/2,W/2,N/2,W/2,0,0,2*Math.PI),Q.fill(),$},saveEditor:function(){if(r||!h)return;let V=this.editor.getCroppedCanvas({fillColor:t??"transparent",height:m,imageSmoothingEnabled:!0,imageSmoothingQuality:"high",width:u});I&&(V=this.getRoundedCanvas(V)),V.toBlob(N=>{_&&this.pond.removeFile(this.pond.getFiles().find(W=>W.filename===this.editingFile.name)?.id,{revert:!0}),this.$nextTick(()=>{this.shouldUpdateState=!1;let W=this.editingFile.name.slice(0,this.editingFile.name.lastIndexOf(".")),$=this.editingFile.name.split(".").pop();$==="svg"&&($="png");let Q=/-v(\d+)/;Q.test(W)?W=W.replace(Q,(Ge,Me)=>`-v${Number(Me)+1}`):W+="-v1",this.pond.addFile(new File([N],`${W}.${$}`,{type:this.editingFile.type==="image/svg+xml"||I?"image/png":this.editingFile.type,lastModified:new Date().getTime()})).then(()=>{this.closeEditor()}).catch(()=>{this.closeEditor()})})},I?"image/png":this.editingFile.type)},destroyEditor:function(){this.editor&&typeof this.editor.destroy=="function"&&this.editor.destroy(),this.editor=null}}}var cr={am:wo,ar:Lo,az:Mo,ca:Ao,ckb:Po,cs:zo,da:Oo,de:Fo,el:Do,en:Co,es:Bo,fa:No,fi:ko,fr:Vo,he:Go,hr:Uo,hu:Wo,id:Ho,it:jo,ja:Yo,km:qo,ko:$o,lt:Xo,lv:Ko,nl:Qo,no:Zo,pl:Jo,pt_BR:_i,pt_PT:_i,ro:er,ru:tr,sk:ir,sv:ar,tr:nr,uk:lr,vi:or,zh_CN:rr,zh_TW:sr};export{Sg as default}; -/*! Bundled license information: - -filepond/dist/filepond.esm.js: - (*! - * FilePond 4.32.8 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -cropperjs/dist/cropper.esm.js: - (*! - * Cropper.js v1.6.2 - * https://fengyuanchen.github.io/cropperjs - * - * Copyright 2015-present Chen Fengyuan - * Released under the MIT license - * - * Date: 2024-04-21T07:43:05.335Z - *) - -filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.esm.js: - (*! - * FilePondPluginFileValidateSize 2.2.8 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.esm.js: - (*! - * FilePondPluginFileValidateType 1.2.9 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-crop/dist/filepond-plugin-image-crop.esm.js: - (*! - * FilePondPluginImageCrop 2.0.6 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-edit/dist/filepond-plugin-image-edit.esm.js: - (*! - * FilePondPluginImageEdit 1.6.3 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-exif-orientation/dist/filepond-plugin-image-exif-orientation.esm.js: - (*! - * FilePondPluginImageExifOrientation 1.0.11 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-preview/dist/filepond-plugin-image-preview.esm.js: - (*! - * FilePondPluginImagePreview 4.6.12 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-resize/dist/filepond-plugin-image-resize.esm.js: - (*! - * FilePondPluginImageResize 2.0.10 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-image-transform/dist/filepond-plugin-image-transform.esm.js: - (*! - * FilePondPluginImageTransform 3.8.7 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit https://pqina.nl/filepond/ for details. - *) - -filepond-plugin-media-preview/dist/filepond-plugin-media-preview.esm.js: - (*! - * FilePondPluginMediaPreview 1.0.11 - * Licensed under MIT, https://opensource.org/licenses/MIT/ - * Please visit undefined for details. - *) -*/ diff --git a/public/js/filament/forms/components/key-value.js b/public/js/filament/forms/components/key-value.js deleted file mode 100644 index 9c847c0..0000000 --- a/public/js/filament/forms/components/key-value.js +++ /dev/null @@ -1 +0,0 @@ -function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; diff --git a/public/js/filament/forms/components/markdown-editor.js b/public/js/filament/forms/components/markdown-editor.js deleted file mode 100644 index 2a70bf9..0000000 --- a/public/js/filament/forms/components/markdown-editor.js +++ /dev/null @@ -1,51 +0,0 @@ -var ss=Object.defineProperty;var Sd=Object.getOwnPropertyDescriptor;var Td=Object.getOwnPropertyNames;var Ld=Object.prototype.hasOwnProperty;var Cd=(o,p)=>()=>(o&&(p=o(o=0)),p);var Ke=(o,p)=>()=>(p||o((p={exports:{}}).exports,p),p.exports);var Ed=(o,p,v,C)=>{if(p&&typeof p=="object"||typeof p=="function")for(let b of Td(p))!Ld.call(o,b)&&b!==v&&ss(o,b,{get:()=>p[b],enumerable:!(C=Sd(p,b))||C.enumerable});return o};var zd=o=>Ed(ss({},"__esModule",{value:!0}),o);var We=Ke((Yo,Qo)=>{(function(o,p){typeof Yo=="object"&&typeof Qo<"u"?Qo.exports=p():typeof define=="function"&&define.amd?define(p):(o=o||self,o.CodeMirror=p())})(Yo,function(){"use strict";var o=navigator.userAgent,p=navigator.platform,v=/gecko\/\d/i.test(o),C=/MSIE \d/.test(o),b=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(o),S=/Edge\/(\d+)/.exec(o),s=C||b||S,h=s&&(C?document.documentMode||6:+(S||b)[1]),g=!S&&/WebKit\//.test(o),T=g&&/Qt\/\d+\.\d+/.test(o),y=!S&&/Chrome\/(\d+)/.exec(o),c=y&&+y[1],d=/Opera\//.test(o),k=/Apple Computer/.test(navigator.vendor),z=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(o),M=/PhantomJS/.test(o),w=k&&(/Mobile\/\w+/.test(o)||navigator.maxTouchPoints>2),W=/Android/.test(o),E=w||W||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(o),O=w||/Mac/.test(p),G=/\bCrOS\b/.test(o),J=/win/i.test(p),re=d&&o.match(/Version\/(\d*\.\d*)/);re&&(re=Number(re[1])),re&&re>=15&&(d=!1,g=!0);var q=O&&(T||d&&(re==null||re<12.11)),I=v||s&&h>=9;function D(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var Q=function(e,t){var n=e.className,r=D(t).exec(n);if(r){var i=n.slice(r.index+r[0].length);e.className=n.slice(0,r.index)+(i?r[1]+i:"")}};function R(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function V(e,t){return R(e).appendChild(t)}function x(e,t,n,r){var i=document.createElement(e);if(n&&(i.className=n),r&&(i.style.cssText=r),typeof t=="string")i.appendChild(document.createTextNode(t));else if(t)for(var a=0;a=t)return l+(t-a);l+=u-a,l+=n-l%n,a=u+1}}var qe=function(){this.id=null,this.f=null,this.time=0,this.handler=Ee(this.onTimeout,this)};qe.prototype.onTimeout=function(e){e.id=0,e.time<=+new Date?e.f():setTimeout(e.handler,e.time-+new Date)},qe.prototype.set=function(e,t){this.f=t;var n=+new Date+e;(!this.id||n=t)return r+Math.min(l,t-i);if(i+=a-r,i+=n-i%n,r=a+1,i>=t)return r}}var U=[""];function Z(e){for(;U.length<=e;)U.push(ce(U)+" ");return U[e]}function ce(e){return e[e.length-1]}function Be(e,t){for(var n=[],r=0;r"\x80"&&(e.toUpperCase()!=e.toLowerCase()||Ue.test(e))}function Me(e,t){return t?t.source.indexOf("\\w")>-1&&we(e)?!0:t.test(e):we(e)}function Le(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var $=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function B(e){return e.charCodeAt(0)>=768&&$.test(e)}function se(e,t,n){for(;(n<0?t>0:tn?-1:1;;){if(t==n)return t;var i=(t+n)/2,a=r<0?Math.ceil(i):Math.floor(i);if(a==t)return e(a)?t:n;e(a)?n=a:t=a+r}}function nt(e,t,n,r){if(!e)return r(t,n,"ltr",0);for(var i=!1,a=0;at||t==n&&l.to==t)&&(r(Math.max(l.from,t),Math.min(l.to,n),l.level==1?"rtl":"ltr",a),i=!0)}i||r(t,n,"ltr")}var dt=null;function Pt(e,t,n){var r;dt=null;for(var i=0;it)return i;a.to==t&&(a.from!=a.to&&n=="before"?r=i:dt=i),a.from==t&&(a.from!=a.to&&n!="before"?r=i:dt=i)}return r??dt}var Ft=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function n(m){return m<=247?e.charAt(m):1424<=m&&m<=1524?"R":1536<=m&&m<=1785?t.charAt(m-1536):1774<=m&&m<=2220?"r":8192<=m&&m<=8203?"w":m==8204?"b":"L"}var r=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,i=/[stwN]/,a=/[LRr]/,l=/[Lb1n]/,u=/[1n]/;function f(m,A,j){this.level=m,this.from=A,this.to=j}return function(m,A){var j=A=="ltr"?"L":"R";if(m.length==0||A=="ltr"&&!r.test(m))return!1;for(var ee=m.length,Y=[],ie=0;ie-1&&(r[t]=i.slice(0,a).concat(i.slice(a+1)))}}}function it(e,t){var n=nr(e,t);if(n.length)for(var r=Array.prototype.slice.call(arguments,2),i=0;i0}function Wt(e){e.prototype.on=function(t,n){Ie(this,t,n)},e.prototype.off=function(t,n){_t(this,t,n)}}function kt(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function Hr(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function Ct(e){return e.defaultPrevented!=null?e.defaultPrevented:e.returnValue==!1}function dr(e){kt(e),Hr(e)}function yn(e){return e.target||e.srcElement}function Ut(e){var t=e.which;return t==null&&(e.button&1?t=1:e.button&2?t=3:e.button&4&&(t=2)),O&&e.ctrlKey&&t==1&&(t=3),t}var eo=function(){if(s&&h<9)return!1;var e=x("div");return"draggable"in e||"dragDrop"in e}(),Br;function ei(e){if(Br==null){var t=x("span","\u200B");V(e,x("span",[t,document.createTextNode("x")])),e.firstChild.offsetHeight!=0&&(Br=t.offsetWidth<=1&&t.offsetHeight>2&&!(s&&h<8))}var n=Br?x("span","\u200B"):x("span","\xA0",null,"display: inline-block; width: 1px; margin-right: -1px");return n.setAttribute("cm-text",""),n}var xn;function pr(e){if(xn!=null)return xn;var t=V(e,document.createTextNode("A\u062EA")),n=X(t,0,1).getBoundingClientRect(),r=X(t,1,2).getBoundingClientRect();return R(e),!n||n.left==n.right?!1:xn=r.right-n.right<3}var Bt=` - -b`.split(/\n/).length!=3?function(e){for(var t=0,n=[],r=e.length;t<=r;){var i=e.indexOf(` -`,t);i==-1&&(i=e.length);var a=e.slice(t,e.charAt(i-1)=="\r"?i-1:i),l=a.indexOf("\r");l!=-1?(n.push(a.slice(0,l)),t+=l+1):(n.push(a),t=i+1)}return n}:function(e){return e.split(/\r\n?|\n/)},hr=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch{return!1}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch{}return!t||t.parentElement()!=e?!1:t.compareEndPoints("StartToEnd",t)!=0},ti=function(){var e=x("div");return"oncopy"in e?!0:(e.setAttribute("oncopy","return;"),typeof e.oncopy=="function")}(),$t=null;function to(e){if($t!=null)return $t;var t=V(e,x("span","x")),n=t.getBoundingClientRect(),r=X(t,0,1).getBoundingClientRect();return $t=Math.abs(n.left-r.left)>1}var Wr={},Kt={};function Gt(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),Wr[e]=t}function Cr(e,t){Kt[e]=t}function Ur(e){if(typeof e=="string"&&Kt.hasOwnProperty(e))e=Kt[e];else if(e&&typeof e.name=="string"&&Kt.hasOwnProperty(e.name)){var t=Kt[e.name];typeof t=="string"&&(t={name:t}),e=oe(t,e),e.name=t.name}else{if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return Ur("application/xml");if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return Ur("application/json")}return typeof e=="string"?{name:e}:e||{name:"null"}}function $r(e,t){t=Ur(t);var n=Wr[t.name];if(!n)return $r(e,"text/plain");var r=n(e,t);if(gr.hasOwnProperty(t.name)){var i=gr[t.name];for(var a in i)i.hasOwnProperty(a)&&(r.hasOwnProperty(a)&&(r["_"+a]=r[a]),r[a]=i[a])}if(r.name=t.name,t.helperType&&(r.helperType=t.helperType),t.modeProps)for(var l in t.modeProps)r[l]=t.modeProps[l];return r}var gr={};function Kr(e,t){var n=gr.hasOwnProperty(e)?gr[e]:gr[e]={};ge(t,n)}function Vt(e,t){if(t===!0)return t;if(e.copyState)return e.copyState(t);var n={};for(var r in t){var i=t[r];i instanceof Array&&(i=i.concat([])),n[r]=i}return n}function _n(e,t){for(var n;e.innerMode&&(n=e.innerMode(t),!(!n||n.mode==e));)t=n.state,e=n.mode;return n||{mode:e,state:t}}function Gr(e,t,n){return e.startState?e.startState(t,n):!0}var at=function(e,t,n){this.pos=this.start=0,this.string=e,this.tabSize=t||8,this.lastColumnPos=this.lastColumnValue=0,this.lineStart=0,this.lineOracle=n};at.prototype.eol=function(){return this.pos>=this.string.length},at.prototype.sol=function(){return this.pos==this.lineStart},at.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},at.prototype.next=function(){if(this.post},at.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},at.prototype.skipToEnd=function(){this.pos=this.string.length},at.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},at.prototype.backUp=function(e){this.pos-=e},at.prototype.column=function(){return this.lastColumnPos0?null:(a&&t!==!1&&(this.pos+=a[0].length),a)}},at.prototype.current=function(){return this.string.slice(this.start,this.pos)},at.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}},at.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)},at.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};function Ae(e,t){if(t-=e.first,t<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],a=i.chunkSize();if(t=e.first&&tn?ne(n,Ae(e,n).text.length):Sc(t,Ae(e,t.line).text.length)}function Sc(e,t){var n=e.ch;return n==null||n>t?ne(e.line,t):n<0?ne(e.line,0):e}function ca(e,t){for(var n=[],r=0;rthis.maxLookAhead&&(this.maxLookAhead=e),t},Jt.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},Jt.prototype.nextLine=function(){this.line++,this.maxLookAhead>0&&this.maxLookAhead--},Jt.fromSaved=function(e,t,n){return t instanceof ri?new Jt(e,Vt(e.mode,t.state),n,t.lookAhead):new Jt(e,Vt(e.mode,t),n)},Jt.prototype.save=function(e){var t=e!==!1?Vt(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new ri(t,this.maxLookAhead):t};function fa(e,t,n,r){var i=[e.state.modeGen],a={};va(e,t.text,e.doc.mode,n,function(m,A){return i.push(m,A)},a,r);for(var l=n.state,u=function(m){n.baseTokens=i;var A=e.state.overlays[m],j=1,ee=0;n.state=!0,va(e,t.text,A.mode,n,function(Y,ie){for(var ue=j;eeY&&i.splice(j,1,Y,i[j+1],me),j+=2,ee=Math.min(Y,me)}if(ie)if(A.opaque)i.splice(ue,j-ue,Y,"overlay "+ie),j=ue+2;else for(;uee.options.maxHighlightLength&&Vt(e.doc.mode,r.state),a=fa(e,t,r);i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=a.styles,a.classes?t.styleClasses=a.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function wn(e,t,n){var r=e.doc,i=e.display;if(!r.mode.startState)return new Jt(r,!0,t);var a=Tc(e,t,n),l=a>r.first&&Ae(r,a-1).stateAfter,u=l?Jt.fromSaved(r,l,a):new Jt(r,Gr(r.mode),a);return r.iter(a,t,function(f){ro(e,f.text,u);var m=u.line;f.stateAfter=m==t-1||m%5==0||m>=i.viewFrom&&mt.start)return a}throw new Error("Mode "+e.name+" failed to advance stream.")}var ha=function(e,t,n){this.start=e.start,this.end=e.pos,this.string=e.current(),this.type=t||null,this.state=n};function ga(e,t,n,r){var i=e.doc,a=i.mode,l;t=Re(i,t);var u=Ae(i,t.line),f=wn(e,t.line,n),m=new at(u.text,e.options.tabSize,f),A;for(r&&(A=[]);(r||m.pose.options.maxHighlightLength?(u=!1,l&&ro(e,t,r,A.pos),A.pos=t.length,j=null):j=ma(no(n,A,r.state,ee),a),ee){var Y=ee[0].name;Y&&(j="m-"+(j?Y+" "+j:Y))}if(!u||m!=j){for(;fl;--u){if(u<=a.first)return a.first;var f=Ae(a,u-1),m=f.stateAfter;if(m&&(!n||u+(m instanceof ri?m.lookAhead:0)<=a.modeFrontier))return u;var A=Oe(f.text,null,e.options.tabSize);(i==null||r>A)&&(i=u-1,r=A)}return i}function Lc(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiern;r--){var i=Ae(e,r).stateAfter;if(i&&(!(i instanceof ri)||r+i.lookAhead=t:a.to>t);(r||(r=[])).push(new ni(l,a.from,f?null:a.to))}}return r}function Dc(e,t,n){var r;if(e)for(var i=0;i=t:a.to>t);if(u||a.from==t&&l.type=="bookmark"&&(!n||a.marker.insertLeft)){var f=a.from==null||(l.inclusiveLeft?a.from<=t:a.from0&&u)for(var Ce=0;Ce0)){var A=[f,1],j=ye(m.from,u.from),ee=ye(m.to,u.to);(j<0||!l.inclusiveLeft&&!j)&&A.push({from:m.from,to:u.from}),(ee>0||!l.inclusiveRight&&!ee)&&A.push({from:u.to,to:m.to}),i.splice.apply(i,A),f+=A.length-3}}return i}function xa(e){var t=e.markedSpans;if(t){for(var n=0;nt)&&(!r||oo(r,a.marker)<0)&&(r=a.marker)}return r}function Sa(e,t,n,r,i){var a=Ae(e,t),l=or&&a.markedSpans;if(l)for(var u=0;u=0&&j<=0||A<=0&&j>=0)&&(A<=0&&(f.marker.inclusiveRight&&i.inclusiveLeft?ye(m.to,n)>=0:ye(m.to,n)>0)||A>=0&&(f.marker.inclusiveRight&&i.inclusiveLeft?ye(m.from,r)<=0:ye(m.from,r)<0)))return!0}}}function Zt(e){for(var t;t=wa(e);)e=t.find(-1,!0).line;return e}function Ic(e){for(var t;t=ai(e);)e=t.find(1,!0).line;return e}function Nc(e){for(var t,n;t=ai(e);)e=t.find(1,!0).line,(n||(n=[])).push(e);return n}function ao(e,t){var n=Ae(e,t),r=Zt(n);return n==r?t:_(r)}function Ta(e,t){if(t>e.lastLine())return t;var n=Ae(e,t),r;if(!mr(e,n))return t;for(;r=ai(n);)n=r.find(1,!0).line;return _(n)+1}function mr(e,t){var n=or&&t.markedSpans;if(n){for(var r=void 0,i=0;it.maxLineLength&&(t.maxLineLength=i,t.maxLine=r)})}var Xr=function(e,t,n){this.text=e,_a(this,t),this.height=n?n(this):1};Xr.prototype.lineNo=function(){return _(this)},Wt(Xr);function Oc(e,t,n,r){e.text=t,e.stateAfter&&(e.stateAfter=null),e.styles&&(e.styles=null),e.order!=null&&(e.order=null),xa(e),_a(e,n);var i=r?r(e):1;i!=e.height&&jt(e,i)}function Pc(e){e.parent=null,xa(e)}var jc={},Rc={};function La(e,t){if(!e||/^\s*$/.test(e))return null;var n=t.addModeClass?Rc:jc;return n[e]||(n[e]=e.replace(/\S+/g,"cm-$&"))}function Ca(e,t){var n=K("span",null,null,g?"padding-right: .1px":null),r={pre:K("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var a=i?t.rest[i-1]:t.line,l=void 0;r.pos=0,r.addToken=Bc,pr(e.display.measure)&&(l=Pe(a,e.doc.direction))&&(r.addToken=Uc(r.addToken,l)),r.map=[];var u=t!=e.display.externalMeasured&&_(a);$c(a,r,da(e,a,u)),a.styleClasses&&(a.styleClasses.bgClass&&(r.bgClass=xe(a.styleClasses.bgClass,r.bgClass||"")),a.styleClasses.textClass&&(r.textClass=xe(a.styleClasses.textClass,r.textClass||""))),r.map.length==0&&r.map.push(0,0,r.content.appendChild(ei(e.display.measure))),i==0?(t.measure.map=r.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(r.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(g){var f=r.content.lastChild;(/\bcm-tab\b/.test(f.className)||f.querySelector&&f.querySelector(".cm-tab"))&&(r.content.className="cm-tab-wrap-hack")}return it(e,"renderLine",e,t.line,r.pre),r.pre.className&&(r.textClass=xe(r.pre.className,r.textClass||"")),r}function Hc(e){var t=x("span","\u2022","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function Bc(e,t,n,r,i,a,l){if(t){var u=e.splitSpaces?Wc(t,e.trailingSpace):t,f=e.cm.state.specialChars,m=!1,A;if(!f.test(t))e.col+=t.length,A=document.createTextNode(u),e.map.push(e.pos,e.pos+t.length,A),s&&h<9&&(m=!0),e.pos+=t.length;else{A=document.createDocumentFragment();for(var j=0;;){f.lastIndex=j;var ee=f.exec(t),Y=ee?ee.index-j:t.length-j;if(Y){var ie=document.createTextNode(u.slice(j,j+Y));s&&h<9?A.appendChild(x("span",[ie])):A.appendChild(ie),e.map.push(e.pos,e.pos+Y,ie),e.col+=Y,e.pos+=Y}if(!ee)break;j+=Y+1;var ue=void 0;if(ee[0]==" "){var me=e.cm.options.tabSize,ve=me-e.col%me;ue=A.appendChild(x("span",Z(ve),"cm-tab")),ue.setAttribute("role","presentation"),ue.setAttribute("cm-text"," "),e.col+=ve}else ee[0]=="\r"||ee[0]==` -`?(ue=A.appendChild(x("span",ee[0]=="\r"?"\u240D":"\u2424","cm-invalidchar")),ue.setAttribute("cm-text",ee[0]),e.col+=1):(ue=e.cm.options.specialCharPlaceholder(ee[0]),ue.setAttribute("cm-text",ee[0]),s&&h<9?A.appendChild(x("span",[ue])):A.appendChild(ue),e.col+=1);e.map.push(e.pos,e.pos+1,ue),e.pos++}}if(e.trailingSpace=u.charCodeAt(t.length-1)==32,n||r||i||m||a||l){var _e=n||"";r&&(_e+=r),i&&(_e+=i);var be=x("span",[A],_e,a);if(l)for(var Ce in l)l.hasOwnProperty(Ce)&&Ce!="style"&&Ce!="class"&&be.setAttribute(Ce,l[Ce]);return e.content.appendChild(be)}e.content.appendChild(A)}}function Wc(e,t){if(e.length>1&&!/ /.test(e))return e;for(var n=t,r="",i=0;im&&j.from<=m));ee++);if(j.to>=A)return e(n,r,i,a,l,u,f);e(n,r.slice(0,j.to-m),i,a,null,u,f),a=null,r=r.slice(j.to-m),m=j.to}}}function Ea(e,t,n,r){var i=!r&&n.widgetNode;i&&e.map.push(e.pos,e.pos+t,i),!r&&e.cm.display.input.needsContentAttribute&&(i||(i=e.content.appendChild(document.createElement("span"))),i.setAttribute("cm-marker",n.id)),i&&(e.cm.display.input.setUneditable(i),e.content.appendChild(i)),e.pos+=t,e.trailingSpace=!1}function $c(e,t,n){var r=e.markedSpans,i=e.text,a=0;if(!r){for(var l=1;lf||$e.collapsed&&Fe.to==f&&Fe.from==f)){if(Fe.to!=null&&Fe.to!=f&&Y>Fe.to&&(Y=Fe.to,ue=""),$e.className&&(ie+=" "+$e.className),$e.css&&(ee=(ee?ee+";":"")+$e.css),$e.startStyle&&Fe.from==f&&(me+=" "+$e.startStyle),$e.endStyle&&Fe.to==Y&&(Ce||(Ce=[])).push($e.endStyle,Fe.to),$e.title&&((_e||(_e={})).title=$e.title),$e.attributes)for(var Ve in $e.attributes)(_e||(_e={}))[Ve]=$e.attributes[Ve];$e.collapsed&&(!ve||oo(ve.marker,$e)<0)&&(ve=Fe)}else Fe.from>f&&Y>Fe.from&&(Y=Fe.from)}if(Ce)for(var vt=0;vt=u)break;for(var Ot=Math.min(u,Y);;){if(A){var At=f+A.length;if(!ve){var ut=At>Ot?A.slice(0,Ot-f):A;t.addToken(t,ut,j?j+ie:ie,me,f+ut.length==Y?ue:"",ee,_e)}if(At>=Ot){A=A.slice(Ot-f),f=Ot;break}f=At,me=""}A=i.slice(a,a=n[m++]),j=La(n[m++],t.cm.options)}}}function za(e,t,n){this.line=t,this.rest=Nc(t),this.size=this.rest?_(ce(this.rest))-n+1:1,this.node=this.text=null,this.hidden=mr(e,t)}function si(e,t,n){for(var r=[],i,a=t;a2&&a.push((f.bottom+m.top)/2-n.top)}}a.push(n.bottom-n.top)}}function Na(e,t,n){if(e.line==t)return{map:e.measure.map,cache:e.measure.cache};if(e.rest){for(var r=0;rn)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}}function rf(e,t){t=Zt(t);var n=_(t),r=e.display.externalMeasured=new za(e.doc,t,n);r.lineN=n;var i=r.built=Ca(e,r);return r.text=i.pre,V(e.display.lineMeasure,i.pre),r}function Oa(e,t,n,r){return tr(e,Qr(e,t),n,r)}function po(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&tt)&&(a=f-u,i=a-1,t>=f&&(l="right")),i!=null){if(r=e[m+2],u==f&&n==(r.insertLeft?"left":"right")&&(l=n),n=="left"&&i==0)for(;m&&e[m-2]==e[m-3]&&e[m-1].insertLeft;)r=e[(m-=3)+2],l="left";if(n=="right"&&i==f-u)for(;m=0&&(n=e[i]).left==n.right;i--);return n}function of(e,t,n,r){var i=ja(t.map,n,r),a=i.node,l=i.start,u=i.end,f=i.collapse,m;if(a.nodeType==3){for(var A=0;A<4;A++){for(;l&&B(t.line.text.charAt(i.coverStart+l));)--l;for(;i.coverStart+u0&&(f=r="right");var j;e.options.lineWrapping&&(j=a.getClientRects()).length>1?m=j[r=="right"?j.length-1:0]:m=a.getBoundingClientRect()}if(s&&h<9&&!l&&(!m||!m.left&&!m.right)){var ee=a.parentNode.getClientRects()[0];ee?m={left:ee.left,right:ee.left+Jr(e.display),top:ee.top,bottom:ee.bottom}:m=Pa}for(var Y=m.top-t.rect.top,ie=m.bottom-t.rect.top,ue=(Y+ie)/2,me=t.view.measure.heights,ve=0;ve=r.text.length?(f=r.text.length,m="before"):f<=0&&(f=0,m="after"),!u)return l(m=="before"?f-1:f,m=="before");function A(ie,ue,me){var ve=u[ue],_e=ve.level==1;return l(me?ie-1:ie,_e!=me)}var j=Pt(u,f,m),ee=dt,Y=A(f,j,m=="before");return ee!=null&&(Y.other=A(f,ee,m!="before")),Y}function $a(e,t){var n=0;t=Re(e.doc,t),e.options.lineWrapping||(n=Jr(e.display)*t.ch);var r=Ae(e.doc,t.line),i=ar(r)+ui(e.display);return{left:n,right:n,top:i,bottom:i+r.height}}function go(e,t,n,r,i){var a=ne(e,t,n);return a.xRel=i,r&&(a.outside=r),a}function mo(e,t,n){var r=e.doc;if(n+=e.display.viewOffset,n<0)return go(r.first,0,null,-1,-1);var i=P(r,n),a=r.first+r.size-1;if(i>a)return go(r.first+r.size-1,Ae(r,a).text.length,null,1,1);t<0&&(t=0);for(var l=Ae(r,i);;){var u=lf(e,l,i,t,n),f=Fc(l,u.ch+(u.xRel>0||u.outside>0?1:0));if(!f)return u;var m=f.find(1);if(m.line==i)return m;l=Ae(r,i=m.line)}}function Ka(e,t,n,r){r-=ho(t);var i=t.text.length,a=De(function(l){return tr(e,n,l-1).bottom<=r},i,0);return i=De(function(l){return tr(e,n,l).top>r},a,i),{begin:a,end:i}}function Ga(e,t,n,r){n||(n=Qr(e,t));var i=ci(e,t,tr(e,n,r),"line").top;return Ka(e,t,n,i)}function vo(e,t,n,r){return e.bottom<=n?!1:e.top>n?!0:(r?e.left:e.right)>t}function lf(e,t,n,r,i){i-=ar(t);var a=Qr(e,t),l=ho(t),u=0,f=t.text.length,m=!0,A=Pe(t,e.doc.direction);if(A){var j=(e.options.lineWrapping?uf:sf)(e,t,n,a,A,r,i);m=j.level!=1,u=m?j.from:j.to-1,f=m?j.to:j.from-1}var ee=null,Y=null,ie=De(function(Ne){var Fe=tr(e,a,Ne);return Fe.top+=l,Fe.bottom+=l,vo(Fe,r,i,!1)?(Fe.top<=i&&Fe.left<=r&&(ee=Ne,Y=Fe),!0):!1},u,f),ue,me,ve=!1;if(Y){var _e=r-Y.left=Ce.bottom?1:0}return ie=se(t.text,ie,1),go(n,ie,me,ve,r-ue)}function sf(e,t,n,r,i,a,l){var u=De(function(j){var ee=i[j],Y=ee.level!=1;return vo(Xt(e,ne(n,Y?ee.to:ee.from,Y?"before":"after"),"line",t,r),a,l,!0)},0,i.length-1),f=i[u];if(u>0){var m=f.level!=1,A=Xt(e,ne(n,m?f.from:f.to,m?"after":"before"),"line",t,r);vo(A,a,l,!0)&&A.top>l&&(f=i[u-1])}return f}function uf(e,t,n,r,i,a,l){var u=Ka(e,t,r,l),f=u.begin,m=u.end;/\s/.test(t.text.charAt(m-1))&&m--;for(var A=null,j=null,ee=0;ee=m||Y.to<=f)){var ie=Y.level!=1,ue=tr(e,r,ie?Math.min(m,Y.to)-1:Math.max(f,Y.from)).right,me=ueme)&&(A=Y,j=me)}}return A||(A=i[i.length-1]),A.fromm&&(A={from:A.from,to:m,level:A.level}),A}var zr;function Vr(e){if(e.cachedTextHeight!=null)return e.cachedTextHeight;if(zr==null){zr=x("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)zr.appendChild(document.createTextNode("x")),zr.appendChild(x("br"));zr.appendChild(document.createTextNode("x"))}V(e.measure,zr);var n=zr.offsetHeight/50;return n>3&&(e.cachedTextHeight=n),R(e.measure),n||1}function Jr(e){if(e.cachedCharWidth!=null)return e.cachedCharWidth;var t=x("span","xxxxxxxxxx"),n=x("pre",[t],"CodeMirror-line-like");V(e.measure,n);var r=t.getBoundingClientRect(),i=(r.right-r.left)/10;return i>2&&(e.cachedCharWidth=i),i||10}function bo(e){for(var t=e.display,n={},r={},i=t.gutters.clientLeft,a=t.gutters.firstChild,l=0;a;a=a.nextSibling,++l){var u=e.display.gutterSpecs[l].className;n[u]=a.offsetLeft+a.clientLeft+i,r[u]=a.clientWidth}return{fixedPos:yo(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:n,gutterWidth:r,wrapperWidth:t.wrapper.clientWidth}}function yo(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function Za(e){var t=Vr(e.display),n=e.options.lineWrapping,r=n&&Math.max(5,e.display.scroller.clientWidth/Jr(e.display)-3);return function(i){if(mr(e.doc,i))return 0;var a=0;if(i.widgets)for(var l=0;l0&&(m=Ae(e.doc,f.line).text).length==f.ch){var A=Oe(m,m.length,e.options.tabSize)-m.length;f=ne(f.line,Math.max(0,Math.round((a-Ia(e.display).left)/Jr(e.display))-A))}return f}function Ar(e,t){if(t>=e.display.viewTo||(t-=e.display.viewFrom,t<0))return null;for(var n=e.display.view,r=0;rt)&&(i.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=i.viewTo)or&&ao(e.doc,t)i.viewFrom?br(e):(i.viewFrom+=r,i.viewTo+=r);else if(t<=i.viewFrom&&n>=i.viewTo)br(e);else if(t<=i.viewFrom){var a=di(e,n,n+r,1);a?(i.view=i.view.slice(a.index),i.viewFrom=a.lineN,i.viewTo+=r):br(e)}else if(n>=i.viewTo){var l=di(e,t,t,-1);l?(i.view=i.view.slice(0,l.index),i.viewTo=l.lineN):br(e)}else{var u=di(e,t,t,-1),f=di(e,n,n+r,1);u&&f?(i.view=i.view.slice(0,u.index).concat(si(e,u.lineN,f.lineN)).concat(i.view.slice(f.index)),i.viewTo+=r):br(e)}var m=i.externalMeasured;m&&(n=i.lineN&&t=r.viewTo)){var a=r.view[Ar(e,t)];if(a.node!=null){var l=a.changes||(a.changes=[]);Se(l,n)==-1&&l.push(n)}}}function br(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function di(e,t,n,r){var i=Ar(e,t),a,l=e.display.view;if(!or||n==e.doc.first+e.doc.size)return{index:i,lineN:n};for(var u=e.display.viewFrom,f=0;f0){if(i==l.length-1)return null;a=u+l[i].size-t,i++}else a=u-t;t+=a,n+=a}for(;ao(e.doc,n)!=n;){if(i==(r<0?0:l.length-1))return null;n+=r*l[i-(r<0?1:0)].size,i+=r}return{index:i,lineN:n}}function cf(e,t,n){var r=e.display,i=r.view;i.length==0||t>=r.viewTo||n<=r.viewFrom?(r.view=si(e,t,n),r.viewFrom=t):(r.viewFrom>t?r.view=si(e,t,r.viewFrom).concat(r.view):r.viewFromn&&(r.view=r.view.slice(0,Ar(e,n)))),r.viewTo=n}function Xa(e){for(var t=e.display.view,n=0,r=0;r=e.display.viewTo||f.to().line0?l:e.defaultCharWidth())+"px"}if(r.other){var u=n.appendChild(x("div","\xA0","CodeMirror-cursor CodeMirror-secondarycursor"));u.style.display="",u.style.left=r.other.left+"px",u.style.top=r.other.top+"px",u.style.height=(r.other.bottom-r.other.top)*.85+"px"}}function pi(e,t){return e.top-t.top||e.left-t.left}function ff(e,t,n){var r=e.display,i=e.doc,a=document.createDocumentFragment(),l=Ia(e.display),u=l.left,f=Math.max(r.sizerWidth,Er(e)-r.sizer.offsetLeft)-l.right,m=i.direction=="ltr";function A(be,Ce,Ne,Fe){Ce<0&&(Ce=0),Ce=Math.round(Ce),Fe=Math.round(Fe),a.appendChild(x("div",null,"CodeMirror-selected","position: absolute; left: "+be+`px; - top: `+Ce+"px; width: "+(Ne??f-be)+`px; - height: `+(Fe-Ce)+"px"))}function j(be,Ce,Ne){var Fe=Ae(i,be),$e=Fe.text.length,Ve,vt;function rt(ut,Dt){return fi(e,ne(be,ut),"div",Fe,Dt)}function Ot(ut,Dt,yt){var ft=Ga(e,Fe,null,ut),ct=Dt=="ltr"==(yt=="after")?"left":"right",lt=yt=="after"?ft.begin:ft.end-(/\s/.test(Fe.text.charAt(ft.end-1))?2:1);return rt(lt,ct)[ct]}var At=Pe(Fe,i.direction);return nt(At,Ce||0,Ne??$e,function(ut,Dt,yt,ft){var ct=yt=="ltr",lt=rt(ut,ct?"left":"right"),qt=rt(Dt-1,ct?"right":"left"),pn=Ce==null&&ut==0,Sr=Ne==null&&Dt==$e,St=ft==0,rr=!At||ft==At.length-1;if(qt.top-lt.top<=3){var bt=(m?pn:Sr)&&St,Zo=(m?Sr:pn)&&rr,cr=bt?u:(ct?lt:qt).left,Nr=Zo?f:(ct?qt:lt).right;A(cr,lt.top,Nr-cr,lt.bottom)}else{var Or,Lt,hn,Xo;ct?(Or=m&&pn&&St?u:lt.left,Lt=m?f:Ot(ut,yt,"before"),hn=m?u:Ot(Dt,yt,"after"),Xo=m&&Sr&&rr?f:qt.right):(Or=m?Ot(ut,yt,"before"):u,Lt=!m&&pn&&St?f:lt.right,hn=!m&&Sr&&rr?u:qt.left,Xo=m?Ot(Dt,yt,"after"):f),A(Or,lt.top,Lt-Or,lt.bottom),lt.bottom0?t.blinker=setInterval(function(){e.hasFocus()||en(e),t.cursorDiv.style.visibility=(n=!n)?"":"hidden"},e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function Qa(e){e.hasFocus()||(e.display.input.focus(),e.state.focused||So(e))}function wo(e){e.state.delayingBlurEvent=!0,setTimeout(function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,e.state.focused&&en(e))},100)}function So(e,t){e.state.delayingBlurEvent&&!e.state.draggingText&&(e.state.delayingBlurEvent=!1),e.options.readOnly!="nocursor"&&(e.state.focused||(it(e,"focus",e,t),e.state.focused=!0,le(e.display.wrapper,"CodeMirror-focused"),!e.curOp&&e.display.selForContextMenu!=e.doc.sel&&(e.display.input.reset(),g&&setTimeout(function(){return e.display.input.reset(!0)},20)),e.display.input.receivedFocus()),ko(e))}function en(e,t){e.state.delayingBlurEvent||(e.state.focused&&(it(e,"blur",e,t),e.state.focused=!1,Q(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout(function(){e.state.focused||(e.display.shift=!1)},150))}function hi(e){for(var t=e.display,n=t.lineDiv.offsetTop,r=Math.max(0,t.scroller.getBoundingClientRect().top),i=t.lineDiv.getBoundingClientRect().top,a=0,l=0;l.005||Y<-.005)&&(ie.display.sizerWidth){var ue=Math.ceil(A/Jr(e.display));ue>e.display.maxLineLength&&(e.display.maxLineLength=ue,e.display.maxLine=u.line,e.display.maxLineChanged=!0)}}}Math.abs(a)>2&&(t.scroller.scrollTop+=a)}function Va(e){if(e.widgets)for(var t=0;t=l&&(a=P(t,ar(Ae(t,f))-e.wrapper.clientHeight),l=f)}return{from:a,to:Math.max(l,a+1)}}function df(e,t){if(!ot(e,"scrollCursorIntoView")){var n=e.display,r=n.sizer.getBoundingClientRect(),i=null,a=n.wrapper.ownerDocument;if(t.top+r.top<0?i=!0:t.bottom+r.top>(a.defaultView.innerHeight||a.documentElement.clientHeight)&&(i=!1),i!=null&&!M){var l=x("div","\u200B",null,`position: absolute; - top: `+(t.top-n.viewOffset-ui(e.display))+`px; - height: `+(t.bottom-t.top+er(e)+n.barHeight)+`px; - left: `+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(l),l.scrollIntoView(i),e.display.lineSpace.removeChild(l)}}}function pf(e,t,n,r){r==null&&(r=0);var i;!e.options.lineWrapping&&t==n&&(n=t.sticky=="before"?ne(t.line,t.ch+1,"before"):t,t=t.ch?ne(t.line,t.sticky=="before"?t.ch-1:t.ch,"after"):t);for(var a=0;a<5;a++){var l=!1,u=Xt(e,t),f=!n||n==t?u:Xt(e,n);i={left:Math.min(u.left,f.left),top:Math.min(u.top,f.top)-r,right:Math.max(u.left,f.left),bottom:Math.max(u.bottom,f.bottom)+r};var m=To(e,i),A=e.doc.scrollTop,j=e.doc.scrollLeft;if(m.scrollTop!=null&&(An(e,m.scrollTop),Math.abs(e.doc.scrollTop-A)>1&&(l=!0)),m.scrollLeft!=null&&(Dr(e,m.scrollLeft),Math.abs(e.doc.scrollLeft-j)>1&&(l=!0)),!l)break}return i}function hf(e,t){var n=To(e,t);n.scrollTop!=null&&An(e,n.scrollTop),n.scrollLeft!=null&&Dr(e,n.scrollLeft)}function To(e,t){var n=e.display,r=Vr(e.display);t.top<0&&(t.top=0);var i=e.curOp&&e.curOp.scrollTop!=null?e.curOp.scrollTop:n.scroller.scrollTop,a=fo(e),l={};t.bottom-t.top>a&&(t.bottom=t.top+a);var u=e.doc.height+co(n),f=t.topu-r;if(t.topi+a){var A=Math.min(t.top,(m?u:t.bottom)-a);A!=i&&(l.scrollTop=A)}var j=e.options.fixedGutter?0:n.gutters.offsetWidth,ee=e.curOp&&e.curOp.scrollLeft!=null?e.curOp.scrollLeft:n.scroller.scrollLeft-j,Y=Er(e)-n.gutters.offsetWidth,ie=t.right-t.left>Y;return ie&&(t.right=t.left+Y),t.left<10?l.scrollLeft=0:t.leftY+ee-3&&(l.scrollLeft=t.right+(ie?0:10)-Y),l}function Lo(e,t){t!=null&&(mi(e),e.curOp.scrollTop=(e.curOp.scrollTop==null?e.doc.scrollTop:e.curOp.scrollTop)+t)}function tn(e){mi(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function Mn(e,t,n){(t!=null||n!=null)&&mi(e),t!=null&&(e.curOp.scrollLeft=t),n!=null&&(e.curOp.scrollTop=n)}function gf(e,t){mi(e),e.curOp.scrollToPos=t}function mi(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var n=$a(e,t.from),r=$a(e,t.to);Ja(e,n,r,t.margin)}}function Ja(e,t,n,r){var i=To(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});Mn(e,i.scrollLeft,i.scrollTop)}function An(e,t){Math.abs(e.doc.scrollTop-t)<2||(v||Eo(e,{top:t}),el(e,t,!0),v&&Eo(e),Fn(e,100))}function el(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),!(e.display.scroller.scrollTop==t&&!n)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Dr(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),!((n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r)&&(e.doc.scrollLeft=t,ol(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function Dn(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+co(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+er(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}var qr=function(e,t,n){this.cm=n;var r=this.vert=x("div",[x("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=x("div",[x("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),Ie(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),Ie(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,s&&h<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")};qr.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1,r=e.nativeBarWidth;if(n){this.vert.style.display="block",this.vert.style.bottom=t?r+"px":"0";var i=e.viewHeight-(t?r:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+i)+"px"}else this.vert.scrollTop=0,this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=n?r+"px":"0",this.horiz.style.left=e.barLeft+"px";var a=e.viewWidth-e.barLeft-(n?r:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+a)+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(r==0&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:n?r:0,bottom:t?r:0}},qr.prototype.setScrollLeft=function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")},qr.prototype.setScrollTop=function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert,"vert")},qr.prototype.zeroWidthHack=function(){var e=O&&!z?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.visibility=this.vert.style.visibility="hidden",this.disableHoriz=new qe,this.disableVert=new qe},qr.prototype.enableZeroWidthBar=function(e,t,n){e.style.visibility="";function r(){var i=e.getBoundingClientRect(),a=n=="vert"?document.elementFromPoint(i.right-1,(i.top+i.bottom)/2):document.elementFromPoint((i.right+i.left)/2,i.bottom-1);a!=e?e.style.visibility="hidden":t.set(1e3,r)}t.set(1e3,r)},qr.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)};var qn=function(){};qn.prototype.update=function(){return{bottom:0,right:0}},qn.prototype.setScrollLeft=function(){},qn.prototype.setScrollTop=function(){},qn.prototype.clear=function(){};function rn(e,t){t||(t=Dn(e));var n=e.display.barWidth,r=e.display.barHeight;tl(e,t);for(var i=0;i<4&&n!=e.display.barWidth||r!=e.display.barHeight;i++)n!=e.display.barWidth&&e.options.lineWrapping&&hi(e),tl(e,Dn(e)),n=e.display.barWidth,r=e.display.barHeight}function tl(e,t){var n=e.display,r=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=r.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=r.bottom)+"px",n.heightForcer.style.borderBottom=r.bottom+"px solid transparent",r.right&&r.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=r.bottom+"px",n.scrollbarFiller.style.width=r.right+"px"):n.scrollbarFiller.style.display="",r.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=r.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}var rl={native:qr,null:qn};function nl(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&Q(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new rl[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),Ie(t,"mousedown",function(){e.state.focused&&setTimeout(function(){return e.display.input.focus()},0)}),t.setAttribute("cm-not-content","true")},function(t,n){n=="horizontal"?Dr(e,t):An(e,t)},e),e.display.scrollbars.addClass&&le(e.display.wrapper,e.display.scrollbars.addClass)}var mf=0;function Fr(e){e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:0,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++mf,markArrays:null},Kc(e.curOp)}function Ir(e){var t=e.curOp;t&&Zc(t,function(n){for(var r=0;r=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new vi(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function yf(e){e.updatedDisplay=e.mustUpdate&&Co(e.cm,e.update)}function xf(e){var t=e.cm,n=t.display;e.updatedDisplay&&hi(t),e.barMeasure=Dn(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=Oa(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+er(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-Er(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection())}function _f(e){var t=e.cm;e.adjustWidthTo!=null&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeft=e.display.viewTo)){var n=+new Date+e.options.workTime,r=wn(e,t.highlightFrontier),i=[];t.iter(r.line,Math.min(t.first+t.size,e.display.viewTo+500),function(a){if(r.line>=e.display.viewFrom){var l=a.styles,u=a.text.length>e.options.maxHighlightLength?Vt(t.mode,r.state):null,f=fa(e,a,r,!0);u&&(r.state=u),a.styles=f.styles;var m=a.styleClasses,A=f.classes;A?a.styleClasses=A:m&&(a.styleClasses=null);for(var j=!l||l.length!=a.styles.length||m!=A&&(!m||!A||m.bgClass!=A.bgClass||m.textClass!=A.textClass),ee=0;!j&&een)return Fn(e,e.options.workDelay),!0}),t.highlightFrontier=r.line,t.modeFrontier=Math.max(t.modeFrontier,r.line),i.length&&Nt(e,function(){for(var a=0;a=n.viewFrom&&t.visible.to<=n.viewTo&&(n.updateLineNumbers==null||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&Xa(e)==0)return!1;al(e)&&(br(e),t.dims=bo(e));var i=r.first+r.size,a=Math.max(t.visible.from-e.options.viewportMargin,r.first),l=Math.min(i,t.visible.to+e.options.viewportMargin);n.viewFroml&&n.viewTo-l<20&&(l=Math.min(i,n.viewTo)),or&&(a=ao(e.doc,a),l=Ta(e.doc,l));var u=a!=n.viewFrom||l!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;cf(e,a,l),n.viewOffset=ar(Ae(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";var f=Xa(e);if(!u&&f==0&&!t.force&&n.renderedView==n.view&&(n.updateLineNumbers==null||n.updateLineNumbers>=n.viewTo))return!1;var m=Tf(e);return f>4&&(n.lineDiv.style.display="none"),Cf(e,n.updateLineNumbers,t.dims),f>4&&(n.lineDiv.style.display=""),n.renderedView=n.view,Lf(m),R(n.cursorDiv),R(n.selectionDiv),n.gutters.style.height=n.sizer.style.minHeight=0,u&&(n.lastWrapHeight=t.wrapperHeight,n.lastWrapWidth=t.wrapperWidth,Fn(e,400)),n.updateLineNumbers=null,!0}function il(e,t){for(var n=t.viewport,r=!0;;r=!1){if(!r||!e.options.lineWrapping||t.oldDisplayWidth==Er(e)){if(n&&n.top!=null&&(n={top:Math.min(e.doc.height+co(e.display)-fo(e),n.top)}),t.visible=gi(e.display,e.doc,n),t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break}else r&&(t.visible=gi(e.display,e.doc,n));if(!Co(e,t))break;hi(e);var i=Dn(e);zn(e),rn(e,i),Mo(e,i),t.force=!1}t.signal(e,"update",e),(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo)&&(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Eo(e,t){var n=new vi(e,t);if(Co(e,n)){hi(e),il(e,n);var r=Dn(e);zn(e),rn(e,r),Mo(e,r),n.finish()}}function Cf(e,t,n){var r=e.display,i=e.options.lineNumbers,a=r.lineDiv,l=a.firstChild;function u(ie){var ue=ie.nextSibling;return g&&O&&e.display.currentWheelTarget==ie?ie.style.display="none":ie.parentNode.removeChild(ie),ue}for(var f=r.view,m=r.viewFrom,A=0;A-1&&(Y=!1),Ma(e,j,m,n)),Y&&(R(j.lineNumber),j.lineNumber.appendChild(document.createTextNode(he(e.options,m)))),l=j.node.nextSibling}m+=j.size}for(;l;)l=u(l)}function zo(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px",ht(e,"gutterChanged",e)}function Mo(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+er(e)+"px"}function ol(e){var t=e.display,n=t.view;if(!(!t.alignWidgets&&(!t.gutters.firstChild||!e.options.fixedGutter))){for(var r=yo(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,a=r+"px",l=0;l=105&&(i.wrapper.style.clipPath="inset(0px)"),i.wrapper.setAttribute("translate","no"),s&&h<8&&(i.gutters.style.zIndex=-1,i.scroller.style.paddingRight=0),!g&&!(v&&E)&&(i.scroller.draggable=!0),e&&(e.appendChild?e.appendChild(i.wrapper):e(i.wrapper)),i.viewFrom=i.viewTo=t.first,i.reportedViewFrom=i.reportedViewTo=t.first,i.view=[],i.renderedView=null,i.externalMeasured=null,i.viewOffset=0,i.lastWrapHeight=i.lastWrapWidth=0,i.updateLineNumbers=null,i.nativeBarWidth=i.barHeight=i.barWidth=0,i.scrollbarsClipped=!1,i.lineNumWidth=i.lineNumInnerWidth=i.lineNumChars=null,i.alignWidgets=!1,i.cachedCharWidth=i.cachedTextHeight=i.cachedPaddingH=null,i.maxLine=null,i.maxLineLength=0,i.maxLineChanged=!1,i.wheelDX=i.wheelDY=i.wheelStartX=i.wheelStartY=null,i.shift=!1,i.selForContextMenu=null,i.activeTouch=null,i.gutterSpecs=Ao(r.gutters,r.lineNumbers),ll(i),n.init(i)}var bi=0,sr=null;s?sr=-.53:v?sr=15:y?sr=-.7:k&&(sr=-1/3);function sl(e){var t=e.wheelDeltaX,n=e.wheelDeltaY;return t==null&&e.detail&&e.axis==e.HORIZONTAL_AXIS&&(t=e.detail),n==null&&e.detail&&e.axis==e.VERTICAL_AXIS?n=e.detail:n==null&&(n=e.wheelDelta),{x:t,y:n}}function zf(e){var t=sl(e);return t.x*=sr,t.y*=sr,t}function ul(e,t){y&&c==102&&(e.display.chromeScrollHack==null?e.display.sizer.style.pointerEvents="none":clearTimeout(e.display.chromeScrollHack),e.display.chromeScrollHack=setTimeout(function(){e.display.chromeScrollHack=null,e.display.sizer.style.pointerEvents=""},100));var n=sl(t),r=n.x,i=n.y,a=sr;t.deltaMode===0&&(r=t.deltaX,i=t.deltaY,a=1);var l=e.display,u=l.scroller,f=u.scrollWidth>u.clientWidth,m=u.scrollHeight>u.clientHeight;if(r&&f||i&&m){if(i&&O&&g){e:for(var A=t.target,j=l.view;A!=u;A=A.parentNode)for(var ee=0;ee=0&&ye(e,r.to())<=0)return n}return-1};var Ye=function(e,t){this.anchor=e,this.head=t};Ye.prototype.from=function(){return Zr(this.anchor,this.head)},Ye.prototype.to=function(){return Et(this.anchor,this.head)},Ye.prototype.empty=function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch};function Yt(e,t,n){var r=e&&e.options.selectionsMayTouch,i=t[n];t.sort(function(ee,Y){return ye(ee.from(),Y.from())}),n=Se(t,i);for(var a=1;a0:f>=0){var m=Zr(u.from(),l.from()),A=Et(u.to(),l.to()),j=u.empty()?l.from()==l.head:u.from()==u.head;a<=n&&--n,t.splice(--a,2,new Ye(j?A:m,j?m:A))}}return new Rt(t,n)}function yr(e,t){return new Rt([new Ye(e,t||e)],0)}function xr(e){return e.text?ne(e.from.line+e.text.length-1,ce(e.text).length+(e.text.length==1?e.from.ch:0)):e.to}function cl(e,t){if(ye(e,t.from)<0)return e;if(ye(e,t.to)<=0)return xr(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,r=e.ch;return e.line==t.to.line&&(r+=xr(t).ch-t.to.ch),ne(n,r)}function Do(e,t){for(var n=[],r=0;r1&&e.remove(u.line+1,ie-1),e.insert(u.line+1,ve)}ht(e,"change",e,t)}function _r(e,t,n){function r(i,a,l){if(i.linked)for(var u=0;u1&&!e.done[e.done.length-2].ranges)return e.done.pop(),ce(e.done)}function ml(e,t,n,r){var i=e.history;i.undone.length=0;var a=+new Date,l,u;if((i.lastOp==r||i.lastOrigin==t.origin&&t.origin&&(t.origin.charAt(0)=="+"&&i.lastModTime>a-(e.cm?e.cm.options.historyEventDelay:500)||t.origin.charAt(0)=="*"))&&(l=Df(i,i.lastOp==r)))u=ce(l.changes),ye(t.from,t.to)==0&&ye(t.from,u.to)==0?u.to=xr(t):l.changes.push(Io(e,t));else{var f=ce(i.done);for((!f||!f.ranges)&&xi(e.sel,i.done),l={changes:[Io(e,t)],generation:i.generation},i.done.push(l);i.done.length>i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(n),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=a,i.lastOp=i.lastSelOp=r,i.lastOrigin=i.lastSelOrigin=t.origin,u||it(e,"historyAdded")}function qf(e,t,n,r){var i=t.charAt(0);return i=="*"||i=="+"&&n.ranges.length==r.ranges.length&&n.somethingSelected()==r.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function Ff(e,t,n,r){var i=e.history,a=r&&r.origin;n==i.lastSelOp||a&&i.lastSelOrigin==a&&(i.lastModTime==i.lastSelTime&&i.lastOrigin==a||qf(e,a,ce(i.done),t))?i.done[i.done.length-1]=t:xi(t,i.done),i.lastSelTime=+new Date,i.lastSelOrigin=a,i.lastSelOp=n,r&&r.clearRedo!==!1&&gl(i.undone)}function xi(e,t){var n=ce(t);n&&n.ranges&&n.equals(e)||t.push(e)}function vl(e,t,n,r){var i=t["spans_"+e.id],a=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,r),function(l){l.markedSpans&&((i||(i=t["spans_"+e.id]={}))[a]=l.markedSpans),++a})}function If(e){if(!e)return null;for(var t,n=0;n-1&&(ce(u)[j]=m[j],delete m[j])}}return r}function No(e,t,n,r){if(r){var i=e.anchor;if(n){var a=ye(t,i)<0;a!=ye(n,i)<0?(i=t,t=n):a!=ye(t,n)<0&&(t=n)}return new Ye(i,t)}else return new Ye(n||t,t)}function _i(e,t,n,r,i){i==null&&(i=e.cm&&(e.cm.display.shift||e.extend)),wt(e,new Rt([No(e.sel.primary(),t,n,i)],0),r)}function yl(e,t,n){for(var r=[],i=e.cm&&(e.cm.display.shift||e.extend),a=0;a=t.ch:u.to>t.ch))){if(i&&(it(f,"beforeCursorEnter"),f.explicitlyCleared))if(a.markedSpans){--l;continue}else break;if(!f.atomic)continue;if(n){var j=f.find(r<0?1:-1),ee=void 0;if((r<0?A:m)&&(j=Tl(e,j,-r,j&&j.line==t.line?a:null)),j&&j.line==t.line&&(ee=ye(j,n))&&(r<0?ee<0:ee>0))return on(e,j,t,r,i)}var Y=f.find(r<0?-1:1);return(r<0?m:A)&&(Y=Tl(e,Y,r,Y.line==t.line?a:null)),Y?on(e,Y,t,r,i):null}}return t}function wi(e,t,n,r,i){var a=r||1,l=on(e,t,n,a,i)||!i&&on(e,t,n,a,!0)||on(e,t,n,-a,i)||!i&&on(e,t,n,-a,!0);return l||(e.cantEdit=!0,ne(e.first,0))}function Tl(e,t,n,r){return n<0&&t.ch==0?t.line>e.first?Re(e,ne(t.line-1)):null:n>0&&t.ch==(r||Ae(e,t.line)).text.length?t.line=0;--i)El(e,{from:r[i].from,to:r[i].to,text:i?[""]:t.text,origin:t.origin});else El(e,t)}}function El(e,t){if(!(t.text.length==1&&t.text[0]==""&&ye(t.from,t.to)==0)){var n=Do(e,t);ml(e,t,n,e.cm?e.cm.curOp.id:NaN),On(e,t,n,io(e,t));var r=[];_r(e,function(i,a){!a&&Se(r,i.history)==-1&&(Dl(i.history,t),r.push(i.history)),On(i,t,null,io(i,t))})}}function Si(e,t,n){var r=e.cm&&e.cm.state.suppressEdits;if(!(r&&!n)){for(var i=e.history,a,l=e.sel,u=t=="undo"?i.done:i.undone,f=t=="undo"?i.undone:i.done,m=0;m=0;--Y){var ie=ee(Y);if(ie)return ie.v}}}}function zl(e,t){if(t!=0&&(e.first+=t,e.sel=new Rt(Be(e.sel.ranges,function(i){return new Ye(ne(i.anchor.line+t,i.anchor.ch),ne(i.head.line+t,i.head.ch))}),e.sel.primIndex),e.cm)){zt(e.cm,e.first,e.first-t,t);for(var n=e.cm.display,r=n.viewFrom;re.lastLine())){if(t.from.linea&&(t={from:t.from,to:ne(a,Ae(e,a).text.length),text:[t.text[0]],origin:t.origin}),t.removed=ir(e,t.from,t.to),n||(n=Do(e,t)),e.cm?Pf(e.cm,t,r):Fo(e,t,r),ki(e,n,ke),e.cantEdit&&wi(e,ne(e.firstLine(),0))&&(e.cantEdit=!1)}}function Pf(e,t,n){var r=e.doc,i=e.display,a=t.from,l=t.to,u=!1,f=a.line;e.options.lineWrapping||(f=_(Zt(Ae(r,a.line))),r.iter(f,l.line+1,function(Y){if(Y==i.maxLine)return u=!0,!0})),r.sel.contains(t.from,t.to)>-1&&Ht(e),Fo(r,t,n,Za(e)),e.options.lineWrapping||(r.iter(f,a.line+t.text.length,function(Y){var ie=li(Y);ie>i.maxLineLength&&(i.maxLine=Y,i.maxLineLength=ie,i.maxLineChanged=!0,u=!1)}),u&&(e.curOp.updateMaxLine=!0)),Lc(r,a.line),Fn(e,400);var m=t.text.length-(l.line-a.line)-1;t.full?zt(e):a.line==l.line&&t.text.length==1&&!dl(e.doc,t)?vr(e,a.line,"text"):zt(e,a.line,l.line+1,m);var A=It(e,"changes"),j=It(e,"change");if(j||A){var ee={from:a,to:l,text:t.text,removed:t.removed,origin:t.origin};j&&ht(e,"change",e,ee),A&&(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(ee)}e.display.selForContextMenu=null}function ln(e,t,n,r,i){var a;r||(r=n),ye(r,n)<0&&(a=[r,n],n=a[0],r=a[1]),typeof t=="string"&&(t=e.splitLines(t)),an(e,{from:n,to:r,text:t,origin:i})}function Ml(e,t,n,r){n1||!(this.children[0]instanceof jn))){var u=[];this.collapse(u),this.children=[new jn(u)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t50){for(var l=i.lines.length%25+25,u=l;u10);e.parent.maybeSpill()}},iterN:function(e,t,n){for(var r=0;re.display.maxLineLength&&(e.display.maxLine=m,e.display.maxLineLength=A,e.display.maxLineChanged=!0)}r!=null&&e&&this.collapsed&&zt(e,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&wl(e.doc)),e&&ht(e,"markerCleared",e,this,r,i),t&&Ir(e),this.parent&&this.parent.clear()}},kr.prototype.find=function(e,t){e==null&&this.type=="bookmark"&&(e=1);for(var n,r,i=0;i0||l==0&&a.clearWhenEmpty!==!1)return a;if(a.replacedWith&&(a.collapsed=!0,a.widgetNode=K("span",[a.replacedWith],"CodeMirror-widget"),r.handleMouseEvents||a.widgetNode.setAttribute("cm-ignore-events","true"),r.insertLeft&&(a.widgetNode.insertLeft=!0)),a.collapsed){if(Sa(e,t.line,t,n,a)||t.line!=n.line&&Sa(e,n.line,t,n,a))throw new Error("Inserting collapsed marker partially overlapping an existing one");Ec()}a.addToHistory&&ml(e,{from:t,to:n,origin:"markText"},e.sel,NaN);var u=t.line,f=e.cm,m;if(e.iter(u,n.line+1,function(j){f&&a.collapsed&&!f.options.lineWrapping&&Zt(j)==f.display.maxLine&&(m=!0),a.collapsed&&u!=t.line&&jt(j,0),Mc(j,new ni(a,u==t.line?t.ch:null,u==n.line?n.ch:null),e.cm&&e.cm.curOp),++u}),a.collapsed&&e.iter(t.line,n.line+1,function(j){mr(e,j)&&jt(j,0)}),a.clearOnEnter&&Ie(a,"beforeCursorEnter",function(){return a.clear()}),a.readOnly&&(Cc(),(e.history.done.length||e.history.undone.length)&&e.clearHistory()),a.collapsed&&(a.id=++Fl,a.atomic=!0),f){if(m&&(f.curOp.updateMaxLine=!0),a.collapsed)zt(f,t.line,n.line+1);else if(a.className||a.startStyle||a.endStyle||a.css||a.attributes||a.title)for(var A=t.line;A<=n.line;A++)vr(f,A,"text");a.atomic&&wl(f.doc),ht(f,"markerAdded",f,a)}return a}var Bn=function(e,t){this.markers=e,this.primary=t;for(var n=0;n=0;f--)an(this,r[f]);u?_l(this,u):this.cm&&tn(this.cm)}),undo:mt(function(){Si(this,"undo")}),redo:mt(function(){Si(this,"redo")}),undoSelection:mt(function(){Si(this,"undo",!0)}),redoSelection:mt(function(){Si(this,"redo",!0)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,n=0,r=0;r=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(e,t,n){e=Re(this,e),t=Re(this,t);var r=[],i=e.line;return this.iter(e.line,t.line+1,function(a){var l=a.markedSpans;if(l)for(var u=0;u=f.to||f.from==null&&i!=e.line||f.from!=null&&i==t.line&&f.from>=t.ch)&&(!n||n(f.marker))&&r.push(f.marker.parent||f.marker)}++i}),r},getAllMarks:function(){var e=[];return this.iter(function(t){var n=t.markedSpans;if(n)for(var r=0;re)return t=e,!0;e-=a,++n}),Re(this,ne(n,t))},indexFromPos:function(e){e=Re(this,e);var t=e.ch;if(e.linet&&(t=e.from),e.to!=null&&e.to-1){t.state.draggingText(e),setTimeout(function(){return t.display.input.focus()},20);return}try{var A=e.dataTransfer.getData("Text");if(A){var j;if(t.state.draggingText&&!t.state.draggingText.copy&&(j=t.listSelections()),ki(t.doc,yr(n,n)),j)for(var ee=0;ee=0;u--)ln(e.doc,"",r[u].from,r[u].to,"+delete");tn(e)})}function Po(e,t,n){var r=se(e.text,t+n,n);return r<0||r>e.text.length?null:r}function jo(e,t,n){var r=Po(e,t.ch,n);return r==null?null:new ne(t.line,r,n<0?"after":"before")}function Ro(e,t,n,r,i){if(e){t.doc.direction=="rtl"&&(i=-i);var a=Pe(n,t.doc.direction);if(a){var l=i<0?ce(a):a[0],u=i<0==(l.level==1),f=u?"after":"before",m;if(l.level>0||t.doc.direction=="rtl"){var A=Qr(t,n);m=i<0?n.text.length-1:0;var j=tr(t,A,m).top;m=De(function(ee){return tr(t,A,ee).top==j},i<0==(l.level==1)?l.from:l.to-1,m),f=="before"&&(m=Po(n,m,1))}else m=i<0?l.to:l.from;return new ne(r,m,f)}}return new ne(r,i<0?n.text.length:0,i<0?"before":"after")}function Vf(e,t,n,r){var i=Pe(t,e.doc.direction);if(!i)return jo(t,n,r);n.ch>=t.text.length?(n.ch=t.text.length,n.sticky="before"):n.ch<=0&&(n.ch=0,n.sticky="after");var a=Pt(i,n.ch,n.sticky),l=i[a];if(e.doc.direction=="ltr"&&l.level%2==0&&(r>0?l.to>n.ch:l.from=l.from&&ee>=A.begin)){var Y=j?"before":"after";return new ne(n.line,ee,Y)}}var ie=function(ve,_e,be){for(var Ce=function(Ve,vt){return vt?new ne(n.line,u(Ve,1),"before"):new ne(n.line,Ve,"after")};ve>=0&&ve0==(Ne.level!=1),$e=Fe?be.begin:u(be.end,-1);if(Ne.from<=$e&&$e0?A.end:u(A.begin,-1);return me!=null&&!(r>0&&me==t.text.length)&&(ue=ie(r>0?0:i.length-1,r,m(me)),ue)?ue:null}var $n={selectAll:Ll,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),ke)},killLine:function(e){return cn(e,function(t){if(t.empty()){var n=Ae(e.doc,t.head.line).text.length;return t.head.ch==n&&t.head.line0)i=new ne(i.line,i.ch+1),e.replaceRange(a.charAt(i.ch-1)+a.charAt(i.ch-2),ne(i.line,i.ch-2),i,"+transpose");else if(i.line>e.doc.first){var l=Ae(e.doc,i.line-1).text;l&&(i=new ne(i.line,1),e.replaceRange(a.charAt(0)+e.doc.lineSeparator()+l.charAt(l.length-1),ne(i.line-1,l.length-1),i,"+transpose"))}}n.push(new Ye(i,i))}e.setSelections(n)})},newlineAndIndent:function(e){return Nt(e,function(){for(var t=e.listSelections(),n=t.length-1;n>=0;n--)e.replaceRange(e.doc.lineSeparator(),t[n].anchor,t[n].head,"+input");t=e.listSelections();for(var r=0;re&&ye(t,this.pos)==0&&n==this.button};var Gn,Zn;function od(e,t){var n=+new Date;return Zn&&Zn.compare(n,e,t)?(Gn=Zn=null,"triple"):Gn&&Gn.compare(n,e,t)?(Zn=new Bo(n,e,t),Gn=null,"double"):(Gn=new Bo(n,e,t),Zn=null,"single")}function Yl(e){var t=this,n=t.display;if(!(ot(t,e)||n.activeTouch&&n.input.supportsTouch())){if(n.input.ensurePolled(),n.shift=e.shiftKey,lr(n,e)){g||(n.scroller.draggable=!1,setTimeout(function(){return n.scroller.draggable=!0},100));return}if(!Wo(t,e)){var r=Mr(t,e),i=Ut(e),a=r?od(r,i):"single";pe(t).focus(),i==1&&t.state.selectingText&&t.state.selectingText(e),!(r&&ad(t,i,r,a,e))&&(i==1?r?sd(t,r,a,e):yn(e)==n.scroller&&kt(e):i==2?(r&&_i(t.doc,r),setTimeout(function(){return n.input.focus()},20)):i==3&&(I?t.display.input.onContextMenu(e):wo(t)))}}}function ad(e,t,n,r,i){var a="Click";return r=="double"?a="Double"+a:r=="triple"&&(a="Triple"+a),a=(t==1?"Left":t==2?"Middle":"Right")+a,Kn(e,Hl(a,i),i,function(l){if(typeof l=="string"&&(l=$n[l]),!l)return!1;var u=!1;try{e.isReadOnly()&&(e.state.suppressEdits=!0),u=l(e,n)!=Ze}finally{e.state.suppressEdits=!1}return u})}function ld(e,t,n){var r=e.getOption("configureMouse"),i=r?r(e,t,n):{};if(i.unit==null){var a=G?n.shiftKey&&n.metaKey:n.altKey;i.unit=a?"rectangle":t=="single"?"char":t=="double"?"word":"line"}return(i.extend==null||e.doc.extend)&&(i.extend=e.doc.extend||n.shiftKey),i.addNew==null&&(i.addNew=O?n.metaKey:n.ctrlKey),i.moveOnDrag==null&&(i.moveOnDrag=!(O?n.altKey:n.ctrlKey)),i}function sd(e,t,n,r){s?setTimeout(Ee(Qa,e),0):e.curOp.focus=H(de(e));var i=ld(e,n,r),a=e.doc.sel,l;e.options.dragDrop&&eo&&!e.isReadOnly()&&n=="single"&&(l=a.contains(t))>-1&&(ye((l=a.ranges[l]).from(),t)<0||t.xRel>0)&&(ye(l.to(),t)>0||t.xRel<0)?ud(e,r,t,i):cd(e,r,t,i)}function ud(e,t,n,r){var i=e.display,a=!1,l=gt(e,function(m){g&&(i.scroller.draggable=!1),e.state.draggingText=!1,e.state.delayingBlurEvent&&(e.hasFocus()?e.state.delayingBlurEvent=!1:wo(e)),_t(i.wrapper.ownerDocument,"mouseup",l),_t(i.wrapper.ownerDocument,"mousemove",u),_t(i.scroller,"dragstart",f),_t(i.scroller,"drop",l),a||(kt(m),r.addNew||_i(e.doc,n,null,null,r.extend),g&&!k||s&&h==9?setTimeout(function(){i.wrapper.ownerDocument.body.focus({preventScroll:!0}),i.input.focus()},20):i.input.focus())}),u=function(m){a=a||Math.abs(t.clientX-m.clientX)+Math.abs(t.clientY-m.clientY)>=10},f=function(){return a=!0};g&&(i.scroller.draggable=!0),e.state.draggingText=l,l.copy=!r.moveOnDrag,Ie(i.wrapper.ownerDocument,"mouseup",l),Ie(i.wrapper.ownerDocument,"mousemove",u),Ie(i.scroller,"dragstart",f),Ie(i.scroller,"drop",l),e.state.delayingBlurEvent=!0,setTimeout(function(){return i.input.focus()},20),i.scroller.dragDrop&&i.scroller.dragDrop()}function Ql(e,t,n){if(n=="char")return new Ye(t,t);if(n=="word")return e.findWordAt(t);if(n=="line")return new Ye(ne(t.line,0),Re(e.doc,ne(t.line+1,0)));var r=n(e,t);return new Ye(r.from,r.to)}function cd(e,t,n,r){s&&wo(e);var i=e.display,a=e.doc;kt(t);var l,u,f=a.sel,m=f.ranges;if(r.addNew&&!r.extend?(u=a.sel.contains(n),u>-1?l=m[u]:l=new Ye(n,n)):(l=a.sel.primary(),u=a.sel.primIndex),r.unit=="rectangle")r.addNew||(l=new Ye(n,n)),n=Mr(e,t,!0,!0),u=-1;else{var A=Ql(e,n,r.unit);r.extend?l=No(l,A.anchor,A.head,r.extend):l=A}r.addNew?u==-1?(u=m.length,wt(a,Yt(e,m.concat([l]),u),{scroll:!1,origin:"*mouse"})):m.length>1&&m[u].empty()&&r.unit=="char"&&!r.extend?(wt(a,Yt(e,m.slice(0,u).concat(m.slice(u+1)),0),{scroll:!1,origin:"*mouse"}),f=a.sel):Oo(a,u,l,Je):(u=0,wt(a,new Rt([l],0),Je),f=a.sel);var j=n;function ee(be){if(ye(j,be)!=0)if(j=be,r.unit=="rectangle"){for(var Ce=[],Ne=e.options.tabSize,Fe=Oe(Ae(a,n.line).text,n.ch,Ne),$e=Oe(Ae(a,be.line).text,be.ch,Ne),Ve=Math.min(Fe,$e),vt=Math.max(Fe,$e),rt=Math.min(n.line,be.line),Ot=Math.min(e.lastLine(),Math.max(n.line,be.line));rt<=Ot;rt++){var At=Ae(a,rt).text,ut=Ge(At,Ve,Ne);Ve==vt?Ce.push(new Ye(ne(rt,ut),ne(rt,ut))):At.length>ut&&Ce.push(new Ye(ne(rt,ut),ne(rt,Ge(At,vt,Ne))))}Ce.length||Ce.push(new Ye(n,n)),wt(a,Yt(e,f.ranges.slice(0,u).concat(Ce),u),{origin:"*mouse",scroll:!1}),e.scrollIntoView(be)}else{var Dt=l,yt=Ql(e,be,r.unit),ft=Dt.anchor,ct;ye(yt.anchor,ft)>0?(ct=yt.head,ft=Zr(Dt.from(),yt.anchor)):(ct=yt.anchor,ft=Et(Dt.to(),yt.head));var lt=f.ranges.slice(0);lt[u]=fd(e,new Ye(Re(a,ft),ct)),wt(a,Yt(e,lt,u),Je)}}var Y=i.wrapper.getBoundingClientRect(),ie=0;function ue(be){var Ce=++ie,Ne=Mr(e,be,!0,r.unit=="rectangle");if(Ne)if(ye(Ne,j)!=0){e.curOp.focus=H(de(e)),ee(Ne);var Fe=gi(i,a);(Ne.line>=Fe.to||Ne.lineY.bottom?20:0;$e&&setTimeout(gt(e,function(){ie==Ce&&(i.scroller.scrollTop+=$e,ue(be))}),50)}}function me(be){e.state.selectingText=!1,ie=1/0,be&&(kt(be),i.input.focus()),_t(i.wrapper.ownerDocument,"mousemove",ve),_t(i.wrapper.ownerDocument,"mouseup",_e),a.history.lastSelOrigin=null}var ve=gt(e,function(be){be.buttons===0||!Ut(be)?me(be):ue(be)}),_e=gt(e,me);e.state.selectingText=_e,Ie(i.wrapper.ownerDocument,"mousemove",ve),Ie(i.wrapper.ownerDocument,"mouseup",_e)}function fd(e,t){var n=t.anchor,r=t.head,i=Ae(e.doc,n.line);if(ye(n,r)==0&&n.sticky==r.sticky)return t;var a=Pe(i);if(!a)return t;var l=Pt(a,n.ch,n.sticky),u=a[l];if(u.from!=n.ch&&u.to!=n.ch)return t;var f=l+(u.from==n.ch==(u.level!=1)?0:1);if(f==0||f==a.length)return t;var m;if(r.line!=n.line)m=(r.line-n.line)*(e.doc.direction=="ltr"?1:-1)>0;else{var A=Pt(a,r.ch,r.sticky),j=A-l||(r.ch-n.ch)*(u.level==1?-1:1);A==f-1||A==f?m=j<0:m=j>0}var ee=a[f+(m?-1:0)],Y=m==(ee.level==1),ie=Y?ee.from:ee.to,ue=Y?"after":"before";return n.ch==ie&&n.sticky==ue?t:new Ye(new ne(n.line,ie,ue),r)}function Vl(e,t,n,r){var i,a;if(t.touches)i=t.touches[0].clientX,a=t.touches[0].clientY;else try{i=t.clientX,a=t.clientY}catch{return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&kt(t);var l=e.display,u=l.lineDiv.getBoundingClientRect();if(a>u.bottom||!It(e,n))return Ct(t);a-=u.top-l.viewOffset;for(var f=0;f=i){var A=P(e.doc,a),j=e.display.gutterSpecs[f];return it(e,n,e,A,j.className,t),Ct(t)}}}function Wo(e,t){return Vl(e,t,"gutterClick",!0)}function Jl(e,t){lr(e.display,t)||dd(e,t)||ot(e,t,"contextmenu")||I||e.display.input.onContextMenu(t)}function dd(e,t){return It(e,"gutterContextMenu")?Vl(e,t,"gutterContextMenu",!1):!1}function es(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),En(e)}var fn={toString:function(){return"CodeMirror.Init"}},ts={},Ei={};function pd(e){var t=e.optionHandlers;function n(r,i,a,l){e.defaults[r]=i,a&&(t[r]=l?function(u,f,m){m!=fn&&a(u,f,m)}:a)}e.defineOption=n,e.Init=fn,n("value","",function(r,i){return r.setValue(i)},!0),n("mode",null,function(r,i){r.doc.modeOption=i,qo(r)},!0),n("indentUnit",2,qo,!0),n("indentWithTabs",!1),n("smartIndent",!0),n("tabSize",4,function(r){Nn(r),En(r),zt(r)},!0),n("lineSeparator",null,function(r,i){if(r.doc.lineSep=i,!!i){var a=[],l=r.doc.first;r.doc.iter(function(f){for(var m=0;;){var A=f.text.indexOf(i,m);if(A==-1)break;m=A+i.length,a.push(ne(l,A))}l++});for(var u=a.length-1;u>=0;u--)ln(r.doc,i,a[u],ne(a[u].line,a[u].ch+i.length))}}),n("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g,function(r,i,a){r.state.specialChars=new RegExp(i.source+(i.test(" ")?"":"| "),"g"),a!=fn&&r.refresh()}),n("specialCharPlaceholder",Hc,function(r){return r.refresh()},!0),n("electricChars",!0),n("inputStyle",E?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},!0),n("spellcheck",!1,function(r,i){return r.getInputField().spellcheck=i},!0),n("autocorrect",!1,function(r,i){return r.getInputField().autocorrect=i},!0),n("autocapitalize",!1,function(r,i){return r.getInputField().autocapitalize=i},!0),n("rtlMoveVisually",!J),n("wholeLineUpdateBefore",!0),n("theme","default",function(r){es(r),In(r)},!0),n("keyMap","default",function(r,i,a){var l=Li(i),u=a!=fn&&Li(a);u&&u.detach&&u.detach(r,l),l.attach&&l.attach(r,u||null)}),n("extraKeys",null),n("configureMouse",null),n("lineWrapping",!1,gd,!0),n("gutters",[],function(r,i){r.display.gutterSpecs=Ao(i,r.options.lineNumbers),In(r)},!0),n("fixedGutter",!0,function(r,i){r.display.gutters.style.left=i?yo(r.display)+"px":"0",r.refresh()},!0),n("coverGutterNextToScrollbar",!1,function(r){return rn(r)},!0),n("scrollbarStyle","native",function(r){nl(r),rn(r),r.display.scrollbars.setScrollTop(r.doc.scrollTop),r.display.scrollbars.setScrollLeft(r.doc.scrollLeft)},!0),n("lineNumbers",!1,function(r,i){r.display.gutterSpecs=Ao(r.options.gutters,i),In(r)},!0),n("firstLineNumber",1,In,!0),n("lineNumberFormatter",function(r){return r},In,!0),n("showCursorWhenSelecting",!1,zn,!0),n("resetSelectionOnContextMenu",!0),n("lineWiseCopyCut",!0),n("pasteLinesPerSelection",!0),n("selectionsMayTouch",!1),n("readOnly",!1,function(r,i){i=="nocursor"&&(en(r),r.display.input.blur()),r.display.input.readOnlyChanged(i)}),n("screenReaderLabel",null,function(r,i){i=i===""?null:i,r.display.input.screenReaderLabelChanged(i)}),n("disableInput",!1,function(r,i){i||r.display.input.reset()},!0),n("dragDrop",!0,hd),n("allowDropFileTypes",null),n("cursorBlinkRate",530),n("cursorScrollMargin",0),n("cursorHeight",1,zn,!0),n("singleCursorHeightPerLine",!0,zn,!0),n("workTime",100),n("workDelay",100),n("flattenSpans",!0,Nn,!0),n("addModeClass",!1,Nn,!0),n("pollInterval",100),n("undoDepth",200,function(r,i){return r.doc.history.undoDepth=i}),n("historyEventDelay",1250),n("viewportMargin",10,function(r){return r.refresh()},!0),n("maxHighlightLength",1e4,Nn,!0),n("moveInputWithCursor",!0,function(r,i){i||r.display.input.resetPosition()}),n("tabindex",null,function(r,i){return r.display.input.getField().tabIndex=i||""}),n("autofocus",null),n("direction","ltr",function(r,i){return r.doc.setDirection(i)},!0),n("phrases",null)}function hd(e,t,n){var r=n&&n!=fn;if(!t!=!r){var i=e.display.dragFunctions,a=t?Ie:_t;a(e.display.scroller,"dragstart",i.start),a(e.display.scroller,"dragenter",i.enter),a(e.display.scroller,"dragover",i.over),a(e.display.scroller,"dragleave",i.leave),a(e.display.scroller,"drop",i.drop)}}function gd(e){e.options.lineWrapping?(le(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(Q(e.display.wrapper,"CodeMirror-wrap"),so(e)),xo(e),zt(e),En(e),setTimeout(function(){return rn(e)},100)}function tt(e,t){var n=this;if(!(this instanceof tt))return new tt(e,t);this.options=t=t?ge(t):{},ge(ts,t,!1);var r=t.value;typeof r=="string"?r=new Mt(r,t.mode,null,t.lineSeparator,t.direction):t.mode&&(r.modeOption=t.mode),this.doc=r;var i=new tt.inputStyles[t.inputStyle](this),a=this.display=new Ef(e,r,i,t);a.wrapper.CodeMirror=this,es(this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),nl(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new qe,keySeq:null,specialChars:null},t.autofocus&&!E&&a.input.focus(),s&&h<11&&setTimeout(function(){return n.display.input.reset(!0)},20),md(this),Gf(),Fr(this),this.curOp.forceUpdate=!0,pl(this,r),t.autofocus&&!E||this.hasFocus()?setTimeout(function(){n.hasFocus()&&!n.state.focused&&So(n)},20):en(this);for(var l in Ei)Ei.hasOwnProperty(l)&&Ei[l](this,t[l],fn);al(this),t.finishInit&&t.finishInit(this);for(var u=0;u20*20}Ie(t.scroller,"touchstart",function(f){if(!ot(e,f)&&!a(f)&&!Wo(e,f)){t.input.ensurePolled(),clearTimeout(n);var m=+new Date;t.activeTouch={start:m,moved:!1,prev:m-r.end<=300?r:null},f.touches.length==1&&(t.activeTouch.left=f.touches[0].pageX,t.activeTouch.top=f.touches[0].pageY)}}),Ie(t.scroller,"touchmove",function(){t.activeTouch&&(t.activeTouch.moved=!0)}),Ie(t.scroller,"touchend",function(f){var m=t.activeTouch;if(m&&!lr(t,f)&&m.left!=null&&!m.moved&&new Date-m.start<300){var A=e.coordsChar(t.activeTouch,"page"),j;!m.prev||l(m,m.prev)?j=new Ye(A,A):!m.prev.prev||l(m,m.prev.prev)?j=e.findWordAt(A):j=new Ye(ne(A.line,0),Re(e.doc,ne(A.line+1,0))),e.setSelection(j.anchor,j.head),e.focus(),kt(f)}i()}),Ie(t.scroller,"touchcancel",i),Ie(t.scroller,"scroll",function(){t.scroller.clientHeight&&(An(e,t.scroller.scrollTop),Dr(e,t.scroller.scrollLeft,!0),it(e,"scroll",e))}),Ie(t.scroller,"mousewheel",function(f){return ul(e,f)}),Ie(t.scroller,"DOMMouseScroll",function(f){return ul(e,f)}),Ie(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0}),t.dragFunctions={enter:function(f){ot(e,f)||dr(f)},over:function(f){ot(e,f)||(Kf(e,f),dr(f))},start:function(f){return $f(e,f)},drop:gt(e,Uf),leave:function(f){ot(e,f)||Ol(e)}};var u=t.input.getField();Ie(u,"keyup",function(f){return Zl.call(e,f)}),Ie(u,"keydown",gt(e,Gl)),Ie(u,"keypress",gt(e,Xl)),Ie(u,"focus",function(f){return So(e,f)}),Ie(u,"blur",function(f){return en(e,f)})}var Uo=[];tt.defineInitHook=function(e){return Uo.push(e)};function Xn(e,t,n,r){var i=e.doc,a;n==null&&(n="add"),n=="smart"&&(i.mode.indent?a=wn(e,t).state:n="prev");var l=e.options.tabSize,u=Ae(i,t),f=Oe(u.text,null,l);u.stateAfter&&(u.stateAfter=null);var m=u.text.match(/^\s*/)[0],A;if(!r&&!/\S/.test(u.text))A=0,n="not";else if(n=="smart"&&(A=i.mode.indent(a,u.text.slice(m.length),u.text),A==Ze||A>150)){if(!r)return;n="prev"}n=="prev"?t>i.first?A=Oe(Ae(i,t-1).text,null,l):A=0:n=="add"?A=f+e.options.indentUnit:n=="subtract"?A=f-e.options.indentUnit:typeof n=="number"&&(A=f+n),A=Math.max(0,A);var j="",ee=0;if(e.options.indentWithTabs)for(var Y=Math.floor(A/l);Y;--Y)ee+=l,j+=" ";if(eel,f=Bt(t),m=null;if(u&&r.ranges.length>1)if(Qt&&Qt.text.join(` -`)==t){if(r.ranges.length%Qt.text.length==0){m=[];for(var A=0;A=0;ee--){var Y=r.ranges[ee],ie=Y.from(),ue=Y.to();Y.empty()&&(n&&n>0?ie=ne(ie.line,ie.ch-n):e.state.overwrite&&!u?ue=ne(ue.line,Math.min(Ae(a,ue.line).text.length,ue.ch+ce(f).length)):u&&Qt&&Qt.lineWise&&Qt.text.join(` -`)==f.join(` -`)&&(ie=ue=ne(ie.line,0)));var me={from:ie,to:ue,text:m?m[ee%m.length]:f,origin:i||(u?"paste":e.state.cutIncoming>l?"cut":"+input")};an(e.doc,me),ht(e,"inputRead",e,me)}t&&!u&&ns(e,t),tn(e),e.curOp.updateInput<2&&(e.curOp.updateInput=j),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function rs(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");if(n)return e.preventDefault(),!t.isReadOnly()&&!t.options.disableInput&&t.hasFocus()&&Nt(t,function(){return $o(t,n,0,null,"paste")}),!0}function ns(e,t){if(!(!e.options.electricChars||!e.options.smartIndent))for(var n=e.doc.sel,r=n.ranges.length-1;r>=0;r--){var i=n.ranges[r];if(!(i.head.ch>100||r&&n.ranges[r-1].head.line==i.head.line)){var a=e.getModeAt(i.head),l=!1;if(a.electricChars){for(var u=0;u-1){l=Xn(e,i.head.line,"smart");break}}else a.electricInput&&a.electricInput.test(Ae(e.doc,i.head.line).text.slice(0,i.head.ch))&&(l=Xn(e,i.head.line,"smart"));l&&ht(e,"electricInput",e,i.head.line)}}}function is(e){for(var t=[],n=[],r=0;ra&&(Xn(this,u.head.line,r,!0),a=u.head.line,l==this.doc.sel.primIndex&&tn(this));else{var f=u.from(),m=u.to(),A=Math.max(a,f.line);a=Math.min(this.lastLine(),m.line-(m.ch?0:1))+1;for(var j=A;j0&&Oo(this.doc,l,new Ye(f,ee[l].to()),ke)}}}),getTokenAt:function(r,i){return ga(this,r,i)},getLineTokens:function(r,i){return ga(this,ne(r),i,!0)},getTokenTypeAt:function(r){r=Re(this.doc,r);var i=da(this,Ae(this.doc,r.line)),a=0,l=(i.length-1)/2,u=r.ch,f;if(u==0)f=i[2];else for(;;){var m=a+l>>1;if((m?i[m*2-1]:0)>=u)l=m;else if(i[m*2+1]f&&(r=f,l=!0),u=Ae(this.doc,r)}else u=r;return ci(this,u,{top:0,left:0},i||"page",a||l).top+(l?this.doc.height-ar(u):0)},defaultTextHeight:function(){return Vr(this.display)},defaultCharWidth:function(){return Jr(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(r,i,a,l,u){var f=this.display;r=Xt(this,Re(this.doc,r));var m=r.bottom,A=r.left;if(i.style.position="absolute",i.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(i),f.sizer.appendChild(i),l=="over")m=r.top;else if(l=="above"||l=="near"){var j=Math.max(f.wrapper.clientHeight,this.doc.height),ee=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);(l=="above"||r.bottom+i.offsetHeight>j)&&r.top>i.offsetHeight?m=r.top-i.offsetHeight:r.bottom+i.offsetHeight<=j&&(m=r.bottom),A+i.offsetWidth>ee&&(A=ee-i.offsetWidth)}i.style.top=m+"px",i.style.left=i.style.right="",u=="right"?(A=f.sizer.clientWidth-i.offsetWidth,i.style.right="0px"):(u=="left"?A=0:u=="middle"&&(A=(f.sizer.clientWidth-i.offsetWidth)/2),i.style.left=A+"px"),a&&hf(this,{left:A,top:m,right:A+i.offsetWidth,bottom:m+i.offsetHeight})},triggerOnKeyDown:Tt(Gl),triggerOnKeyPress:Tt(Xl),triggerOnKeyUp:Zl,triggerOnMouseDown:Tt(Yl),execCommand:function(r){if($n.hasOwnProperty(r))return $n[r].call(null,this)},triggerElectric:Tt(function(r){ns(this,r)}),findPosH:function(r,i,a,l){var u=1;i<0&&(u=-1,i=-i);for(var f=Re(this.doc,r),m=0;m0&&A(a.charAt(l-1));)--l;for(;u.5||this.options.lineWrapping)&&xo(this),it(this,"refresh",this)}),swapDoc:Tt(function(r){var i=this.doc;return i.cm=null,this.state.selectingText&&this.state.selectingText(),pl(this,r),En(this),this.display.input.reset(),Mn(this,r.scrollLeft,r.scrollTop),this.curOp.forceScroll=!0,ht(this,"swapDoc",this,i),i}),phrase:function(r){var i=this.options.phrases;return i&&Object.prototype.hasOwnProperty.call(i,r)?i[r]:r},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},Wt(e),e.registerHelper=function(r,i,a){n.hasOwnProperty(r)||(n[r]=e[r]={_global:[]}),n[r][i]=a},e.registerGlobalHelper=function(r,i,a,l){e.registerHelper(r,i,l),n[r]._global.push({pred:a,val:l})}}function Go(e,t,n,r,i){var a=t,l=n,u=Ae(e,t.line),f=i&&e.direction=="rtl"?-n:n;function m(){var _e=t.line+f;return _e=e.first+e.size?!1:(t=new ne(_e,t.ch,t.sticky),u=Ae(e,_e))}function A(_e){var be;if(r=="codepoint"){var Ce=u.text.charCodeAt(t.ch+(n>0?0:-1));if(isNaN(Ce))be=null;else{var Ne=n>0?Ce>=55296&&Ce<56320:Ce>=56320&&Ce<57343;be=new ne(t.line,Math.max(0,Math.min(u.text.length,t.ch+n*(Ne?2:1))),-n)}}else i?be=Vf(e.cm,u,t,n):be=jo(u,t,n);if(be==null)if(!_e&&m())t=Ro(i,e.cm,u,t.line,f);else return!1;else t=be;return!0}if(r=="char"||r=="codepoint")A();else if(r=="column")A(!0);else if(r=="word"||r=="group")for(var j=null,ee=r=="group",Y=e.cm&&e.cm.getHelper(t,"wordChars"),ie=!0;!(n<0&&!A(!ie));ie=!1){var ue=u.text.charAt(t.ch)||` -`,me=Me(ue,Y)?"w":ee&&ue==` -`?"n":!ee||/\s/.test(ue)?null:"p";if(ee&&!ie&&!me&&(me="s"),j&&j!=me){n<0&&(n=1,A(),t.sticky="after");break}if(me&&(j=me),n>0&&!A(!ie))break}var ve=wi(e,t,a,l,!0);return Xe(a,ve)&&(ve.hitSide=!0),ve}function as(e,t,n,r){var i=e.doc,a=t.left,l;if(r=="page"){var u=Math.min(e.display.wrapper.clientHeight,pe(e).innerHeight||i(e).documentElement.clientHeight),f=Math.max(u-.5*Vr(e.display),3);l=(n>0?t.bottom:t.top)+n*f}else r=="line"&&(l=n>0?t.bottom+3:t.top-3);for(var m;m=mo(e,a,l),!!m.outside;){if(n<0?l<=0:l>=i.height){m.hitSide=!0;break}l+=n*5}return m}var Qe=function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new qe,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null};Qe.prototype.init=function(e){var t=this,n=this,r=n.cm,i=n.div=e.lineDiv;i.contentEditable=!0,Ko(i,r.options.spellcheck,r.options.autocorrect,r.options.autocapitalize);function a(u){for(var f=u.target;f;f=f.parentNode){if(f==i)return!0;if(/\bCodeMirror-(?:line)?widget\b/.test(f.className))break}return!1}Ie(i,"paste",function(u){!a(u)||ot(r,u)||rs(u,r)||h<=11&&setTimeout(gt(r,function(){return t.updateFromDOM()}),20)}),Ie(i,"compositionstart",function(u){t.composing={data:u.data,done:!1}}),Ie(i,"compositionupdate",function(u){t.composing||(t.composing={data:u.data,done:!1})}),Ie(i,"compositionend",function(u){t.composing&&(u.data!=t.composing.data&&t.readFromDOMSoon(),t.composing.done=!0)}),Ie(i,"touchstart",function(){return n.forceCompositionEnd()}),Ie(i,"input",function(){t.composing||t.readFromDOMSoon()});function l(u){if(!(!a(u)||ot(r,u))){if(r.somethingSelected())zi({lineWise:!1,text:r.getSelections()}),u.type=="cut"&&r.replaceSelection("",null,"cut");else if(r.options.lineWiseCopyCut){var f=is(r);zi({lineWise:!0,text:f.text}),u.type=="cut"&&r.operation(function(){r.setSelections(f.ranges,0,ke),r.replaceSelection("",null,"cut")})}else return;if(u.clipboardData){u.clipboardData.clearData();var m=Qt.text.join(` -`);if(u.clipboardData.setData("Text",m),u.clipboardData.getData("Text")==m){u.preventDefault();return}}var A=os(),j=A.firstChild;Ko(j),r.display.lineSpace.insertBefore(A,r.display.lineSpace.firstChild),j.value=Qt.text.join(` -`);var ee=H(ze(i));F(j),setTimeout(function(){r.display.lineSpace.removeChild(A),ee.focus(),ee==i&&n.showPrimarySelection()},50)}}Ie(i,"copy",l),Ie(i,"cut",l)},Qe.prototype.screenReaderLabelChanged=function(e){e?this.div.setAttribute("aria-label",e):this.div.removeAttribute("aria-label")},Qe.prototype.prepareSelection=function(){var e=Ya(this.cm,!1);return e.focus=H(ze(this.div))==this.div,e},Qe.prototype.showSelection=function(e,t){!e||!this.cm.display.view.length||((e.focus||t)&&this.showPrimarySelection(),this.showMultipleSelections(e))},Qe.prototype.getSelection=function(){return this.cm.display.wrapper.ownerDocument.getSelection()},Qe.prototype.showPrimarySelection=function(){var e=this.getSelection(),t=this.cm,n=t.doc.sel.primary(),r=n.from(),i=n.to();if(t.display.viewTo==t.display.viewFrom||r.line>=t.display.viewTo||i.line=t.display.viewFrom&&ls(t,r)||{node:u[0].measure.map[2],offset:0},m=i.linee.firstLine()&&(r=ne(r.line-1,Ae(e.doc,r.line-1).length)),i.ch==Ae(e.doc,i.line).text.length&&i.linet.viewTo-1)return!1;var a,l,u;r.line==t.viewFrom||(a=Ar(e,r.line))==0?(l=_(t.view[0].line),u=t.view[0].node):(l=_(t.view[a].line),u=t.view[a-1].node.nextSibling);var f=Ar(e,i.line),m,A;if(f==t.view.length-1?(m=t.viewTo-1,A=t.lineDiv.lastChild):(m=_(t.view[f+1].line)-1,A=t.view[f+1].node.previousSibling),!u)return!1;for(var j=e.doc.splitLines(yd(e,u,A,l,m)),ee=ir(e.doc,ne(l,0),ne(m,Ae(e.doc,m).text.length));j.length>1&&ee.length>1;)if(ce(j)==ce(ee))j.pop(),ee.pop(),m--;else if(j[0]==ee[0])j.shift(),ee.shift(),l++;else break;for(var Y=0,ie=0,ue=j[0],me=ee[0],ve=Math.min(ue.length,me.length);Yr.ch&&_e.charCodeAt(_e.length-ie-1)==be.charCodeAt(be.length-ie-1);)Y--,ie++;j[j.length-1]=_e.slice(0,_e.length-ie).replace(/^\u200b+/,""),j[0]=j[0].slice(Y).replace(/\u200b+$/,"");var Ne=ne(l,Y),Fe=ne(m,ee.length?ce(ee).length-ie:0);if(j.length>1||j[0]||ye(Ne,Fe))return ln(e.doc,j,Ne,Fe,"+input"),!0},Qe.prototype.ensurePolled=function(){this.forceCompositionEnd()},Qe.prototype.reset=function(){this.forceCompositionEnd()},Qe.prototype.forceCompositionEnd=function(){this.composing&&(clearTimeout(this.readDOMTimeout),this.composing=null,this.updateFromDOM(),this.div.blur(),this.div.focus())},Qe.prototype.readFromDOMSoon=function(){var e=this;this.readDOMTimeout==null&&(this.readDOMTimeout=setTimeout(function(){if(e.readDOMTimeout=null,e.composing)if(e.composing.done)e.composing=null;else return;e.updateFromDOM()},80))},Qe.prototype.updateFromDOM=function(){var e=this;(this.cm.isReadOnly()||!this.pollContent())&&Nt(this.cm,function(){return zt(e.cm)})},Qe.prototype.setUneditable=function(e){e.contentEditable="false"},Qe.prototype.onKeyPress=function(e){e.charCode==0||this.composing||(e.preventDefault(),this.cm.isReadOnly()||gt(this.cm,$o)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0))},Qe.prototype.readOnlyChanged=function(e){this.div.contentEditable=String(e!="nocursor")},Qe.prototype.onContextMenu=function(){},Qe.prototype.resetPosition=function(){},Qe.prototype.needsContentAttribute=!0;function ls(e,t){var n=po(e,t.line);if(!n||n.hidden)return null;var r=Ae(e.doc,t.line),i=Na(n,r,t.line),a=Pe(r,e.doc.direction),l="left";if(a){var u=Pt(a,t.ch);l=u%2?"right":"left"}var f=ja(i.map,t.ch,l);return f.offset=f.collapse=="right"?f.end:f.start,f}function bd(e){for(var t=e;t;t=t.parentNode)if(/CodeMirror-gutter-wrapper/.test(t.className))return!0;return!1}function dn(e,t){return t&&(e.bad=!0),e}function yd(e,t,n,r,i){var a="",l=!1,u=e.doc.lineSeparator(),f=!1;function m(Y){return function(ie){return ie.id==Y}}function A(){l&&(a+=u,f&&(a+=u),l=f=!1)}function j(Y){Y&&(A(),a+=Y)}function ee(Y){if(Y.nodeType==1){var ie=Y.getAttribute("cm-text");if(ie){j(ie);return}var ue=Y.getAttribute("cm-marker"),me;if(ue){var ve=e.findMarks(ne(r,0),ne(i+1,0),m(+ue));ve.length&&(me=ve[0].find(0))&&j(ir(e.doc,me.from,me.to).join(u));return}if(Y.getAttribute("contenteditable")=="false")return;var _e=/^(pre|div|p|li|table|br)$/i.test(Y.nodeName);if(!/^br$/i.test(Y.nodeName)&&Y.textContent.length==0)return;_e&&A();for(var be=0;be=9&&t.hasSelection&&(t.hasSelection=null),n.poll()}),Ie(i,"paste",function(l){ot(r,l)||rs(l,r)||(r.state.pasteIncoming=+new Date,n.fastPoll())});function a(l){if(!ot(r,l)){if(r.somethingSelected())zi({lineWise:!1,text:r.getSelections()});else if(r.options.lineWiseCopyCut){var u=is(r);zi({lineWise:!0,text:u.text}),l.type=="cut"?r.setSelections(u.ranges,null,ke):(n.prevInput="",i.value=u.text.join(` -`),F(i))}else return;l.type=="cut"&&(r.state.cutIncoming=+new Date)}}Ie(i,"cut",a),Ie(i,"copy",a),Ie(e.scroller,"paste",function(l){if(!(lr(e,l)||ot(r,l))){if(!i.dispatchEvent){r.state.pasteIncoming=+new Date,n.focus();return}var u=new Event("paste");u.clipboardData=l.clipboardData,i.dispatchEvent(u)}}),Ie(e.lineSpace,"selectstart",function(l){lr(e,l)||kt(l)}),Ie(i,"compositionstart",function(){var l=r.getCursor("from");n.composing&&n.composing.range.clear(),n.composing={start:l,range:r.markText(l,r.getCursor("to"),{className:"CodeMirror-composing"})}}),Ie(i,"compositionend",function(){n.composing&&(n.poll(),n.composing.range.clear(),n.composing=null)})},st.prototype.createField=function(e){this.wrapper=os(),this.textarea=this.wrapper.firstChild;var t=this.cm.options;Ko(this.textarea,t.spellcheck,t.autocorrect,t.autocapitalize)},st.prototype.screenReaderLabelChanged=function(e){e?this.textarea.setAttribute("aria-label",e):this.textarea.removeAttribute("aria-label")},st.prototype.prepareSelection=function(){var e=this.cm,t=e.display,n=e.doc,r=Ya(e);if(e.options.moveInputWithCursor){var i=Xt(e,n.sel.primary().head,"div"),a=t.wrapper.getBoundingClientRect(),l=t.lineDiv.getBoundingClientRect();r.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,i.top+l.top-a.top)),r.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,i.left+l.left-a.left))}return r},st.prototype.showSelection=function(e){var t=this.cm,n=t.display;V(n.cursorDiv,e.cursors),V(n.selectionDiv,e.selection),e.teTop!=null&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},st.prototype.reset=function(e){if(!(this.contextMenuPending||this.composing&&e)){var t=this.cm;if(this.resetting=!0,t.somethingSelected()){this.prevInput="";var n=t.getSelection();this.textarea.value=n,t.state.focused&&F(this.textarea),s&&h>=9&&(this.hasSelection=n)}else e||(this.prevInput=this.textarea.value="",s&&h>=9&&(this.hasSelection=null));this.resetting=!1}},st.prototype.getField=function(){return this.textarea},st.prototype.supportsTouch=function(){return!1},st.prototype.focus=function(){if(this.cm.options.readOnly!="nocursor"&&(!E||H(ze(this.textarea))!=this.textarea))try{this.textarea.focus()}catch{}},st.prototype.blur=function(){this.textarea.blur()},st.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0},st.prototype.receivedFocus=function(){this.slowPoll()},st.prototype.slowPoll=function(){var e=this;this.pollingFast||this.polling.set(this.cm.options.pollInterval,function(){e.poll(),e.cm.state.focused&&e.slowPoll()})},st.prototype.fastPoll=function(){var e=!1,t=this;t.pollingFast=!0;function n(){var r=t.poll();!r&&!e?(e=!0,t.polling.set(60,n)):(t.pollingFast=!1,t.slowPoll())}t.polling.set(20,n)},st.prototype.poll=function(){var e=this,t=this.cm,n=this.textarea,r=this.prevInput;if(this.contextMenuPending||this.resetting||!t.state.focused||hr(n)&&!r&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq)return!1;var i=n.value;if(i==r&&!t.somethingSelected())return!1;if(s&&h>=9&&this.hasSelection===i||O&&/[\uf700-\uf7ff]/.test(i))return t.display.input.reset(),!1;if(t.doc.sel==t.display.selForContextMenu){var a=i.charCodeAt(0);if(a==8203&&!r&&(r="\u200B"),a==8666)return this.reset(),this.cm.execCommand("undo")}for(var l=0,u=Math.min(r.length,i.length);l1e3||i.indexOf(` -`)>-1?n.value=e.prevInput="":e.prevInput=i,e.composing&&(e.composing.range.clear(),e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"}))}),!0},st.prototype.ensurePolled=function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},st.prototype.onKeyPress=function(){s&&h>=9&&(this.hasSelection=null),this.fastPoll()},st.prototype.onContextMenu=function(e){var t=this,n=t.cm,r=n.display,i=t.textarea;t.contextMenuPending&&t.contextMenuPending();var a=Mr(n,e),l=r.scroller.scrollTop;if(!a||d)return;var u=n.options.resetSelectionOnContextMenu;u&&n.doc.sel.contains(a)==-1&>(n,wt)(n.doc,yr(a),ke);var f=i.style.cssText,m=t.wrapper.style.cssText,A=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static",i.style.cssText=`position: absolute; width: 30px; height: 30px; - top: `+(e.clientY-A.top-5)+"px; left: "+(e.clientX-A.left-5)+`px; - z-index: 1000; background: `+(s?"rgba(255, 255, 255, .05)":"transparent")+`; - outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);`;var j;g&&(j=i.ownerDocument.defaultView.scrollY),r.input.focus(),g&&i.ownerDocument.defaultView.scrollTo(null,j),r.input.reset(),n.somethingSelected()||(i.value=t.prevInput=" "),t.contextMenuPending=Y,r.selForContextMenu=n.doc.sel,clearTimeout(r.detectingSelectAll);function ee(){if(i.selectionStart!=null){var ue=n.somethingSelected(),me="\u200B"+(ue?i.value:"");i.value="\u21DA",i.value=me,t.prevInput=ue?"":"\u200B",i.selectionStart=1,i.selectionEnd=me.length,r.selForContextMenu=n.doc.sel}}function Y(){if(t.contextMenuPending==Y&&(t.contextMenuPending=!1,t.wrapper.style.cssText=m,i.style.cssText=f,s&&h<9&&r.scrollbars.setScrollTop(r.scroller.scrollTop=l),i.selectionStart!=null)){(!s||s&&h<9)&&ee();var ue=0,me=function(){r.selForContextMenu==n.doc.sel&&i.selectionStart==0&&i.selectionEnd>0&&t.prevInput=="\u200B"?gt(n,Ll)(n):ue++<10?r.detectingSelectAll=setTimeout(me,500):(r.selForContextMenu=null,r.input.reset())};r.detectingSelectAll=setTimeout(me,200)}}if(s&&h>=9&&ee(),I){dr(e);var ie=function(){_t(window,"mouseup",ie),setTimeout(Y,20)};Ie(window,"mouseup",ie)}else setTimeout(Y,50)},st.prototype.readOnlyChanged=function(e){e||this.reset(),this.textarea.disabled=e=="nocursor",this.textarea.readOnly=!!e},st.prototype.setUneditable=function(){},st.prototype.needsContentAttribute=!1;function _d(e,t){if(t=t?ge(t):{},t.value=e.value,!t.tabindex&&e.tabIndex&&(t.tabindex=e.tabIndex),!t.placeholder&&e.placeholder&&(t.placeholder=e.placeholder),t.autofocus==null){var n=H(ze(e));t.autofocus=n==e||e.getAttribute("autofocus")!=null&&n==document.body}function r(){e.value=u.getValue()}var i;if(e.form&&(Ie(e.form,"submit",r),!t.leaveSubmitMethodAlone)){var a=e.form;i=a.submit;try{var l=a.submit=function(){r(),a.submit=i,a.submit(),a.submit=l}}catch{}}t.finishInit=function(f){f.save=r,f.getTextArea=function(){return e},f.toTextArea=function(){f.toTextArea=isNaN,r(),e.parentNode.removeChild(f.getWrapperElement()),e.style.display="",e.form&&(_t(e.form,"submit",r),!t.leaveSubmitMethodAlone&&typeof e.form.submit=="function"&&(e.form.submit=i))}},e.style.display="none";var u=tt(function(f){return e.parentNode.insertBefore(f,e.nextSibling)},t);return u}function kd(e){e.off=_t,e.on=Ie,e.wheelEventPixels=zf,e.Doc=Mt,e.splitLines=Bt,e.countColumn=Oe,e.findColumn=Ge,e.isWordChar=we,e.Pass=Ze,e.signal=it,e.Line=Xr,e.changeEnd=xr,e.scrollbarModel=rl,e.Pos=ne,e.cmpPos=ye,e.modes=Wr,e.mimeModes=Kt,e.resolveMode=Ur,e.getMode=$r,e.modeExtensions=gr,e.extendMode=Kr,e.copyState=Vt,e.startState=Gr,e.innerMode=_n,e.commands=$n,e.keyMap=ur,e.keyName=Bl,e.isModifierKey=Rl,e.lookupKey=un,e.normalizeKeyMap=Qf,e.StringStream=at,e.SharedTextMarker=Bn,e.TextMarker=kr,e.LineWidget=Hn,e.e_preventDefault=kt,e.e_stopPropagation=Hr,e.e_stop=dr,e.addClass=le,e.contains=N,e.rmClass=Q,e.keyNames=wr}pd(tt),vd(tt);var wd="iter insert remove copy getEditor constructor".split(" ");for(var Ai in Mt.prototype)Mt.prototype.hasOwnProperty(Ai)&&Se(wd,Ai)<0&&(tt.prototype[Ai]=function(e){return function(){return e.apply(this.doc,arguments)}}(Mt.prototype[Ai]));return Wt(Mt),tt.inputStyles={textarea:st,contenteditable:Qe},tt.defineMode=function(e){!tt.defaults.mode&&e!="null"&&(tt.defaults.mode=e),Gt.apply(this,arguments)},tt.defineMIME=Cr,tt.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}}),tt.defineMIME("text/plain","null"),tt.defineExtension=function(e,t){tt.prototype[e]=t},tt.defineDocExtension=function(e,t){Mt.prototype[e]=t},tt.fromTextArea=_d,kd(tt),tt.version="5.65.19",tt})});var Yn=Ke((us,cs)=>{(function(o){typeof us=="object"&&typeof cs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.overlayMode=function(p,v,C){return{startState:function(){return{base:o.startState(p),overlay:o.startState(v),basePos:0,baseCur:null,overlayPos:0,overlayCur:null,streamSeen:null}},copyState:function(b){return{base:o.copyState(p,b.base),overlay:o.copyState(v,b.overlay),basePos:b.basePos,baseCur:null,overlayPos:b.overlayPos,overlayCur:null}},token:function(b,S){return(b!=S.streamSeen||Math.min(S.basePos,S.overlayPos){(function(o){typeof fs=="object"&&typeof ds=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";var p=/^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,v=/^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,C=/[*+-]\s/;o.commands.newlineAndIndentContinueMarkdownList=function(S){if(S.getOption("disableInput"))return o.Pass;for(var s=S.listSelections(),h=[],g=0;g\s*$/.test(z),E=!/>\s*$/.test(z);(W||E)&&S.replaceRange("",{line:T.line,ch:0},{line:T.line,ch:T.ch+1}),h[g]=` -`}else{var O=M[1],G=M[5],J=!(C.test(M[2])||M[2].indexOf(">")>=0),re=J?parseInt(M[3],10)+1+M[4]:M[2].replace("x"," ");h[g]=` -`+O+re+G,J&&b(S,T)}}S.replaceSelections(h)};function b(S,s){var h=s.line,g=0,T=0,y=p.exec(S.getLine(h)),c=y[1];do{g+=1;var d=h+g,k=S.getLine(d),z=p.exec(k);if(z){var M=z[1],w=parseInt(y[3],10)+g-T,W=parseInt(z[3],10),E=W;if(c===M&&!isNaN(W))w===W&&(E=W+1),w>W&&(E=w+1),S.replaceRange(k.replace(p,M+E+z[4]+z[5]),{line:d,ch:0},{line:d,ch:k.length});else{if(c.length>M.length||c.length{(function(o){typeof hs=="object"&&typeof gs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){o.defineOption("placeholder","",function(h,g,T){var y=T&&T!=o.Init;if(g&&!y)h.on("blur",b),h.on("change",S),h.on("swapDoc",S),o.on(h.getInputField(),"compositionupdate",h.state.placeholderCompose=function(){C(h)}),S(h);else if(!g&&y){h.off("blur",b),h.off("change",S),h.off("swapDoc",S),o.off(h.getInputField(),"compositionupdate",h.state.placeholderCompose),p(h);var c=h.getWrapperElement();c.className=c.className.replace(" CodeMirror-empty","")}g&&!h.hasFocus()&&b(h)});function p(h){h.state.placeholder&&(h.state.placeholder.parentNode.removeChild(h.state.placeholder),h.state.placeholder=null)}function v(h){p(h);var g=h.state.placeholder=document.createElement("pre");g.style.cssText="height: 0; overflow: visible",g.style.direction=h.getOption("direction"),g.className="CodeMirror-placeholder CodeMirror-line-like";var T=h.getOption("placeholder");typeof T=="string"&&(T=document.createTextNode(T)),g.appendChild(T),h.display.lineSpace.insertBefore(g,h.display.lineSpace.firstChild)}function C(h){setTimeout(function(){var g=!1;if(h.lineCount()==1){var T=h.getInputField();g=T.nodeName=="TEXTAREA"?!h.getLine(0).length:!/[^\u200b]/.test(T.querySelector(".CodeMirror-line").textContent)}g?v(h):p(h)},20)}function b(h){s(h)&&v(h)}function S(h){var g=h.getWrapperElement(),T=s(h);g.className=g.className.replace(" CodeMirror-empty","")+(T?" CodeMirror-empty":""),T?v(h):p(h)}function s(h){return h.lineCount()===1&&h.getLine(0)===""}})});var ys=Ke((vs,bs)=>{(function(o){typeof vs=="object"&&typeof bs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineOption("styleSelectedText",!1,function(y,c,d){var k=d&&d!=o.Init;c&&!k?(y.state.markedSelection=[],y.state.markedSelectionStyle=typeof c=="string"?c:"CodeMirror-selectedtext",g(y),y.on("cursorActivity",p),y.on("change",v)):!c&&k&&(y.off("cursorActivity",p),y.off("change",v),h(y),y.state.markedSelection=y.state.markedSelectionStyle=null)});function p(y){y.state.markedSelection&&y.operation(function(){T(y)})}function v(y){y.state.markedSelection&&y.state.markedSelection.length&&y.operation(function(){h(y)})}var C=8,b=o.Pos,S=o.cmpPos;function s(y,c,d,k){if(S(c,d)!=0)for(var z=y.state.markedSelection,M=y.state.markedSelectionStyle,w=c.line;;){var W=w==c.line?c:b(w,0),E=w+C,O=E>=d.line,G=O?d:b(E,0),J=y.markText(W,G,{className:M});if(k==null?z.push(J):z.splice(k++,0,J),O)break;w=E}}function h(y){for(var c=y.state.markedSelection,d=0;d1)return g(y);var c=y.getCursor("start"),d=y.getCursor("end"),k=y.state.markedSelection;if(!k.length)return s(y,c,d);var z=k[0].find(),M=k[k.length-1].find();if(!z||!M||d.line-c.line<=C||S(c,M.to)>=0||S(d,z.from)<=0)return g(y);for(;S(c,z.from)>0;)k.shift().clear(),z=k[0].find();for(S(c,z.from)<0&&(z.to.line-c.line0&&(d.line-M.from.line{(function(o){typeof xs=="object"&&typeof _s=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";var p=o.Pos;function v(w){var W=w.flags;return W??(w.ignoreCase?"i":"")+(w.global?"g":"")+(w.multiline?"m":"")}function C(w,W){for(var E=v(w),O=E,G=0;Gre);q++){var I=w.getLine(J++);O=O==null?I:O+` -`+I}G=G*2,W.lastIndex=E.ch;var D=W.exec(O);if(D){var Q=O.slice(0,D.index).split(` -`),R=D[0].split(` -`),V=E.line+Q.length-1,x=Q[Q.length-1].length;return{from:p(V,x),to:p(V+R.length-1,R.length==1?x+R[0].length:R[R.length-1].length),match:D}}}}function h(w,W,E){for(var O,G=0;G<=w.length;){W.lastIndex=G;var J=W.exec(w);if(!J)break;var re=J.index+J[0].length;if(re>w.length-E)break;(!O||re>O.index+O[0].length)&&(O=J),G=J.index+1}return O}function g(w,W,E){W=C(W,"g");for(var O=E.line,G=E.ch,J=w.firstLine();O>=J;O--,G=-1){var re=w.getLine(O),q=h(re,W,G<0?0:re.length-G);if(q)return{from:p(O,q.index),to:p(O,q.index+q[0].length),match:q}}}function T(w,W,E){if(!b(W))return g(w,W,E);W=C(W,"gm");for(var O,G=1,J=w.getLine(E.line).length-E.ch,re=E.line,q=w.firstLine();re>=q;){for(var I=0;I=q;I++){var D=w.getLine(re--);O=O==null?D:D+` -`+O}G*=2;var Q=h(O,W,J);if(Q){var R=O.slice(0,Q.index).split(` -`),V=Q[0].split(` -`),x=re+R.length,K=R[R.length-1].length;return{from:p(x,K),to:p(x+V.length-1,V.length==1?K+V[0].length:V[V.length-1].length),match:Q}}}}var y,c;String.prototype.normalize?(y=function(w){return w.normalize("NFD").toLowerCase()},c=function(w){return w.normalize("NFD")}):(y=function(w){return w.toLowerCase()},c=function(w){return w});function d(w,W,E,O){if(w.length==W.length)return E;for(var G=0,J=E+Math.max(0,w.length-W.length);;){if(G==J)return G;var re=G+J>>1,q=O(w.slice(0,re)).length;if(q==E)return re;q>E?J=re:G=re+1}}function k(w,W,E,O){if(!W.length)return null;var G=O?y:c,J=G(W).split(/\r|\n\r?/);e:for(var re=E.line,q=E.ch,I=w.lastLine()+1-J.length;re<=I;re++,q=0){var D=w.getLine(re).slice(q),Q=G(D);if(J.length==1){var R=Q.indexOf(J[0]);if(R==-1)continue e;var E=d(D,Q,R,G)+q;return{from:p(re,d(D,Q,R,G)+q),to:p(re,d(D,Q,R+J[0].length,G)+q)}}else{var V=Q.length-J[0].length;if(Q.slice(V)!=J[0])continue e;for(var x=1;x=I;re--,q=-1){var D=w.getLine(re);q>-1&&(D=D.slice(0,q));var Q=G(D);if(J.length==1){var R=Q.lastIndexOf(J[0]);if(R==-1)continue e;return{from:p(re,d(D,Q,R,G)),to:p(re,d(D,Q,R+J[0].length,G))}}else{var V=J[J.length-1];if(Q.slice(0,V.length)!=V)continue e;for(var x=1,E=re-J.length+1;x(this.doc.getLine(W.line)||"").length&&(W.ch=0,W.line++)),o.cmpPos(W,this.doc.clipPos(W))!=0))return this.atOccurrence=!1;var E=this.matches(w,W);if(this.afterEmptyMatch=E&&o.cmpPos(E.from,E.to)==0,E)return this.pos=E,this.atOccurrence=!0,this.pos.match||!0;var O=p(w?this.doc.firstLine():this.doc.lastLine()+1,0);return this.pos={from:O,to:O},this.atOccurrence=!1},from:function(){if(this.atOccurrence)return this.pos.from},to:function(){if(this.atOccurrence)return this.pos.to},replace:function(w,W){if(this.atOccurrence){var E=o.splitLines(w);this.doc.replaceRange(E,this.pos.from,this.pos.to,W),this.pos.to=p(this.pos.from.line+E.length-1,E[E.length-1].length+(E.length==1?this.pos.from.ch:0))}}},o.defineExtension("getSearchCursor",function(w,W,E){return new M(this.doc,w,W,E)}),o.defineDocExtension("getSearchCursor",function(w,W,E){return new M(this,w,W,E)}),o.defineExtension("selectMatches",function(w,W){for(var E=[],O=this.getSearchCursor(w,this.getCursor("from"),W);O.findNext()&&!(o.cmpPos(O.to(),this.getCursor("to"))>0);)E.push({anchor:O.from(),head:O.to()});E.length&&this.setSelections(E,0)})})});var Vo=Ke((ws,Ss)=>{(function(o){typeof ws=="object"&&typeof Ss=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";function p(N,H,le,xe,F,L){this.indented=N,this.column=H,this.type=le,this.info=xe,this.align=F,this.prev=L}function v(N,H,le,xe){var F=N.indented;return N.context&&N.context.type=="statement"&&le!="statement"&&(F=N.context.indented),N.context=new p(F,H,le,xe,null,N.context)}function C(N){var H=N.context.type;return(H==")"||H=="]"||H=="}")&&(N.indented=N.context.indented),N.context=N.context.prev}function b(N,H,le){if(H.prevToken=="variable"||H.prevToken=="type"||/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(N.string.slice(0,le))||H.typeAtEndOfLine&&N.column()==N.indentation())return!0}function S(N){for(;;){if(!N||N.type=="top")return!0;if(N.type=="}"&&N.prev.info!="namespace")return!1;N=N.prev}}o.defineMode("clike",function(N,H){var le=N.indentUnit,xe=H.statementIndentUnit||le,F=H.dontAlignCalls,L=H.keywords||{},de=H.types||{},ze=H.builtin||{},pe=H.blockKeywords||{},Ee=H.defKeywords||{},ge=H.atoms||{},Oe=H.hooks||{},qe=H.multiLineStrings,Se=H.indentStatements!==!1,je=H.indentSwitch!==!1,Ze=H.namespaceSeparator,ke=H.isPunctuationChar||/[\[\]{}\(\),;\:\.]/,Je=H.numberStart||/[\d\.]/,He=H.number||/^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,Ge=H.isOperatorChar||/[+\-*&%=<>!?|\/]/,U=H.isIdentifierChar||/[\w\$_\xa1-\uffff]/,Z=H.isReservedIdentifier||!1,ce,Be;function te(we,Me){var Le=we.next();if(Oe[Le]){var $=Oe[Le](we,Me);if($!==!1)return $}if(Le=='"'||Le=="'")return Me.tokenize=fe(Le),Me.tokenize(we,Me);if(Je.test(Le)){if(we.backUp(1),we.match(He))return"number";we.next()}if(ke.test(Le))return ce=Le,null;if(Le=="/"){if(we.eat("*"))return Me.tokenize=oe,oe(we,Me);if(we.eat("/"))return we.skipToEnd(),"comment"}if(Ge.test(Le)){for(;!we.match(/^\/[\/*]/,!1)&&we.eat(Ge););return"operator"}if(we.eatWhile(U),Ze)for(;we.match(Ze);)we.eatWhile(U);var B=we.current();return h(L,B)?(h(pe,B)&&(ce="newstatement"),h(Ee,B)&&(Be=!0),"keyword"):h(de,B)?"type":h(ze,B)||Z&&Z(B)?(h(pe,B)&&(ce="newstatement"),"builtin"):h(ge,B)?"atom":"variable"}function fe(we){return function(Me,Le){for(var $=!1,B,se=!1;(B=Me.next())!=null;){if(B==we&&!$){se=!0;break}$=!$&&B=="\\"}return(se||!($||qe))&&(Le.tokenize=null),"string"}}function oe(we,Me){for(var Le=!1,$;$=we.next();){if($=="/"&&Le){Me.tokenize=null;break}Le=$=="*"}return"comment"}function Ue(we,Me){H.typeFirstDefinitions&&we.eol()&&S(Me.context)&&(Me.typeAtEndOfLine=b(we,Me,we.pos))}return{startState:function(we){return{tokenize:null,context:new p((we||0)-le,0,"top",null,!1),indented:0,startOfLine:!0,prevToken:null}},token:function(we,Me){var Le=Me.context;if(we.sol()&&(Le.align==null&&(Le.align=!1),Me.indented=we.indentation(),Me.startOfLine=!0),we.eatSpace())return Ue(we,Me),null;ce=Be=null;var $=(Me.tokenize||te)(we,Me);if($=="comment"||$=="meta")return $;if(Le.align==null&&(Le.align=!0),ce==";"||ce==":"||ce==","&&we.match(/^\s*(?:\/\/.*)?$/,!1))for(;Me.context.type=="statement";)C(Me);else if(ce=="{")v(Me,we.column(),"}");else if(ce=="[")v(Me,we.column(),"]");else if(ce=="(")v(Me,we.column(),")");else if(ce=="}"){for(;Le.type=="statement";)Le=C(Me);for(Le.type=="}"&&(Le=C(Me));Le.type=="statement";)Le=C(Me)}else ce==Le.type?C(Me):Se&&((Le.type=="}"||Le.type=="top")&&ce!=";"||Le.type=="statement"&&ce=="newstatement")&&v(Me,we.column(),"statement",we.current());if($=="variable"&&(Me.prevToken=="def"||H.typeFirstDefinitions&&b(we,Me,we.start)&&S(Me.context)&&we.match(/^\s*\(/,!1))&&($="def"),Oe.token){var B=Oe.token(we,Me,$);B!==void 0&&($=B)}return $=="def"&&H.styleDefs===!1&&($="variable"),Me.startOfLine=!1,Me.prevToken=Be?"def":$||ce,Ue(we,Me),$},indent:function(we,Me){if(we.tokenize!=te&&we.tokenize!=null||we.typeAtEndOfLine&&S(we.context))return o.Pass;var Le=we.context,$=Me&&Me.charAt(0),B=$==Le.type;if(Le.type=="statement"&&$=="}"&&(Le=Le.prev),H.dontIndentStatements)for(;Le.type=="statement"&&H.dontIndentStatements.test(Le.info);)Le=Le.prev;if(Oe.indent){var se=Oe.indent(we,Le,Me,le);if(typeof se=="number")return se}var De=Le.prev&&Le.prev.info=="switch";if(H.allmanIndentation&&/[{(]/.test($)){for(;Le.type!="top"&&Le.type!="}";)Le=Le.prev;return Le.indented}return Le.type=="statement"?Le.indented+($=="{"?0:xe):Le.align&&(!F||Le.type!=")")?Le.column+(B?0:1):Le.type==")"&&!B?Le.indented+xe:Le.indented+(B?0:le)+(!B&&De&&!/^(?:case|default)\b/.test(Me)?le:0)},electricInput:je?/^\s*(?:case .*?:|default:|\{\}?|\})$/:/^\s*[{}]$/,blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:"//",fold:"brace"}});function s(N){for(var H={},le=N.split(" "),xe=0;xe!?|\/#:@]/,hooks:{"@":function(N){return N.eatWhile(/[\w\$_]/),"meta"},'"':function(N,H){return N.match('""')?(H.tokenize=R,H.tokenize(N,H)):!1},"'":function(N){return N.match(/^(\\[^'\s]+|[^\\'])'/)?"string-2":(N.eatWhile(/[\w\$_\xa1-\uffff]/),"atom")},"=":function(N,H){var le=H.context;return le.type=="}"&&le.align&&N.eat(">")?(H.context=new p(le.indented,le.column,le.type,le.info,null,le.prev),"operator"):!1},"/":function(N,H){return N.eat("*")?(H.tokenize=V(1),H.tokenize(N,H)):!1}},modeProps:{closeBrackets:{pairs:'()[]{}""',triples:'"'}}});function x(N){return function(H,le){for(var xe=!1,F,L=!1;!H.eol();){if(!N&&!xe&&H.match('"')){L=!0;break}if(N&&H.match('"""')){L=!0;break}F=H.next(),!xe&&F=="$"&&H.match("{")&&H.skipTo("}"),xe=!xe&&F=="\\"&&!N}return(L||!N)&&(le.tokenize=null),"string"}}Q("text/x-kotlin",{name:"clike",keywords:s("package as typealias class interface this super val operator var fun for is in This throw return annotation break continue object if else while do try when !in !is as? file import where by get set abstract enum open inner override private public internal protected catch finally out final vararg reified dynamic companion constructor init sealed field property receiver param sparam lateinit data inline noinline tailrec external annotation crossinline const operator infix suspend actual expect setparam value"),types:s("Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy LazyThreadSafetyMode LongArray Nothing ShortArray Unit"),intendSwitch:!1,indentStatements:!1,multiLineStrings:!0,number:/^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,blockKeywords:s("catch class do else finally for if where try while enum"),defKeywords:s("class val var object interface fun"),atoms:s("true false null this"),hooks:{"@":function(N){return N.eatWhile(/[\w\$_]/),"meta"},"*":function(N,H){return H.prevToken=="."?"variable":"operator"},'"':function(N,H){return H.tokenize=x(N.match('""')),H.tokenize(N,H)},"/":function(N,H){return N.eat("*")?(H.tokenize=V(1),H.tokenize(N,H)):!1},indent:function(N,H,le,xe){var F=le&&le.charAt(0);if((N.prevToken=="}"||N.prevToken==")")&&le=="")return N.indented;if(N.prevToken=="operator"&&le!="}"&&N.context.type!="}"||N.prevToken=="variable"&&F=="."||(N.prevToken=="}"||N.prevToken==")")&&F==".")return xe*2+H.indented;if(H.align&&H.type=="}")return H.indented+(N.context.type==(le||"").charAt(0)?0:xe)}},modeProps:{closeBrackets:{triples:'"'}}}),Q(["x-shader/x-vertex","x-shader/x-fragment"],{name:"clike",keywords:s("sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow const attribute uniform varying break continue discard return for while do if else struct in out inout"),types:s("float int bool void vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 mat2 mat3 mat4"),blockKeywords:s("for while do if else struct"),builtin:s("radians degrees sin cos tan asin acos atan pow exp log exp2 sqrt inversesqrt abs sign floor ceil fract mod min max clamp mix step smoothstep length distance dot cross normalize ftransform faceforward reflect refract matrixCompMult lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual any all not texture1D texture1DProj texture1DLod texture1DProjLod texture2D texture2DProj texture2DLod texture2DProjLod texture3D texture3DProj texture3DLod texture3DProjLod textureCube textureCubeLod shadow1D shadow2D shadow1DProj shadow2DProj shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod dFdx dFdy fwidth noise1 noise2 noise3 noise4"),atoms:s("true false gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_FogCoord gl_PointCoord gl_Position gl_PointSize gl_ClipVertex gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor gl_TexCoord gl_FogFragCoord gl_FragCoord gl_FrontFacing gl_FragData gl_FragDepth gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose gl_ProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixInverseTranspose gl_TextureMatrixInverseTranspose gl_NormalScale gl_DepthRange gl_ClipPlane gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel gl_FrontLightModelProduct gl_BackLightModelProduct gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ gl_FogParameters gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits gl_MaxDrawBuffers"),indentSwitch:!1,hooks:{"#":E},modeProps:{fold:["brace","include"]}}),Q("text/x-nesc",{name:"clike",keywords:s(g+" as atomic async call command component components configuration event generic implementation includes interface module new norace nx_struct nx_union post provides signal task uses abstract extends"),types:z,blockKeywords:s(w),atoms:s("null true false"),hooks:{"#":E},modeProps:{fold:["brace","include"]}}),Q("text/x-objectivec",{name:"clike",keywords:s(g+" "+y),types:M,builtin:s(c),blockKeywords:s(w+" @synthesize @try @catch @finally @autoreleasepool @synchronized"),defKeywords:s(W+" @interface @implementation @protocol @class"),dontIndentStatements:/^@.*$/,typeFirstDefinitions:!0,atoms:s("YES NO NULL Nil nil true false nullptr"),isReservedIdentifier:G,hooks:{"#":E,"*":O},modeProps:{fold:["brace","include"]}}),Q("text/x-objectivec++",{name:"clike",keywords:s(g+" "+y+" "+T),types:M,builtin:s(c),blockKeywords:s(w+" @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"),defKeywords:s(W+" @interface @implementation @protocol @class class namespace"),dontIndentStatements:/^@.*$|^template$/,typeFirstDefinitions:!0,atoms:s("YES NO NULL Nil nil true false nullptr"),isReservedIdentifier:G,hooks:{"#":E,"*":O,u:re,U:re,L:re,R:re,0:J,1:J,2:J,3:J,4:J,5:J,6:J,7:J,8:J,9:J,token:function(N,H,le){if(le=="variable"&&N.peek()=="("&&(H.prevToken==";"||H.prevToken==null||H.prevToken=="}")&&q(N.current()))return"def"}},namespaceSeparator:"::",modeProps:{fold:["brace","include"]}}),Q("text/x-squirrel",{name:"clike",keywords:s("base break clone continue const default delete enum extends function in class foreach local resume return this throw typeof yield constructor instanceof static"),types:z,blockKeywords:s("case catch class else for foreach if switch try while"),defKeywords:s("function local class"),typeFirstDefinitions:!0,atoms:s("true false null"),hooks:{"#":E},modeProps:{fold:["brace","include"]}});var K=null;function X(N){return function(H,le){for(var xe=!1,F,L=!1;!H.eol();){if(!xe&&H.match('"')&&(N=="single"||H.match('""'))){L=!0;break}if(!xe&&H.match("``")){K=X(N),L=!0;break}F=H.next(),xe=N=="single"&&!xe&&F=="\\"}return L&&(le.tokenize=null),"string"}}Q("text/x-ceylon",{name:"clike",keywords:s("abstracts alias assembly assert assign break case catch class continue dynamic else exists extends finally for function given if import in interface is let module new nonempty object of out outer package return satisfies super switch then this throw try value void while"),types:function(N){var H=N.charAt(0);return H===H.toUpperCase()&&H!==H.toLowerCase()},blockKeywords:s("case catch class dynamic else finally for function if interface module new object switch try while"),defKeywords:s("class dynamic function interface module object package value"),builtin:s("abstract actual aliased annotation by default deprecated doc final formal late license native optional sealed see serializable shared suppressWarnings tagged throws variable"),isPunctuationChar:/[\[\]{}\(\),;\:\.`]/,isOperatorChar:/[+\-*&%=<>!?|^~:\/]/,numberStart:/[\d#$]/,number:/^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,multiLineStrings:!0,typeFirstDefinitions:!0,atoms:s("true false null larger smaller equal empty finished"),indentSwitch:!1,styleDefs:!1,hooks:{"@":function(N){return N.eatWhile(/[\w\$_]/),"meta"},'"':function(N,H){return H.tokenize=X(N.match('""')?"triple":"single"),H.tokenize(N,H)},"`":function(N,H){return!K||!N.match("`")?!1:(H.tokenize=K,K=null,H.tokenize(N,H))},"'":function(N){return N.eatWhile(/[\w\$_\xa1-\uffff]/),"atom"},token:function(N,H,le){if((le=="variable"||le=="type")&&H.prevToken==".")return"variable-2"}},modeProps:{fold:["brace","import"],closeBrackets:{triples:'"'}}})})});var Cs=Ke((Ts,Ls)=>{(function(o){typeof Ts=="object"&&typeof Ls=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("cmake",function(){var p=/({)?[a-zA-Z0-9_]+(})?/;function v(b,S){for(var s,h,g=!1;!b.eol()&&(s=b.next())!=S.pending;){if(s==="$"&&h!="\\"&&S.pending=='"'){g=!0;break}h=s}return g&&b.backUp(1),s==S.pending?S.continueString=!1:S.continueString=!0,"string"}function C(b,S){var s=b.next();return s==="$"?b.match(p)?"variable-2":"variable":S.continueString?(b.backUp(1),v(b,S)):b.match(/(\s+)?\w+\(/)||b.match(/(\s+)?\w+\ \(/)?(b.backUp(1),"def"):s=="#"?(b.skipToEnd(),"comment"):s=="'"||s=='"'?(S.pending=s,v(b,S)):s=="("||s==")"?"bracket":s.match(/[0-9]/)?"number":(b.eatWhile(/[\w-]/),null)}return{startState:function(){var b={};return b.inDefinition=!1,b.inInclude=!1,b.continueString=!1,b.pending=!1,b},token:function(b,S){return b.eatSpace()?null:C(b,S)}}}),o.defineMIME("text/x-cmake","cmake")})});var gn=Ke((Es,zs)=>{(function(o){typeof Es=="object"&&typeof zs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("css",function(I,D){var Q=D.inline;D.propertyKeywords||(D=o.resolveMode("text/css"));var R=I.indentUnit,V=D.tokenHooks,x=D.documentTypes||{},K=D.mediaTypes||{},X=D.mediaFeatures||{},N=D.mediaValueKeywords||{},H=D.propertyKeywords||{},le=D.nonStandardPropertyKeywords||{},xe=D.fontProperties||{},F=D.counterDescriptors||{},L=D.colorKeywords||{},de=D.valueKeywords||{},ze=D.allowNested,pe=D.lineComment,Ee=D.supportsAtComponent===!0,ge=I.highlightNonStandardPropertyKeywords!==!1,Oe,qe;function Se(te,fe){return Oe=fe,te}function je(te,fe){var oe=te.next();if(V[oe]){var Ue=V[oe](te,fe);if(Ue!==!1)return Ue}if(oe=="@")return te.eatWhile(/[\w\\\-]/),Se("def",te.current());if(oe=="="||(oe=="~"||oe=="|")&&te.eat("="))return Se(null,"compare");if(oe=='"'||oe=="'")return fe.tokenize=Ze(oe),fe.tokenize(te,fe);if(oe=="#")return te.eatWhile(/[\w\\\-]/),Se("atom","hash");if(oe=="!")return te.match(/^\s*\w*/),Se("keyword","important");if(/\d/.test(oe)||oe=="."&&te.eat(/\d/))return te.eatWhile(/[\w.%]/),Se("number","unit");if(oe==="-"){if(/[\d.]/.test(te.peek()))return te.eatWhile(/[\w.%]/),Se("number","unit");if(te.match(/^-[\w\\\-]*/))return te.eatWhile(/[\w\\\-]/),te.match(/^\s*:/,!1)?Se("variable-2","variable-definition"):Se("variable-2","variable");if(te.match(/^\w+-/))return Se("meta","meta")}else return/[,+>*\/]/.test(oe)?Se(null,"select-op"):oe=="."&&te.match(/^-?[_a-z][_a-z0-9-]*/i)?Se("qualifier","qualifier"):/[:;{}\[\]\(\)]/.test(oe)?Se(null,oe):te.match(/^[\w-.]+(?=\()/)?(/^(url(-prefix)?|domain|regexp)$/i.test(te.current())&&(fe.tokenize=ke),Se("variable callee","variable")):/[\w\\\-]/.test(oe)?(te.eatWhile(/[\w\\\-]/),Se("property","word")):Se(null,null)}function Ze(te){return function(fe,oe){for(var Ue=!1,we;(we=fe.next())!=null;){if(we==te&&!Ue){te==")"&&fe.backUp(1);break}Ue=!Ue&&we=="\\"}return(we==te||!Ue&&te!=")")&&(oe.tokenize=null),Se("string","string")}}function ke(te,fe){return te.next(),te.match(/^\s*[\"\')]/,!1)?fe.tokenize=null:fe.tokenize=Ze(")"),Se(null,"(")}function Je(te,fe,oe){this.type=te,this.indent=fe,this.prev=oe}function He(te,fe,oe,Ue){return te.context=new Je(oe,fe.indentation()+(Ue===!1?0:R),te.context),oe}function Ge(te){return te.context.prev&&(te.context=te.context.prev),te.context.type}function U(te,fe,oe){return Be[oe.context.type](te,fe,oe)}function Z(te,fe,oe,Ue){for(var we=Ue||1;we>0;we--)oe.context=oe.context.prev;return U(te,fe,oe)}function ce(te){var fe=te.current().toLowerCase();de.hasOwnProperty(fe)?qe="atom":L.hasOwnProperty(fe)?qe="keyword":qe="variable"}var Be={};return Be.top=function(te,fe,oe){if(te=="{")return He(oe,fe,"block");if(te=="}"&&oe.context.prev)return Ge(oe);if(Ee&&/@component/i.test(te))return He(oe,fe,"atComponentBlock");if(/^@(-moz-)?document$/i.test(te))return He(oe,fe,"documentTypes");if(/^@(media|supports|(-moz-)?document|import)$/i.test(te))return He(oe,fe,"atBlock");if(/^@(font-face|counter-style)/i.test(te))return oe.stateArg=te,"restricted_atBlock_before";if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(te))return"keyframes";if(te&&te.charAt(0)=="@")return He(oe,fe,"at");if(te=="hash")qe="builtin";else if(te=="word")qe="tag";else{if(te=="variable-definition")return"maybeprop";if(te=="interpolation")return He(oe,fe,"interpolation");if(te==":")return"pseudo";if(ze&&te=="(")return He(oe,fe,"parens")}return oe.context.type},Be.block=function(te,fe,oe){if(te=="word"){var Ue=fe.current().toLowerCase();return H.hasOwnProperty(Ue)?(qe="property","maybeprop"):le.hasOwnProperty(Ue)?(qe=ge?"string-2":"property","maybeprop"):ze?(qe=fe.match(/^\s*:(?:\s|$)/,!1)?"property":"tag","block"):(qe+=" error","maybeprop")}else return te=="meta"?"block":!ze&&(te=="hash"||te=="qualifier")?(qe="error","block"):Be.top(te,fe,oe)},Be.maybeprop=function(te,fe,oe){return te==":"?He(oe,fe,"prop"):U(te,fe,oe)},Be.prop=function(te,fe,oe){if(te==";")return Ge(oe);if(te=="{"&&ze)return He(oe,fe,"propBlock");if(te=="}"||te=="{")return Z(te,fe,oe);if(te=="(")return He(oe,fe,"parens");if(te=="hash"&&!/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(fe.current()))qe+=" error";else if(te=="word")ce(fe);else if(te=="interpolation")return He(oe,fe,"interpolation");return"prop"},Be.propBlock=function(te,fe,oe){return te=="}"?Ge(oe):te=="word"?(qe="property","maybeprop"):oe.context.type},Be.parens=function(te,fe,oe){return te=="{"||te=="}"?Z(te,fe,oe):te==")"?Ge(oe):te=="("?He(oe,fe,"parens"):te=="interpolation"?He(oe,fe,"interpolation"):(te=="word"&&ce(fe),"parens")},Be.pseudo=function(te,fe,oe){return te=="meta"?"pseudo":te=="word"?(qe="variable-3",oe.context.type):U(te,fe,oe)},Be.documentTypes=function(te,fe,oe){return te=="word"&&x.hasOwnProperty(fe.current())?(qe="tag",oe.context.type):Be.atBlock(te,fe,oe)},Be.atBlock=function(te,fe,oe){if(te=="(")return He(oe,fe,"atBlock_parens");if(te=="}"||te==";")return Z(te,fe,oe);if(te=="{")return Ge(oe)&&He(oe,fe,ze?"block":"top");if(te=="interpolation")return He(oe,fe,"interpolation");if(te=="word"){var Ue=fe.current().toLowerCase();Ue=="only"||Ue=="not"||Ue=="and"||Ue=="or"?qe="keyword":K.hasOwnProperty(Ue)?qe="attribute":X.hasOwnProperty(Ue)?qe="property":N.hasOwnProperty(Ue)?qe="keyword":H.hasOwnProperty(Ue)?qe="property":le.hasOwnProperty(Ue)?qe=ge?"string-2":"property":de.hasOwnProperty(Ue)?qe="atom":L.hasOwnProperty(Ue)?qe="keyword":qe="error"}return oe.context.type},Be.atComponentBlock=function(te,fe,oe){return te=="}"?Z(te,fe,oe):te=="{"?Ge(oe)&&He(oe,fe,ze?"block":"top",!1):(te=="word"&&(qe="error"),oe.context.type)},Be.atBlock_parens=function(te,fe,oe){return te==")"?Ge(oe):te=="{"||te=="}"?Z(te,fe,oe,2):Be.atBlock(te,fe,oe)},Be.restricted_atBlock_before=function(te,fe,oe){return te=="{"?He(oe,fe,"restricted_atBlock"):te=="word"&&oe.stateArg=="@counter-style"?(qe="variable","restricted_atBlock_before"):U(te,fe,oe)},Be.restricted_atBlock=function(te,fe,oe){return te=="}"?(oe.stateArg=null,Ge(oe)):te=="word"?(oe.stateArg=="@font-face"&&!xe.hasOwnProperty(fe.current().toLowerCase())||oe.stateArg=="@counter-style"&&!F.hasOwnProperty(fe.current().toLowerCase())?qe="error":qe="property","maybeprop"):"restricted_atBlock"},Be.keyframes=function(te,fe,oe){return te=="word"?(qe="variable","keyframes"):te=="{"?He(oe,fe,"top"):U(te,fe,oe)},Be.at=function(te,fe,oe){return te==";"?Ge(oe):te=="{"||te=="}"?Z(te,fe,oe):(te=="word"?qe="tag":te=="hash"&&(qe="builtin"),"at")},Be.interpolation=function(te,fe,oe){return te=="}"?Ge(oe):te=="{"||te==";"?Z(te,fe,oe):(te=="word"?qe="variable":te!="variable"&&te!="("&&te!=")"&&(qe="error"),"interpolation")},{startState:function(te){return{tokenize:null,state:Q?"block":"top",stateArg:null,context:new Je(Q?"block":"top",te||0,null)}},token:function(te,fe){if(!fe.tokenize&&te.eatSpace())return null;var oe=(fe.tokenize||je)(te,fe);return oe&&typeof oe=="object"&&(Oe=oe[1],oe=oe[0]),qe=oe,Oe!="comment"&&(fe.state=Be[fe.state](Oe,te,fe)),qe},indent:function(te,fe){var oe=te.context,Ue=fe&&fe.charAt(0),we=oe.indent;return oe.type=="prop"&&(Ue=="}"||Ue==")")&&(oe=oe.prev),oe.prev&&(Ue=="}"&&(oe.type=="block"||oe.type=="top"||oe.type=="interpolation"||oe.type=="restricted_atBlock")?(oe=oe.prev,we=oe.indent):(Ue==")"&&(oe.type=="parens"||oe.type=="atBlock_parens")||Ue=="{"&&(oe.type=="at"||oe.type=="atBlock"))&&(we=Math.max(0,oe.indent-R))),we},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:pe,fold:"brace"}});function p(I){for(var D={},Q=0;Q{(function(o){typeof Ms=="object"&&typeof As=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("diff",function(){var p={"+":"positive","-":"negative","@":"meta"};return{token:function(v){var C=v.string.search(/[\t ]+?$/);if(!v.sol()||C===0)return v.skipToEnd(),("error "+(p[v.string.charAt(0)]||"")).replace(/ $/,"");var b=p[v.peek()]||v.skipToEnd();return C===-1?v.skipToEnd():v.pos=C,b}}}),o.defineMIME("text/x-diff","diff")})});var mn=Ke((qs,Fs)=>{(function(o){typeof qs=="object"&&typeof Fs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";var p={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},v={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,allowMissingTagName:!1,caseFold:!1};o.defineMode("xml",function(C,b){var S=C.indentUnit,s={},h=b.htmlMode?p:v;for(var g in h)s[g]=h[g];for(var g in b)s[g]=b[g];var T,y;function c(x,K){function X(le){return K.tokenize=le,le(x,K)}var N=x.next();if(N=="<")return x.eat("!")?x.eat("[")?x.match("CDATA[")?X(z("atom","]]>")):null:x.match("--")?X(z("comment","-->")):x.match("DOCTYPE",!0,!0)?(x.eatWhile(/[\w\._\-]/),X(M(1))):null:x.eat("?")?(x.eatWhile(/[\w\._\-]/),K.tokenize=z("meta","?>"),"meta"):(T=x.eat("/")?"closeTag":"openTag",K.tokenize=d,"tag bracket");if(N=="&"){var H;return x.eat("#")?x.eat("x")?H=x.eatWhile(/[a-fA-F\d]/)&&x.eat(";"):H=x.eatWhile(/[\d]/)&&x.eat(";"):H=x.eatWhile(/[\w\.\-:]/)&&x.eat(";"),H?"atom":"error"}else return x.eatWhile(/[^&<]/),null}c.isInText=!0;function d(x,K){var X=x.next();if(X==">"||X=="/"&&x.eat(">"))return K.tokenize=c,T=X==">"?"endTag":"selfcloseTag","tag bracket";if(X=="=")return T="equals",null;if(X=="<"){K.tokenize=c,K.state=G,K.tagName=K.tagStart=null;var N=K.tokenize(x,K);return N?N+" tag error":"tag error"}else return/[\'\"]/.test(X)?(K.tokenize=k(X),K.stringStartCol=x.column(),K.tokenize(x,K)):(x.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function k(x){var K=function(X,N){for(;!X.eol();)if(X.next()==x){N.tokenize=d;break}return"string"};return K.isInAttribute=!0,K}function z(x,K){return function(X,N){for(;!X.eol();){if(X.match(K)){N.tokenize=c;break}X.next()}return x}}function M(x){return function(K,X){for(var N;(N=K.next())!=null;){if(N=="<")return X.tokenize=M(x+1),X.tokenize(K,X);if(N==">")if(x==1){X.tokenize=c;break}else return X.tokenize=M(x-1),X.tokenize(K,X)}return"meta"}}function w(x){return x&&x.toLowerCase()}function W(x,K,X){this.prev=x.context,this.tagName=K||"",this.indent=x.indented,this.startOfLine=X,(s.doNotIndent.hasOwnProperty(K)||x.context&&x.context.noIndent)&&(this.noIndent=!0)}function E(x){x.context&&(x.context=x.context.prev)}function O(x,K){for(var X;;){if(!x.context||(X=x.context.tagName,!s.contextGrabbers.hasOwnProperty(w(X))||!s.contextGrabbers[w(X)].hasOwnProperty(w(K))))return;E(x)}}function G(x,K,X){return x=="openTag"?(X.tagStart=K.column(),J):x=="closeTag"?re:G}function J(x,K,X){return x=="word"?(X.tagName=K.current(),y="tag",D):s.allowMissingTagName&&x=="endTag"?(y="tag bracket",D(x,K,X)):(y="error",J)}function re(x,K,X){if(x=="word"){var N=K.current();return X.context&&X.context.tagName!=N&&s.implicitlyClosed.hasOwnProperty(w(X.context.tagName))&&E(X),X.context&&X.context.tagName==N||s.matchClosing===!1?(y="tag",q):(y="tag error",I)}else return s.allowMissingTagName&&x=="endTag"?(y="tag bracket",q(x,K,X)):(y="error",I)}function q(x,K,X){return x!="endTag"?(y="error",q):(E(X),G)}function I(x,K,X){return y="error",q(x,K,X)}function D(x,K,X){if(x=="word")return y="attribute",Q;if(x=="endTag"||x=="selfcloseTag"){var N=X.tagName,H=X.tagStart;return X.tagName=X.tagStart=null,x=="selfcloseTag"||s.autoSelfClosers.hasOwnProperty(w(N))?O(X,N):(O(X,N),X.context=new W(X,N,H==X.indented)),G}return y="error",D}function Q(x,K,X){return x=="equals"?R:(s.allowMissing||(y="error"),D(x,K,X))}function R(x,K,X){return x=="string"?V:x=="word"&&s.allowUnquoted?(y="string",D):(y="error",D(x,K,X))}function V(x,K,X){return x=="string"?V:D(x,K,X)}return{startState:function(x){var K={tokenize:c,state:G,indented:x||0,tagName:null,tagStart:null,context:null};return x!=null&&(K.baseIndent=x),K},token:function(x,K){if(!K.tagName&&x.sol()&&(K.indented=x.indentation()),x.eatSpace())return null;T=null;var X=K.tokenize(x,K);return(X||T)&&X!="comment"&&(y=null,K.state=K.state(T||X,x,K),y&&(X=y=="error"?X+" error":y)),X},indent:function(x,K,X){var N=x.context;if(x.tokenize.isInAttribute)return x.tagStart==x.indented?x.stringStartCol+1:x.indented+S;if(N&&N.noIndent)return o.Pass;if(x.tokenize!=d&&x.tokenize!=c)return X?X.match(/^(\s*)/)[0].length:0;if(x.tagName)return s.multilineTagIndentPastTag!==!1?x.tagStart+x.tagName.length+2:x.tagStart+S*(s.multilineTagIndentFactor||1);if(s.alignCDATA&&/$/,blockCommentStart:"",configuration:s.htmlMode?"html":"xml",helperType:s.htmlMode?"html":"xml",skipAttribute:function(x){x.state==R&&(x.state=D)},xmlCurrentTag:function(x){return x.tagName?{name:x.tagName,close:x.type=="closeTag"}:null},xmlCurrentContext:function(x){for(var K=[],X=x.context;X;X=X.prev)K.push(X.tagName);return K.reverse()}}}),o.defineMIME("text/xml","xml"),o.defineMIME("application/xml","xml"),o.mimeModes.hasOwnProperty("text/html")||o.defineMIME("text/html",{name:"xml",htmlMode:!0})})});var vn=Ke((Is,Ns)=>{(function(o){typeof Is=="object"&&typeof Ns=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("javascript",function(p,v){var C=p.indentUnit,b=v.statementIndent,S=v.jsonld,s=v.json||S,h=v.trackScope!==!1,g=v.typescript,T=v.wordCharacters||/[\w$\xa1-\uffff]/,y=function(){function _(pt){return{type:pt,style:"keyword"}}var P=_("keyword a"),ae=_("keyword b"),he=_("keyword c"),ne=_("keyword d"),ye=_("operator"),Xe={type:"atom",style:"atom"};return{if:_("if"),while:P,with:P,else:ae,do:ae,try:ae,finally:ae,return:ne,break:ne,continue:ne,new:_("new"),delete:he,void:he,throw:he,debugger:_("debugger"),var:_("var"),const:_("var"),let:_("var"),function:_("function"),catch:_("catch"),for:_("for"),switch:_("switch"),case:_("case"),default:_("default"),in:ye,typeof:ye,instanceof:ye,true:Xe,false:Xe,null:Xe,undefined:Xe,NaN:Xe,Infinity:Xe,this:_("this"),class:_("class"),super:_("atom"),yield:he,export:_("export"),import:_("import"),extends:he,await:he}}(),c=/[+\-*&%=<>!?|~^@]/,d=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function k(_){for(var P=!1,ae,he=!1;(ae=_.next())!=null;){if(!P){if(ae=="/"&&!he)return;ae=="["?he=!0:he&&ae=="]"&&(he=!1)}P=!P&&ae=="\\"}}var z,M;function w(_,P,ae){return z=_,M=ae,P}function W(_,P){var ae=_.next();if(ae=='"'||ae=="'")return P.tokenize=E(ae),P.tokenize(_,P);if(ae=="."&&_.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/))return w("number","number");if(ae=="."&&_.match(".."))return w("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(ae))return w(ae);if(ae=="="&&_.eat(">"))return w("=>","operator");if(ae=="0"&&_.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/))return w("number","number");if(/\d/.test(ae))return _.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/),w("number","number");if(ae=="/")return _.eat("*")?(P.tokenize=O,O(_,P)):_.eat("/")?(_.skipToEnd(),w("comment","comment")):jt(_,P,1)?(k(_),_.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/),w("regexp","string-2")):(_.eat("="),w("operator","operator",_.current()));if(ae=="`")return P.tokenize=G,G(_,P);if(ae=="#"&&_.peek()=="!")return _.skipToEnd(),w("meta","meta");if(ae=="#"&&_.eatWhile(T))return w("variable","property");if(ae=="<"&&_.match("!--")||ae=="-"&&_.match("->")&&!/\S/.test(_.string.slice(0,_.start)))return _.skipToEnd(),w("comment","comment");if(c.test(ae))return(ae!=">"||!P.lexical||P.lexical.type!=">")&&(_.eat("=")?(ae=="!"||ae=="=")&&_.eat("="):/[<>*+\-|&?]/.test(ae)&&(_.eat(ae),ae==">"&&_.eat(ae))),ae=="?"&&_.eat(".")?w("."):w("operator","operator",_.current());if(T.test(ae)){_.eatWhile(T);var he=_.current();if(P.lastType!="."){if(y.propertyIsEnumerable(he)){var ne=y[he];return w(ne.type,ne.style,he)}if(he=="async"&&_.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/,!1))return w("async","keyword",he)}return w("variable","variable",he)}}function E(_){return function(P,ae){var he=!1,ne;if(S&&P.peek()=="@"&&P.match(d))return ae.tokenize=W,w("jsonld-keyword","meta");for(;(ne=P.next())!=null&&!(ne==_&&!he);)he=!he&&ne=="\\";return he||(ae.tokenize=W),w("string","string")}}function O(_,P){for(var ae=!1,he;he=_.next();){if(he=="/"&&ae){P.tokenize=W;break}ae=he=="*"}return w("comment","comment")}function G(_,P){for(var ae=!1,he;(he=_.next())!=null;){if(!ae&&(he=="`"||he=="$"&&_.eat("{"))){P.tokenize=W;break}ae=!ae&&he=="\\"}return w("quasi","string-2",_.current())}var J="([{}])";function re(_,P){P.fatArrowAt&&(P.fatArrowAt=null);var ae=_.string.indexOf("=>",_.start);if(!(ae<0)){if(g){var he=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(_.string.slice(_.start,ae));he&&(ae=he.index)}for(var ne=0,ye=!1,Xe=ae-1;Xe>=0;--Xe){var pt=_.string.charAt(Xe),Et=J.indexOf(pt);if(Et>=0&&Et<3){if(!ne){++Xe;break}if(--ne==0){pt=="("&&(ye=!0);break}}else if(Et>=3&&Et<6)++ne;else if(T.test(pt))ye=!0;else if(/["'\/`]/.test(pt))for(;;--Xe){if(Xe==0)return;var Zr=_.string.charAt(Xe-1);if(Zr==pt&&_.string.charAt(Xe-2)!="\\"){Xe--;break}}else if(ye&&!ne){++Xe;break}}ye&&!ne&&(P.fatArrowAt=Xe)}}var q={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,import:!0,"jsonld-keyword":!0};function I(_,P,ae,he,ne,ye){this.indented=_,this.column=P,this.type=ae,this.prev=ne,this.info=ye,he!=null&&(this.align=he)}function D(_,P){if(!h)return!1;for(var ae=_.localVars;ae;ae=ae.next)if(ae.name==P)return!0;for(var he=_.context;he;he=he.prev)for(var ae=he.vars;ae;ae=ae.next)if(ae.name==P)return!0}function Q(_,P,ae,he,ne){var ye=_.cc;for(R.state=_,R.stream=ne,R.marked=null,R.cc=ye,R.style=P,_.lexical.hasOwnProperty("align")||(_.lexical.align=!0);;){var Xe=ye.length?ye.pop():s?Se:Oe;if(Xe(ae,he)){for(;ye.length&&ye[ye.length-1].lex;)ye.pop()();return R.marked?R.marked:ae=="variable"&&D(_,he)?"variable-2":P}}}var R={state:null,column:null,marked:null,cc:null};function V(){for(var _=arguments.length-1;_>=0;_--)R.cc.push(arguments[_])}function x(){return V.apply(null,arguments),!0}function K(_,P){for(var ae=P;ae;ae=ae.next)if(ae.name==_)return!0;return!1}function X(_){var P=R.state;if(R.marked="def",!!h){if(P.context){if(P.lexical.info=="var"&&P.context&&P.context.block){var ae=N(_,P.context);if(ae!=null){P.context=ae;return}}else if(!K(_,P.localVars)){P.localVars=new xe(_,P.localVars);return}}v.globalVars&&!K(_,P.globalVars)&&(P.globalVars=new xe(_,P.globalVars))}}function N(_,P){if(P)if(P.block){var ae=N(_,P.prev);return ae?ae==P.prev?P:new le(ae,P.vars,!0):null}else return K(_,P.vars)?P:new le(P.prev,new xe(_,P.vars),!1);else return null}function H(_){return _=="public"||_=="private"||_=="protected"||_=="abstract"||_=="readonly"}function le(_,P,ae){this.prev=_,this.vars=P,this.block=ae}function xe(_,P){this.name=_,this.next=P}var F=new xe("this",new xe("arguments",null));function L(){R.state.context=new le(R.state.context,R.state.localVars,!1),R.state.localVars=F}function de(){R.state.context=new le(R.state.context,R.state.localVars,!0),R.state.localVars=null}L.lex=de.lex=!0;function ze(){R.state.localVars=R.state.context.vars,R.state.context=R.state.context.prev}ze.lex=!0;function pe(_,P){var ae=function(){var he=R.state,ne=he.indented;if(he.lexical.type=="stat")ne=he.lexical.indented;else for(var ye=he.lexical;ye&&ye.type==")"&&ye.align;ye=ye.prev)ne=ye.indented;he.lexical=new I(ne,R.stream.column(),_,null,he.lexical,P)};return ae.lex=!0,ae}function Ee(){var _=R.state;_.lexical.prev&&(_.lexical.type==")"&&(_.indented=_.lexical.indented),_.lexical=_.lexical.prev)}Ee.lex=!0;function ge(_){function P(ae){return ae==_?x():_==";"||ae=="}"||ae==")"||ae=="]"?V():x(P)}return P}function Oe(_,P){return _=="var"?x(pe("vardef",P),Hr,ge(";"),Ee):_=="keyword a"?x(pe("form"),Ze,Oe,Ee):_=="keyword b"?x(pe("form"),Oe,Ee):_=="keyword d"?R.stream.match(/^\s*$/,!1)?x():x(pe("stat"),Je,ge(";"),Ee):_=="debugger"?x(ge(";")):_=="{"?x(pe("}"),de,De,Ee,ze):_==";"?x():_=="if"?(R.state.lexical.info=="else"&&R.state.cc[R.state.cc.length-1]==Ee&&R.state.cc.pop()(),x(pe("form"),Ze,Oe,Ee,Br)):_=="function"?x(Bt):_=="for"?x(pe("form"),de,ei,Oe,ze,Ee):_=="class"||g&&P=="interface"?(R.marked="keyword",x(pe("form",_=="class"?_:P),Wr,Ee)):_=="variable"?g&&P=="declare"?(R.marked="keyword",x(Oe)):g&&(P=="module"||P=="enum"||P=="type")&&R.stream.match(/^\s*\w/,!1)?(R.marked="keyword",P=="enum"?x(Ae):P=="type"?x(ti,ge("operator"),Pe,ge(";")):x(pe("form"),Ct,ge("{"),pe("}"),De,Ee,Ee)):g&&P=="namespace"?(R.marked="keyword",x(pe("form"),Se,Oe,Ee)):g&&P=="abstract"?(R.marked="keyword",x(Oe)):x(pe("stat"),Ue):_=="switch"?x(pe("form"),Ze,ge("{"),pe("}","switch"),de,De,Ee,Ee,ze):_=="case"?x(Se,ge(":")):_=="default"?x(ge(":")):_=="catch"?x(pe("form"),L,qe,Oe,Ee,ze):_=="export"?x(pe("stat"),Ur,Ee):_=="import"?x(pe("stat"),gr,Ee):_=="async"?x(Oe):P=="@"?x(Se,Oe):V(pe("stat"),Se,ge(";"),Ee)}function qe(_){if(_=="(")return x($t,ge(")"))}function Se(_,P){return ke(_,P,!1)}function je(_,P){return ke(_,P,!0)}function Ze(_){return _!="("?V():x(pe(")"),Je,ge(")"),Ee)}function ke(_,P,ae){if(R.state.fatArrowAt==R.stream.start){var he=ae?Be:ce;if(_=="(")return x(L,pe(")"),B($t,")"),Ee,ge("=>"),he,ze);if(_=="variable")return V(L,Ct,ge("=>"),he,ze)}var ne=ae?Ge:He;return q.hasOwnProperty(_)?x(ne):_=="function"?x(Bt,ne):_=="class"||g&&P=="interface"?(R.marked="keyword",x(pe("form"),to,Ee)):_=="keyword c"||_=="async"?x(ae?je:Se):_=="("?x(pe(")"),Je,ge(")"),Ee,ne):_=="operator"||_=="spread"?x(ae?je:Se):_=="["?x(pe("]"),at,Ee,ne):_=="{"?se(Me,"}",null,ne):_=="quasi"?V(U,ne):_=="new"?x(te(ae)):x()}function Je(_){return _.match(/[;\}\)\],]/)?V():V(Se)}function He(_,P){return _==","?x(Je):Ge(_,P,!1)}function Ge(_,P,ae){var he=ae==!1?He:Ge,ne=ae==!1?Se:je;if(_=="=>")return x(L,ae?Be:ce,ze);if(_=="operator")return/\+\+|--/.test(P)||g&&P=="!"?x(he):g&&P=="<"&&R.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/,!1)?x(pe(">"),B(Pe,">"),Ee,he):P=="?"?x(Se,ge(":"),ne):x(ne);if(_=="quasi")return V(U,he);if(_!=";"){if(_=="(")return se(je,")","call",he);if(_==".")return x(we,he);if(_=="[")return x(pe("]"),Je,ge("]"),Ee,he);if(g&&P=="as")return R.marked="keyword",x(Pe,he);if(_=="regexp")return R.state.lastType=R.marked="operator",R.stream.backUp(R.stream.pos-R.stream.start-1),x(ne)}}function U(_,P){return _!="quasi"?V():P.slice(P.length-2)!="${"?x(U):x(Je,Z)}function Z(_){if(_=="}")return R.marked="string-2",R.state.tokenize=G,x(U)}function ce(_){return re(R.stream,R.state),V(_=="{"?Oe:Se)}function Be(_){return re(R.stream,R.state),V(_=="{"?Oe:je)}function te(_){return function(P){return P=="."?x(_?oe:fe):P=="variable"&&g?x(It,_?Ge:He):V(_?je:Se)}}function fe(_,P){if(P=="target")return R.marked="keyword",x(He)}function oe(_,P){if(P=="target")return R.marked="keyword",x(Ge)}function Ue(_){return _==":"?x(Ee,Oe):V(He,ge(";"),Ee)}function we(_){if(_=="variable")return R.marked="property",x()}function Me(_,P){if(_=="async")return R.marked="property",x(Me);if(_=="variable"||R.style=="keyword"){if(R.marked="property",P=="get"||P=="set")return x(Le);var ae;return g&&R.state.fatArrowAt==R.stream.start&&(ae=R.stream.match(/^\s*:\s*/,!1))&&(R.state.fatArrowAt=R.stream.pos+ae[0].length),x($)}else{if(_=="number"||_=="string")return R.marked=S?"property":R.style+" property",x($);if(_=="jsonld-keyword")return x($);if(g&&H(P))return R.marked="keyword",x(Me);if(_=="[")return x(Se,nt,ge("]"),$);if(_=="spread")return x(je,$);if(P=="*")return R.marked="keyword",x(Me);if(_==":")return V($)}}function Le(_){return _!="variable"?V($):(R.marked="property",x(Bt))}function $(_){if(_==":")return x(je);if(_=="(")return V(Bt)}function B(_,P,ae){function he(ne,ye){if(ae?ae.indexOf(ne)>-1:ne==","){var Xe=R.state.lexical;return Xe.info=="call"&&(Xe.pos=(Xe.pos||0)+1),x(function(pt,Et){return pt==P||Et==P?V():V(_)},he)}return ne==P||ye==P?x():ae&&ae.indexOf(";")>-1?V(_):x(ge(P))}return function(ne,ye){return ne==P||ye==P?x():V(_,he)}}function se(_,P,ae){for(var he=3;he"),Pe);if(_=="quasi")return V(_t,Ht)}function xt(_){if(_=="=>")return x(Pe)}function Ie(_){return _.match(/[\}\)\]]/)?x():_==","||_==";"?x(Ie):V(nr,Ie)}function nr(_,P){if(_=="variable"||R.style=="keyword")return R.marked="property",x(nr);if(P=="?"||_=="number"||_=="string")return x(nr);if(_==":")return x(Pe);if(_=="[")return x(ge("variable"),dt,ge("]"),nr);if(_=="(")return V(hr,nr);if(!_.match(/[;\}\)\],]/))return x()}function _t(_,P){return _!="quasi"?V():P.slice(P.length-2)!="${"?x(_t):x(Pe,it)}function it(_){if(_=="}")return R.marked="string-2",R.state.tokenize=G,x(_t)}function ot(_,P){return _=="variable"&&R.stream.match(/^\s*[?:]/,!1)||P=="?"?x(ot):_==":"?x(Pe):_=="spread"?x(ot):V(Pe)}function Ht(_,P){if(P=="<")return x(pe(">"),B(Pe,">"),Ee,Ht);if(P=="|"||_=="."||P=="&")return x(Pe);if(_=="[")return x(Pe,ge("]"),Ht);if(P=="extends"||P=="implements")return R.marked="keyword",x(Pe);if(P=="?")return x(Pe,ge(":"),Pe)}function It(_,P){if(P=="<")return x(pe(">"),B(Pe,">"),Ee,Ht)}function Wt(){return V(Pe,kt)}function kt(_,P){if(P=="=")return x(Pe)}function Hr(_,P){return P=="enum"?(R.marked="keyword",x(Ae)):V(Ct,nt,Ut,eo)}function Ct(_,P){if(g&&H(P))return R.marked="keyword",x(Ct);if(_=="variable")return X(P),x();if(_=="spread")return x(Ct);if(_=="[")return se(yn,"]");if(_=="{")return se(dr,"}")}function dr(_,P){return _=="variable"&&!R.stream.match(/^\s*:/,!1)?(X(P),x(Ut)):(_=="variable"&&(R.marked="property"),_=="spread"?x(Ct):_=="}"?V():_=="["?x(Se,ge("]"),ge(":"),dr):x(ge(":"),Ct,Ut))}function yn(){return V(Ct,Ut)}function Ut(_,P){if(P=="=")return x(je)}function eo(_){if(_==",")return x(Hr)}function Br(_,P){if(_=="keyword b"&&P=="else")return x(pe("form","else"),Oe,Ee)}function ei(_,P){if(P=="await")return x(ei);if(_=="(")return x(pe(")"),xn,Ee)}function xn(_){return _=="var"?x(Hr,pr):_=="variable"?x(pr):V(pr)}function pr(_,P){return _==")"?x():_==";"?x(pr):P=="in"||P=="of"?(R.marked="keyword",x(Se,pr)):V(Se,pr)}function Bt(_,P){if(P=="*")return R.marked="keyword",x(Bt);if(_=="variable")return X(P),x(Bt);if(_=="(")return x(L,pe(")"),B($t,")"),Ee,Pt,Oe,ze);if(g&&P=="<")return x(pe(">"),B(Wt,">"),Ee,Bt)}function hr(_,P){if(P=="*")return R.marked="keyword",x(hr);if(_=="variable")return X(P),x(hr);if(_=="(")return x(L,pe(")"),B($t,")"),Ee,Pt,ze);if(g&&P=="<")return x(pe(">"),B(Wt,">"),Ee,hr)}function ti(_,P){if(_=="keyword"||_=="variable")return R.marked="type",x(ti);if(P=="<")return x(pe(">"),B(Wt,">"),Ee)}function $t(_,P){return P=="@"&&x(Se,$t),_=="spread"?x($t):g&&H(P)?(R.marked="keyword",x($t)):g&&_=="this"?x(nt,Ut):V(Ct,nt,Ut)}function to(_,P){return _=="variable"?Wr(_,P):Kt(_,P)}function Wr(_,P){if(_=="variable")return X(P),x(Kt)}function Kt(_,P){if(P=="<")return x(pe(">"),B(Wt,">"),Ee,Kt);if(P=="extends"||P=="implements"||g&&_==",")return P=="implements"&&(R.marked="keyword"),x(g?Pe:Se,Kt);if(_=="{")return x(pe("}"),Gt,Ee)}function Gt(_,P){if(_=="async"||_=="variable"&&(P=="static"||P=="get"||P=="set"||g&&H(P))&&R.stream.match(/^\s+#?[\w$\xa1-\uffff]/,!1))return R.marked="keyword",x(Gt);if(_=="variable"||R.style=="keyword")return R.marked="property",x(Cr,Gt);if(_=="number"||_=="string")return x(Cr,Gt);if(_=="[")return x(Se,nt,ge("]"),Cr,Gt);if(P=="*")return R.marked="keyword",x(Gt);if(g&&_=="(")return V(hr,Gt);if(_==";"||_==",")return x(Gt);if(_=="}")return x();if(P=="@")return x(Se,Gt)}function Cr(_,P){if(P=="!"||P=="?")return x(Cr);if(_==":")return x(Pe,Ut);if(P=="=")return x(je);var ae=R.state.lexical.prev,he=ae&&ae.info=="interface";return V(he?hr:Bt)}function Ur(_,P){return P=="*"?(R.marked="keyword",x(Gr,ge(";"))):P=="default"?(R.marked="keyword",x(Se,ge(";"))):_=="{"?x(B($r,"}"),Gr,ge(";")):V(Oe)}function $r(_,P){if(P=="as")return R.marked="keyword",x(ge("variable"));if(_=="variable")return V(je,$r)}function gr(_){return _=="string"?x():_=="("?V(Se):_=="."?V(He):V(Kr,Vt,Gr)}function Kr(_,P){return _=="{"?se(Kr,"}"):(_=="variable"&&X(P),P=="*"&&(R.marked="keyword"),x(_n))}function Vt(_){if(_==",")return x(Kr,Vt)}function _n(_,P){if(P=="as")return R.marked="keyword",x(Kr)}function Gr(_,P){if(P=="from")return R.marked="keyword",x(Se)}function at(_){return _=="]"?x():V(B(je,"]"))}function Ae(){return V(pe("form"),Ct,ge("{"),pe("}"),B(ir,"}"),Ee,Ee)}function ir(){return V(Ct,Ut)}function kn(_,P){return _.lastType=="operator"||_.lastType==","||c.test(P.charAt(0))||/[,.]/.test(P.charAt(0))}function jt(_,P,ae){return P.tokenize==W&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(P.lastType)||P.lastType=="quasi"&&/\{\s*$/.test(_.string.slice(0,_.pos-(ae||0)))}return{startState:function(_){var P={tokenize:W,lastType:"sof",cc:[],lexical:new I((_||0)-C,0,"block",!1),localVars:v.localVars,context:v.localVars&&new le(null,null,!1),indented:_||0};return v.globalVars&&typeof v.globalVars=="object"&&(P.globalVars=v.globalVars),P},token:function(_,P){if(_.sol()&&(P.lexical.hasOwnProperty("align")||(P.lexical.align=!1),P.indented=_.indentation(),re(_,P)),P.tokenize!=O&&_.eatSpace())return null;var ae=P.tokenize(_,P);return z=="comment"?ae:(P.lastType=z=="operator"&&(M=="++"||M=="--")?"incdec":z,Q(P,ae,z,M,_))},indent:function(_,P){if(_.tokenize==O||_.tokenize==G)return o.Pass;if(_.tokenize!=W)return 0;var ae=P&&P.charAt(0),he=_.lexical,ne;if(!/^\s*else\b/.test(P))for(var ye=_.cc.length-1;ye>=0;--ye){var Xe=_.cc[ye];if(Xe==Ee)he=he.prev;else if(Xe!=Br&&Xe!=ze)break}for(;(he.type=="stat"||he.type=="form")&&(ae=="}"||(ne=_.cc[_.cc.length-1])&&(ne==He||ne==Ge)&&!/^[,\.=+\-*:?[\(]/.test(P));)he=he.prev;b&&he.type==")"&&he.prev.type=="stat"&&(he=he.prev);var pt=he.type,Et=ae==pt;return pt=="vardef"?he.indented+(_.lastType=="operator"||_.lastType==","?he.info.length+1:0):pt=="form"&&ae=="{"?he.indented:pt=="form"?he.indented+C:pt=="stat"?he.indented+(kn(_,P)?b||C:0):he.info=="switch"&&!Et&&v.doubleIndentSwitch!=!1?he.indented+(/^(?:case|default)\b/.test(P)?C:2*C):he.align?he.column+(Et?0:1):he.indented+(Et?0:C)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:s?null:"/*",blockCommentEnd:s?null:"*/",blockCommentContinue:s?null:" * ",lineComment:s?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:s?"json":"javascript",jsonldMode:S,jsonMode:s,expressionAllowed:jt,skipExpression:function(_){Q(_,"atom","atom","true",new o.StringStream("",2,null))}}}),o.registerHelper("wordChars","javascript",/[\w$]/),o.defineMIME("text/javascript","javascript"),o.defineMIME("text/ecmascript","javascript"),o.defineMIME("application/javascript","javascript"),o.defineMIME("application/x-javascript","javascript"),o.defineMIME("application/ecmascript","javascript"),o.defineMIME("application/json",{name:"javascript",json:!0}),o.defineMIME("application/x-json",{name:"javascript",json:!0}),o.defineMIME("application/manifest+json",{name:"javascript",json:!0}),o.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),o.defineMIME("text/typescript",{name:"javascript",typescript:!0}),o.defineMIME("application/typescript",{name:"javascript",typescript:!0})})});var Qn=Ke((Os,Ps)=>{(function(o){typeof Os=="object"&&typeof Ps=="object"?o(We(),mn(),vn(),gn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],o):o(CodeMirror)})(function(o){"use strict";var p={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function v(T,y,c){var d=T.current(),k=d.search(y);return k>-1?T.backUp(d.length-k):d.match(/<\/?$/)&&(T.backUp(d.length),T.match(y,!1)||T.match(d)),c}var C={};function b(T){var y=C[T];return y||(C[T]=new RegExp("\\s+"+T+`\\s*=\\s*('|")?([^'"]+)('|")?\\s*`))}function S(T,y){var c=T.match(b(y));return c?/^\s*(.*?)\s*$/.exec(c[2])[1]:""}function s(T,y){return new RegExp((y?"^":"")+"","i")}function h(T,y){for(var c in T)for(var d=y[c]||(y[c]=[]),k=T[c],z=k.length-1;z>=0;z--)d.unshift(k[z])}function g(T,y){for(var c=0;c=0;M--)d.script.unshift(["type",z[M].matches,z[M].mode]);function w(W,E){var O=c.token(W,E.htmlState),G=/\btag\b/.test(O),J;if(G&&!/[<>\s\/]/.test(W.current())&&(J=E.htmlState.tagName&&E.htmlState.tagName.toLowerCase())&&d.hasOwnProperty(J))E.inTag=J+" ";else if(E.inTag&&G&&/>$/.test(W.current())){var re=/^([\S]+) (.*)/.exec(E.inTag);E.inTag=null;var q=W.current()==">"&&g(d[re[1]],re[2]),I=o.getMode(T,q),D=s(re[1],!0),Q=s(re[1],!1);E.token=function(R,V){return R.match(D,!1)?(V.token=w,V.localState=V.localMode=null,null):v(R,Q,V.localMode.token(R,V.localState))},E.localMode=I,E.localState=o.startState(I,c.indent(E.htmlState,"",""))}else E.inTag&&(E.inTag+=W.current(),W.eol()&&(E.inTag+=" "));return O}return{startState:function(){var W=o.startState(c);return{token:w,inTag:null,localMode:null,localState:null,htmlState:W}},copyState:function(W){var E;return W.localState&&(E=o.copyState(W.localMode,W.localState)),{token:W.token,inTag:W.inTag,localMode:W.localMode,localState:E,htmlState:o.copyState(c,W.htmlState)}},token:function(W,E){return E.token(W,E)},indent:function(W,E,O){return!W.localMode||/^\s*<\//.test(E)?c.indent(W.htmlState,E,O):W.localMode.indent?W.localMode.indent(W.localState,E,O):o.Pass},innerMode:function(W){return{state:W.localState||W.htmlState,mode:W.localMode||c}}}},"xml","javascript","css"),o.defineMIME("text/html","htmlmixed")})});var Hs=Ke((js,Rs)=>{(function(o){typeof js=="object"&&typeof Rs=="object"?o(We(),Qn(),Yn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../htmlmixed/htmlmixed","../../addon/mode/overlay"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("django:inner",function(){var p=["block","endblock","for","endfor","true","false","filter","endfilter","loop","none","self","super","if","elif","endif","as","else","import","with","endwith","without","context","ifequal","endifequal","ifnotequal","endifnotequal","extends","include","load","comment","endcomment","empty","url","static","trans","blocktrans","endblocktrans","now","regroup","lorem","ifchanged","endifchanged","firstof","debug","cycle","csrf_token","autoescape","endautoescape","spaceless","endspaceless","ssi","templatetag","verbatim","endverbatim","widthratio"],v=["add","addslashes","capfirst","center","cut","date","default","default_if_none","dictsort","dictsortreversed","divisibleby","escape","escapejs","filesizeformat","first","floatformat","force_escape","get_digit","iriencode","join","last","length","length_is","linebreaks","linebreaksbr","linenumbers","ljust","lower","make_list","phone2numeric","pluralize","pprint","random","removetags","rjust","safe","safeseq","slice","slugify","stringformat","striptags","time","timesince","timeuntil","title","truncatechars","truncatechars_html","truncatewords","truncatewords_html","unordered_list","upper","urlencode","urlize","urlizetrunc","wordcount","wordwrap","yesno"],C=["==","!=","<",">","<=",">="],b=["in","not","or","and"];p=new RegExp("^\\b("+p.join("|")+")\\b"),v=new RegExp("^\\b("+v.join("|")+")\\b"),C=new RegExp("^\\b("+C.join("|")+")\\b"),b=new RegExp("^\\b("+b.join("|")+")\\b");function S(c,d){if(c.match("{{"))return d.tokenize=h,"tag";if(c.match("{%"))return d.tokenize=g,"tag";if(c.match("{#"))return d.tokenize=T,"comment";for(;c.next()!=null&&!c.match(/\{[{%#]/,!1););return null}function s(c,d){return function(k,z){if(!z.escapeNext&&k.eat(c))z.tokenize=d;else{z.escapeNext&&(z.escapeNext=!1);var M=k.next();M=="\\"&&(z.escapeNext=!0)}return"string"}}function h(c,d){if(d.waitDot){if(d.waitDot=!1,c.peek()!=".")return"null";if(c.match(/\.\W+/))return"error";if(c.eat("."))return d.waitProperty=!0,"null";throw Error("Unexpected error while waiting for property.")}if(d.waitPipe){if(d.waitPipe=!1,c.peek()!="|")return"null";if(c.match(/\.\W+/))return"error";if(c.eat("|"))return d.waitFilter=!0,"null";throw Error("Unexpected error while waiting for filter.")}return d.waitProperty&&(d.waitProperty=!1,c.match(/\b(\w+)\b/))?(d.waitDot=!0,d.waitPipe=!0,"property"):d.waitFilter&&(d.waitFilter=!1,c.match(v))?"variable-2":c.eatSpace()?(d.waitProperty=!1,"null"):c.match(/\b\d+(\.\d+)?\b/)?"number":c.match("'")?(d.tokenize=s("'",d.tokenize),"string"):c.match('"')?(d.tokenize=s('"',d.tokenize),"string"):c.match(/\b(\w+)\b/)&&!d.foundVariable?(d.waitDot=!0,d.waitPipe=!0,"variable"):c.match("}}")?(d.waitProperty=null,d.waitFilter=null,d.waitDot=null,d.waitPipe=null,d.tokenize=S,"tag"):(c.next(),"null")}function g(c,d){if(d.waitDot){if(d.waitDot=!1,c.peek()!=".")return"null";if(c.match(/\.\W+/))return"error";if(c.eat("."))return d.waitProperty=!0,"null";throw Error("Unexpected error while waiting for property.")}if(d.waitPipe){if(d.waitPipe=!1,c.peek()!="|")return"null";if(c.match(/\.\W+/))return"error";if(c.eat("|"))return d.waitFilter=!0,"null";throw Error("Unexpected error while waiting for filter.")}if(d.waitProperty&&(d.waitProperty=!1,c.match(/\b(\w+)\b/)))return d.waitDot=!0,d.waitPipe=!0,"property";if(d.waitFilter&&(d.waitFilter=!1,c.match(v)))return"variable-2";if(c.eatSpace())return d.waitProperty=!1,"null";if(c.match(/\b\d+(\.\d+)?\b/))return"number";if(c.match("'"))return d.tokenize=s("'",d.tokenize),"string";if(c.match('"'))return d.tokenize=s('"',d.tokenize),"string";if(c.match(C))return"operator";if(c.match(b))return"keyword";var k=c.match(p);return k?(k[0]=="comment"&&(d.blockCommentTag=!0),"keyword"):c.match(/\b(\w+)\b/)?(d.waitDot=!0,d.waitPipe=!0,"variable"):c.match("%}")?(d.waitProperty=null,d.waitFilter=null,d.waitDot=null,d.waitPipe=null,d.blockCommentTag?(d.blockCommentTag=!1,d.tokenize=y):d.tokenize=S,"tag"):(c.next(),"null")}function T(c,d){return c.match(/^.*?#\}/)?d.tokenize=S:c.skipToEnd(),"comment"}function y(c,d){return c.match(/\{%\s*endcomment\s*%\}/,!1)?(d.tokenize=g,c.match("{%"),"tag"):(c.next(),"comment")}return{startState:function(){return{tokenize:S}},token:function(c,d){return d.tokenize(c,d)},blockCommentStart:"{% comment %}",blockCommentEnd:"{% endcomment %}"}}),o.defineMode("django",function(p){var v=o.getMode(p,"text/html"),C=o.getMode(p,"django:inner");return o.overlayMode(v,C)}),o.defineMIME("text/x-django","django")})});var Di=Ke((Bs,Ws)=>{(function(o){typeof Bs=="object"&&typeof Ws=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineSimpleMode=function(y,c){o.defineMode(y,function(d){return o.simpleMode(d,c)})},o.simpleMode=function(y,c){p(c,"start");var d={},k=c.meta||{},z=!1;for(var M in c)if(M!=k&&c.hasOwnProperty(M))for(var w=d[M]=[],W=c[M],E=0;E2&&O.token&&typeof O.token!="string"){for(var re=2;re-1)return o.Pass;var M=d.indent.length-1,w=y[d.state];e:for(;;){for(var W=0;W{(function(o){typeof Us=="object"&&typeof $s=="object"?o(We(),Di()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../../addon/mode/simple"],o):o(CodeMirror)})(function(o){"use strict";var p="from",v=new RegExp("^(\\s*)\\b("+p+")\\b","i"),C=["run","cmd","entrypoint","shell"],b=new RegExp("^(\\s*)("+C.join("|")+")(\\s+\\[)","i"),S="expose",s=new RegExp("^(\\s*)("+S+")(\\s+)","i"),h=["arg","from","maintainer","label","env","add","copy","volume","user","workdir","onbuild","stopsignal","healthcheck","shell"],g=[p,S].concat(C).concat(h),T="("+g.join("|")+")",y=new RegExp("^(\\s*)"+T+"(\\s*)(#.*)?$","i"),c=new RegExp("^(\\s*)"+T+"(\\s+)","i");o.defineSimpleMode("dockerfile",{start:[{regex:/^\s*#.*$/,sol:!0,token:"comment"},{regex:v,token:[null,"keyword"],sol:!0,next:"from"},{regex:y,token:[null,"keyword",null,"error"],sol:!0},{regex:b,token:[null,"keyword",null],sol:!0,next:"array"},{regex:s,token:[null,"keyword",null],sol:!0,next:"expose"},{regex:c,token:[null,"keyword",null],sol:!0,next:"arguments"},{regex:/./,token:null}],from:[{regex:/\s*$/,token:null,next:"start"},{regex:/(\s*)(#.*)$/,token:[null,"error"],next:"start"},{regex:/(\s*\S+\s+)(as)/i,token:[null,"keyword"],next:"start"},{token:null,next:"start"}],single:[{regex:/(?:[^\\']|\\.)/,token:"string"},{regex:/'/,token:"string",pop:!0}],double:[{regex:/(?:[^\\"]|\\.)/,token:"string"},{regex:/"/,token:"string",pop:!0}],array:[{regex:/\]/,token:null,next:"start"},{regex:/"(?:[^\\"]|\\.)*"?/,token:"string"}],expose:[{regex:/\d+$/,token:"number",next:"start"},{regex:/[^\d]+$/,token:null,next:"start"},{regex:/\d+/,token:"number"},{regex:/[^\d]+/,token:null},{token:null,next:"start"}],arguments:[{regex:/^\s*#.*$/,sol:!0,token:"comment"},{regex:/"(?:[^\\"]|\\.)*"?$/,token:"string",next:"start"},{regex:/"/,token:"string",push:"double"},{regex:/'(?:[^\\']|\\.)*'?$/,token:"string",next:"start"},{regex:/'/,token:"string",push:"single"},{regex:/[^#"']+[\\`]$/,token:null},{regex:/[^#"']+$/,token:null,next:"start"},{regex:/[^#"']+/,token:null},{token:null,next:"start"}],meta:{lineComment:"#"}}),o.defineMIME("text/x-dockerfile","dockerfile")})});var Xs=Ke((Gs,Zs)=>{(function(o){typeof Gs=="object"&&typeof Zs=="object"?o(We()):typeof define=="function"&&define.amd?define(["../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.modeInfo=[{name:"APL",mime:"text/apl",mode:"apl",ext:["dyalog","apl"]},{name:"PGP",mimes:["application/pgp","application/pgp-encrypted","application/pgp-keys","application/pgp-signature"],mode:"asciiarmor",ext:["asc","pgp","sig"]},{name:"ASN.1",mime:"text/x-ttcn-asn",mode:"asn.1",ext:["asn","asn1"]},{name:"Asterisk",mime:"text/x-asterisk",mode:"asterisk",file:/^extensions\.conf$/i},{name:"Brainfuck",mime:"text/x-brainfuck",mode:"brainfuck",ext:["b","bf"]},{name:"C",mime:"text/x-csrc",mode:"clike",ext:["c","h","ino"]},{name:"C++",mime:"text/x-c++src",mode:"clike",ext:["cpp","c++","cc","cxx","hpp","h++","hh","hxx"],alias:["cpp"]},{name:"Cobol",mime:"text/x-cobol",mode:"cobol",ext:["cob","cpy","cbl"]},{name:"C#",mime:"text/x-csharp",mode:"clike",ext:["cs"],alias:["csharp","cs"]},{name:"Clojure",mime:"text/x-clojure",mode:"clojure",ext:["clj","cljc","cljx"]},{name:"ClojureScript",mime:"text/x-clojurescript",mode:"clojure",ext:["cljs"]},{name:"Closure Stylesheets (GSS)",mime:"text/x-gss",mode:"css",ext:["gss"]},{name:"CMake",mime:"text/x-cmake",mode:"cmake",ext:["cmake","cmake.in"],file:/^CMakeLists\.txt$/},{name:"CoffeeScript",mimes:["application/vnd.coffeescript","text/coffeescript","text/x-coffeescript"],mode:"coffeescript",ext:["coffee"],alias:["coffee","coffee-script"]},{name:"Common Lisp",mime:"text/x-common-lisp",mode:"commonlisp",ext:["cl","lisp","el"],alias:["lisp"]},{name:"Cypher",mime:"application/x-cypher-query",mode:"cypher",ext:["cyp","cypher"]},{name:"Cython",mime:"text/x-cython",mode:"python",ext:["pyx","pxd","pxi"]},{name:"Crystal",mime:"text/x-crystal",mode:"crystal",ext:["cr"]},{name:"CSS",mime:"text/css",mode:"css",ext:["css"]},{name:"CQL",mime:"text/x-cassandra",mode:"sql",ext:["cql"]},{name:"D",mime:"text/x-d",mode:"d",ext:["d"]},{name:"Dart",mimes:["application/dart","text/x-dart"],mode:"dart",ext:["dart"]},{name:"diff",mime:"text/x-diff",mode:"diff",ext:["diff","patch"]},{name:"Django",mime:"text/x-django",mode:"django"},{name:"Dockerfile",mime:"text/x-dockerfile",mode:"dockerfile",file:/^Dockerfile$/},{name:"DTD",mime:"application/xml-dtd",mode:"dtd",ext:["dtd"]},{name:"Dylan",mime:"text/x-dylan",mode:"dylan",ext:["dylan","dyl","intr"]},{name:"EBNF",mime:"text/x-ebnf",mode:"ebnf"},{name:"ECL",mime:"text/x-ecl",mode:"ecl",ext:["ecl"]},{name:"edn",mime:"application/edn",mode:"clojure",ext:["edn"]},{name:"Eiffel",mime:"text/x-eiffel",mode:"eiffel",ext:["e"]},{name:"Elm",mime:"text/x-elm",mode:"elm",ext:["elm"]},{name:"Embedded JavaScript",mime:"application/x-ejs",mode:"htmlembedded",ext:["ejs"]},{name:"Embedded Ruby",mime:"application/x-erb",mode:"htmlembedded",ext:["erb"]},{name:"Erlang",mime:"text/x-erlang",mode:"erlang",ext:["erl"]},{name:"Esper",mime:"text/x-esper",mode:"sql"},{name:"Factor",mime:"text/x-factor",mode:"factor",ext:["factor"]},{name:"FCL",mime:"text/x-fcl",mode:"fcl"},{name:"Forth",mime:"text/x-forth",mode:"forth",ext:["forth","fth","4th"]},{name:"Fortran",mime:"text/x-fortran",mode:"fortran",ext:["f","for","f77","f90","f95"]},{name:"F#",mime:"text/x-fsharp",mode:"mllike",ext:["fs"],alias:["fsharp"]},{name:"Gas",mime:"text/x-gas",mode:"gas",ext:["s"]},{name:"Gherkin",mime:"text/x-feature",mode:"gherkin",ext:["feature"]},{name:"GitHub Flavored Markdown",mime:"text/x-gfm",mode:"gfm",file:/^(readme|contributing|history)\.md$/i},{name:"Go",mime:"text/x-go",mode:"go",ext:["go"]},{name:"Groovy",mime:"text/x-groovy",mode:"groovy",ext:["groovy","gradle"],file:/^Jenkinsfile$/},{name:"HAML",mime:"text/x-haml",mode:"haml",ext:["haml"]},{name:"Haskell",mime:"text/x-haskell",mode:"haskell",ext:["hs"]},{name:"Haskell (Literate)",mime:"text/x-literate-haskell",mode:"haskell-literate",ext:["lhs"]},{name:"Haxe",mime:"text/x-haxe",mode:"haxe",ext:["hx"]},{name:"HXML",mime:"text/x-hxml",mode:"haxe",ext:["hxml"]},{name:"ASP.NET",mime:"application/x-aspx",mode:"htmlembedded",ext:["aspx"],alias:["asp","aspx"]},{name:"HTML",mime:"text/html",mode:"htmlmixed",ext:["html","htm","handlebars","hbs"],alias:["xhtml"]},{name:"HTTP",mime:"message/http",mode:"http"},{name:"IDL",mime:"text/x-idl",mode:"idl",ext:["pro"]},{name:"Pug",mime:"text/x-pug",mode:"pug",ext:["jade","pug"],alias:["jade"]},{name:"Java",mime:"text/x-java",mode:"clike",ext:["java"]},{name:"Java Server Pages",mime:"application/x-jsp",mode:"htmlembedded",ext:["jsp"],alias:["jsp"]},{name:"JavaScript",mimes:["text/javascript","text/ecmascript","application/javascript","application/x-javascript","application/ecmascript"],mode:"javascript",ext:["js"],alias:["ecmascript","js","node"]},{name:"JSON",mimes:["application/json","application/x-json"],mode:"javascript",ext:["json","map"],alias:["json5"]},{name:"JSON-LD",mime:"application/ld+json",mode:"javascript",ext:["jsonld"],alias:["jsonld"]},{name:"JSX",mime:"text/jsx",mode:"jsx",ext:["jsx"]},{name:"Jinja2",mime:"text/jinja2",mode:"jinja2",ext:["j2","jinja","jinja2"]},{name:"Julia",mime:"text/x-julia",mode:"julia",ext:["jl"],alias:["jl"]},{name:"Kotlin",mime:"text/x-kotlin",mode:"clike",ext:["kt"]},{name:"LESS",mime:"text/x-less",mode:"css",ext:["less"]},{name:"LiveScript",mime:"text/x-livescript",mode:"livescript",ext:["ls"],alias:["ls"]},{name:"Lua",mime:"text/x-lua",mode:"lua",ext:["lua"]},{name:"Markdown",mime:"text/x-markdown",mode:"markdown",ext:["markdown","md","mkd"]},{name:"mIRC",mime:"text/mirc",mode:"mirc"},{name:"MariaDB SQL",mime:"text/x-mariadb",mode:"sql"},{name:"Mathematica",mime:"text/x-mathematica",mode:"mathematica",ext:["m","nb","wl","wls"]},{name:"Modelica",mime:"text/x-modelica",mode:"modelica",ext:["mo"]},{name:"MUMPS",mime:"text/x-mumps",mode:"mumps",ext:["mps"]},{name:"MS SQL",mime:"text/x-mssql",mode:"sql"},{name:"mbox",mime:"application/mbox",mode:"mbox",ext:["mbox"]},{name:"MySQL",mime:"text/x-mysql",mode:"sql"},{name:"Nginx",mime:"text/x-nginx-conf",mode:"nginx",file:/nginx.*\.conf$/i},{name:"NSIS",mime:"text/x-nsis",mode:"nsis",ext:["nsh","nsi"]},{name:"NTriples",mimes:["application/n-triples","application/n-quads","text/n-triples"],mode:"ntriples",ext:["nt","nq"]},{name:"Objective-C",mime:"text/x-objectivec",mode:"clike",ext:["m"],alias:["objective-c","objc"]},{name:"Objective-C++",mime:"text/x-objectivec++",mode:"clike",ext:["mm"],alias:["objective-c++","objc++"]},{name:"OCaml",mime:"text/x-ocaml",mode:"mllike",ext:["ml","mli","mll","mly"]},{name:"Octave",mime:"text/x-octave",mode:"octave",ext:["m"]},{name:"Oz",mime:"text/x-oz",mode:"oz",ext:["oz"]},{name:"Pascal",mime:"text/x-pascal",mode:"pascal",ext:["p","pas"]},{name:"PEG.js",mime:"null",mode:"pegjs",ext:["jsonld"]},{name:"Perl",mime:"text/x-perl",mode:"perl",ext:["pl","pm"]},{name:"PHP",mimes:["text/x-php","application/x-httpd-php","application/x-httpd-php-open"],mode:"php",ext:["php","php3","php4","php5","php7","phtml"]},{name:"Pig",mime:"text/x-pig",mode:"pig",ext:["pig"]},{name:"Plain Text",mime:"text/plain",mode:"null",ext:["txt","text","conf","def","list","log"]},{name:"PLSQL",mime:"text/x-plsql",mode:"sql",ext:["pls"]},{name:"PostgreSQL",mime:"text/x-pgsql",mode:"sql"},{name:"PowerShell",mime:"application/x-powershell",mode:"powershell",ext:["ps1","psd1","psm1"]},{name:"Properties files",mime:"text/x-properties",mode:"properties",ext:["properties","ini","in"],alias:["ini","properties"]},{name:"ProtoBuf",mime:"text/x-protobuf",mode:"protobuf",ext:["proto"]},{name:"Python",mime:"text/x-python",mode:"python",ext:["BUILD","bzl","py","pyw"],file:/^(BUCK|BUILD)$/},{name:"Puppet",mime:"text/x-puppet",mode:"puppet",ext:["pp"]},{name:"Q",mime:"text/x-q",mode:"q",ext:["q"]},{name:"R",mime:"text/x-rsrc",mode:"r",ext:["r","R"],alias:["rscript"]},{name:"reStructuredText",mime:"text/x-rst",mode:"rst",ext:["rst"],alias:["rst"]},{name:"RPM Changes",mime:"text/x-rpm-changes",mode:"rpm"},{name:"RPM Spec",mime:"text/x-rpm-spec",mode:"rpm",ext:["spec"]},{name:"Ruby",mime:"text/x-ruby",mode:"ruby",ext:["rb"],alias:["jruby","macruby","rake","rb","rbx"]},{name:"Rust",mime:"text/x-rustsrc",mode:"rust",ext:["rs"]},{name:"SAS",mime:"text/x-sas",mode:"sas",ext:["sas"]},{name:"Sass",mime:"text/x-sass",mode:"sass",ext:["sass"]},{name:"Scala",mime:"text/x-scala",mode:"clike",ext:["scala"]},{name:"Scheme",mime:"text/x-scheme",mode:"scheme",ext:["scm","ss"]},{name:"SCSS",mime:"text/x-scss",mode:"css",ext:["scss"]},{name:"Shell",mimes:["text/x-sh","application/x-sh"],mode:"shell",ext:["sh","ksh","bash"],alias:["bash","sh","zsh"],file:/^PKGBUILD$/},{name:"Sieve",mime:"application/sieve",mode:"sieve",ext:["siv","sieve"]},{name:"Slim",mimes:["text/x-slim","application/x-slim"],mode:"slim",ext:["slim"]},{name:"Smalltalk",mime:"text/x-stsrc",mode:"smalltalk",ext:["st"]},{name:"Smarty",mime:"text/x-smarty",mode:"smarty",ext:["tpl"]},{name:"Solr",mime:"text/x-solr",mode:"solr"},{name:"SML",mime:"text/x-sml",mode:"mllike",ext:["sml","sig","fun","smackspec"]},{name:"Soy",mime:"text/x-soy",mode:"soy",ext:["soy"],alias:["closure template"]},{name:"SPARQL",mime:"application/sparql-query",mode:"sparql",ext:["rq","sparql"],alias:["sparul"]},{name:"Spreadsheet",mime:"text/x-spreadsheet",mode:"spreadsheet",alias:["excel","formula"]},{name:"SQL",mime:"text/x-sql",mode:"sql",ext:["sql"]},{name:"SQLite",mime:"text/x-sqlite",mode:"sql"},{name:"Squirrel",mime:"text/x-squirrel",mode:"clike",ext:["nut"]},{name:"Stylus",mime:"text/x-styl",mode:"stylus",ext:["styl"]},{name:"Swift",mime:"text/x-swift",mode:"swift",ext:["swift"]},{name:"sTeX",mime:"text/x-stex",mode:"stex"},{name:"LaTeX",mime:"text/x-latex",mode:"stex",ext:["text","ltx","tex"],alias:["tex"]},{name:"SystemVerilog",mime:"text/x-systemverilog",mode:"verilog",ext:["v","sv","svh"]},{name:"Tcl",mime:"text/x-tcl",mode:"tcl",ext:["tcl"]},{name:"Textile",mime:"text/x-textile",mode:"textile",ext:["textile"]},{name:"TiddlyWiki",mime:"text/x-tiddlywiki",mode:"tiddlywiki"},{name:"Tiki wiki",mime:"text/tiki",mode:"tiki"},{name:"TOML",mime:"text/x-toml",mode:"toml",ext:["toml"]},{name:"Tornado",mime:"text/x-tornado",mode:"tornado"},{name:"troff",mime:"text/troff",mode:"troff",ext:["1","2","3","4","5","6","7","8","9"]},{name:"TTCN",mime:"text/x-ttcn",mode:"ttcn",ext:["ttcn","ttcn3","ttcnpp"]},{name:"TTCN_CFG",mime:"text/x-ttcn-cfg",mode:"ttcn-cfg",ext:["cfg"]},{name:"Turtle",mime:"text/turtle",mode:"turtle",ext:["ttl"]},{name:"TypeScript",mime:"application/typescript",mode:"javascript",ext:["ts"],alias:["ts"]},{name:"TypeScript-JSX",mime:"text/typescript-jsx",mode:"jsx",ext:["tsx"],alias:["tsx"]},{name:"Twig",mime:"text/x-twig",mode:"twig"},{name:"Web IDL",mime:"text/x-webidl",mode:"webidl",ext:["webidl"]},{name:"VB.NET",mime:"text/x-vb",mode:"vb",ext:["vb"]},{name:"VBScript",mime:"text/vbscript",mode:"vbscript",ext:["vbs"]},{name:"Velocity",mime:"text/velocity",mode:"velocity",ext:["vtl"]},{name:"Verilog",mime:"text/x-verilog",mode:"verilog",ext:["v"]},{name:"VHDL",mime:"text/x-vhdl",mode:"vhdl",ext:["vhd","vhdl"]},{name:"Vue.js Component",mimes:["script/x-vue","text/x-vue"],mode:"vue",ext:["vue"]},{name:"XML",mimes:["application/xml","text/xml"],mode:"xml",ext:["xml","xsl","xsd","svg"],alias:["rss","wsdl","xsd"]},{name:"XQuery",mime:"application/xquery",mode:"xquery",ext:["xy","xquery"]},{name:"Yacas",mime:"text/x-yacas",mode:"yacas",ext:["ys"]},{name:"YAML",mimes:["text/x-yaml","text/yaml"],mode:"yaml",ext:["yaml","yml"],alias:["yml"]},{name:"Z80",mime:"text/x-z80",mode:"z80",ext:["z80"]},{name:"mscgen",mime:"text/x-mscgen",mode:"mscgen",ext:["mscgen","mscin","msc"]},{name:"xu",mime:"text/x-xu",mode:"mscgen",ext:["xu"]},{name:"msgenny",mime:"text/x-msgenny",mode:"mscgen",ext:["msgenny"]},{name:"WebAssembly",mime:"text/webassembly",mode:"wast",ext:["wat","wast"]}];for(var p=0;p-1&&C.substring(s+1,C.length);if(h)return o.findModeByExtension(h)},o.findModeByName=function(C){C=C.toLowerCase();for(var b=0;b{(function(o){typeof Ys=="object"&&typeof Qs=="object"?o(We(),mn(),Xs()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../xml/xml","../meta"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("markdown",function(p,v){var C=o.getMode(p,"text/html"),b=C.name=="null";function S(F){if(o.findModeByName){var L=o.findModeByName(F);L&&(F=L.mime||L.mimes[0])}var de=o.getMode(p,F);return de.name=="null"?null:de}v.highlightFormatting===void 0&&(v.highlightFormatting=!1),v.maxBlockquoteDepth===void 0&&(v.maxBlockquoteDepth=0),v.taskLists===void 0&&(v.taskLists=!1),v.strikethrough===void 0&&(v.strikethrough=!1),v.emoji===void 0&&(v.emoji=!1),v.fencedCodeBlockHighlighting===void 0&&(v.fencedCodeBlockHighlighting=!0),v.fencedCodeBlockDefaultMode===void 0&&(v.fencedCodeBlockDefaultMode="text/plain"),v.xml===void 0&&(v.xml=!0),v.tokenTypeOverrides===void 0&&(v.tokenTypeOverrides={});var s={header:"header",code:"comment",quote:"quote",list1:"variable-2",list2:"variable-3",list3:"keyword",hr:"hr",image:"image",imageAltText:"image-alt-text",imageMarker:"image-marker",formatting:"formatting",linkInline:"link",linkEmail:"link",linkText:"link",linkHref:"string",em:"em",strong:"strong",strikethrough:"strikethrough",emoji:"builtin"};for(var h in s)s.hasOwnProperty(h)&&v.tokenTypeOverrides[h]&&(s[h]=v.tokenTypeOverrides[h]);var g=/^([*\-_])(?:\s*\1){2,}\s*$/,T=/^(?:[*\-+]|^[0-9]+([.)]))\s+/,y=/^\[(x| )\](?=\s)/i,c=v.allowAtxHeaderWithoutSpace?/^(#+)/:/^(#+)(?: |$)/,d=/^ {0,3}(?:\={1,}|-{2,})\s*$/,k=/^[^#!\[\]*_\\<>` "'(~:]+/,z=/^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/,M=/^\s*\[[^\]]+?\]:.*$/,w=/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/,W=" ";function E(F,L,de){return L.f=L.inline=de,de(F,L)}function O(F,L,de){return L.f=L.block=de,de(F,L)}function G(F){return!F||!/\S/.test(F.string)}function J(F){if(F.linkTitle=!1,F.linkHref=!1,F.linkText=!1,F.em=!1,F.strong=!1,F.strikethrough=!1,F.quote=0,F.indentedCode=!1,F.f==q){var L=b;if(!L){var de=o.innerMode(C,F.htmlState);L=de.mode.name=="xml"&&de.state.tagStart===null&&!de.state.context&&de.state.tokenize.isInText}L&&(F.f=R,F.block=re,F.htmlState=null)}return F.trailingSpace=0,F.trailingSpaceNewLine=!1,F.prevLine=F.thisLine,F.thisLine={stream:null},null}function re(F,L){var de=F.column()===L.indentation,ze=G(L.prevLine.stream),pe=L.indentedCode,Ee=L.prevLine.hr,ge=L.list!==!1,Oe=(L.listStack[L.listStack.length-1]||0)+3;L.indentedCode=!1;var qe=L.indentation;if(L.indentationDiff===null&&(L.indentationDiff=L.indentation,ge)){for(L.list=null;qe=4&&(pe||L.prevLine.fencedCodeEnd||L.prevLine.header||ze))return F.skipToEnd(),L.indentedCode=!0,s.code;if(F.eatSpace())return null;if(de&&L.indentation<=Oe&&(Ze=F.match(c))&&Ze[1].length<=6)return L.quote=0,L.header=Ze[1].length,L.thisLine.header=!0,v.highlightFormatting&&(L.formatting="header"),L.f=L.inline,D(L);if(L.indentation<=Oe&&F.eat(">"))return L.quote=de?1:L.quote+1,v.highlightFormatting&&(L.formatting="quote"),F.eatSpace(),D(L);if(!je&&!L.setext&&de&&L.indentation<=Oe&&(Ze=F.match(T))){var ke=Ze[1]?"ol":"ul";return L.indentation=qe+F.current().length,L.list=!0,L.quote=0,L.listStack.push(L.indentation),L.em=!1,L.strong=!1,L.code=!1,L.strikethrough=!1,v.taskLists&&F.match(y,!1)&&(L.taskList=!0),L.f=L.inline,v.highlightFormatting&&(L.formatting=["list","list-"+ke]),D(L)}else{if(de&&L.indentation<=Oe&&(Ze=F.match(z,!0)))return L.quote=0,L.fencedEndRE=new RegExp(Ze[1]+"+ *$"),L.localMode=v.fencedCodeBlockHighlighting&&S(Ze[2]||v.fencedCodeBlockDefaultMode),L.localMode&&(L.localState=o.startState(L.localMode)),L.f=L.block=I,v.highlightFormatting&&(L.formatting="code-block"),L.code=-1,D(L);if(L.setext||(!Se||!ge)&&!L.quote&&L.list===!1&&!L.code&&!je&&!M.test(F.string)&&(Ze=F.lookAhead(1))&&(Ze=Ze.match(d)))return L.setext?(L.header=L.setext,L.setext=0,F.skipToEnd(),v.highlightFormatting&&(L.formatting="header")):(L.header=Ze[0].charAt(0)=="="?1:2,L.setext=L.header),L.thisLine.header=!0,L.f=L.inline,D(L);if(je)return F.skipToEnd(),L.hr=!0,L.thisLine.hr=!0,s.hr;if(F.peek()==="[")return E(F,L,N)}return E(F,L,L.inline)}function q(F,L){var de=C.token(F,L.htmlState);if(!b){var ze=o.innerMode(C,L.htmlState);(ze.mode.name=="xml"&&ze.state.tagStart===null&&!ze.state.context&&ze.state.tokenize.isInText||L.md_inside&&F.current().indexOf(">")>-1)&&(L.f=R,L.block=re,L.htmlState=null)}return de}function I(F,L){var de=L.listStack[L.listStack.length-1]||0,ze=L.indentation=F.quote?L.push(s.formatting+"-"+F.formatting[de]+"-"+F.quote):L.push("error"))}if(F.taskOpen)return L.push("meta"),L.length?L.join(" "):null;if(F.taskClosed)return L.push("property"),L.length?L.join(" "):null;if(F.linkHref?L.push(s.linkHref,"url"):(F.strong&&L.push(s.strong),F.em&&L.push(s.em),F.strikethrough&&L.push(s.strikethrough),F.emoji&&L.push(s.emoji),F.linkText&&L.push(s.linkText),F.code&&L.push(s.code),F.image&&L.push(s.image),F.imageAltText&&L.push(s.imageAltText,"link"),F.imageMarker&&L.push(s.imageMarker)),F.header&&L.push(s.header,s.header+"-"+F.header),F.quote&&(L.push(s.quote),!v.maxBlockquoteDepth||v.maxBlockquoteDepth>=F.quote?L.push(s.quote+"-"+F.quote):L.push(s.quote+"-"+v.maxBlockquoteDepth)),F.list!==!1){var ze=(F.listStack.length-1)%3;ze?ze===1?L.push(s.list2):L.push(s.list3):L.push(s.list1)}return F.trailingSpaceNewLine?L.push("trailing-space-new-line"):F.trailingSpace&&L.push("trailing-space-"+(F.trailingSpace%2?"a":"b")),L.length?L.join(" "):null}function Q(F,L){if(F.match(k,!0))return D(L)}function R(F,L){var de=L.text(F,L);if(typeof de<"u")return de;if(L.list)return L.list=null,D(L);if(L.taskList){var ze=F.match(y,!0)[1]===" ";return ze?L.taskOpen=!0:L.taskClosed=!0,v.highlightFormatting&&(L.formatting="task"),L.taskList=!1,D(L)}if(L.taskOpen=!1,L.taskClosed=!1,L.header&&F.match(/^#+$/,!0))return v.highlightFormatting&&(L.formatting="header"),D(L);var pe=F.next();if(L.linkTitle){L.linkTitle=!1;var Ee=pe;pe==="("&&(Ee=")"),Ee=(Ee+"").replace(/([.?*+^\[\]\\(){}|-])/g,"\\$1");var ge="^\\s*(?:[^"+Ee+"\\\\]+|\\\\\\\\|\\\\.)"+Ee;if(F.match(new RegExp(ge),!0))return s.linkHref}if(pe==="`"){var Oe=L.formatting;v.highlightFormatting&&(L.formatting="code"),F.eatWhile("`");var qe=F.current().length;if(L.code==0&&(!L.quote||qe==1))return L.code=qe,D(L);if(qe==L.code){var Se=D(L);return L.code=0,Se}else return L.formatting=Oe,D(L)}else if(L.code)return D(L);if(pe==="\\"&&(F.next(),v.highlightFormatting)){var je=D(L),Ze=s.formatting+"-escape";return je?je+" "+Ze:Ze}if(pe==="!"&&F.match(/\[[^\]]*\] ?(?:\(|\[)/,!1))return L.imageMarker=!0,L.image=!0,v.highlightFormatting&&(L.formatting="image"),D(L);if(pe==="["&&L.imageMarker&&F.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/,!1))return L.imageMarker=!1,L.imageAltText=!0,v.highlightFormatting&&(L.formatting="image"),D(L);if(pe==="]"&&L.imageAltText){v.highlightFormatting&&(L.formatting="image");var je=D(L);return L.imageAltText=!1,L.image=!1,L.inline=L.f=x,je}if(pe==="["&&!L.image)return L.linkText&&F.match(/^.*?\]/)||(L.linkText=!0,v.highlightFormatting&&(L.formatting="link")),D(L);if(pe==="]"&&L.linkText){v.highlightFormatting&&(L.formatting="link");var je=D(L);return L.linkText=!1,L.inline=L.f=F.match(/\(.*?\)| ?\[.*?\]/,!1)?x:R,je}if(pe==="<"&&F.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/,!1)){L.f=L.inline=V,v.highlightFormatting&&(L.formatting="link");var je=D(L);return je?je+=" ":je="",je+s.linkInline}if(pe==="<"&&F.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/,!1)){L.f=L.inline=V,v.highlightFormatting&&(L.formatting="link");var je=D(L);return je?je+=" ":je="",je+s.linkEmail}if(v.xml&&pe==="<"&&F.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i,!1)){var ke=F.string.indexOf(">",F.pos);if(ke!=-1){var Je=F.string.substring(F.start,ke);/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(Je)&&(L.md_inside=!0)}return F.backUp(1),L.htmlState=o.startState(C),O(F,L,q)}if(v.xml&&pe==="<"&&F.match(/^\/\w*?>/))return L.md_inside=!1,"tag";if(pe==="*"||pe==="_"){for(var He=1,Ge=F.pos==1?" ":F.string.charAt(F.pos-2);He<3&&F.eat(pe);)He++;var U=F.peek()||" ",Z=!/\s/.test(U)&&(!w.test(U)||/\s/.test(Ge)||w.test(Ge)),ce=!/\s/.test(Ge)&&(!w.test(Ge)||/\s/.test(U)||w.test(U)),Be=null,te=null;if(He%2&&(!L.em&&Z&&(pe==="*"||!ce||w.test(Ge))?Be=!0:L.em==pe&&ce&&(pe==="*"||!Z||w.test(U))&&(Be=!1)),He>1&&(!L.strong&&Z&&(pe==="*"||!ce||w.test(Ge))?te=!0:L.strong==pe&&ce&&(pe==="*"||!Z||w.test(U))&&(te=!1)),te!=null||Be!=null){v.highlightFormatting&&(L.formatting=Be==null?"strong":te==null?"em":"strong em"),Be===!0&&(L.em=pe),te===!0&&(L.strong=pe);var Se=D(L);return Be===!1&&(L.em=!1),te===!1&&(L.strong=!1),Se}}else if(pe===" "&&(F.eat("*")||F.eat("_"))){if(F.peek()===" ")return D(L);F.backUp(1)}if(v.strikethrough){if(pe==="~"&&F.eatWhile(pe)){if(L.strikethrough){v.highlightFormatting&&(L.formatting="strikethrough");var Se=D(L);return L.strikethrough=!1,Se}else if(F.match(/^[^\s]/,!1))return L.strikethrough=!0,v.highlightFormatting&&(L.formatting="strikethrough"),D(L)}else if(pe===" "&&F.match("~~",!0)){if(F.peek()===" ")return D(L);F.backUp(2)}}if(v.emoji&&pe===":"&&F.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)){L.emoji=!0,v.highlightFormatting&&(L.formatting="emoji");var fe=D(L);return L.emoji=!1,fe}return pe===" "&&(F.match(/^ +$/,!1)?L.trailingSpace++:L.trailingSpace&&(L.trailingSpaceNewLine=!0)),D(L)}function V(F,L){var de=F.next();if(de===">"){L.f=L.inline=R,v.highlightFormatting&&(L.formatting="link");var ze=D(L);return ze?ze+=" ":ze="",ze+s.linkInline}return F.match(/^[^>]+/,!0),s.linkInline}function x(F,L){if(F.eatSpace())return null;var de=F.next();return de==="("||de==="["?(L.f=L.inline=X(de==="("?")":"]"),v.highlightFormatting&&(L.formatting="link-string"),L.linkHref=!0,D(L)):"error"}var K={")":/^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,"]":/^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/};function X(F){return function(L,de){var ze=L.next();if(ze===F){de.f=de.inline=R,v.highlightFormatting&&(de.formatting="link-string");var pe=D(de);return de.linkHref=!1,pe}return L.match(K[F]),de.linkHref=!0,D(de)}}function N(F,L){return F.match(/^([^\]\\]|\\.)*\]:/,!1)?(L.f=H,F.next(),v.highlightFormatting&&(L.formatting="link"),L.linkText=!0,D(L)):E(F,L,R)}function H(F,L){if(F.match("]:",!0)){L.f=L.inline=le,v.highlightFormatting&&(L.formatting="link");var de=D(L);return L.linkText=!1,de}return F.match(/^([^\]\\]|\\.)+/,!0),s.linkText}function le(F,L){return F.eatSpace()?null:(F.match(/^[^\s]+/,!0),F.peek()===void 0?L.linkTitle=!0:F.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/,!0),L.f=L.inline=R,s.linkHref+" url")}var xe={startState:function(){return{f:re,prevLine:{stream:null},thisLine:{stream:null},block:re,htmlState:null,indentation:0,inline:R,text:Q,formatting:!1,linkText:!1,linkHref:!1,linkTitle:!1,code:0,em:!1,strong:!1,header:0,setext:0,hr:!1,taskList:!1,list:!1,listStack:[],quote:0,trailingSpace:0,trailingSpaceNewLine:!1,strikethrough:!1,emoji:!1,fencedEndRE:null}},copyState:function(F){return{f:F.f,prevLine:F.prevLine,thisLine:F.thisLine,block:F.block,htmlState:F.htmlState&&o.copyState(C,F.htmlState),indentation:F.indentation,localMode:F.localMode,localState:F.localMode?o.copyState(F.localMode,F.localState):null,inline:F.inline,text:F.text,formatting:!1,linkText:F.linkText,linkTitle:F.linkTitle,linkHref:F.linkHref,code:F.code,em:F.em,strong:F.strong,strikethrough:F.strikethrough,emoji:F.emoji,header:F.header,setext:F.setext,hr:F.hr,taskList:F.taskList,list:F.list,listStack:F.listStack.slice(0),quote:F.quote,indentedCode:F.indentedCode,trailingSpace:F.trailingSpace,trailingSpaceNewLine:F.trailingSpaceNewLine,md_inside:F.md_inside,fencedEndRE:F.fencedEndRE}},token:function(F,L){if(L.formatting=!1,F!=L.thisLine.stream){if(L.header=0,L.hr=!1,F.match(/^\s*$/,!0))return J(L),null;if(L.prevLine=L.thisLine,L.thisLine={stream:F},L.taskList=!1,L.trailingSpace=0,L.trailingSpaceNewLine=!1,!L.localState&&(L.f=L.block,L.f!=q)){var de=F.match(/^\s*/,!0)[0].replace(/\t/g,W).length;if(L.indentation=de,L.indentationDiff=null,de>0)return null}}return L.f(F,L)},innerMode:function(F){return F.block==q?{state:F.htmlState,mode:C}:F.localState?{state:F.localState,mode:F.localMode}:{state:F,mode:xe}},indent:function(F,L,de){return F.block==q&&C.indent?C.indent(F.htmlState,L,de):F.localState&&F.localMode.indent?F.localMode.indent(F.localState,L,de):o.Pass},blankLine:J,getType:D,blockCommentStart:"",closeBrackets:"()[]{}''\"\"``",fold:"markdown"};return xe},"xml"),o.defineMIME("text/markdown","markdown"),o.defineMIME("text/x-markdown","markdown")})});var eu=Ke((Vs,Js)=>{(function(o){typeof Vs=="object"&&typeof Js=="object"?o(We(),Jo(),Yn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../markdown/markdown","../../addon/mode/overlay"],o):o(CodeMirror)})(function(o){"use strict";var p=/^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i;o.defineMode("gfm",function(v,C){var b=0;function S(T){return T.code=!1,null}var s={startState:function(){return{code:!1,codeBlock:!1,ateSpace:!1}},copyState:function(T){return{code:T.code,codeBlock:T.codeBlock,ateSpace:T.ateSpace}},token:function(T,y){if(y.combineTokens=null,y.codeBlock)return T.match(/^```+/)?(y.codeBlock=!1,null):(T.skipToEnd(),null);if(T.sol()&&(y.code=!1),T.sol()&&T.match(/^```+/))return T.skipToEnd(),y.codeBlock=!0,null;if(T.peek()==="`"){T.next();var c=T.pos;T.eatWhile("`");var d=1+T.pos-c;return y.code?d===b&&(y.code=!1):(b=d,y.code=!0),null}else if(y.code)return T.next(),null;if(T.eatSpace())return y.ateSpace=!0,null;if((T.sol()||y.ateSpace)&&(y.ateSpace=!1,C.gitHubSpice!==!1)){if(T.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/))return y.combineTokens=!0,"link";if(T.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/))return y.combineTokens=!0,"link"}return T.match(p)&&T.string.slice(T.start-2,T.start)!="]("&&(T.start==0||/\W/.test(T.string.charAt(T.start-1)))?(y.combineTokens=!0,"link"):(T.next(),null)},blankLine:S},h={taskLists:!0,strikethrough:!0,emoji:!0};for(var g in C)h[g]=C[g];return h.name="markdown",o.overlayMode(o.getMode(v,h),s)},"markdown"),o.defineMIME("text/x-gfm","gfm")})});var nu=Ke((tu,ru)=>{(function(o){typeof tu=="object"&&typeof ru=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("go",function(p){var v=p.indentUnit,C={break:!0,case:!0,chan:!0,const:!0,continue:!0,default:!0,defer:!0,else:!0,fallthrough:!0,for:!0,func:!0,go:!0,goto:!0,if:!0,import:!0,interface:!0,map:!0,package:!0,range:!0,return:!0,select:!0,struct:!0,switch:!0,type:!0,var:!0,bool:!0,byte:!0,complex64:!0,complex128:!0,float32:!0,float64:!0,int8:!0,int16:!0,int32:!0,int64:!0,string:!0,uint8:!0,uint16:!0,uint32:!0,uint64:!0,int:!0,uint:!0,uintptr:!0,error:!0,rune:!0,any:!0,comparable:!0},b={true:!0,false:!0,iota:!0,nil:!0,append:!0,cap:!0,close:!0,complex:!0,copy:!0,delete:!0,imag:!0,len:!0,make:!0,new:!0,panic:!0,print:!0,println:!0,real:!0,recover:!0},S=/[+\-*&^%:=<>!|\/]/,s;function h(k,z){var M=k.next();if(M=='"'||M=="'"||M=="`")return z.tokenize=g(M),z.tokenize(k,z);if(/[\d\.]/.test(M))return M=="."?k.match(/^[0-9_]+([eE][\-+]?[0-9_]+)?/):M=="0"?k.match(/^[xX][0-9a-fA-F_]+/)||k.match(/^[0-7_]+/):k.match(/^[0-9_]*\.?[0-9_]*([eE][\-+]?[0-9_]+)?/),"number";if(/[\[\]{}\(\),;\:\.]/.test(M))return s=M,null;if(M=="/"){if(k.eat("*"))return z.tokenize=T,T(k,z);if(k.eat("/"))return k.skipToEnd(),"comment"}if(S.test(M))return k.eatWhile(S),"operator";k.eatWhile(/[\w\$_\xa1-\uffff]/);var w=k.current();return C.propertyIsEnumerable(w)?((w=="case"||w=="default")&&(s="case"),"keyword"):b.propertyIsEnumerable(w)?"atom":"variable"}function g(k){return function(z,M){for(var w=!1,W,E=!1;(W=z.next())!=null;){if(W==k&&!w){E=!0;break}w=!w&&k!="`"&&W=="\\"}return(E||!(w||k=="`"))&&(M.tokenize=h),"string"}}function T(k,z){for(var M=!1,w;w=k.next();){if(w=="/"&&M){z.tokenize=h;break}M=w=="*"}return"comment"}function y(k,z,M,w,W){this.indented=k,this.column=z,this.type=M,this.align=w,this.prev=W}function c(k,z,M){return k.context=new y(k.indented,z,M,null,k.context)}function d(k){if(k.context.prev){var z=k.context.type;return(z==")"||z=="]"||z=="}")&&(k.indented=k.context.indented),k.context=k.context.prev}}return{startState:function(k){return{tokenize:null,context:new y((k||0)-v,0,"top",!1),indented:0,startOfLine:!0}},token:function(k,z){var M=z.context;if(k.sol()&&(M.align==null&&(M.align=!1),z.indented=k.indentation(),z.startOfLine=!0,M.type=="case"&&(M.type="}")),k.eatSpace())return null;s=null;var w=(z.tokenize||h)(k,z);return w=="comment"||(M.align==null&&(M.align=!0),s=="{"?c(z,k.column(),"}"):s=="["?c(z,k.column(),"]"):s=="("?c(z,k.column(),")"):s=="case"?M.type="case":(s=="}"&&M.type=="}"||s==M.type)&&d(z),z.startOfLine=!1),w},indent:function(k,z){if(k.tokenize!=h&&k.tokenize!=null)return o.Pass;var M=k.context,w=z&&z.charAt(0);if(M.type=="case"&&/^(?:case|default)\b/.test(z))return k.context.type="}",M.indented;var W=w==M.type;return M.align?M.column+(W?0:1):M.indented+(W?0:v)},electricChars:"{}):",closeBrackets:"()[]{}''\"\"``",fold:"brace",blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//"}}),o.defineMIME("text/x-go","go")})});var au=Ke((iu,ou)=>{(function(o){typeof iu=="object"&&typeof ou=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("http",function(){function p(T,y){return T.skipToEnd(),y.cur=h,"error"}function v(T,y){return T.match(/^HTTP\/\d\.\d/)?(y.cur=C,"keyword"):T.match(/^[A-Z]+/)&&/[ \t]/.test(T.peek())?(y.cur=S,"keyword"):p(T,y)}function C(T,y){var c=T.match(/^\d+/);if(!c)return p(T,y);y.cur=b;var d=Number(c[0]);return d>=100&&d<200?"positive informational":d>=200&&d<300?"positive success":d>=300&&d<400?"positive redirect":d>=400&&d<500?"negative client-error":d>=500&&d<600?"negative server-error":"error"}function b(T,y){return T.skipToEnd(),y.cur=h,null}function S(T,y){return T.eatWhile(/\S/),y.cur=s,"string-2"}function s(T,y){return T.match(/^HTTP\/\d\.\d$/)?(y.cur=h,"keyword"):p(T,y)}function h(T){return T.sol()&&!T.eat(/[ \t]/)?T.match(/^.*?:/)?"atom":(T.skipToEnd(),"error"):(T.skipToEnd(),"string")}function g(T){return T.skipToEnd(),null}return{token:function(T,y){var c=y.cur;return c!=h&&c!=g&&T.eatSpace()?null:c(T,y)},blankLine:function(T){T.cur=g},startState:function(){return{cur:v}}}}),o.defineMIME("message/http","http")})});var uu=Ke((lu,su)=>{(function(o){typeof lu=="object"&&typeof su=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("jinja2",function(){var p=["and","as","block","endblock","by","cycle","debug","else","elif","extends","filter","endfilter","firstof","do","for","endfor","if","endif","ifchanged","endifchanged","ifequal","endifequal","ifnotequal","set","raw","endraw","endifnotequal","in","include","load","not","now","or","parsed","regroup","reversed","spaceless","call","endcall","macro","endmacro","endspaceless","ssi","templatetag","openblock","closeblock","openvariable","closevariable","without","context","openbrace","closebrace","opencomment","closecomment","widthratio","url","with","endwith","get_current_language","trans","endtrans","noop","blocktrans","endblocktrans","get_available_languages","get_current_language_bidi","pluralize","autoescape","endautoescape"],v=/^[+\-*&%=<>!?|~^]/,C=/^[:\[\(\{]/,b=["true","false"],S=/^(\d[+\-\*\/])?\d+(\.\d+)?/;p=new RegExp("(("+p.join(")|(")+"))\\b"),b=new RegExp("(("+b.join(")|(")+"))\\b");function s(h,g){var T=h.peek();if(g.incomment)return h.skipTo("#}")?(h.eatWhile(/\#|}/),g.incomment=!1):h.skipToEnd(),"comment";if(g.intag){if(g.operator){if(g.operator=!1,h.match(b))return"atom";if(h.match(S))return"number"}if(g.sign){if(g.sign=!1,h.match(b))return"atom";if(h.match(S))return"number"}if(g.instring)return T==g.instring&&(g.instring=!1),h.next(),"string";if(T=="'"||T=='"')return g.instring=T,h.next(),"string";if(g.inbraces>0&&T==")")h.next(),g.inbraces--;else if(T=="(")h.next(),g.inbraces++;else if(g.inbrackets>0&&T=="]")h.next(),g.inbrackets--;else if(T=="[")h.next(),g.inbrackets++;else{if(!g.lineTag&&(h.match(g.intag+"}")||h.eat("-")&&h.match(g.intag+"}")))return g.intag=!1,"tag";if(h.match(v))return g.operator=!0,"operator";if(h.match(C))g.sign=!0;else{if(h.column()==1&&g.lineTag&&h.match(p))return"keyword";if(h.eat(" ")||h.sol()){if(h.match(p))return"keyword";if(h.match(b))return"atom";if(h.match(S))return"number";h.sol()&&h.next()}else h.next()}}return"variable"}else if(h.eat("{")){if(h.eat("#"))return g.incomment=!0,h.skipTo("#}")?(h.eatWhile(/\#|}/),g.incomment=!1):h.skipToEnd(),"comment";if(T=h.eat(/\{|%/))return g.intag=T,g.inbraces=0,g.inbrackets=0,T=="{"&&(g.intag="}"),h.eat("-"),"tag"}else if(h.eat("#")){if(h.peek()=="#")return h.skipToEnd(),"comment";if(!h.eol())return g.intag=!0,g.lineTag=!0,g.inbraces=0,g.inbrackets=0,"tag"}h.next()}return{startState:function(){return{tokenize:s,inbrackets:0,inbraces:0}},token:function(h,g){var T=g.tokenize(h,g);return h.eol()&&g.lineTag&&!g.instring&&g.inbraces==0&&g.inbrackets==0&&(g.intag=!1,g.lineTag=!1),T},blockCommentStart:"{#",blockCommentEnd:"#}",lineComment:"##"}}),o.defineMIME("text/jinja2","jinja2")})});var du=Ke((cu,fu)=>{(function(o){typeof cu=="object"&&typeof fu=="object"?o(We(),mn(),vn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript"],o):o(CodeMirror)})(function(o){"use strict";function p(C,b,S,s){this.state=C,this.mode=b,this.depth=S,this.prev=s}function v(C){return new p(o.copyState(C.mode,C.state),C.mode,C.depth,C.prev&&v(C.prev))}o.defineMode("jsx",function(C,b){var S=o.getMode(C,{name:"xml",allowMissing:!0,multilineTagIndentPastTag:!1,allowMissingTagName:!0}),s=o.getMode(C,b&&b.base||"javascript");function h(c){var d=c.tagName;c.tagName=null;var k=S.indent(c,"","");return c.tagName=d,k}function g(c,d){return d.context.mode==S?T(c,d,d.context):y(c,d,d.context)}function T(c,d,k){if(k.depth==2)return c.match(/^.*?\*\//)?k.depth=1:c.skipToEnd(),"comment";if(c.peek()=="{"){S.skipAttribute(k.state);var z=h(k.state),M=k.state.context;if(M&&c.match(/^[^>]*>\s*$/,!1)){for(;M.prev&&!M.startOfLine;)M=M.prev;M.startOfLine?z-=C.indentUnit:k.prev.state.lexical&&(z=k.prev.state.lexical.indented)}else k.depth==1&&(z+=C.indentUnit);return d.context=new p(o.startState(s,z),s,0,d.context),null}if(k.depth==1){if(c.peek()=="<")return S.skipAttribute(k.state),d.context=new p(o.startState(S,h(k.state)),S,0,d.context),null;if(c.match("//"))return c.skipToEnd(),"comment";if(c.match("/*"))return k.depth=2,g(c,d)}var w=S.token(c,k.state),W=c.current(),E;return/\btag\b/.test(w)?/>$/.test(W)?k.state.context?k.depth=0:d.context=d.context.prev:/^-1&&c.backUp(W.length-E),w}function y(c,d,k){if(c.peek()=="<"&&!c.match(/^<([^<>]|<[^>]*>)+,\s*>/,!1)&&s.expressionAllowed(c,k.state))return d.context=new p(o.startState(S,s.indent(k.state,"","")),S,0,d.context),s.skipExpression(k.state),null;var z=s.token(c,k.state);if(!z&&k.depth!=null){var M=c.current();M=="{"?k.depth++:M=="}"&&--k.depth==0&&(d.context=d.context.prev)}return z}return{startState:function(){return{context:new p(o.startState(s),s)}},copyState:function(c){return{context:v(c.context)}},token:g,indent:function(c,d,k){return c.context.mode.indent(c.context.state,d,k)},innerMode:function(c){return c.context}}},"xml","javascript"),o.defineMIME("text/jsx","jsx"),o.defineMIME("text/typescript-jsx",{name:"jsx",base:{name:"javascript",typescript:!0}})})});var gu=Ke((pu,hu)=>{(function(o){typeof pu=="object"&&typeof hu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("nginx",function(p){function v(k){for(var z={},M=k.split(" "),w=0;w*\/]/.test(w)?g(null,"select-op"):/[;{}:\[\]]/.test(w)?g(null,w):(k.eatWhile(/[\w\\\-]/),g("variable","variable"))}function y(k,z){for(var M=!1,w;(w=k.next())!=null;){if(M&&w=="/"){z.tokenize=T;break}M=w=="*"}return g("comment","comment")}function c(k,z){for(var M=0,w;(w=k.next())!=null;){if(M>=2&&w==">"){z.tokenize=T;break}M=w=="-"?M+1:0}return g("comment","comment")}function d(k){return function(z,M){for(var w=!1,W;(W=z.next())!=null&&!(W==k&&!w);)w=!w&&W=="\\";return w||(M.tokenize=T),g("string","string")}}return{startState:function(k){return{tokenize:T,baseIndent:k||0,stack:[]}},token:function(k,z){if(k.eatSpace())return null;h=null;var M=z.tokenize(k,z),w=z.stack[z.stack.length-1];return h=="hash"&&w=="rule"?M="atom":M=="variable"&&(w=="rule"?M="number":(!w||w=="@media{")&&(M="tag")),w=="rule"&&/^[\{\};]$/.test(h)&&z.stack.pop(),h=="{"?w=="@media"?z.stack[z.stack.length-1]="@media{":z.stack.push("{"):h=="}"?z.stack.pop():h=="@media"?z.stack.push("@media"):w=="{"&&h!="comment"&&z.stack.push("rule"),M},indent:function(k,z){var M=k.stack.length;return/^\}/.test(z)&&(M-=k.stack[k.stack.length-1]=="rule"?2:1),k.baseIndent+M*s},electricChars:"}"}}),o.defineMIME("text/x-nginx-conf","nginx")})});var bu=Ke((mu,vu)=>{(function(o){typeof mu=="object"&&typeof vu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("pascal",function(){function p(T){for(var y={},c=T.split(" "),d=0;d!?|\/]/;function S(T,y){var c=T.next();if(c=="#"&&y.startOfLine)return T.skipToEnd(),"meta";if(c=='"'||c=="'")return y.tokenize=s(c),y.tokenize(T,y);if(c=="("&&T.eat("*"))return y.tokenize=h,h(T,y);if(c=="{")return y.tokenize=g,g(T,y);if(/[\[\]\(\),;\:\.]/.test(c))return null;if(/\d/.test(c))return T.eatWhile(/[\w\.]/),"number";if(c=="/"&&T.eat("/"))return T.skipToEnd(),"comment";if(b.test(c))return T.eatWhile(b),"operator";T.eatWhile(/[\w\$_]/);var d=T.current().toLowerCase();return v.propertyIsEnumerable(d)?"keyword":C.propertyIsEnumerable(d)?"atom":"variable"}function s(T){return function(y,c){for(var d=!1,k,z=!1;(k=y.next())!=null;){if(k==T&&!d){z=!0;break}d=!d&&k=="\\"}return(z||!d)&&(c.tokenize=null),"string"}}function h(T,y){for(var c=!1,d;d=T.next();){if(d==")"&&c){y.tokenize=null;break}c=d=="*"}return"comment"}function g(T,y){for(var c;c=T.next();)if(c=="}"){y.tokenize=null;break}return"comment"}return{startState:function(){return{tokenize:null}},token:function(T,y){if(T.eatSpace())return null;var c=(y.tokenize||S)(T,y);return c=="comment"||c=="meta",c},electricChars:"{}"}}),o.defineMIME("text/x-pascal","pascal")})});var _u=Ke((yu,xu)=>{(function(o){typeof yu=="object"&&typeof xu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("perl",function(){var S={"->":4,"++":4,"--":4,"**":4,"=~":4,"!~":4,"*":4,"/":4,"%":4,x:4,"+":4,"-":4,".":4,"<<":4,">>":4,"<":4,">":4,"<=":4,">=":4,lt:4,gt:4,le:4,ge:4,"==":4,"!=":4,"<=>":4,eq:4,ne:4,cmp:4,"~~":4,"&":4,"|":4,"^":4,"&&":4,"||":4,"//":4,"..":4,"...":4,"?":4,":":4,"=":4,"+=":4,"-=":4,"*=":4,",":4,"=>":4,"::":4,not:4,and:4,or:4,xor:4,BEGIN:[5,1],END:[5,1],PRINT:[5,1],PRINTF:[5,1],GETC:[5,1],READ:[5,1],READLINE:[5,1],DESTROY:[5,1],TIE:[5,1],TIEHANDLE:[5,1],UNTIE:[5,1],STDIN:5,STDIN_TOP:5,STDOUT:5,STDOUT_TOP:5,STDERR:5,STDERR_TOP:5,$ARG:5,$_:5,"@ARG":5,"@_":5,$LIST_SEPARATOR:5,'$"':5,$PROCESS_ID:5,$PID:5,$$:5,$REAL_GROUP_ID:5,$GID:5,"$(":5,$EFFECTIVE_GROUP_ID:5,$EGID:5,"$)":5,$PROGRAM_NAME:5,$0:5,$SUBSCRIPT_SEPARATOR:5,$SUBSEP:5,"$;":5,$REAL_USER_ID:5,$UID:5,"$<":5,$EFFECTIVE_USER_ID:5,$EUID:5,"$>":5,$a:5,$b:5,$COMPILING:5,"$^C":5,$DEBUGGING:5,"$^D":5,"${^ENCODING}":5,$ENV:5,"%ENV":5,$SYSTEM_FD_MAX:5,"$^F":5,"@F":5,"${^GLOBAL_PHASE}":5,"$^H":5,"%^H":5,"@INC":5,"%INC":5,$INPLACE_EDIT:5,"$^I":5,"$^M":5,$OSNAME:5,"$^O":5,"${^OPEN}":5,$PERLDB:5,"$^P":5,$SIG:5,"%SIG":5,$BASETIME:5,"$^T":5,"${^TAINT}":5,"${^UNICODE}":5,"${^UTF8CACHE}":5,"${^UTF8LOCALE}":5,$PERL_VERSION:5,"$^V":5,"${^WIN32_SLOPPY_STAT}":5,$EXECUTABLE_NAME:5,"$^X":5,$1:5,$MATCH:5,"$&":5,"${^MATCH}":5,$PREMATCH:5,"$`":5,"${^PREMATCH}":5,$POSTMATCH:5,"$'":5,"${^POSTMATCH}":5,$LAST_PAREN_MATCH:5,"$+":5,$LAST_SUBMATCH_RESULT:5,"$^N":5,"@LAST_MATCH_END":5,"@+":5,"%LAST_PAREN_MATCH":5,"%+":5,"@LAST_MATCH_START":5,"@-":5,"%LAST_MATCH_START":5,"%-":5,$LAST_REGEXP_CODE_RESULT:5,"$^R":5,"${^RE_DEBUG_FLAGS}":5,"${^RE_TRIE_MAXBUF}":5,$ARGV:5,"@ARGV":5,ARGV:5,ARGVOUT:5,$OUTPUT_FIELD_SEPARATOR:5,$OFS:5,"$,":5,$INPUT_LINE_NUMBER:5,$NR:5,"$.":5,$INPUT_RECORD_SEPARATOR:5,$RS:5,"$/":5,$OUTPUT_RECORD_SEPARATOR:5,$ORS:5,"$\\":5,$OUTPUT_AUTOFLUSH:5,"$|":5,$ACCUMULATOR:5,"$^A":5,$FORMAT_FORMFEED:5,"$^L":5,$FORMAT_PAGE_NUMBER:5,"$%":5,$FORMAT_LINES_LEFT:5,"$-":5,$FORMAT_LINE_BREAK_CHARACTERS:5,"$:":5,$FORMAT_LINES_PER_PAGE:5,"$=":5,$FORMAT_TOP_NAME:5,"$^":5,$FORMAT_NAME:5,"$~":5,"${^CHILD_ERROR_NATIVE}":5,$EXTENDED_OS_ERROR:5,"$^E":5,$EXCEPTIONS_BEING_CAUGHT:5,"$^S":5,$WARNING:5,"$^W":5,"${^WARNING_BITS}":5,$OS_ERROR:5,$ERRNO:5,"$!":5,"%OS_ERROR":5,"%ERRNO":5,"%!":5,$CHILD_ERROR:5,"$?":5,$EVAL_ERROR:5,"$@":5,$OFMT:5,"$#":5,"$*":5,$ARRAY_BASE:5,"$[":5,$OLD_PERL_VERSION:5,"$]":5,if:[1,1],elsif:[1,1],else:[1,1],while:[1,1],unless:[1,1],for:[1,1],foreach:[1,1],abs:1,accept:1,alarm:1,atan2:1,bind:1,binmode:1,bless:1,bootstrap:1,break:1,caller:1,chdir:1,chmod:1,chomp:1,chop:1,chown:1,chr:1,chroot:1,close:1,closedir:1,connect:1,continue:[1,1],cos:1,crypt:1,dbmclose:1,dbmopen:1,default:1,defined:1,delete:1,die:1,do:1,dump:1,each:1,endgrent:1,endhostent:1,endnetent:1,endprotoent:1,endpwent:1,endservent:1,eof:1,eval:1,exec:1,exists:1,exit:1,exp:1,fcntl:1,fileno:1,flock:1,fork:1,format:1,formline:1,getc:1,getgrent:1,getgrgid:1,getgrnam:1,gethostbyaddr:1,gethostbyname:1,gethostent:1,getlogin:1,getnetbyaddr:1,getnetbyname:1,getnetent:1,getpeername:1,getpgrp:1,getppid:1,getpriority:1,getprotobyname:1,getprotobynumber:1,getprotoent:1,getpwent:1,getpwnam:1,getpwuid:1,getservbyname:1,getservbyport:1,getservent:1,getsockname:1,getsockopt:1,given:1,glob:1,gmtime:1,goto:1,grep:1,hex:1,import:1,index:1,int:1,ioctl:1,join:1,keys:1,kill:1,last:1,lc:1,lcfirst:1,length:1,link:1,listen:1,local:2,localtime:1,lock:1,log:1,lstat:1,m:null,map:1,mkdir:1,msgctl:1,msgget:1,msgrcv:1,msgsnd:1,my:2,new:1,next:1,no:1,oct:1,open:1,opendir:1,ord:1,our:2,pack:1,package:1,pipe:1,pop:1,pos:1,print:1,printf:1,prototype:1,push:1,q:null,qq:null,qr:null,quotemeta:null,qw:null,qx:null,rand:1,read:1,readdir:1,readline:1,readlink:1,readpipe:1,recv:1,redo:1,ref:1,rename:1,require:1,reset:1,return:1,reverse:1,rewinddir:1,rindex:1,rmdir:1,s:null,say:1,scalar:1,seek:1,seekdir:1,select:1,semctl:1,semget:1,semop:1,send:1,setgrent:1,sethostent:1,setnetent:1,setpgrp:1,setpriority:1,setprotoent:1,setpwent:1,setservent:1,setsockopt:1,shift:1,shmctl:1,shmget:1,shmread:1,shmwrite:1,shutdown:1,sin:1,sleep:1,socket:1,socketpair:1,sort:1,splice:1,split:1,sprintf:1,sqrt:1,srand:1,stat:1,state:1,study:1,sub:1,substr:1,symlink:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,tell:1,telldir:1,tie:1,tied:1,time:1,times:1,tr:null,truncate:1,uc:1,ucfirst:1,umask:1,undef:1,unlink:1,unpack:1,unshift:1,untie:1,use:1,utime:1,values:1,vec:1,wait:1,waitpid:1,wantarray:1,warn:1,when:1,write:1,y:null},s="string-2",h=/[goseximacplud]/;function g(c,d,k,z,M){return d.chain=null,d.style=null,d.tail=null,d.tokenize=function(w,W){for(var E=!1,O,G=0;O=w.next();){if(O===k[G]&&!E)return k[++G]!==void 0?(W.chain=k[G],W.style=z,W.tail=M):M&&w.eatWhile(M),W.tokenize=y,z;E=!E&&O=="\\"}return z},d.tokenize(c,d)}function T(c,d,k){return d.tokenize=function(z,M){return z.string==k&&(M.tokenize=y),z.skipToEnd(),"string"},d.tokenize(c,d)}function y(c,d){if(c.eatSpace())return null;if(d.chain)return g(c,d,d.chain,d.style,d.tail);if(c.match(/^(\-?((\d[\d_]*)?\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F_]+|0b[01_]+|\d[\d_]*(e[+-]?\d+)?)/))return"number";if(c.match(/^<<(?=[_a-zA-Z])/))return c.eatWhile(/\w/),T(c,d,c.current().substr(2));if(c.sol()&&c.match(/^\=item(?!\w)/))return T(c,d,"=cut");var k=c.next();if(k=='"'||k=="'"){if(v(c,3)=="<<"+k){var z=c.pos;c.eatWhile(/\w/);var M=c.current().substr(1);if(M&&c.eat(k))return T(c,d,M);c.pos=z}return g(c,d,[k],"string")}if(k=="q"){var w=p(c,-2);if(!(w&&/\w/.test(w))){if(w=p(c,0),w=="x"){if(w=p(c,1),w=="(")return b(c,2),g(c,d,[")"],s,h);if(w=="[")return b(c,2),g(c,d,["]"],s,h);if(w=="{")return b(c,2),g(c,d,["}"],s,h);if(w=="<")return b(c,2),g(c,d,[">"],s,h);if(/[\^'"!~\/]/.test(w))return b(c,1),g(c,d,[c.eat(w)],s,h)}else if(w=="q"){if(w=p(c,1),w=="(")return b(c,2),g(c,d,[")"],"string");if(w=="[")return b(c,2),g(c,d,["]"],"string");if(w=="{")return b(c,2),g(c,d,["}"],"string");if(w=="<")return b(c,2),g(c,d,[">"],"string");if(/[\^'"!~\/]/.test(w))return b(c,1),g(c,d,[c.eat(w)],"string")}else if(w=="w"){if(w=p(c,1),w=="(")return b(c,2),g(c,d,[")"],"bracket");if(w=="[")return b(c,2),g(c,d,["]"],"bracket");if(w=="{")return b(c,2),g(c,d,["}"],"bracket");if(w=="<")return b(c,2),g(c,d,[">"],"bracket");if(/[\^'"!~\/]/.test(w))return b(c,1),g(c,d,[c.eat(w)],"bracket")}else if(w=="r"){if(w=p(c,1),w=="(")return b(c,2),g(c,d,[")"],s,h);if(w=="[")return b(c,2),g(c,d,["]"],s,h);if(w=="{")return b(c,2),g(c,d,["}"],s,h);if(w=="<")return b(c,2),g(c,d,[">"],s,h);if(/[\^'"!~\/]/.test(w))return b(c,1),g(c,d,[c.eat(w)],s,h)}else if(/[\^'"!~\/(\[{<]/.test(w)){if(w=="(")return b(c,1),g(c,d,[")"],"string");if(w=="[")return b(c,1),g(c,d,["]"],"string");if(w=="{")return b(c,1),g(c,d,["}"],"string");if(w=="<")return b(c,1),g(c,d,[">"],"string");if(/[\^'"!~\/]/.test(w))return g(c,d,[c.eat(w)],"string")}}}if(k=="m"){var w=p(c,-2);if(!(w&&/\w/.test(w))&&(w=c.eat(/[(\[{<\^'"!~\/]/),w)){if(/[\^'"!~\/]/.test(w))return g(c,d,[w],s,h);if(w=="(")return g(c,d,[")"],s,h);if(w=="[")return g(c,d,["]"],s,h);if(w=="{")return g(c,d,["}"],s,h);if(w=="<")return g(c,d,[">"],s,h)}}if(k=="s"){var w=/[\/>\]})\w]/.test(p(c,-2));if(!w&&(w=c.eat(/[(\[{<\^'"!~\/]/),w))return w=="["?g(c,d,["]","]"],s,h):w=="{"?g(c,d,["}","}"],s,h):w=="<"?g(c,d,[">",">"],s,h):w=="("?g(c,d,[")",")"],s,h):g(c,d,[w,w],s,h)}if(k=="y"){var w=/[\/>\]})\w]/.test(p(c,-2));if(!w&&(w=c.eat(/[(\[{<\^'"!~\/]/),w))return w=="["?g(c,d,["]","]"],s,h):w=="{"?g(c,d,["}","}"],s,h):w=="<"?g(c,d,[">",">"],s,h):w=="("?g(c,d,[")",")"],s,h):g(c,d,[w,w],s,h)}if(k=="t"){var w=/[\/>\]})\w]/.test(p(c,-2));if(!w&&(w=c.eat("r"),w&&(w=c.eat(/[(\[{<\^'"!~\/]/),w)))return w=="["?g(c,d,["]","]"],s,h):w=="{"?g(c,d,["}","}"],s,h):w=="<"?g(c,d,[">",">"],s,h):w=="("?g(c,d,[")",")"],s,h):g(c,d,[w,w],s,h)}if(k=="`")return g(c,d,[k],"variable-2");if(k=="/")return/~\s*$/.test(v(c))?g(c,d,[k],s,h):"operator";if(k=="$"){var z=c.pos;if(c.eatWhile(/\d/)||c.eat("{")&&c.eatWhile(/\d/)&&c.eat("}"))return"variable-2";c.pos=z}if(/[$@%]/.test(k)){var z=c.pos;if(c.eat("^")&&c.eat(/[A-Z]/)||!/[@$%&]/.test(p(c,-2))&&c.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){var w=c.current();if(S[w])return"variable-2"}c.pos=z}if(/[$@%&]/.test(k)&&(c.eatWhile(/[\w$]/)||c.eat("{")&&c.eatWhile(/[\w$]/)&&c.eat("}"))){var w=c.current();return S[w]?"variable-2":"variable"}if(k=="#"&&p(c,-2)!="$")return c.skipToEnd(),"comment";if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(k)){var z=c.pos;if(c.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/),S[c.current()])return"operator";c.pos=z}if(k=="_"&&c.pos==1){if(C(c,6)=="_END__")return g(c,d,["\0"],"comment");if(C(c,7)=="_DATA__")return g(c,d,["\0"],"variable-2");if(C(c,7)=="_C__")return g(c,d,["\0"],"string")}if(/\w/.test(k)){var z=c.pos;if(p(c,-2)=="{"&&(p(c,0)=="}"||c.eatWhile(/\w/)&&p(c,0)=="}"))return"string";c.pos=z}if(/[A-Z]/.test(k)){var W=p(c,-2),z=c.pos;if(c.eatWhile(/[A-Z_]/),/[\da-z]/.test(p(c,0)))c.pos=z;else{var w=S[c.current()];return w?(w[1]&&(w=w[0]),W!=":"?w==1?"keyword":w==2?"def":w==3?"atom":w==4?"operator":w==5?"variable-2":"meta":"meta"):"meta"}}if(/[a-zA-Z_]/.test(k)){var W=p(c,-2);c.eatWhile(/\w/);var w=S[c.current()];return w?(w[1]&&(w=w[0]),W!=":"?w==1?"keyword":w==2?"def":w==3?"atom":w==4?"operator":w==5?"variable-2":"meta":"meta"):"meta"}return null}return{startState:function(){return{tokenize:y,chain:null,style:null,tail:null}},token:function(c,d){return(d.tokenize||y)(c,d)},lineComment:"#"}}),o.registerHelper("wordChars","perl",/[\w$]/),o.defineMIME("text/x-perl","perl");function p(S,s){return S.string.charAt(S.pos+(s||0))}function v(S,s){if(s){var h=S.pos-s;return S.string.substr(h>=0?h:0,s)}else return S.string.substr(0,S.pos-1)}function C(S,s){var h=S.string.length,g=h-S.pos+1;return S.string.substr(S.pos,s&&s=(g=S.string.length-1)?S.pos=g:S.pos=h}})});var Su=Ke((ku,wu)=>{(function(o){typeof ku=="object"&&typeof wu=="object"?o(We(),Qn(),Vo()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../htmlmixed/htmlmixed","../clike/clike"],o):o(CodeMirror)})(function(o){"use strict";function p(T){for(var y={},c=T.split(" "),d=0;d\w/,!1)&&(y.tokenize=v([[["->",null]],[[/[\w]+/,"variable"]]],c,d)),"variable-2";for(var k=!1;!T.eol()&&(k||d===!1||!T.match("{$",!1)&&!T.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/,!1));){if(!k&&T.match(c)){y.tokenize=null,y.tokStack.pop(),y.tokStack.pop();break}k=T.next()=="\\"&&!k}return"string"}var S="abstract and array as break case catch class clone const continue declare default do else elseif enddeclare endfor endforeach endif endswitch endwhile enum extends final for foreach function global goto if implements interface instanceof namespace new or private protected public static switch throw trait try use var while xor die echo empty exit eval include include_once isset list require require_once return print unset __halt_compiler self static parent yield insteadof finally readonly match",s="true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__",h="func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage memory_get_peak_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";o.registerHelper("hintWords","php",[S,s,h].join(" ").split(" ")),o.registerHelper("wordChars","php",/[\w$]/);var g={name:"clike",helperType:"php",keywords:p(S),blockKeywords:p("catch do else elseif for foreach if switch try while finally"),defKeywords:p("class enum function interface namespace trait"),atoms:p(s),builtin:p(h),multiLineStrings:!0,hooks:{$:function(T){return T.eatWhile(/[\w\$_]/),"variable-2"},"<":function(T,y){var c;if(c=T.match(/^<<\s*/)){var d=T.eat(/['"]/);T.eatWhile(/[\w\.]/);var k=T.current().slice(c[0].length+(d?2:1));if(d&&T.eat(d),k)return(y.tokStack||(y.tokStack=[])).push(k,0),y.tokenize=C(k,d!="'"),"string"}return!1},"#":function(T){for(;!T.eol()&&!T.match("?>",!1);)T.next();return"comment"},"/":function(T){if(T.eat("/")){for(;!T.eol()&&!T.match("?>",!1);)T.next();return"comment"}return!1},'"':function(T,y){return(y.tokStack||(y.tokStack=[])).push('"',0),y.tokenize=C('"'),"string"},"{":function(T,y){return y.tokStack&&y.tokStack.length&&y.tokStack[y.tokStack.length-1]++,!1},"}":function(T,y){return y.tokStack&&y.tokStack.length>0&&!--y.tokStack[y.tokStack.length-1]&&(y.tokenize=C(y.tokStack[y.tokStack.length-2])),!1}}};o.defineMode("php",function(T,y){var c=o.getMode(T,y&&y.htmlMode||"text/html"),d=o.getMode(T,g);function k(z,M){var w=M.curMode==d;if(z.sol()&&M.pending&&M.pending!='"'&&M.pending!="'"&&(M.pending=null),w)return w&&M.php.tokenize==null&&z.match("?>")?(M.curMode=c,M.curState=M.html,M.php.context.prev||(M.php=null),"meta"):d.token(z,M.curState);if(z.match(/^<\?\w*/))return M.curMode=d,M.php||(M.php=o.startState(d,c.indent(M.html,"",""))),M.curState=M.php,"meta";if(M.pending=='"'||M.pending=="'"){for(;!z.eol()&&z.next()!=M.pending;);var W="string"}else if(M.pending&&z.pos/.test(E)?M.pending=G[0]:M.pending={end:z.pos,style:W},z.backUp(E.length-O)),W}return{startState:function(){var z=o.startState(c),M=y.startOpen?o.startState(d):null;return{html:z,php:M,curMode:y.startOpen?d:c,curState:y.startOpen?M:z,pending:null}},copyState:function(z){var M=z.html,w=o.copyState(c,M),W=z.php,E=W&&o.copyState(d,W),O;return z.curMode==c?O=w:O=E,{html:w,php:E,curMode:z.curMode,curState:O,pending:z.pending}},token:k,indent:function(z,M,w){return z.curMode!=d&&/^\s*<\//.test(M)||z.curMode==d&&/^\?>/.test(M)?c.indent(z.html,M,w):z.curMode.indent(z.curState,M,w)},blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//",innerMode:function(z){return{state:z.curState,mode:z.curMode}}}},"htmlmixed","clike"),o.defineMIME("application/x-httpd-php","php"),o.defineMIME("application/x-httpd-php-open",{name:"php",startOpen:!0}),o.defineMIME("text/x-php",g)})});var Cu=Ke((Tu,Lu)=>{(function(o){typeof Tu=="object"&&typeof Lu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";function p(s){return new RegExp("^(("+s.join(")|(")+"))\\b","i")}var v=["package","message","import","syntax","required","optional","repeated","reserved","default","extensions","packed","bool","bytes","double","enum","float","string","int32","int64","uint32","uint64","sint32","sint64","fixed32","fixed64","sfixed32","sfixed64","option","service","rpc","returns"],C=p(v);o.registerHelper("hintWords","protobuf",v);var b=new RegExp("^[_A-Za-z\xA1-\uFFFF][_A-Za-z0-9\xA1-\uFFFF]*");function S(s){return s.eatSpace()?null:s.match("//")?(s.skipToEnd(),"comment"):s.match(/^[0-9\.+-]/,!1)&&(s.match(/^[+-]?0x[0-9a-fA-F]+/)||s.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?/)||s.match(/^[+-]?\d+([EeDd][+-]?\d+)?/))?"number":s.match(/^"([^"]|(""))*"/)||s.match(/^'([^']|(''))*'/)?"string":s.match(C)?"keyword":s.match(b)?"variable":(s.next(),null)}o.defineMode("protobuf",function(){return{token:S,fold:"brace"}}),o.defineMIME("text/x-protobuf","protobuf")})});var Mu=Ke((Eu,zu)=>{(function(o){typeof Eu=="object"&&typeof zu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";function p(h){return new RegExp("^(("+h.join(")|(")+"))\\b")}var v=p(["and","or","not","is"]),C=["as","assert","break","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","lambda","pass","raise","return","try","while","with","yield","in","False","True"],b=["abs","all","any","bin","bool","bytearray","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip","__import__","NotImplemented","Ellipsis","__debug__"];o.registerHelper("hintWords","python",C.concat(b).concat(["exec","print"]));function S(h){return h.scopes[h.scopes.length-1]}o.defineMode("python",function(h,g){for(var T="error",y=g.delimiters||g.singleDelimiters||/^[\(\)\[\]\{\}@,:`=;\.\\]/,c=[g.singleOperators,g.doubleOperators,g.doubleDelimiters,g.tripleDelimiters,g.operators||/^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/],d=0;dH?D(X):le0&&R(K,X)&&(xe+=" "+T),xe}}return re(K,X)}function re(K,X,N){if(K.eatSpace())return null;if(!N&&K.match(/^#.*/))return"comment";if(K.match(/^[0-9\.]/,!1)){var H=!1;if(K.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)&&(H=!0),K.match(/^[\d_]+\.\d*/)&&(H=!0),K.match(/^\.\d+/)&&(H=!0),H)return K.eat(/J/i),"number";var le=!1;if(K.match(/^0x[0-9a-f_]+/i)&&(le=!0),K.match(/^0b[01_]+/i)&&(le=!0),K.match(/^0o[0-7_]+/i)&&(le=!0),K.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)&&(K.eat(/J/i),le=!0),K.match(/^0(?![\dx])/i)&&(le=!0),le)return K.eat(/L/i),"number"}if(K.match(E)){var xe=K.current().toLowerCase().indexOf("f")!==-1;return xe?(X.tokenize=q(K.current(),X.tokenize),X.tokenize(K,X)):(X.tokenize=I(K.current(),X.tokenize),X.tokenize(K,X))}for(var F=0;F=0;)K=K.substr(1);var N=K.length==1,H="string";function le(F){return function(L,de){var ze=re(L,de,!0);return ze=="punctuation"&&(L.current()=="{"?de.tokenize=le(F+1):L.current()=="}"&&(F>1?de.tokenize=le(F-1):de.tokenize=xe)),ze}}function xe(F,L){for(;!F.eol();)if(F.eatWhile(/[^'"\{\}\\]/),F.eat("\\")){if(F.next(),N&&F.eol())return H}else{if(F.match(K))return L.tokenize=X,H;if(F.match("{{"))return H;if(F.match("{",!1))return L.tokenize=le(0),F.current()?H:L.tokenize(F,L);if(F.match("}}"))return H;if(F.match("}"))return T;F.eat(/['"]/)}if(N){if(g.singleLineStringErrors)return T;L.tokenize=X}return H}return xe.isString=!0,xe}function I(K,X){for(;"rubf".indexOf(K.charAt(0).toLowerCase())>=0;)K=K.substr(1);var N=K.length==1,H="string";function le(xe,F){for(;!xe.eol();)if(xe.eatWhile(/[^'"\\]/),xe.eat("\\")){if(xe.next(),N&&xe.eol())return H}else{if(xe.match(K))return F.tokenize=X,H;xe.eat(/['"]/)}if(N){if(g.singleLineStringErrors)return T;F.tokenize=X}return H}return le.isString=!0,le}function D(K){for(;S(K).type!="py";)K.scopes.pop();K.scopes.push({offset:S(K).offset+h.indentUnit,type:"py",align:null})}function Q(K,X,N){var H=K.match(/^[\s\[\{\(]*(?:#|$)/,!1)?null:K.column()+1;X.scopes.push({offset:X.indent+k,type:N,align:H})}function R(K,X){for(var N=K.indentation();X.scopes.length>1&&S(X).offset>N;){if(S(X).type!="py")return!0;X.scopes.pop()}return S(X).offset!=N}function V(K,X){K.sol()&&(X.beginningOfLine=!0,X.dedent=!1);var N=X.tokenize(K,X),H=K.current();if(X.beginningOfLine&&H=="@")return K.match(W,!1)?"meta":w?"operator":T;if(/\S/.test(H)&&(X.beginningOfLine=!1),(N=="variable"||N=="builtin")&&X.lastToken=="meta"&&(N="meta"),(H=="pass"||H=="return")&&(X.dedent=!0),H=="lambda"&&(X.lambda=!0),H==":"&&!X.lambda&&S(X).type=="py"&&K.match(/^\s*(?:#|$)/,!1)&&D(X),H.length==1&&!/string|comment/.test(N)){var le="[({".indexOf(H);if(le!=-1&&Q(K,X,"])}".slice(le,le+1)),le="])}".indexOf(H),le!=-1)if(S(X).type==H)X.indent=X.scopes.pop().offset-k;else return T}return X.dedent&&K.eol()&&S(X).type=="py"&&X.scopes.length>1&&X.scopes.pop(),N}var x={startState:function(K){return{tokenize:J,scopes:[{offset:K||0,type:"py",align:null}],indent:K||0,lastToken:null,lambda:!1,dedent:0}},token:function(K,X){var N=X.errorToken;N&&(X.errorToken=!1);var H=V(K,X);return H&&H!="comment"&&(X.lastToken=H=="keyword"||H=="punctuation"?K.current():H),H=="punctuation"&&(H=null),K.eol()&&X.lambda&&(X.lambda=!1),N?H+" "+T:H},indent:function(K,X){if(K.tokenize!=J)return K.tokenize.isString?o.Pass:0;var N=S(K),H=N.type==X.charAt(0)||N.type=="py"&&!K.dedent&&/^(else:|elif |except |finally:)/.test(X);return N.align!=null?N.align-(H?1:0):N.offset-(H?k:0)},electricInput:/^\s*([\}\]\)]|else:|elif |except |finally:)$/,closeBrackets:{triples:`'"`},lineComment:"#",fold:"indent"};return x}),o.defineMIME("text/x-python","python");var s=function(h){return h.split(" ")};o.defineMIME("text/x-cython",{name:"python",extra_keywords:s("by cdef cimport cpdef ctypedef enum except extern gil include nogil property public readonly struct union DEF IF ELIF ELSE")})})});var qu=Ke((Au,Du)=>{(function(o){typeof Au=="object"&&typeof Du=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";function p(g){for(var T={},y=0,c=g.length;y]/)?(E.eat(/[\<\>]/),"atom"):E.eat(/[\+\-\*\/\&\|\:\!]/)?"atom":E.eat(/[a-zA-Z$@_\xa1-\uffff]/)?(E.eatWhile(/[\w$\xa1-\uffff]/),E.eat(/[\?\!\=]/),"atom"):"operator";if(G=="@"&&E.match(/^@?[a-zA-Z_\xa1-\uffff]/))return E.eat("@"),E.eatWhile(/[\w\xa1-\uffff]/),"variable-2";if(G=="$")return E.eat(/[a-zA-Z_]/)?E.eatWhile(/[\w]/):E.eat(/\d/)?E.eat(/\d/):E.next(),"variable-3";if(/[a-zA-Z_\xa1-\uffff]/.test(G))return E.eatWhile(/[\w\xa1-\uffff]/),E.eat(/[\?\!]/),E.eat(":")?"atom":"ident";if(G=="|"&&(O.varList||O.lastTok=="{"||O.lastTok=="do"))return T="|",null;if(/[\(\)\[\]{}\\;]/.test(G))return T=G,null;if(G=="-"&&E.eat(">"))return"arrow";if(/[=+\-\/*:\.^%<>~|]/.test(G)){var D=E.eatWhile(/[=+\-\/*:\.^%<>~|]/);return G=="."&&!D&&(T="."),"operator"}else return null}}}function d(E){for(var O=E.pos,G=0,J,re=!1,q=!1;(J=E.next())!=null;)if(q)q=!1;else{if("[{(".indexOf(J)>-1)G++;else if("]})".indexOf(J)>-1){if(G--,G<0)break}else if(J=="/"&&G==0){re=!0;break}q=J=="\\"}return E.backUp(E.pos-O),re}function k(E){return E||(E=1),function(O,G){if(O.peek()=="}"){if(E==1)return G.tokenize.pop(),G.tokenize[G.tokenize.length-1](O,G);G.tokenize[G.tokenize.length-1]=k(E-1)}else O.peek()=="{"&&(G.tokenize[G.tokenize.length-1]=k(E+1));return c(O,G)}}function z(){var E=!1;return function(O,G){return E?(G.tokenize.pop(),G.tokenize[G.tokenize.length-1](O,G)):(E=!0,c(O,G))}}function M(E,O,G,J){return function(re,q){var I=!1,D;for(q.context.type==="read-quoted-paused"&&(q.context=q.context.prev,re.eat("}"));(D=re.next())!=null;){if(D==E&&(J||!I)){q.tokenize.pop();break}if(G&&D=="#"&&!I){if(re.eat("{")){E=="}"&&(q.context={prev:q.context,type:"read-quoted-paused"}),q.tokenize.push(k());break}else if(/[@\$]/.test(re.peek())){q.tokenize.push(z());break}}I=!I&&D=="\\"}return O}}function w(E,O){return function(G,J){return O&&G.eatSpace(),G.match(E)?J.tokenize.pop():G.skipToEnd(),"string"}}function W(E,O){return E.sol()&&E.match("=end")&&E.eol()&&O.tokenize.pop(),E.skipToEnd(),"comment"}return{startState:function(){return{tokenize:[c],indented:0,context:{type:"top",indented:-g.indentUnit},continuedLine:!1,lastTok:null,varList:!1}},token:function(E,O){T=null,E.sol()&&(O.indented=E.indentation());var G=O.tokenize[O.tokenize.length-1](E,O),J,re=T;if(G=="ident"){var q=E.current();G=O.lastTok=="."?"property":C.propertyIsEnumerable(E.current())?"keyword":/^[A-Z]/.test(q)?"tag":O.lastTok=="def"||O.lastTok=="class"||O.varList?"def":"variable",G=="keyword"&&(re=q,b.propertyIsEnumerable(q)?J="indent":S.propertyIsEnumerable(q)?J="dedent":((q=="if"||q=="unless")&&E.column()==E.indentation()||q=="do"&&O.context.indented{(function(o){typeof Fu=="object"&&typeof Iu=="object"?o(We(),Di()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../../addon/mode/simple"],o):o(CodeMirror)})(function(o){"use strict";o.defineSimpleMode("rust",{start:[{regex:/b?"/,token:"string",next:"string"},{regex:/b?r"/,token:"string",next:"string_raw"},{regex:/b?r#+"/,token:"string",next:"string_raw_hash"},{regex:/'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/,token:"string-2"},{regex:/b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/,token:"string-2"},{regex:/(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/,token:"number"},{regex:/(let(?:\s+mut)?|fn|enum|mod|struct|type|union)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,token:["keyword",null,"def"]},{regex:/(?:abstract|alignof|as|async|await|box|break|continue|const|crate|do|dyn|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,token:"keyword"},{regex:/\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/,token:"atom"},{regex:/\b(?:true|false|Some|None|Ok|Err)\b/,token:"builtin"},{regex:/\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,token:["keyword",null,"def"]},{regex:/#!?\[.*\]/,token:"meta"},{regex:/\/\/.*/,token:"comment"},{regex:/\/\*/,token:"comment",next:"comment"},{regex:/[-+\/*=<>!]+/,token:"operator"},{regex:/[a-zA-Z_]\w*!/,token:"variable-3"},{regex:/[a-zA-Z_]\w*/,token:"variable"},{regex:/[\{\[\(]/,indent:!0},{regex:/[\}\]\)]/,dedent:!0}],string:[{regex:/"/,token:"string",next:"start"},{regex:/(?:[^\\"]|\\(?:.|$))*/,token:"string"}],string_raw:[{regex:/"/,token:"string",next:"start"},{regex:/[^"]*/,token:"string"}],string_raw_hash:[{regex:/"#+/,token:"string",next:"start"},{regex:/(?:[^"]|"(?!#))*/,token:"string"}],comment:[{regex:/.*?\*\//,token:"comment",next:"start"},{regex:/.*/,token:"comment"}],meta:{dontIndentStates:["comment"],electricInput:/^\s*\}$/,blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//",fold:"brace"}}),o.defineMIME("text/x-rustsrc","rust"),o.defineMIME("text/rust","rust")})});var ea=Ke((Ou,Pu)=>{(function(o){typeof Ou=="object"&&typeof Pu=="object"?o(We(),gn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../css/css"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("sass",function(p){var v=o.mimeModes["text/css"],C=v.propertyKeywords||{},b=v.colorKeywords||{},S=v.valueKeywords||{},s=v.fontProperties||{};function h(q){return new RegExp("^"+q.join("|"))}var g=["true","false","null","auto"],T=new RegExp("^"+g.join("|")),y=["\\(","\\)","=",">","<","==",">=","<=","\\+","-","\\!=","/","\\*","%","and","or","not",";","\\{","\\}",":"],c=h(y),d=/^::?[a-zA-Z_][\w\-]*/,k;function z(q){return!q.peek()||q.match(/\s+$/,!1)}function M(q,I){var D=q.peek();return D===")"?(q.next(),I.tokenizer=J,"operator"):D==="("?(q.next(),q.eatSpace(),"operator"):D==="'"||D==='"'?(I.tokenizer=W(q.next()),"string"):(I.tokenizer=W(")",!1),"string")}function w(q,I){return function(D,Q){return D.sol()&&D.indentation()<=q?(Q.tokenizer=J,J(D,Q)):(I&&D.skipTo("*/")?(D.next(),D.next(),Q.tokenizer=J):D.skipToEnd(),"comment")}}function W(q,I){I==null&&(I=!0);function D(Q,R){var V=Q.next(),x=Q.peek(),K=Q.string.charAt(Q.pos-2),X=V!=="\\"&&x===q||V===q&&K!=="\\";return X?(V!==q&&I&&Q.next(),z(Q)&&(R.cursorHalf=0),R.tokenizer=J,"string"):V==="#"&&x==="{"?(R.tokenizer=E(D),Q.next(),"operator"):"string"}return D}function E(q){return function(I,D){return I.peek()==="}"?(I.next(),D.tokenizer=q,"operator"):J(I,D)}}function O(q){if(q.indentCount==0){q.indentCount++;var I=q.scopes[0].offset,D=I+p.indentUnit;q.scopes.unshift({offset:D})}}function G(q){q.scopes.length!=1&&q.scopes.shift()}function J(q,I){var D=q.peek();if(q.match("/*"))return I.tokenizer=w(q.indentation(),!0),I.tokenizer(q,I);if(q.match("//"))return I.tokenizer=w(q.indentation(),!1),I.tokenizer(q,I);if(q.match("#{"))return I.tokenizer=E(J),"operator";if(D==='"'||D==="'")return q.next(),I.tokenizer=W(D),"string";if(I.cursorHalf){if(D==="#"&&(q.next(),q.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/))||q.match(/^-?[0-9\.]+/))return z(q)&&(I.cursorHalf=0),"number";if(q.match(/^(px|em|in)\b/))return z(q)&&(I.cursorHalf=0),"unit";if(q.match(T))return z(q)&&(I.cursorHalf=0),"keyword";if(q.match(/^url/)&&q.peek()==="(")return I.tokenizer=M,z(q)&&(I.cursorHalf=0),"atom";if(D==="$")return q.next(),q.eatWhile(/[\w-]/),z(q)&&(I.cursorHalf=0),"variable-2";if(D==="!")return q.next(),I.cursorHalf=0,q.match(/^[\w]+/)?"keyword":"operator";if(q.match(c))return z(q)&&(I.cursorHalf=0),"operator";if(q.eatWhile(/[\w-]/))return z(q)&&(I.cursorHalf=0),k=q.current().toLowerCase(),S.hasOwnProperty(k)?"atom":b.hasOwnProperty(k)?"keyword":C.hasOwnProperty(k)?(I.prevProp=q.current().toLowerCase(),"property"):"tag";if(z(q))return I.cursorHalf=0,null}else{if(D==="-"&&q.match(/^-\w+-/))return"meta";if(D==="."){if(q.next(),q.match(/^[\w-]+/))return O(I),"qualifier";if(q.peek()==="#")return O(I),"tag"}if(D==="#"){if(q.next(),q.match(/^[\w-]+/))return O(I),"builtin";if(q.peek()==="#")return O(I),"tag"}if(D==="$")return q.next(),q.eatWhile(/[\w-]/),"variable-2";if(q.match(/^-?[0-9\.]+/))return"number";if(q.match(/^(px|em|in)\b/))return"unit";if(q.match(T))return"keyword";if(q.match(/^url/)&&q.peek()==="(")return I.tokenizer=M,"atom";if(D==="="&&q.match(/^=[\w-]+/))return O(I),"meta";if(D==="+"&&q.match(/^\+[\w-]+/))return"variable-3";if(D==="@"&&q.match("@extend")&&(q.match(/\s*[\w]/)||G(I)),q.match(/^@(else if|if|media|else|for|each|while|mixin|function)/))return O(I),"def";if(D==="@")return q.next(),q.eatWhile(/[\w-]/),"def";if(q.eatWhile(/[\w-]/))if(q.match(/ *: *[\w-\+\$#!\("']/,!1)){k=q.current().toLowerCase();var Q=I.prevProp+"-"+k;return C.hasOwnProperty(Q)?"property":C.hasOwnProperty(k)?(I.prevProp=k,"property"):s.hasOwnProperty(k)?"property":"tag"}else return q.match(/ *:/,!1)?(O(I),I.cursorHalf=1,I.prevProp=q.current().toLowerCase(),"property"):(q.match(/ *,/,!1)||O(I),"tag");if(D===":")return q.match(d)?"variable-3":(q.next(),I.cursorHalf=1,"operator")}return q.match(c)?"operator":(q.next(),null)}function re(q,I){q.sol()&&(I.indentCount=0);var D=I.tokenizer(q,I),Q=q.current();if((Q==="@return"||Q==="}")&&G(I),D!==null){for(var R=q.pos-Q.length,V=R+p.indentUnit*I.indentCount,x=[],K=0;K{(function(o){typeof ju=="object"&&typeof Ru=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("shell",function(){var p={};function v(d,k){for(var z=0;z1&&d.eat("$");var z=d.next();return/['"({]/.test(z)?(k.tokens[0]=h(z,z=="("?"quote":z=="{"?"def":"string"),c(d,k)):(/\d/.test(z)||d.eatWhile(/\w/),k.tokens.shift(),"def")};function y(d){return function(k,z){return k.sol()&&k.string==d&&z.tokens.shift(),k.skipToEnd(),"string-2"}}function c(d,k){return(k.tokens[0]||s)(d,k)}return{startState:function(){return{tokens:[]}},token:function(d,k){return c(d,k)},closeBrackets:"()[]{}''\"\"``",lineComment:"#",fold:"brace"}}),o.defineMIME("text/x-sh","shell"),o.defineMIME("application/x-sh","shell")})});var Uu=Ke((Bu,Wu)=>{(function(o){typeof Bu=="object"&&typeof Wu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("sql",function(g,T){var y=T.client||{},c=T.atoms||{false:!0,true:!0,null:!0},d=T.builtin||s(h),k=T.keywords||s(S),z=T.operatorChars||/^[*+\-%<>!=&|~^\/]/,M=T.support||{},w=T.hooks||{},W=T.dateSQL||{date:!0,time:!0,timestamp:!0},E=T.backslashStringEscapes!==!1,O=T.brackets||/^[\{}\(\)\[\]]/,G=T.punctuation||/^[;.,:]/;function J(Q,R){var V=Q.next();if(w[V]){var x=w[V](Q,R);if(x!==!1)return x}if(M.hexNumber&&(V=="0"&&Q.match(/^[xX][0-9a-fA-F]+/)||(V=="x"||V=="X")&&Q.match(/^'[0-9a-fA-F]*'/)))return"number";if(M.binaryNumber&&((V=="b"||V=="B")&&Q.match(/^'[01]*'/)||V=="0"&&Q.match(/^b[01]+/)))return"number";if(V.charCodeAt(0)>47&&V.charCodeAt(0)<58)return Q.match(/^[0-9]*(\.[0-9]+)?([eE][-+]?[0-9]+)?/),M.decimallessFloat&&Q.match(/^\.(?!\.)/),"number";if(V=="?"&&(Q.eatSpace()||Q.eol()||Q.eat(";")))return"variable-3";if(V=="'"||V=='"'&&M.doubleQuote)return R.tokenize=re(V),R.tokenize(Q,R);if((M.nCharCast&&(V=="n"||V=="N")||M.charsetCast&&V=="_"&&Q.match(/[a-z][a-z0-9]*/i))&&(Q.peek()=="'"||Q.peek()=='"'))return"keyword";if(M.escapeConstant&&(V=="e"||V=="E")&&(Q.peek()=="'"||Q.peek()=='"'&&M.doubleQuote))return R.tokenize=function(X,N){return(N.tokenize=re(X.next(),!0))(X,N)},"keyword";if(M.commentSlashSlash&&V=="/"&&Q.eat("/"))return Q.skipToEnd(),"comment";if(M.commentHash&&V=="#"||V=="-"&&Q.eat("-")&&(!M.commentSpaceRequired||Q.eat(" ")))return Q.skipToEnd(),"comment";if(V=="/"&&Q.eat("*"))return R.tokenize=q(1),R.tokenize(Q,R);if(V=="."){if(M.zerolessFloat&&Q.match(/^(?:\d+(?:e[+-]?\d+)?)/i))return"number";if(Q.match(/^\.+/))return null;if(Q.match(/^[\w\d_$#]+/))return"variable-2"}else{if(z.test(V))return Q.eatWhile(z),"operator";if(O.test(V))return"bracket";if(G.test(V))return Q.eatWhile(G),"punctuation";if(V=="{"&&(Q.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/)||Q.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/)))return"number";Q.eatWhile(/^[_\w\d]/);var K=Q.current().toLowerCase();return W.hasOwnProperty(K)&&(Q.match(/^( )+'[^']*'/)||Q.match(/^( )+"[^"]*"/))?"number":c.hasOwnProperty(K)?"atom":d.hasOwnProperty(K)?"type":k.hasOwnProperty(K)?"keyword":y.hasOwnProperty(K)?"builtin":null}}function re(Q,R){return function(V,x){for(var K=!1,X;(X=V.next())!=null;){if(X==Q&&!K){x.tokenize=J;break}K=(E||R)&&!K&&X=="\\"}return"string"}}function q(Q){return function(R,V){var x=R.match(/^.*?(\/\*|\*\/)/);return x?x[1]=="/*"?V.tokenize=q(Q+1):Q>1?V.tokenize=q(Q-1):V.tokenize=J:R.skipToEnd(),"comment"}}function I(Q,R,V){R.context={prev:R.context,indent:Q.indentation(),col:Q.column(),type:V}}function D(Q){Q.indent=Q.context.indent,Q.context=Q.context.prev}return{startState:function(){return{tokenize:J,context:null}},token:function(Q,R){if(Q.sol()&&R.context&&R.context.align==null&&(R.context.align=!1),R.tokenize==J&&Q.eatSpace())return null;var V=R.tokenize(Q,R);if(V=="comment")return V;R.context&&R.context.align==null&&(R.context.align=!0);var x=Q.current();return x=="("?I(Q,R,")"):x=="["?I(Q,R,"]"):R.context&&R.context.type==x&&D(R),V},indent:function(Q,R){var V=Q.context;if(!V)return o.Pass;var x=R.charAt(0)==V.type;return V.align?V.col+(x?0:1):V.indent+(x?0:g.indentUnit)},blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:M.commentSlashSlash?"//":M.commentHash?"#":"--",closeBrackets:"()[]{}''\"\"``",config:T}});function p(g){for(var T;(T=g.next())!=null;)if(T=="`"&&!g.eat("`"))return"variable-2";return g.backUp(g.current().length-1),g.eatWhile(/\w/)?"variable-2":null}function v(g){for(var T;(T=g.next())!=null;)if(T=='"'&&!g.eat('"'))return"variable-2";return g.backUp(g.current().length-1),g.eatWhile(/\w/)?"variable-2":null}function C(g){return g.eat("@")&&(g.match("session."),g.match("local."),g.match("global.")),g.eat("'")?(g.match(/^.*'/),"variable-2"):g.eat('"')?(g.match(/^.*"/),"variable-2"):g.eat("`")?(g.match(/^.*`/),"variable-2"):g.match(/^[0-9a-zA-Z$\.\_]+/)?"variable-2":null}function b(g){return g.eat("N")?"atom":g.match(/^[a-zA-Z.#!?]/)?"variable-2":null}var S="alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit ";function s(g){for(var T={},y=g.split(" "),c=0;c!=^\&|\/]/,brackets:/^[\{}\(\)]/,punctuation:/^[;.,:/]/,backslashStringEscapes:!1,dateSQL:s("date datetimeoffset datetime2 smalldatetime datetime time"),hooks:{"@":C}}),o.defineMIME("text/x-mysql",{name:"sql",client:s("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),keywords:s(S+"accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),builtin:s("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),atoms:s("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^]/,dateSQL:s("date time timestamp"),support:s("decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),hooks:{"@":C,"`":p,"\\":b}}),o.defineMIME("text/x-mariadb",{name:"sql",client:s("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),keywords:s(S+"accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group group_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),builtin:s("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),atoms:s("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^]/,dateSQL:s("date time timestamp"),support:s("decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),hooks:{"@":C,"`":p,"\\":b}}),o.defineMIME("text/x-sqlite",{name:"sql",client:s("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"),keywords:s(S+"abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"),builtin:s("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"),atoms:s("null current_date current_time current_timestamp"),operatorChars:/^[*+\-%<>!=&|/~]/,dateSQL:s("date time timestamp datetime"),support:s("decimallessFloat zerolessFloat"),identifierQuote:'"',hooks:{"@":C,":":C,"?":C,$:C,'"':v,"`":p}}),o.defineMIME("text/x-cassandra",{name:"sql",client:{},keywords:s("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"),builtin:s("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"),atoms:s("false true infinity NaN"),operatorChars:/^[<>=]/,dateSQL:{},support:s("commentSlashSlash decimallessFloat"),hooks:{}}),o.defineMIME("text/x-plsql",{name:"sql",client:s("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),keywords:s("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),builtin:s("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"),operatorChars:/^[*\/+\-%<>!=~]/,dateSQL:s("date time timestamp"),support:s("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")}),o.defineMIME("text/x-hive",{name:"sql",keywords:s("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with admin authorization char compact compactions conf cube current current_date current_timestamp day decimal defined dependency directories elem_type exchange file following for grouping hour ignore inner interval jar less logical macro minute month more none noscan over owner partialscan preceding pretty principals protection reload rewrite role roles rollup rows second server sets skewed transactions truncate unbounded unset uri user values window year"),builtin:s("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype key_type utctimestamp value_type varchar"),atoms:s("false true null unknown"),operatorChars:/^[*+\-%<>!=]/,dateSQL:s("date timestamp"),support:s("doubleQuote binaryNumber hexNumber")}),o.defineMIME("text/x-pgsql",{name:"sql",client:s("source"),keywords:s(S+"a abort abs absent absolute access according action ada add admin after aggregate alias all allocate also alter always analyse analyze and any are array array_agg array_max_cardinality as asc asensitive assert assertion assignment asymmetric at atomic attach attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli between bigint binary bit bit_length blob blocked bom boolean both breadth by c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain char char_length character character_length character_set_catalog character_set_name character_set_schema characteristics characters check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column column_name columns command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constant constraint constraint_catalog constraint_name constraint_schema constraints constructor contains content continue control conversion convert copy corr corresponding cost count covar_pop covar_samp create cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datatype date datetime_interval_code datetime_interval_precision day db deallocate debug dec decimal declare default defaults deferrable deferred defined definer degree delete delimiter delimiters dense_rank depends depth deref derived desc describe descriptor detach detail deterministic diagnostics dictionary disable discard disconnect dispatch distinct dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain double drop dump dynamic dynamic_function dynamic_function_code each element else elseif elsif empty enable encoding encrypted end end_frame end_partition endexec enforced enum equals errcode error escape event every except exception exclude excluding exclusive exec execute exists exit exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreach foreign fortran forward found frame_row free freeze from fs full function functions fusion g general generated get global go goto grant granted greatest group grouping groups handler having header hex hierarchy hint hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import in include including increment indent index indexes indicator info inherit inherits initially inline inner inout input insensitive insert instance instantiable instead int integer integrity intersect intersection interval into invoker is isnull isolation join k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like like_regex limit link listen ln load local localtime localtimestamp location locator lock locked log logged loop lower m map mapping match matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized not nothing notice notify notnull nowait nth_value ntile null nullable nullif nulls number numeric object occurrences_regex octet_length octets of off offset oids old on only open operator option options or order ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parallel parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password path percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding precision prepare prepared preserve primary print_strict_params prior privileges procedural procedure procedures program public publication query quote raise range rank read reads real reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result result_oid return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns reverse revoke right role rollback rollup routine routine_catalog routine_name routine_schema routines row row_count row_number rows rowtype rule savepoint scale schema schema_name schemas scope scope_catalog scope_name scope_schema scroll search second section security select selective self sensitive sequence sequences serializable server server_name session session_user set setof sets share show similar simple size skip slice smallint snapshot some source space specific specific_name specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable stacked standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time system_user t table table_name tables tablesample tablespace temp template temporary text then ties time timestamp timezone_hour timezone_minute to token top_level_count trailing transaction transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted union unique unknown unlink unlisten unlogged unnamed unnest until untyped update upper uri usage use_column use_variable user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of values var_pop var_samp varbinary varchar variable_conflict variadic varying verbose version versioning view views volatile warning when whenever where while whitespace width_bucket window with within without work wrapper write xml xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes zone"),builtin:s("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time zone timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"),atoms:s("false true null unknown"),operatorChars:/^[*\/+\-%<>!=&|^\/#@?~]/,backslashStringEscapes:!1,identifierQuote:'"',hooks:{'"':v},dateSQL:s("date time timestamp"),support:s("decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast escapeConstant")}),o.defineMIME("text/x-gql",{name:"sql",keywords:s("ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where"),atoms:s("false true"),builtin:s("blob datetime first key __key__ string integer double boolean null"),operatorChars:/^[*+\-%<>!=]/}),o.defineMIME("text/x-gpsql",{name:"sql",client:s("source"),keywords:s("abort absolute access action active add admin after aggregate all also alter always analyse analyze and any array as asc assertion assignment asymmetric at authorization backward before begin between bigint binary bit boolean both by cache called cascade cascaded case cast chain char character characteristics check checkpoint class close cluster coalesce codegen collate column comment commit committed concurrency concurrently configuration connection constraint constraints contains content continue conversion copy cost cpu_rate_limit create createdb createexttable createrole createuser cross csv cube current current_catalog current_date current_role current_schema current_time current_timestamp current_user cursor cycle data database day deallocate dec decimal declare decode default defaults deferrable deferred definer delete delimiter delimiters deny desc dictionary disable discard distinct distributed do document domain double drop dxl each else enable encoding encrypted end enum errors escape every except exchange exclude excluding exclusive execute exists explain extension external extract false family fetch fields filespace fill filter first float following for force foreign format forward freeze from full function global grant granted greatest group group_id grouping handler hash having header hold host hour identity if ignore ilike immediate immutable implicit in including inclusive increment index indexes inherit inherits initially inline inner inout input insensitive insert instead int integer intersect interval into invoker is isnull isolation join key language large last leading least left level like limit list listen load local localtime localtimestamp location lock log login mapping master match maxvalue median merge minute minvalue missing mode modifies modify month move name names national natural nchar new newline next no nocreatedb nocreateexttable nocreaterole nocreateuser noinherit nologin none noovercommit nosuperuser not nothing notify notnull nowait null nullif nulls numeric object of off offset oids old on only operator option options or order ordered others out outer over overcommit overlaps overlay owned owner parser partial partition partitions passing password percent percentile_cont percentile_disc placing plans position preceding precision prepare prepared preserve primary prior privileges procedural procedure protocol queue quote randomly range read readable reads real reassign recheck recursive ref references reindex reject relative release rename repeatable replace replica reset resource restart restrict returning returns revoke right role rollback rollup rootpartition row rows rule savepoint scatter schema scroll search second security segment select sequence serializable session session_user set setof sets share show similar simple smallint some split sql stable standalone start statement statistics stdin stdout storage strict strip subpartition subpartitions substring superuser symmetric sysid system table tablespace temp template temporary text then threshold ties time timestamp to trailing transaction treat trigger trim true truncate trusted type unbounded uncommitted unencrypted union unique unknown unlisten until update user using vacuum valid validation validator value values varchar variadic varying verbose version view volatile web when where whitespace window with within without work writable write xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot xmlserialize year yes zone"),builtin:s("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"),atoms:s("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^\/#@?~]/,dateSQL:s("date time timestamp"),support:s("decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast")}),o.defineMIME("text/x-sparksql",{name:"sql",keywords:s("add after all alter analyze and anti archive array as asc at between bucket buckets by cache cascade case cast change clear cluster clustered codegen collection column columns comment commit compact compactions compute concatenate cost create cross cube current current_date current_timestamp database databases data dbproperties defined delete delimited deny desc describe dfs directories distinct distribute drop else end escaped except exchange exists explain export extended external false fields fileformat first following for format formatted from full function functions global grant group grouping having if ignore import in index indexes inner inpath inputformat insert intersect interval into is items join keys last lateral lazy left like limit lines list load local location lock locks logical macro map minus msck natural no not null nulls of on optimize option options or order out outer outputformat over overwrite partition partitioned partitions percent preceding principals purge range recordreader recordwriter recover reduce refresh regexp rename repair replace reset restrict revoke right rlike role roles rollback rollup row rows schema schemas select semi separated serde serdeproperties set sets show skewed sort sorted start statistics stored stratify struct table tables tablesample tblproperties temp temporary terminated then to touch transaction transactions transform true truncate unarchive unbounded uncache union unlock unset use using values view when where window with"),builtin:s("abs acos acosh add_months aggregate and any approx_count_distinct approx_percentile array array_contains array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_repeat array_sort array_union arrays_overlap arrays_zip ascii asin asinh assert_true atan atan2 atanh avg base64 between bigint bin binary bit_and bit_count bit_get bit_length bit_or bit_xor bool_and bool_or boolean bround btrim cardinality case cast cbrt ceil ceiling char char_length character_length chr coalesce collect_list collect_set concat concat_ws conv corr cos cosh cot count count_if count_min_sketch covar_pop covar_samp crc32 cume_dist current_catalog current_database current_date current_timestamp current_timezone current_user date date_add date_format date_from_unix_date date_part date_sub date_trunc datediff day dayofmonth dayofweek dayofyear decimal decode degrees delimited dense_rank div double e element_at elt encode every exists exp explode explode_outer expm1 extract factorial filter find_in_set first first_value flatten float floor forall format_number format_string from_csv from_json from_unixtime from_utc_timestamp get_json_object getbit greatest grouping grouping_id hash hex hour hypot if ifnull in initcap inline inline_outer input_file_block_length input_file_block_start input_file_name inputformat instr int isnan isnotnull isnull java_method json_array_length json_object_keys json_tuple kurtosis lag last last_day last_value lcase lead least left length levenshtein like ln locate log log10 log1p log2 lower lpad ltrim make_date make_dt_interval make_interval make_timestamp make_ym_interval map map_concat map_entries map_filter map_from_arrays map_from_entries map_keys map_values map_zip_with max max_by md5 mean min min_by minute mod monotonically_increasing_id month months_between named_struct nanvl negative next_day not now nth_value ntile nullif nvl nvl2 octet_length or outputformat overlay parse_url percent_rank percentile percentile_approx pi pmod posexplode posexplode_outer position positive pow power printf quarter radians raise_error rand randn random rank rcfile reflect regexp regexp_extract regexp_extract_all regexp_like regexp_replace repeat replace reverse right rint rlike round row_number rpad rtrim schema_of_csv schema_of_json second sentences sequence sequencefile serde session_window sha sha1 sha2 shiftleft shiftright shiftrightunsigned shuffle sign signum sin sinh size skewness slice smallint some sort_array soundex space spark_partition_id split sqrt stack std stddev stddev_pop stddev_samp str_to_map string struct substr substring substring_index sum tan tanh textfile timestamp timestamp_micros timestamp_millis timestamp_seconds tinyint to_csv to_date to_json to_timestamp to_unix_timestamp to_utc_timestamp transform transform_keys transform_values translate trim trunc try_add try_divide typeof ucase unbase64 unhex uniontype unix_date unix_micros unix_millis unix_seconds unix_timestamp upper uuid var_pop var_samp variance version weekday weekofyear when width_bucket window xpath xpath_boolean xpath_double xpath_float xpath_int xpath_long xpath_number xpath_short xpath_string xxhash64 year zip_with"),atoms:s("false true null"),operatorChars:/^[*\/+\-%<>!=~&|^]/,dateSQL:s("date time timestamp"),support:s("doubleQuote zerolessFloat")}),o.defineMIME("text/x-esper",{name:"sql",client:s("source"),keywords:s("alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window"),builtin:{},atoms:s("false true null"),operatorChars:/^[*+\-%<>!=&|^\/#@?~]/,dateSQL:s("time"),support:s("decimallessFloat zerolessFloat binaryNumber hexNumber")}),o.defineMIME("text/x-trino",{name:"sql",keywords:s("abs absent acos add admin after all all_match alter analyze and any any_match approx_distinct approx_most_frequent approx_percentile approx_set arbitrary array_agg array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_sort array_union arrays_overlap as asc asin at at_timezone atan atan2 authorization avg bar bernoulli beta_cdf between bing_tile bing_tile_at bing_tile_coordinates bing_tile_polygon bing_tile_quadkey bing_tile_zoom_level bing_tiles_around bit_count bitwise_and bitwise_and_agg bitwise_left_shift bitwise_not bitwise_or bitwise_or_agg bitwise_right_shift bitwise_right_shift_arithmetic bitwise_xor bool_and bool_or both by call cardinality cascade case cast catalogs cbrt ceil ceiling char2hexint checksum chr classify coalesce codepoint column columns combinations comment commit committed concat concat_ws conditional constraint contains contains_sequence convex_hull_agg copartition corr cos cosh cosine_similarity count count_if covar_pop covar_samp crc32 create cross cube cume_dist current current_catalog current_date current_groups current_path current_role current_schema current_time current_timestamp current_timezone current_user data date_add date_diff date_format date_parse date_trunc day day_of_month day_of_week day_of_year deallocate default define definer degrees delete dense_rank deny desc describe descriptor distinct distributed dow doy drop e element_at else empty empty_approx_set encoding end error escape evaluate_classifier_predictions every except excluding execute exists exp explain extract false features fetch filter final first first_value flatten floor following for format format_datetime format_number from from_base from_base32 from_base64 from_base64url from_big_endian_32 from_big_endian_64 from_encoded_polyline from_geojson_geometry from_hex from_ieee754_32 from_ieee754_64 from_iso8601_date from_iso8601_timestamp from_iso8601_timestamp_nanos from_unixtime from_unixtime_nanos from_utf8 full functions geometric_mean geometry_from_hadoop_shape geometry_invalid_reason geometry_nearest_points geometry_to_bing_tiles geometry_union geometry_union_agg grant granted grants graphviz great_circle_distance greatest group grouping groups hamming_distance hash_counts having histogram hmac_md5 hmac_sha1 hmac_sha256 hmac_sha512 hour human_readable_seconds if ignore in including index infinity initial inner input insert intersect intersection_cardinality into inverse_beta_cdf inverse_normal_cdf invoker io is is_finite is_infinite is_json_scalar is_nan isolation jaccard_index join json_array json_array_contains json_array_get json_array_length json_exists json_extract json_extract_scalar json_format json_object json_parse json_query json_size json_value keep key keys kurtosis lag last last_day_of_month last_value lateral lead leading learn_classifier learn_libsvm_classifier learn_libsvm_regressor learn_regressor least left length level levenshtein_distance like limit line_interpolate_point line_interpolate_points line_locate_point listagg ln local localtime localtimestamp log log10 log2 logical lower lpad ltrim luhn_check make_set_digest map_agg map_concat map_entries map_filter map_from_entries map_keys map_union map_values map_zip_with match match_recognize matched matches materialized max max_by md5 measures merge merge_set_digest millisecond min min_by minute mod month multimap_agg multimap_from_entries murmur3 nan natural next nfc nfd nfkc nfkd ngrams no none none_match normal_cdf normalize not now nth_value ntile null nullif nulls numeric_histogram object objectid_timestamp of offset omit on one only option or order ordinality outer output over overflow parse_data_size parse_datetime parse_duration partition partitions passing past path pattern per percent_rank permute pi position pow power preceding prepare privileges properties prune qdigest_agg quarter quotes radians rand random range rank read recursive reduce reduce_agg refresh regexp_count regexp_extract regexp_extract_all regexp_like regexp_position regexp_replace regexp_split regr_intercept regr_slope regress rename render repeat repeatable replace reset respect restrict returning reverse revoke rgb right role roles rollback rollup round row_number rows rpad rtrim running scalar schema schemas second security seek select sequence serializable session set sets sha1 sha256 sha512 show shuffle sign simplify_geometry sin skewness skip slice some soundex spatial_partitioning spatial_partitions split split_part split_to_map split_to_multimap spooky_hash_v2_32 spooky_hash_v2_64 sqrt st_area st_asbinary st_astext st_boundary st_buffer st_centroid st_contains st_convexhull st_coorddim st_crosses st_difference st_dimension st_disjoint st_distance st_endpoint st_envelope st_envelopeaspts st_equals st_exteriorring st_geometries st_geometryfromtext st_geometryn st_geometrytype st_geomfrombinary st_interiorringn st_interiorrings st_intersection st_intersects st_isclosed st_isempty st_isring st_issimple st_isvalid st_length st_linefromtext st_linestring st_multipoint st_numgeometries st_numinteriorring st_numpoints st_overlaps st_point st_pointn st_points st_polygon st_relate st_startpoint st_symdifference st_touches st_union st_within st_x st_xmax st_xmin st_y st_ymax st_ymin start starts_with stats stddev stddev_pop stddev_samp string strpos subset substr substring sum system table tables tablesample tan tanh tdigest_agg text then ties timestamp_objectid timezone_hour timezone_minute to to_base to_base32 to_base64 to_base64url to_big_endian_32 to_big_endian_64 to_char to_date to_encoded_polyline to_geojson_geometry to_geometry to_hex to_ieee754_32 to_ieee754_64 to_iso8601 to_milliseconds to_spherical_geography to_timestamp to_unixtime to_utf8 trailing transaction transform transform_keys transform_values translate trim trim_array true truncate try try_cast type typeof uescape unbounded uncommitted unconditional union unique unknown unmatched unnest update upper url_decode url_encode url_extract_fragment url_extract_host url_extract_parameter url_extract_path url_extract_port url_extract_protocol url_extract_query use user using utf16 utf32 utf8 validate value value_at_quantile values values_at_quantiles var_pop var_samp variance verbose version view week week_of_year when where width_bucket wilson_interval_lower wilson_interval_upper window with with_timezone within without word_stem work wrapper write xxhash64 year year_of_week yow zip zip_with"),builtin:s("array bigint bingtile boolean char codepoints color date decimal double function geometry hyperloglog int integer interval ipaddress joniregexp json json2016 jsonpath kdbtree likepattern map model objectid p4hyperloglog precision qdigest re2jregexp real regressor row setdigest smallint sphericalgeography tdigest time timestamp tinyint uuid varbinary varchar zone"),atoms:s("false true null unknown"),operatorChars:/^[[\]|<>=!\-+*/%]/,dateSQL:s("date time timestamp zone"),support:s("decimallessFloat zerolessFloat hexNumber")})})});var ta=Ke(($u,Ku)=>{(function(o){typeof $u=="object"&&typeof Ku=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("stylus",function(E){for(var O=E.indentUnit,G="",J=w(p),re=/^(a|b|i|s|col|em)$/i,q=w(S),I=w(s),D=w(T),Q=w(g),R=w(v),V=M(v),x=w(b),K=w(C),X=w(h),N=/^\s*([.]{2,3}|&&|\|\||\*\*|[?!=:]?=|[-+*\/%<>]=?|\?:|\~)/,H=M(y),le=w(c),xe=new RegExp(/^\-(moz|ms|o|webkit)-/i),F=w(d),L="",de={},ze,pe,Ee,ge;G.length|~|\/)?\s*[\w-]*([a-z0-9-]|\*|\/\*)(\(|,)?)/),B.context.line.firstWord=L?L[0].replace(/^\s*/,""):"",B.context.line.indent=$.indentation(),ze=$.peek(),$.match("//"))return $.skipToEnd(),["comment","comment"];if($.match("/*"))return B.tokenize=qe,qe($,B);if(ze=='"'||ze=="'")return $.next(),B.tokenize=Se(ze),B.tokenize($,B);if(ze=="@")return $.next(),$.eatWhile(/[\w\\-]/),["def",$.current()];if(ze=="#"){if($.next(),$.match(/^[0-9a-f]{3}([0-9a-f]([0-9a-f]{2}){0,2})?\b(?!-)/i))return["atom","atom"];if($.match(/^[a-z][\w-]*/i))return["builtin","hash"]}return $.match(xe)?["meta","vendor-prefixes"]:$.match(/^-?[0-9]?\.?[0-9]/)?($.eatWhile(/[a-z%]/i),["number","unit"]):ze=="!"?($.next(),[$.match(/^(important|optional)/i)?"keyword":"operator","important"]):ze=="."&&$.match(/^\.[a-z][\w-]*/i)?["qualifier","qualifier"]:$.match(V)?($.peek()=="("&&(B.tokenize=je),["property","word"]):$.match(/^[a-z][\w-]*\(/i)?($.backUp(1),["keyword","mixin"]):$.match(/^(\+|-)[a-z][\w-]*\(/i)?($.backUp(1),["keyword","block-mixin"]):$.string.match(/^\s*&/)&&$.match(/^[-_]+[a-z][\w-]*/)?["qualifier","qualifier"]:$.match(/^(\/|&)(-|_|:|\.|#|[a-z])/)?($.backUp(1),["variable-3","reference"]):$.match(/^&{1}\s*$/)?["variable-3","reference"]:$.match(H)?["operator","operator"]:$.match(/^\$?[-_]*[a-z0-9]+[\w-]*/i)?$.match(/^(\.|\[)[\w-\'\"\]]+/i,!1)&&!U($.current())?($.match("."),["variable-2","variable-name"]):["variable-2","word"]:$.match(N)?["operator",$.current()]:/[:;,{}\[\]\(\)]/.test(ze)?($.next(),[null,ze]):($.next(),[null,null])}function qe($,B){for(var se=!1,De;(De=$.next())!=null;){if(se&&De=="/"){B.tokenize=null;break}se=De=="*"}return["comment","comment"]}function Se($){return function(B,se){for(var De=!1,nt;(nt=B.next())!=null;){if(nt==$&&!De){$==")"&&B.backUp(1);break}De=!De&&nt=="\\"}return(nt==$||!De&&$!=")")&&(se.tokenize=null),["string","string"]}}function je($,B){return $.next(),$.match(/\s*[\"\')]/,!1)?B.tokenize=null:B.tokenize=Se(")"),[null,"("]}function Ze($,B,se,De){this.type=$,this.indent=B,this.prev=se,this.line=De||{firstWord:"",indent:0}}function ke($,B,se,De){return De=De>=0?De:O,$.context=new Ze(se,B.indentation()+De,$.context),se}function Je($,B){var se=$.context.indent-O;return B=B||!1,$.context=$.context.prev,B&&($.context.indent=se),$.context.type}function He($,B,se){return de[se.context.type]($,B,se)}function Ge($,B,se,De){for(var nt=De||1;nt>0;nt--)se.context=se.context.prev;return He($,B,se)}function U($){return $.toLowerCase()in J}function Z($){return $=$.toLowerCase(),$ in q||$ in X}function ce($){return $.toLowerCase()in le}function Be($){return $.toLowerCase().match(xe)}function te($){var B=$.toLowerCase(),se="variable-2";return U($)?se="tag":ce($)?se="block-keyword":Z($)?se="property":B in D||B in F?se="atom":B=="return"||B in Q?se="keyword":$.match(/^[A-Z]/)&&(se="string"),se}function fe($,B){return Me(B)&&($=="{"||$=="]"||$=="hash"||$=="qualifier")||$=="block-mixin"}function oe($,B){return $=="{"&&B.match(/^\s*\$?[\w-]+/i,!1)}function Ue($,B){return $==":"&&B.match(/^[a-z-]+/,!1)}function we($){return $.sol()||$.string.match(new RegExp("^\\s*"+W($.current())))}function Me($){return $.eol()||$.match(/^\s*$/,!1)}function Le($){var B=/^\s*[-_]*[a-z0-9]+[\w-]*/i,se=typeof $=="string"?$.match(B):$.string.match(B);return se?se[0].replace(/^\s*/,""):""}return de.block=function($,B,se){if($=="comment"&&we(B)||$==","&&Me(B)||$=="mixin")return ke(se,B,"block",0);if(oe($,B))return ke(se,B,"interpolation");if(Me(B)&&$=="]"&&!/^\s*(\.|#|:|\[|\*|&)/.test(B.string)&&!U(Le(B)))return ke(se,B,"block",0);if(fe($,B))return ke(se,B,"block");if($=="}"&&Me(B))return ke(se,B,"block",0);if($=="variable-name")return B.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/)||ce(Le(B))?ke(se,B,"variableName"):ke(se,B,"variableName",0);if($=="=")return!Me(B)&&!ce(Le(B))?ke(se,B,"block",0):ke(se,B,"block");if($=="*"&&(Me(B)||B.match(/\s*(,|\.|#|\[|:|{)/,!1)))return ge="tag",ke(se,B,"block");if(Ue($,B))return ke(se,B,"pseudo");if(/@(font-face|media|supports|(-moz-)?document)/.test($))return ke(se,B,Me(B)?"block":"atBlock");if(/@(-(moz|ms|o|webkit)-)?keyframes$/.test($))return ke(se,B,"keyframes");if(/@extends?/.test($))return ke(se,B,"extend",0);if($&&$.charAt(0)=="@")return B.indentation()>0&&Z(B.current().slice(1))?(ge="variable-2","block"):/(@import|@require|@charset)/.test($)?ke(se,B,"block",0):ke(se,B,"block");if($=="reference"&&Me(B))return ke(se,B,"block");if($=="(")return ke(se,B,"parens");if($=="vendor-prefixes")return ke(se,B,"vendorPrefixes");if($=="word"){var De=B.current();if(ge=te(De),ge=="property")return we(B)?ke(se,B,"block",0):(ge="atom","block");if(ge=="tag"){if(/embed|menu|pre|progress|sub|table/.test(De)&&Z(Le(B))||B.string.match(new RegExp("\\[\\s*"+De+"|"+De+"\\s*\\]")))return ge="atom","block";if(re.test(De)&&(we(B)&&B.string.match(/=/)||!we(B)&&!B.string.match(/^(\s*\.|#|\&|\[|\/|>|\*)/)&&!U(Le(B))))return ge="variable-2",ce(Le(B))?"block":ke(se,B,"block",0);if(Me(B))return ke(se,B,"block")}if(ge=="block-keyword")return ge="keyword",B.current(/(if|unless)/)&&!we(B)?"block":ke(se,B,"block");if(De=="return")return ke(se,B,"block",0);if(ge=="variable-2"&&B.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/))return ke(se,B,"block")}return se.context.type},de.parens=function($,B,se){if($=="(")return ke(se,B,"parens");if($==")")return se.context.prev.type=="parens"?Je(se):B.string.match(/^[a-z][\w-]*\(/i)&&Me(B)||ce(Le(B))||/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(Le(B))||!B.string.match(/^-?[a-z][\w-\.\[\]\'\"]*\s*=/)&&U(Le(B))?ke(se,B,"block"):B.string.match(/^[\$-]?[a-z][\w-\.\[\]\'\"]*\s*=/)||B.string.match(/^\s*(\(|\)|[0-9])/)||B.string.match(/^\s+[a-z][\w-]*\(/i)||B.string.match(/^\s+[\$-]?[a-z]/i)?ke(se,B,"block",0):Me(B)?ke(se,B,"block"):ke(se,B,"block",0);if($&&$.charAt(0)=="@"&&Z(B.current().slice(1))&&(ge="variable-2"),$=="word"){var De=B.current();ge=te(De),ge=="tag"&&re.test(De)&&(ge="variable-2"),(ge=="property"||De=="to")&&(ge="atom")}return $=="variable-name"?ke(se,B,"variableName"):Ue($,B)?ke(se,B,"pseudo"):se.context.type},de.vendorPrefixes=function($,B,se){return $=="word"?(ge="property",ke(se,B,"block",0)):Je(se)},de.pseudo=function($,B,se){return Z(Le(B.string))?Ge($,B,se):(B.match(/^[a-z-]+/),ge="variable-3",Me(B)?ke(se,B,"block"):Je(se))},de.atBlock=function($,B,se){if($=="(")return ke(se,B,"atBlock_parens");if(fe($,B))return ke(se,B,"block");if(oe($,B))return ke(se,B,"interpolation");if($=="word"){var De=B.current().toLowerCase();if(/^(only|not|and|or)$/.test(De)?ge="keyword":R.hasOwnProperty(De)?ge="tag":K.hasOwnProperty(De)?ge="attribute":x.hasOwnProperty(De)?ge="property":I.hasOwnProperty(De)?ge="string-2":ge=te(B.current()),ge=="tag"&&Me(B))return ke(se,B,"block")}return $=="operator"&&/^(not|and|or)$/.test(B.current())&&(ge="keyword"),se.context.type},de.atBlock_parens=function($,B,se){if($=="{"||$=="}")return se.context.type;if($==")")return Me(B)?ke(se,B,"block"):ke(se,B,"atBlock");if($=="word"){var De=B.current().toLowerCase();return ge=te(De),/^(max|min)/.test(De)&&(ge="property"),ge=="tag"&&(re.test(De)?ge="variable-2":ge="atom"),se.context.type}return de.atBlock($,B,se)},de.keyframes=function($,B,se){return B.indentation()=="0"&&($=="}"&&we(B)||$=="]"||$=="hash"||$=="qualifier"||U(B.current()))?Ge($,B,se):$=="{"?ke(se,B,"keyframes"):$=="}"?we(B)?Je(se,!0):ke(se,B,"keyframes"):$=="unit"&&/^[0-9]+\%$/.test(B.current())?ke(se,B,"keyframes"):$=="word"&&(ge=te(B.current()),ge=="block-keyword")?(ge="keyword",ke(se,B,"keyframes")):/@(font-face|media|supports|(-moz-)?document)/.test($)?ke(se,B,Me(B)?"block":"atBlock"):$=="mixin"?ke(se,B,"block",0):se.context.type},de.interpolation=function($,B,se){return $=="{"&&Je(se)&&ke(se,B,"block"),$=="}"?B.string.match(/^\s*(\.|#|:|\[|\*|&|>|~|\+|\/)/i)||B.string.match(/^\s*[a-z]/i)&&U(Le(B))?ke(se,B,"block"):!B.string.match(/^(\{|\s*\&)/)||B.match(/\s*[\w-]/,!1)?ke(se,B,"block",0):ke(se,B,"block"):$=="variable-name"?ke(se,B,"variableName",0):($=="word"&&(ge=te(B.current()),ge=="tag"&&(ge="atom")),se.context.type)},de.extend=function($,B,se){return $=="["||$=="="?"extend":$=="]"?Je(se):$=="word"?(ge=te(B.current()),"extend"):Je(se)},de.variableName=function($,B,se){return $=="string"||$=="["||$=="]"||B.current().match(/^(\.|\$)/)?(B.current().match(/^\.[\w-]+/i)&&(ge="variable-2"),"variableName"):Ge($,B,se)},{startState:function($){return{tokenize:null,state:"block",context:new Ze("block",$||0,null)}},token:function($,B){return!B.tokenize&&$.eatSpace()?null:(pe=(B.tokenize||Oe)($,B),pe&&typeof pe=="object"&&(Ee=pe[1],pe=pe[0]),ge=pe,B.state=de[B.state](Ee,$,B),ge)},indent:function($,B,se){var De=$.context,nt=B&&B.charAt(0),dt=De.indent,Pt=Le(B),Ft=se.match(/^\s*/)[0].replace(/\t/g,G).length,Pe=$.context.prev?$.context.prev.line.firstWord:"",xt=$.context.prev?$.context.prev.line.indent:Ft;return De.prev&&(nt=="}"&&(De.type=="block"||De.type=="atBlock"||De.type=="keyframes")||nt==")"&&(De.type=="parens"||De.type=="atBlock_parens")||nt=="{"&&De.type=="at")?dt=De.indent-O:/(\})/.test(nt)||(/@|\$|\d/.test(nt)||/^\{/.test(B)||/^\s*\/(\/|\*)/.test(B)||/^\s*\/\*/.test(Pe)||/^\s*[\w-\.\[\]\'\"]+\s*(\?|:|\+)?=/i.test(B)||/^(\+|-)?[a-z][\w-]*\(/i.test(B)||/^return/.test(B)||ce(Pt)?dt=Ft:/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(nt)||U(Pt)?/\,\s*$/.test(Pe)?dt=xt:/^\s+/.test(se)&&(/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(Pe)||U(Pe))?dt=Ft<=xt?xt:xt+O:dt=Ft:!/,\s*$/.test(se)&&(Be(Pt)||Z(Pt))&&(ce(Pe)?dt=Ft<=xt?xt:xt+O:/^\{/.test(Pe)?dt=Ft<=xt?Ft:xt+O:Be(Pe)||Z(Pe)?dt=Ft>=xt?xt:Ft:/^(\.|#|:|\[|\*|&|@|\+|\-|>|~|\/)/.test(Pe)||/=\s*$/.test(Pe)||U(Pe)||/^\$[\w-\.\[\]\'\"]/.test(Pe)?dt=xt+O:dt=Ft)),dt},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:"//",fold:"indent"}});var p=["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","bgsound","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","nobr","noframes","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track","u","ul","var","video"],v=["domain","regexp","url-prefix","url"],C=["all","aural","braille","handheld","print","projection","screen","tty","tv","embossed"],b=["width","min-width","max-width","height","min-height","max-height","device-width","min-device-width","max-device-width","device-height","min-device-height","max-device-height","aspect-ratio","min-aspect-ratio","max-aspect-ratio","device-aspect-ratio","min-device-aspect-ratio","max-device-aspect-ratio","color","min-color","max-color","color-index","min-color-index","max-color-index","monochrome","min-monochrome","max-monochrome","resolution","min-resolution","max-resolution","scan","grid","dynamic-range","video-dynamic-range"],S=["align-content","align-items","align-self","alignment-adjust","alignment-baseline","anchor-point","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","appearance","azimuth","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","baseline-shift","binding","bleed","bookmark-label","bookmark-level","bookmark-state","bookmark-target","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","color","color-profile","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","crop","cue","cue-after","cue-before","cursor","direction","display","dominant-baseline","drop-initial-after-adjust","drop-initial-after-align","drop-initial-before-adjust","drop-initial-before-align","drop-initial-size","drop-initial-value","elevation","empty-cells","fit","fit-position","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","float-offset","flow-from","flow-into","font","font-feature-settings","font-family","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-position","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","inline-box-align","justify-content","left","letter-spacing","line-break","line-height","line-stacking","line-stacking-ruby","line-stacking-shift","line-stacking-strategy","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marker-offset","marks","marquee-direction","marquee-loop","marquee-play-count","marquee-speed","marquee-style","max-height","max-width","min-height","min-width","move-to","nav-down","nav-index","nav-left","nav-right","nav-up","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-style","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","page-policy","pause","pause-after","pause-before","perspective","perspective-origin","pitch","pitch-range","play-during","position","presentation-level","punctuation-trim","quotes","region-break-after","region-break-before","region-break-inside","region-fragment","rendering-intent","resize","rest","rest-after","rest-before","richness","right","rotation","rotation-point","ruby-align","ruby-overhang","ruby-position","ruby-span","shape-image-threshold","shape-inside","shape-margin","shape-outside","size","speak","speak-as","speak-header","speak-numeral","speak-punctuation","speech-rate","stress","string-set","tab-size","table-layout","target","target-name","target-new","target-position","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-height","text-indent","text-justify","text-outline","text-overflow","text-shadow","text-size-adjust","text-space-collapse","text-transform","text-underline-position","text-wrap","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","z-index","clip-path","clip-rule","mask","enable-background","filter","flood-color","flood-opacity","lighting-color","stop-color","stop-opacity","pointer-events","color-interpolation","color-interpolation-filters","color-rendering","fill","fill-opacity","fill-rule","image-rendering","marker","marker-end","marker-mid","marker-start","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-rendering","baseline-shift","dominant-baseline","glyph-orientation-horizontal","glyph-orientation-vertical","text-anchor","writing-mode","font-smoothing","osx-font-smoothing"],s=["scrollbar-arrow-color","scrollbar-base-color","scrollbar-dark-shadow-color","scrollbar-face-color","scrollbar-highlight-color","scrollbar-shadow-color","scrollbar-3d-light-color","scrollbar-track-color","shape-inside","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","zoom"],h=["font-family","src","unicode-range","font-variant","font-feature-settings","font-stretch","font-weight","font-style"],g=["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"],T=["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","conic-gradient","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","high","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","matrix","matrix3d","media-play-button","media-slider","media-sliderthumb","media-volume-slider","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeating-conic-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scale","scale3d","scaleX","scaleY","scaleZ","scroll","scrollbar","scroll-position","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","spell-out","square","square-button","standard","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale","row","row-reverse","wrap","wrap-reverse","column-reverse","flex-start","flex-end","space-between","space-around","unset"],y=["in","and","or","not","is not","is a","is","isnt","defined","if unless"],c=["for","if","else","unless","from","to"],d=["null","true","false","href","title","type","not-allowed","readonly","disabled"],k=["@font-face","@keyframes","@media","@viewport","@page","@host","@supports","@block","@css"],z=p.concat(v,C,b,S,s,g,T,h,y,c,d,k);function M(E){return E=E.sort(function(O,G){return G>O}),new RegExp("^(("+E.join(")|(")+"))\\b")}function w(E){for(var O={},G=0;G{(function(o){typeof Gu=="object"&&typeof Zu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";function p(q){for(var I={},D=0;D~^?!",h=":;,.(){}[]",g=/^\-?0b[01][01_]*/,T=/^\-?0o[0-7][0-7_]*/,y=/^\-?0x[\dA-Fa-f][\dA-Fa-f_]*(?:(?:\.[\dA-Fa-f][\dA-Fa-f_]*)?[Pp]\-?\d[\d_]*)?/,c=/^\-?\d[\d_]*(?:\.\d[\d_]*)?(?:[Ee]\-?\d[\d_]*)?/,d=/^\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1/,k=/^\.(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/,z=/^\#[A-Za-z]+/,M=/^@(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/;function w(q,I,D){if(q.sol()&&(I.indented=q.indentation()),q.eatSpace())return null;var Q=q.peek();if(Q=="/"){if(q.match("//"))return q.skipToEnd(),"comment";if(q.match("/*"))return I.tokenize.push(O),O(q,I)}if(q.match(z))return"builtin";if(q.match(M))return"attribute";if(q.match(g)||q.match(T)||q.match(y)||q.match(c))return"number";if(q.match(k))return"property";if(s.indexOf(Q)>-1)return q.next(),"operator";if(h.indexOf(Q)>-1)return q.next(),q.match(".."),"punctuation";var R;if(R=q.match(/("""|"|')/)){var V=E.bind(null,R[0]);return I.tokenize.push(V),V(q,I)}if(q.match(d)){var x=q.current();return S.hasOwnProperty(x)?"variable-2":b.hasOwnProperty(x)?"atom":v.hasOwnProperty(x)?(C.hasOwnProperty(x)&&(I.prev="define"),"keyword"):D=="define"?"def":"variable"}return q.next(),null}function W(){var q=0;return function(I,D,Q){var R=w(I,D,Q);if(R=="punctuation"){if(I.current()=="(")++q;else if(I.current()==")"){if(q==0)return I.backUp(1),D.tokenize.pop(),D.tokenize[D.tokenize.length-1](I,D);--q}}return R}}function E(q,I,D){for(var Q=q.length==1,R,V=!1;R=I.peek();)if(V){if(I.next(),R=="(")return D.tokenize.push(W()),"string";V=!1}else{if(I.match(q))return D.tokenize.pop(),"string";I.next(),V=R=="\\"}return Q&&D.tokenize.pop(),"string"}function O(q,I){for(var D;D=q.next();)if(D==="/"&&q.eat("*"))I.tokenize.push(O);else if(D==="*"&&q.eat("/")){I.tokenize.pop();break}return"comment"}function G(q,I,D){this.prev=q,this.align=I,this.indented=D}function J(q,I){var D=I.match(/^\s*($|\/[\/\*])/,!1)?null:I.column()+1;q.context=new G(q.context,D,q.indented)}function re(q){q.context&&(q.indented=q.context.indented,q.context=q.context.prev)}o.defineMode("swift",function(q){return{startState:function(){return{prev:null,context:null,indented:0,tokenize:[]}},token:function(I,D){var Q=D.prev;D.prev=null;var R=D.tokenize[D.tokenize.length-1]||w,V=R(I,D,Q);if(!V||V=="comment"?D.prev=Q:D.prev||(D.prev=V),V=="punctuation"){var x=/[\(\[\{]|([\]\)\}])/.exec(I.current());x&&(x[1]?re:J)(D,I)}return V},indent:function(I,D){var Q=I.context;if(!Q)return 0;var R=/^[\]\}\)]/.test(D);return Q.align!=null?Q.align-(R?1:0):Q.indented+(R?0:q.indentUnit)},electricInput:/^\s*[\)\}\]]$/,lineComment:"//",blockCommentStart:"/*",blockCommentEnd:"*/",fold:"brace",closeBrackets:"()[]{}''\"\"``"}}),o.defineMIME("text/x-swift","swift")})});var Vu=Ke((Yu,Qu)=>{(function(o){typeof Yu=="object"&&typeof Qu=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("coffeescript",function(p,v){var C="error";function b(I){return new RegExp("^(("+I.join(")|(")+"))\\b")}var S=/^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/,s=/^(?:[()\[\]{},:`=;]|\.\.?\.?)/,h=/^[_A-Za-z$][_A-Za-z$0-9]*/,g=/^@[_A-Za-z$][_A-Za-z$0-9]*/,T=b(["and","or","not","is","isnt","in","instanceof","typeof"]),y=["for","while","loop","if","unless","else","switch","try","catch","finally","class"],c=["break","by","continue","debugger","delete","do","in","of","new","return","then","this","@","throw","when","until","extends"],d=b(y.concat(c));y=b(y);var k=/^('{3}|\"{3}|['\"])/,z=/^(\/{3}|\/)/,M=["Infinity","NaN","undefined","null","true","false","on","off","yes","no"],w=b(M);function W(I,D){if(I.sol()){D.scope.align===null&&(D.scope.align=!1);var Q=D.scope.offset;if(I.eatSpace()){var R=I.indentation();return R>Q&&D.scope.type=="coffee"?"indent":R0&&J(I,D)}if(I.eatSpace())return null;var V=I.peek();if(I.match("####"))return I.skipToEnd(),"comment";if(I.match("###"))return D.tokenize=O,D.tokenize(I,D);if(V==="#")return I.skipToEnd(),"comment";if(I.match(/^-?[0-9\.]/,!1)){var x=!1;if(I.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)&&(x=!0),I.match(/^-?\d+\.\d*/)&&(x=!0),I.match(/^-?\.\d+/)&&(x=!0),x)return I.peek()=="."&&I.backUp(1),"number";var K=!1;if(I.match(/^-?0x[0-9a-f]+/i)&&(K=!0),I.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)&&(K=!0),I.match(/^-?0(?![\dx])/i)&&(K=!0),K)return"number"}if(I.match(k))return D.tokenize=E(I.current(),!1,"string"),D.tokenize(I,D);if(I.match(z)){if(I.current()!="/"||I.match(/^.*\//,!1))return D.tokenize=E(I.current(),!0,"string-2"),D.tokenize(I,D);I.backUp(1)}return I.match(S)||I.match(T)?"operator":I.match(s)?"punctuation":I.match(w)?"atom":I.match(g)||D.prop&&I.match(h)?"property":I.match(d)?"keyword":I.match(h)?"variable":(I.next(),C)}function E(I,D,Q){return function(R,V){for(;!R.eol();)if(R.eatWhile(/[^'"\/\\]/),R.eat("\\")){if(R.next(),D&&R.eol())return Q}else{if(R.match(I))return V.tokenize=W,Q;R.eat(/['"\/]/)}return D&&(v.singleLineStringErrors?Q=C:V.tokenize=W),Q}}function O(I,D){for(;!I.eol();){if(I.eatWhile(/[^#]/),I.match("###")){D.tokenize=W;break}I.eatWhile("#")}return"comment"}function G(I,D,Q){Q=Q||"coffee";for(var R=0,V=!1,x=null,K=D.scope;K;K=K.prev)if(K.type==="coffee"||K.type=="}"){R=K.offset+p.indentUnit;break}Q!=="coffee"?(V=null,x=I.column()+I.current().length):D.scope.align&&(D.scope.align=!1),D.scope={offset:R,type:Q,prev:D.scope,align:V,alignOffset:x}}function J(I,D){if(D.scope.prev)if(D.scope.type==="coffee"){for(var Q=I.indentation(),R=!1,V=D.scope;V;V=V.prev)if(Q===V.offset){R=!0;break}if(!R)return!0;for(;D.scope.prev&&D.scope.offset!==Q;)D.scope=D.scope.prev;return!1}else return D.scope=D.scope.prev,!1}function re(I,D){var Q=D.tokenize(I,D),R=I.current();R==="return"&&(D.dedent=!0),((R==="->"||R==="=>")&&I.eol()||Q==="indent")&&G(I,D);var V="[({".indexOf(R);if(V!==-1&&G(I,D,"])}".slice(V,V+1)),y.exec(R)&&G(I,D),R=="then"&&J(I,D),Q==="dedent"&&J(I,D))return C;if(V="])}".indexOf(R),V!==-1){for(;D.scope.type=="coffee"&&D.scope.prev;)D.scope=D.scope.prev;D.scope.type==R&&(D.scope=D.scope.prev)}return D.dedent&&I.eol()&&(D.scope.type=="coffee"&&D.scope.prev&&(D.scope=D.scope.prev),D.dedent=!1),Q}var q={startState:function(I){return{tokenize:W,scope:{offset:I||0,type:"coffee",prev:null,align:!1},prop:!1,dedent:0}},token:function(I,D){var Q=D.scope.align===null&&D.scope;Q&&I.sol()&&(Q.align=!1);var R=re(I,D);return R&&R!="comment"&&(Q&&(Q.align=!0),D.prop=R=="punctuation"&&I.current()=="."),R},indent:function(I,D){if(I.tokenize!=W)return 0;var Q=I.scope,R=D&&"])}".indexOf(D.charAt(0))>-1;if(R)for(;Q.type=="coffee"&&Q.prev;)Q=Q.prev;var V=R&&Q.type===D.charAt(0);return Q.align?Q.alignOffset-(V?1:0):(V?Q.prev:Q).offset},lineComment:"#",fold:"indent"};return q}),o.defineMIME("application/vnd.coffeescript","coffeescript"),o.defineMIME("text/x-coffeescript","coffeescript"),o.defineMIME("text/coffeescript","coffeescript")})});var tc=Ke((Ju,ec)=>{(function(o){typeof Ju=="object"&&typeof ec=="object"?o(We(),vn(),gn(),Qn()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../javascript/javascript","../css/css","../htmlmixed/htmlmixed"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("pug",function(p){var v="keyword",C="meta",b="builtin",S="qualifier",s={"{":"}","(":")","[":"]"},h=o.getMode(p,"javascript");function g(){this.javaScriptLine=!1,this.javaScriptLineExcludesColon=!1,this.javaScriptArguments=!1,this.javaScriptArgumentsDepth=0,this.isInterpolating=!1,this.interpolationNesting=0,this.jsState=o.startState(h),this.restOfLine="",this.isIncludeFiltered=!1,this.isEach=!1,this.lastTag="",this.scriptType="",this.isAttrs=!1,this.attrsNest=[],this.inAttributeName=!0,this.attributeIsType=!1,this.attrValue="",this.indentOf=1/0,this.indentToken="",this.innerMode=null,this.innerState=null,this.innerModeForLine=!1}g.prototype.copy=function(){var U=new g;return U.javaScriptLine=this.javaScriptLine,U.javaScriptLineExcludesColon=this.javaScriptLineExcludesColon,U.javaScriptArguments=this.javaScriptArguments,U.javaScriptArgumentsDepth=this.javaScriptArgumentsDepth,U.isInterpolating=this.isInterpolating,U.interpolationNesting=this.interpolationNesting,U.jsState=o.copyState(h,this.jsState),U.innerMode=this.innerMode,this.innerMode&&this.innerState&&(U.innerState=o.copyState(this.innerMode,this.innerState)),U.restOfLine=this.restOfLine,U.isIncludeFiltered=this.isIncludeFiltered,U.isEach=this.isEach,U.lastTag=this.lastTag,U.scriptType=this.scriptType,U.isAttrs=this.isAttrs,U.attrsNest=this.attrsNest.slice(),U.inAttributeName=this.inAttributeName,U.attributeIsType=this.attributeIsType,U.attrValue=this.attrValue,U.indentOf=this.indentOf,U.indentToken=this.indentToken,U.innerModeForLine=this.innerModeForLine,U};function T(U,Z){if(U.sol()&&(Z.javaScriptLine=!1,Z.javaScriptLineExcludesColon=!1),Z.javaScriptLine){if(Z.javaScriptLineExcludesColon&&U.peek()===":"){Z.javaScriptLine=!1,Z.javaScriptLineExcludesColon=!1;return}var ce=h.token(U,Z.jsState);return U.eol()&&(Z.javaScriptLine=!1),ce||!0}}function y(U,Z){if(Z.javaScriptArguments){if(Z.javaScriptArgumentsDepth===0&&U.peek()!=="("){Z.javaScriptArguments=!1;return}if(U.peek()==="("?Z.javaScriptArgumentsDepth++:U.peek()===")"&&Z.javaScriptArgumentsDepth--,Z.javaScriptArgumentsDepth===0){Z.javaScriptArguments=!1;return}var ce=h.token(U,Z.jsState);return ce||!0}}function c(U){if(U.match(/^yield\b/))return"keyword"}function d(U){if(U.match(/^(?:doctype) *([^\n]+)?/))return C}function k(U,Z){if(U.match("#{"))return Z.isInterpolating=!0,Z.interpolationNesting=0,"punctuation"}function z(U,Z){if(Z.isInterpolating){if(U.peek()==="}"){if(Z.interpolationNesting--,Z.interpolationNesting<0)return U.next(),Z.isInterpolating=!1,"punctuation"}else U.peek()==="{"&&Z.interpolationNesting++;return h.token(U,Z.jsState)||!0}}function M(U,Z){if(U.match(/^case\b/))return Z.javaScriptLine=!0,v}function w(U,Z){if(U.match(/^when\b/))return Z.javaScriptLine=!0,Z.javaScriptLineExcludesColon=!0,v}function W(U){if(U.match(/^default\b/))return v}function E(U,Z){if(U.match(/^extends?\b/))return Z.restOfLine="string",v}function O(U,Z){if(U.match(/^append\b/))return Z.restOfLine="variable",v}function G(U,Z){if(U.match(/^prepend\b/))return Z.restOfLine="variable",v}function J(U,Z){if(U.match(/^block\b *(?:(prepend|append)\b)?/))return Z.restOfLine="variable",v}function re(U,Z){if(U.match(/^include\b/))return Z.restOfLine="string",v}function q(U,Z){if(U.match(/^include:([a-zA-Z0-9\-]+)/,!1)&&U.match("include"))return Z.isIncludeFiltered=!0,v}function I(U,Z){if(Z.isIncludeFiltered){var ce=H(U,Z);return Z.isIncludeFiltered=!1,Z.restOfLine="string",ce}}function D(U,Z){if(U.match(/^mixin\b/))return Z.javaScriptLine=!0,v}function Q(U,Z){if(U.match(/^\+([-\w]+)/))return U.match(/^\( *[-\w]+ *=/,!1)||(Z.javaScriptArguments=!0,Z.javaScriptArgumentsDepth=0),"variable";if(U.match("+#{",!1))return U.next(),Z.mixinCallAfter=!0,k(U,Z)}function R(U,Z){if(Z.mixinCallAfter)return Z.mixinCallAfter=!1,U.match(/^\( *[-\w]+ *=/,!1)||(Z.javaScriptArguments=!0,Z.javaScriptArgumentsDepth=0),!0}function V(U,Z){if(U.match(/^(if|unless|else if|else)\b/))return Z.javaScriptLine=!0,v}function x(U,Z){if(U.match(/^(- *)?(each|for)\b/))return Z.isEach=!0,v}function K(U,Z){if(Z.isEach){if(U.match(/^ in\b/))return Z.javaScriptLine=!0,Z.isEach=!1,v;if(U.sol()||U.eol())Z.isEach=!1;else if(U.next()){for(;!U.match(/^ in\b/,!1)&&U.next(););return"variable"}}}function X(U,Z){if(U.match(/^while\b/))return Z.javaScriptLine=!0,v}function N(U,Z){var ce;if(ce=U.match(/^(\w(?:[-:\w]*\w)?)\/?/))return Z.lastTag=ce[1].toLowerCase(),Z.lastTag==="script"&&(Z.scriptType="application/javascript"),"tag"}function H(U,Z){if(U.match(/^:([\w\-]+)/)){var ce;return p&&p.innerModes&&(ce=p.innerModes(U.current().substring(1))),ce||(ce=U.current().substring(1)),typeof ce=="string"&&(ce=o.getMode(p,ce)),je(U,Z,ce),"atom"}}function le(U,Z){if(U.match(/^(!?=|-)/))return Z.javaScriptLine=!0,"punctuation"}function xe(U){if(U.match(/^#([\w-]+)/))return b}function F(U){if(U.match(/^\.([\w-]+)/))return S}function L(U,Z){if(U.peek()=="(")return U.next(),Z.isAttrs=!0,Z.attrsNest=[],Z.inAttributeName=!0,Z.attrValue="",Z.attributeIsType=!1,"punctuation"}function de(U,Z){if(Z.isAttrs){if(s[U.peek()]&&Z.attrsNest.push(s[U.peek()]),Z.attrsNest[Z.attrsNest.length-1]===U.peek())Z.attrsNest.pop();else if(U.eat(")"))return Z.isAttrs=!1,"punctuation";if(Z.inAttributeName&&U.match(/^[^=,\)!]+/))return(U.peek()==="="||U.peek()==="!")&&(Z.inAttributeName=!1,Z.jsState=o.startState(h),Z.lastTag==="script"&&U.current().trim().toLowerCase()==="type"?Z.attributeIsType=!0:Z.attributeIsType=!1),"attribute";var ce=h.token(U,Z.jsState);if(Z.attributeIsType&&ce==="string"&&(Z.scriptType=U.current().toString()),Z.attrsNest.length===0&&(ce==="string"||ce==="variable"||ce==="keyword"))try{return Function("","var x "+Z.attrValue.replace(/,\s*$/,"").replace(/^!/,"")),Z.inAttributeName=!0,Z.attrValue="",U.backUp(U.current().length),de(U,Z)}catch{}return Z.attrValue+=U.current(),ce||!0}}function ze(U,Z){if(U.match(/^&attributes\b/))return Z.javaScriptArguments=!0,Z.javaScriptArgumentsDepth=0,"keyword"}function pe(U){if(U.sol()&&U.eatSpace())return"indent"}function Ee(U,Z){if(U.match(/^ *\/\/(-)?([^\n]*)/))return Z.indentOf=U.indentation(),Z.indentToken="comment","comment"}function ge(U){if(U.match(/^: */))return"colon"}function Oe(U,Z){if(U.match(/^(?:\| ?| )([^\n]+)/))return"string";if(U.match(/^(<[^\n]*)/,!1))return je(U,Z,"htmlmixed"),Z.innerModeForLine=!0,Ze(U,Z,!0)}function qe(U,Z){if(U.eat(".")){var ce=null;return Z.lastTag==="script"&&Z.scriptType.toLowerCase().indexOf("javascript")!=-1?ce=Z.scriptType.toLowerCase().replace(/"|'/g,""):Z.lastTag==="style"&&(ce="css"),je(U,Z,ce),"dot"}}function Se(U){return U.next(),null}function je(U,Z,ce){ce=o.mimeModes[ce]||ce,ce=p.innerModes&&p.innerModes(ce)||ce,ce=o.mimeModes[ce]||ce,ce=o.getMode(p,ce),Z.indentOf=U.indentation(),ce&&ce.name!=="null"?Z.innerMode=ce:Z.indentToken="string"}function Ze(U,Z,ce){if(U.indentation()>Z.indentOf||Z.innerModeForLine&&!U.sol()||ce)return Z.innerMode?(Z.innerState||(Z.innerState=Z.innerMode.startState?o.startState(Z.innerMode,U.indentation()):{}),U.hideFirstChars(Z.indentOf+2,function(){return Z.innerMode.token(U,Z.innerState)||!0})):(U.skipToEnd(),Z.indentToken);U.sol()&&(Z.indentOf=1/0,Z.indentToken=null,Z.innerMode=null,Z.innerState=null)}function ke(U,Z){if(U.sol()&&(Z.restOfLine=""),Z.restOfLine){U.skipToEnd();var ce=Z.restOfLine;return Z.restOfLine="",ce}}function Je(){return new g}function He(U){return U.copy()}function Ge(U,Z){var ce=Ze(U,Z)||ke(U,Z)||z(U,Z)||I(U,Z)||K(U,Z)||de(U,Z)||T(U,Z)||y(U,Z)||R(U,Z)||c(U)||d(U)||k(U,Z)||M(U,Z)||w(U,Z)||W(U)||E(U,Z)||O(U,Z)||G(U,Z)||J(U,Z)||re(U,Z)||q(U,Z)||D(U,Z)||Q(U,Z)||V(U,Z)||x(U,Z)||X(U,Z)||N(U,Z)||H(U,Z)||le(U,Z)||xe(U)||F(U)||L(U,Z)||ze(U,Z)||pe(U)||Oe(U,Z)||Ee(U,Z)||ge(U)||qe(U,Z)||Se(U);return ce===!0?null:ce}return{startState:Je,copyState:He,token:Ge}},"javascript","css","htmlmixed"),o.defineMIME("text/x-pug","pug"),o.defineMIME("text/x-jade","pug")})});var ic=Ke((rc,nc)=>{(function(o){typeof rc=="object"&&typeof nc=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.multiplexingMode=function(p){var v=Array.prototype.slice.call(arguments,1);function C(b,S,s,h){if(typeof S=="string"){var g=b.indexOf(S,s);return h&&g>-1?g+S.length:g}var T=S.exec(s?b.slice(s):b);return T?T.index+s+(h?T[0].length:0):-1}return{startState:function(){return{outer:o.startState(p),innerActive:null,inner:null,startingInner:!1}},copyState:function(b){return{outer:o.copyState(p,b.outer),innerActive:b.innerActive,inner:b.innerActive&&o.copyState(b.innerActive.mode,b.inner),startingInner:b.startingInner}},token:function(b,S){if(S.innerActive){var z=S.innerActive,h=b.string;if(!z.close&&b.sol())return S.innerActive=S.inner=null,this.token(b,S);var y=z.close&&!S.startingInner?C(h,z.close,b.pos,z.parseDelimiters):-1;if(y==b.pos&&!z.parseDelimiters)return b.match(z.close),S.innerActive=S.inner=null,z.delimStyle&&z.delimStyle+" "+z.delimStyle+"-close";y>-1&&(b.string=h.slice(0,y));var M=z.mode.token(b,S.inner);return y>-1?b.string=h:b.pos>b.start&&(S.startingInner=!1),y==b.pos&&z.parseDelimiters&&(S.innerActive=S.inner=null),z.innerStyle&&(M?M=M+" "+z.innerStyle:M=z.innerStyle),M}else{for(var s=1/0,h=b.string,g=0;g{(function(o){typeof oc=="object"&&typeof ac=="object"?o(We(),Di(),ic()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../../addon/mode/simple","../../addon/mode/multiplex"],o):o(CodeMirror)})(function(o){"use strict";o.defineSimpleMode("handlebars-tags",{start:[{regex:/\{\{\{/,push:"handlebars_raw",token:"tag"},{regex:/\{\{!--/,push:"dash_comment",token:"comment"},{regex:/\{\{!/,push:"comment",token:"comment"},{regex:/\{\{/,push:"handlebars",token:"tag"}],handlebars_raw:[{regex:/\}\}\}/,pop:!0,token:"tag"}],handlebars:[{regex:/\}\}/,pop:!0,token:"tag"},{regex:/"(?:[^\\"]|\\.)*"?/,token:"string"},{regex:/'(?:[^\\']|\\.)*'?/,token:"string"},{regex:/>|[#\/]([A-Za-z_]\w*)/,token:"keyword"},{regex:/(?:else|this)\b/,token:"keyword"},{regex:/\d+/i,token:"number"},{regex:/=|~|@|true|false/,token:"atom"},{regex:/(?:\.\.\/)*(?:[A-Za-z_][\w\.]*)+/,token:"variable-2"}],dash_comment:[{regex:/--\}\}/,pop:!0,token:"comment"},{regex:/./,token:"comment"}],comment:[{regex:/\}\}/,pop:!0,token:"comment"},{regex:/./,token:"comment"}],meta:{blockCommentStart:"{{--",blockCommentEnd:"--}}"}}),o.defineMode("handlebars",function(p,v){var C=o.getMode(p,"handlebars-tags");return!v||!v.base?C:o.multiplexingMode(o.getMode(p,v.base),{open:"{{",close:/\}\}\}?/,mode:C,parseDelimiters:!0})}),o.defineMIME("text/x-handlebars-template","handlebars")})});var cc=Ke((sc,uc)=>{(function(o){"use strict";typeof sc=="object"&&typeof uc=="object"?o(We(),Yn(),mn(),vn(),Vu(),gn(),ea(),ta(),tc(),lc()):typeof define=="function"&&define.amd?define(["../../lib/codemirror","../../addon/mode/overlay","../xml/xml","../javascript/javascript","../coffeescript/coffeescript","../css/css","../sass/sass","../stylus/stylus","../pug/pug","../handlebars/handlebars"],o):o(CodeMirror)})(function(o){var p={script:[["lang",/coffee(script)?/,"coffeescript"],["type",/^(?:text|application)\/(?:x-)?coffee(?:script)?$/,"coffeescript"],["lang",/^babel$/,"javascript"],["type",/^text\/babel$/,"javascript"],["type",/^text\/ecmascript-\d+$/,"javascript"]],style:[["lang",/^stylus$/i,"stylus"],["lang",/^sass$/i,"sass"],["lang",/^less$/i,"text/x-less"],["lang",/^scss$/i,"text/x-scss"],["type",/^(text\/)?(x-)?styl(us)?$/i,"stylus"],["type",/^text\/sass/i,"sass"],["type",/^(text\/)?(x-)?scss$/i,"text/x-scss"],["type",/^(text\/)?(x-)?less$/i,"text/x-less"]],template:[["lang",/^vue-template$/i,"vue"],["lang",/^pug$/i,"pug"],["lang",/^handlebars$/i,"handlebars"],["type",/^(text\/)?(x-)?pug$/i,"pug"],["type",/^text\/x-handlebars-template$/i,"handlebars"],[null,null,"vue-template"]]};o.defineMode("vue-template",function(v,C){var b={token:function(S){if(S.match(/^\{\{.*?\}\}/))return"meta mustache";for(;S.next()&&!S.match("{{",!1););return null}};return o.overlayMode(o.getMode(v,C.backdrop||"text/html"),b)}),o.defineMode("vue",function(v){return o.getMode(v,{name:"htmlmixed",tags:p})},"htmlmixed","xml","javascript","coffeescript","css","sass","stylus","pug","handlebars"),o.defineMIME("script/x-vue","vue"),o.defineMIME("text/x-vue","vue")})});var pc=Ke((fc,dc)=>{(function(o){typeof fc=="object"&&typeof dc=="object"?o(We()):typeof define=="function"&&define.amd?define(["../../lib/codemirror"],o):o(CodeMirror)})(function(o){"use strict";o.defineMode("yaml",function(){var p=["true","false","on","off","yes","no"],v=new RegExp("\\b(("+p.join(")|(")+"))$","i");return{token:function(C,b){var S=C.peek(),s=b.escaped;if(b.escaped=!1,S=="#"&&(C.pos==0||/\s/.test(C.string.charAt(C.pos-1))))return C.skipToEnd(),"comment";if(C.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))return"string";if(b.literal&&C.indentation()>b.keyCol)return C.skipToEnd(),"string";if(b.literal&&(b.literal=!1),C.sol()){if(b.keyCol=0,b.pair=!1,b.pairStart=!1,C.match("---")||C.match("..."))return"def";if(C.match(/\s*-\s+/))return"meta"}if(C.match(/^(\{|\}|\[|\])/))return S=="{"?b.inlinePairs++:S=="}"?b.inlinePairs--:S=="["?b.inlineList++:b.inlineList--,"meta";if(b.inlineList>0&&!s&&S==",")return C.next(),"meta";if(b.inlinePairs>0&&!s&&S==",")return b.keyCol=0,b.pair=!1,b.pairStart=!1,C.next(),"meta";if(b.pairStart){if(C.match(/^\s*(\||\>)\s*/))return b.literal=!0,"meta";if(C.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(b.inlinePairs==0&&C.match(/^\s*-?[0-9\.\,]+\s?$/)||b.inlinePairs>0&&C.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(C.match(v))return"keyword"}return!b.pair&&C.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/)?(b.pair=!0,b.keyCol=C.indentation(),"atom"):b.pair&&C.match(/^:\s*/)?(b.pairStart=!0,"meta"):(b.pairStart=!1,b.escaped=S=="\\",C.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),o.defineMIME("text/x-yaml","yaml"),o.defineMIME("text/yaml","yaml")})});var $d={};function qd(o){for(var p;(p=Md.exec(o))!==null;){var v=p[0];if(v.indexOf("target=")===-1){var C=v.replace(/>$/,' target="_blank">');o=o.replace(v,C)}}return o}function Fd(o){for(var p=new DOMParser,v=p.parseFromString(o,"text/html"),C=v.getElementsByTagName("li"),b=0;b0){for(var d=document.createElement("i"),k=0;k=0&&(y=s.getLineHandle(d),!v(y));d--);var W=s.getTokenAt({line:d,ch:1}),E=C(W).fencedChars,O,G,J,re;v(s.getLineHandle(h.line))?(O="",G=h.line):v(s.getLineHandle(h.line-1))?(O="",G=h.line-1):(O=E+` -`,G=h.line),v(s.getLineHandle(g.line))?(J="",re=g.line,g.ch===0&&(re+=1)):g.ch!==0&&v(s.getLineHandle(g.line+1))?(J="",re=g.line+1):(J=E+` -`,re=g.line+1),g.ch===0&&(re-=1),s.operation(function(){s.replaceRange(J,{line:re,ch:0},{line:re+(J?0:1),ch:0}),s.replaceRange(O,{line:G,ch:0},{line:G+(O?0:1),ch:0})}),s.setSelection({line:G+(O?1:0),ch:0},{line:re+(O?1:-1),ch:0}),s.focus()}else{var q=h.line;if(v(s.getLineHandle(h.line))&&(b(s,h.line+1)==="fenced"?(d=h.line,q=h.line+1):(k=h.line,q=h.line-1)),d===void 0)for(d=q;d>=0&&(y=s.getLineHandle(d),!v(y));d--);if(k===void 0)for(z=s.lineCount(),k=q;k=0;d--)if(y=s.getLineHandle(d),!y.text.match(/^\s*$/)&&b(s,d,y)!=="indented"){d+=1;break}for(z=s.lineCount(),k=h.line;k\s+/,"unordered-list":C,"ordered-list":C},T=function(z,M){var w={quote:">","unordered-list":v,"ordered-list":"%%i."};return w[z].replace("%%i",M)},y=function(z,M){var w={quote:">","unordered-list":"\\"+v,"ordered-list":"\\d+."},W=new RegExp(w[z]);return M&&W.test(M)},c=function(z,M,w){var W=C.exec(M),E=T(z,d);return W!==null?(y(z,W[2])&&(E=""),M=W[1]+E+W[3]+M.replace(b,"").replace(g[z],"$1")):w==!1&&(M=E+" "+M),M},d=1,k=s.line;k<=h.line;k++)(function(z){var M=o.getLine(z);S[p]?M=M.replace(g[p],"$1"):(p=="unordered-list"&&(M=c("ordered-list",M,!0)),M=c(p,M,!1),d+=1),o.replaceRange(M,{line:z,ch:0},{line:z,ch:99999999999999})})(k);o.focus()}}function xc(o,p,v,C){if(!(!o.codemirror||o.isPreviewActive())){var b=o.codemirror,S=Tr(b),s=S[p];if(!s){Rr(b,s,v,C);return}var h=b.getCursor("start"),g=b.getCursor("end"),T=b.getLine(h.line),y=T.slice(0,h.ch),c=T.slice(h.ch);p=="link"?y=y.replace(/(.*)[^!]\[/,"$1"):p=="image"&&(y=y.replace(/(.*)!\[$/,"$1")),c=c.replace(/]\(.*?\)/,""),b.replaceRange(y+c,{line:h.line,ch:0},{line:h.line,ch:99999999999999}),h.ch-=v[0].length,h!==g&&(g.ch-=v[0].length),b.setSelection(h,g),b.focus()}}function sa(o,p,v,C){if(!(!o.codemirror||o.isPreviewActive())){C=typeof C>"u"?v:C;var b=o.codemirror,S=Tr(b),s,h=v,g=C,T=b.getCursor("start"),y=b.getCursor("end");S[p]?(s=b.getLine(T.line),h=s.slice(0,T.ch),g=s.slice(T.ch),p=="bold"?(h=h.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/,""),g=g.replace(/(\*\*|__)/,"")):p=="italic"?(h=h.replace(/(\*|_)(?![\s\S]*(\*|_))/,""),g=g.replace(/(\*|_)/,"")):p=="strikethrough"&&(h=h.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/,""),g=g.replace(/(\*\*|~~)/,"")),b.replaceRange(h+g,{line:T.line,ch:0},{line:T.line,ch:99999999999999}),p=="bold"||p=="strikethrough"?(T.ch-=2,T!==y&&(y.ch-=2)):p=="italic"&&(T.ch-=1,T!==y&&(y.ch-=1))):(s=b.getSelection(),p=="bold"?(s=s.split("**").join(""),s=s.split("__").join("")):p=="italic"?(s=s.split("*").join(""),s=s.split("_").join("")):p=="strikethrough"&&(s=s.split("~~").join("")),b.replaceSelection(h+s+g),T.ch+=v.length,y.ch=T.ch+s.length),b.setSelection(T,y),b.focus()}}function Pd(o){if(!o.getWrapperElement().lastChild.classList.contains("editor-preview-active"))for(var p=o.getCursor("start"),v=o.getCursor("end"),C,b=p.line;b<=v.line;b++)C=o.getLine(b),C=C.replace(/^[ ]*([# ]+|\*|-|[> ]+|[0-9]+(.|\)))[ ]*/,""),o.replaceRange(C,{line:b,ch:0},{line:b,ch:99999999999999})}function Fi(o,p){if(Math.abs(o)<1024)return""+o+p[0];var v=0;do o/=1024,++v;while(Math.abs(o)>=1024&&v=19968?C+=v[b].length:C+=1;return C}function Te(o){o=o||{},o.parent=this;var p=!0;if(o.autoDownloadFontAwesome===!1&&(p=!1),o.autoDownloadFontAwesome!==!0)for(var v=document.styleSheets,C=0;C-1&&(p=!1);if(p){var b=document.createElement("link");b.rel="stylesheet",b.href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css",document.getElementsByTagName("head")[0].appendChild(b)}if(o.element)this.element=o.element;else if(o.element===null){console.log("EasyMDE: Error. No element was found.");return}if(o.toolbar===void 0){o.toolbar=[];for(var S in Pr)Object.prototype.hasOwnProperty.call(Pr,S)&&(S.indexOf("separator-")!=-1&&o.toolbar.push("|"),(Pr[S].default===!0||o.showIcons&&o.showIcons.constructor===Array&&o.showIcons.indexOf(S)!=-1)&&o.toolbar.push(S))}if(Object.prototype.hasOwnProperty.call(o,"previewClass")||(o.previewClass="editor-preview"),Object.prototype.hasOwnProperty.call(o,"status")||(o.status=["autosave","lines","words","cursor"],o.uploadImage&&o.status.unshift("upload-image")),o.previewRender||(o.previewRender=function(h){return this.parent.markdown(h)}),o.parsingConfig=fr({highlightFormatting:!0},o.parsingConfig||{}),o.insertTexts=fr({},jd,o.insertTexts||{}),o.promptTexts=fr({},Rd,o.promptTexts||{}),o.blockStyles=fr({},Bd,o.blockStyles||{}),o.autosave!=null&&(o.autosave.timeFormat=fr({},Hd,o.autosave.timeFormat||{})),o.iconClassMap=fr({},et,o.iconClassMap||{}),o.shortcuts=fr({},Ad,o.shortcuts||{}),o.maxHeight=o.maxHeight||void 0,o.direction=o.direction||"ltr",typeof o.maxHeight<"u"?o.minHeight=o.maxHeight:o.minHeight=o.minHeight||"300px",o.errorCallback=o.errorCallback||function(h){alert(h)},o.uploadImage=o.uploadImage||!1,o.imageMaxSize=o.imageMaxSize||2097152,o.imageAccept=o.imageAccept||"image/png, image/jpeg, image/gif, image/avif",o.imageTexts=fr({},Wd,o.imageTexts||{}),o.errorMessages=fr({},Ud,o.errorMessages||{}),o.imagePathAbsolute=o.imagePathAbsolute||!1,o.imageCSRFName=o.imageCSRFName||"csrfmiddlewaretoken",o.imageCSRFHeader=o.imageCSRFHeader||!1,o.autosave!=null&&o.autosave.unique_id!=null&&o.autosave.unique_id!=""&&(o.autosave.uniqueId=o.autosave.unique_id),o.overlayMode&&o.overlayMode.combine===void 0&&(o.overlayMode.combine=!0),this.options=o,this.render(),o.initialValue&&(!this.options.autosave||this.options.autosave.foundSavedValue!==!0)&&this.value(o.initialValue),o.uploadImage){var s=this;this.codemirror.on("dragenter",function(h,g){s.updateStatusBar("upload-image",s.options.imageTexts.sbOnDragEnter),g.stopPropagation(),g.preventDefault()}),this.codemirror.on("dragend",function(h,g){s.updateStatusBar("upload-image",s.options.imageTexts.sbInit),g.stopPropagation(),g.preventDefault()}),this.codemirror.on("dragleave",function(h,g){s.updateStatusBar("upload-image",s.options.imageTexts.sbInit),g.stopPropagation(),g.preventDefault()}),this.codemirror.on("dragover",function(h,g){s.updateStatusBar("upload-image",s.options.imageTexts.sbOnDragEnter),g.stopPropagation(),g.preventDefault()}),this.codemirror.on("drop",function(h,g){g.stopPropagation(),g.preventDefault(),o.imageUploadFunction?s.uploadImagesUsingCustomFunction(o.imageUploadFunction,g.dataTransfer.files):s.uploadImages(g.dataTransfer.files)}),this.codemirror.on("paste",function(h,g){o.imageUploadFunction?s.uploadImagesUsingCustomFunction(o.imageUploadFunction,g.clipboardData.files):s.uploadImages(g.clipboardData.files)})}}function kc(){if(typeof localStorage=="object")try{localStorage.setItem("smde_localStorage",1),localStorage.removeItem("smde_localStorage")}catch{return!1}else return!1;return!0}var mc,Md,Vn,Ad,Dd,ra,hc,et,Pr,jd,Rd,Hd,Bd,Wd,Ud,wc=Cd(()=>{mc=/Mac/.test(navigator.platform),Md=new RegExp(/()+?/g),Vn={toggleBold:Ii,toggleItalic:Ni,drawLink:Gi,toggleHeadingSmaller:Jn,toggleHeadingBigger:Ri,drawImage:Zi,toggleBlockquote:ji,toggleOrderedList:$i,toggleUnorderedList:Ui,toggleCodeBlock:Pi,togglePreview:Ji,toggleStrikethrough:Oi,toggleHeading1:Hi,toggleHeading2:Bi,toggleHeading3:Wi,toggleHeading4:na,toggleHeading5:ia,toggleHeading6:oa,cleanBlock:Ki,drawTable:Xi,drawHorizontalRule:Yi,undo:Qi,redo:Vi,toggleSideBySide:bn,toggleFullScreen:jr},Ad={toggleBold:"Cmd-B",toggleItalic:"Cmd-I",drawLink:"Cmd-K",toggleHeadingSmaller:"Cmd-H",toggleHeadingBigger:"Shift-Cmd-H",toggleHeading1:"Ctrl+Alt+1",toggleHeading2:"Ctrl+Alt+2",toggleHeading3:"Ctrl+Alt+3",toggleHeading4:"Ctrl+Alt+4",toggleHeading5:"Ctrl+Alt+5",toggleHeading6:"Ctrl+Alt+6",cleanBlock:"Cmd-E",drawImage:"Cmd-Alt-I",toggleBlockquote:"Cmd-'",toggleOrderedList:"Cmd-Alt-L",toggleUnorderedList:"Cmd-L",toggleCodeBlock:"Cmd-Alt-C",togglePreview:"Cmd-P",toggleSideBySide:"F9",toggleFullScreen:"F11"},Dd=function(o){for(var p in Vn)if(Vn[p]===o)return p;return null},ra=function(){var o=!1;return function(p){(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(p)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(p.substr(0,4)))&&(o=!0)}(navigator.userAgent||navigator.vendor||window.opera),o};hc="";et={bold:"fa fa-bold",italic:"fa fa-italic",strikethrough:"fa fa-strikethrough",heading:"fa fa-header fa-heading","heading-smaller":"fa fa-header fa-heading header-smaller","heading-bigger":"fa fa-header fa-heading header-bigger","heading-1":"fa fa-header fa-heading header-1","heading-2":"fa fa-header fa-heading header-2","heading-3":"fa fa-header fa-heading header-3",code:"fa fa-code",quote:"fa fa-quote-left","ordered-list":"fa fa-list-ol","unordered-list":"fa fa-list-ul","clean-block":"fa fa-eraser",link:"fa fa-link",image:"fa fa-image","upload-image":"fa fa-image",table:"fa fa-table","horizontal-rule":"fa fa-minus",preview:"fa fa-eye","side-by-side":"fa fa-columns",fullscreen:"fa fa-arrows-alt",guide:"fa fa-question-circle",undo:"fa fa-undo",redo:"fa fa-repeat fa-redo"},Pr={bold:{name:"bold",action:Ii,className:et.bold,title:"Bold",default:!0},italic:{name:"italic",action:Ni,className:et.italic,title:"Italic",default:!0},strikethrough:{name:"strikethrough",action:Oi,className:et.strikethrough,title:"Strikethrough"},heading:{name:"heading",action:Jn,className:et.heading,title:"Heading",default:!0},"heading-smaller":{name:"heading-smaller",action:Jn,className:et["heading-smaller"],title:"Smaller Heading"},"heading-bigger":{name:"heading-bigger",action:Ri,className:et["heading-bigger"],title:"Bigger Heading"},"heading-1":{name:"heading-1",action:Hi,className:et["heading-1"],title:"Big Heading"},"heading-2":{name:"heading-2",action:Bi,className:et["heading-2"],title:"Medium Heading"},"heading-3":{name:"heading-3",action:Wi,className:et["heading-3"],title:"Small Heading"},"separator-1":{name:"separator-1"},code:{name:"code",action:Pi,className:et.code,title:"Code"},quote:{name:"quote",action:ji,className:et.quote,title:"Quote",default:!0},"unordered-list":{name:"unordered-list",action:Ui,className:et["unordered-list"],title:"Generic List",default:!0},"ordered-list":{name:"ordered-list",action:$i,className:et["ordered-list"],title:"Numbered List",default:!0},"clean-block":{name:"clean-block",action:Ki,className:et["clean-block"],title:"Clean block"},"separator-2":{name:"separator-2"},link:{name:"link",action:Gi,className:et.link,title:"Create Link",default:!0},image:{name:"image",action:Zi,className:et.image,title:"Insert Image",default:!0},"upload-image":{name:"upload-image",action:aa,className:et["upload-image"],title:"Import an image"},table:{name:"table",action:Xi,className:et.table,title:"Insert Table"},"horizontal-rule":{name:"horizontal-rule",action:Yi,className:et["horizontal-rule"],title:"Insert Horizontal Line"},"separator-3":{name:"separator-3"},preview:{name:"preview",action:Ji,className:et.preview,noDisable:!0,title:"Toggle Preview",default:!0},"side-by-side":{name:"side-by-side",action:bn,className:et["side-by-side"],noDisable:!0,noMobile:!0,title:"Toggle Side by Side",default:!0},fullscreen:{name:"fullscreen",action:jr,className:et.fullscreen,noDisable:!0,noMobile:!0,title:"Toggle Fullscreen",default:!0},"separator-4":{name:"separator-4"},guide:{name:"guide",action:"https://www.markdownguide.org/basic-syntax/",className:et.guide,noDisable:!0,title:"Markdown Guide",default:!0},"separator-5":{name:"separator-5"},undo:{name:"undo",action:Qi,className:et.undo,noDisable:!0,title:"Undo"},redo:{name:"redo",action:Vi,className:et.redo,noDisable:!0,title:"Redo"}},jd={link:["[","](#url#)"],image:["![","](#url#)"],uploadedImage:["![](#url#)",""],table:["",` - -| Column 1 | Column 2 | Column 3 | -| -------- | -------- | -------- | -| Text | Text | Text | - -`],horizontalRule:["",` - ------ - -`]},Rd={link:"URL for the link:",image:"URL of the image:"},Hd={locale:"en-US",format:{hour:"2-digit",minute:"2-digit"}},Bd={bold:"**",code:"```",italic:"*"},Wd={sbInit:"Attach files by drag and dropping or pasting from clipboard.",sbOnDragEnter:"Drop image to upload it.",sbOnDrop:"Uploading image #images_names#...",sbProgress:"Uploading #file_name#: #progress#%",sbOnUploaded:"Uploaded #image_name#",sizeUnits:" B, KB, MB"},Ud={noFileGiven:"You must select a file.",typeNotAllowed:"This image type is not allowed.",fileTooLarge:`Image #image_name# is too big (#image_size#). -Maximum file size is #image_max_size#.`,importError:"Something went wrong when uploading the image #image_name#."};Te.prototype.uploadImages=function(o,p,v){if(o.length!==0){for(var C=[],b=0;b!(q.closest&&q.closest(".editor-toolbar")||q.offsetParent===null)),re=J.indexOf(O);re!==-1&&re+1!(q.closest&&q.closest(".editor-toolbar")||q.offsetParent===null)),re=J.indexOf(O);if(re!==-1)for(let q=re-1;q>=0;q--){let I=J[q];if(I){I.focus();break}}}}for(var s in p.shortcuts)p.shortcuts[s]!==null&&Vn[s]!==null&&function(E){C[vc(p.shortcuts[E])]=function(){var O=Vn[E];typeof O=="function"?O(v):typeof O=="string"&&window.open(O,"_blank")}}(s);C.Enter="newlineAndIndentContinueMarkdownList",C.Tab=E=>{let O=E.getSelection();O&&O.length>0?E.execCommand("indentMore"):b(E)},C["Shift-Tab"]=E=>{let O=E.getSelection();O&&O.length>0?E.execCommand("indentLess"):S(E)},C.Esc=function(E){E.getOption("fullScreen")&&jr(v)},this.documentOnKeyDown=function(E){E=E||window.event,E.keyCode==27&&v.codemirror.getOption("fullScreen")&&jr(v)},document.addEventListener("keydown",this.documentOnKeyDown,!1);var h,g;p.overlayMode?(CodeMirror.defineMode("overlay-mode",function(E){return CodeMirror.overlayMode(CodeMirror.getMode(E,p.spellChecker!==!1?"spell-checker":"gfm"),p.overlayMode.mode,p.overlayMode.combine)}),h="overlay-mode",g=p.parsingConfig,g.gitHubSpice=!1):(h=p.parsingConfig,h.name="gfm",h.gitHubSpice=!1),p.spellChecker!==!1&&(h="spell-checker",g=p.parsingConfig,g.name="gfm",g.gitHubSpice=!1,typeof p.spellChecker=="function"?p.spellChecker({codeMirrorInstance:CodeMirror}):CodeMirrorSpellChecker({codeMirrorInstance:CodeMirror}));function T(E,O,G){return{addNew:!1}}if(CodeMirror.getMode("php").mime="text/x-php",this.codemirror=CodeMirror.fromTextArea(o,{mode:h,backdrop:g,theme:p.theme!=null?p.theme:"easymde",tabSize:p.tabSize!=null?p.tabSize:2,indentUnit:p.tabSize!=null?p.tabSize:2,indentWithTabs:p.indentWithTabs!==!1,lineNumbers:p.lineNumbers===!0,autofocus:p.autofocus===!0,extraKeys:C,direction:p.direction,lineWrapping:p.lineWrapping!==!1,allowDropFileTypes:["text/plain"],placeholder:p.placeholder||o.getAttribute("placeholder")||"",styleSelectedText:p.styleSelectedText!=null?p.styleSelectedText:!ra(),scrollbarStyle:p.scrollbarStyle!=null?p.scrollbarStyle:"native",configureMouse:T,inputStyle:p.inputStyle!=null?p.inputStyle:ra()?"contenteditable":"textarea",spellcheck:p.nativeSpellcheck!=null?p.nativeSpellcheck:!0,autoRefresh:p.autoRefresh!=null?p.autoRefresh:!1}),this.codemirror.getScrollerElement().style.minHeight=p.minHeight,typeof p.maxHeight<"u"&&(this.codemirror.getScrollerElement().style.height=p.maxHeight),p.forceSync===!0){var y=this.codemirror;y.on("change",function(){y.save()})}this.gui={};var c=document.createElement("div");c.classList.add("EasyMDEContainer"),c.setAttribute("role","application");var d=this.codemirror.getWrapperElement();d.parentNode.insertBefore(c,d),c.appendChild(d),p.toolbar!==!1&&(this.gui.toolbar=this.createToolbar()),p.status!==!1&&(this.gui.statusbar=this.createStatusbar()),p.autosave!=null&&p.autosave.enabled===!0&&(this.autosave(),this.codemirror.on("change",function(){clearTimeout(v._autosave_timeout),v._autosave_timeout=setTimeout(function(){v.autosave()},v.options.autosave.submit_delay||v.options.autosave.delay||1e3)}));function k(E,O){var G,J=window.getComputedStyle(document.querySelector(".CodeMirror-sizer")).width.replace("px","");return E=2){var J=G[1];if(p.imagesPreviewHandler){var re=p.imagesPreviewHandler(G[1]);typeof re=="string"&&(J=re)}if(window.EMDEimagesCache[J])M(O,window.EMDEimagesCache[J]);else{var q=document.createElement("img");q.onload=function(){window.EMDEimagesCache[J]={naturalWidth:q.naturalWidth,naturalHeight:q.naturalHeight,url:J},M(O,window.EMDEimagesCache[J])},q.src=J}}}})}this.codemirror.on("update",function(){w()}),this.gui.sideBySide=this.createSideBySide(),this._rendered=this.element,(p.autofocus===!0||o.autofocus)&&this.codemirror.focus();var W=this.codemirror;setTimeout(function(){W.refresh()}.bind(W),0)};Te.prototype.cleanup=function(){document.removeEventListener("keydown",this.documentOnKeyDown)};Te.prototype.autosave=function(){if(kc()){var o=this;if(this.options.autosave.uniqueId==null||this.options.autosave.uniqueId==""){console.log("EasyMDE: You must set a uniqueId to use the autosave feature");return}this.options.autosave.binded!==!0&&(o.element.form!=null&&o.element.form!=null&&o.element.form.addEventListener("submit",function(){clearTimeout(o.autosaveTimeoutId),o.autosaveTimeoutId=void 0,localStorage.removeItem("smde_"+o.options.autosave.uniqueId)}),this.options.autosave.binded=!0),this.options.autosave.loaded!==!0&&(typeof localStorage.getItem("smde_"+this.options.autosave.uniqueId)=="string"&&localStorage.getItem("smde_"+this.options.autosave.uniqueId)!=""&&(this.codemirror.setValue(localStorage.getItem("smde_"+this.options.autosave.uniqueId)),this.options.autosave.foundSavedValue=!0),this.options.autosave.loaded=!0);var p=o.value();p!==""?localStorage.setItem("smde_"+this.options.autosave.uniqueId,p):localStorage.removeItem("smde_"+this.options.autosave.uniqueId);var v=document.getElementById("autosaved");if(v!=null&&v!=null&&v!=""){var C=new Date,b=new Intl.DateTimeFormat([this.options.autosave.timeFormat.locale,"en-US"],this.options.autosave.timeFormat.format).format(C),S=this.options.autosave.text==null?"Autosaved: ":this.options.autosave.text;v.innerHTML=S+b}}else console.log("EasyMDE: localStorage not available, cannot autosave")};Te.prototype.clearAutosavedValue=function(){if(kc()){if(this.options.autosave==null||this.options.autosave.uniqueId==null||this.options.autosave.uniqueId==""){console.log("EasyMDE: You must set a uniqueId to clear the autosave value");return}localStorage.removeItem("smde_"+this.options.autosave.uniqueId)}else console.log("EasyMDE: localStorage not available, cannot autosave")};Te.prototype.openBrowseFileWindow=function(o,p){var v=this,C=this.gui.toolbar.getElementsByClassName("imageInput")[0];C.click();function b(S){v.options.imageUploadFunction?v.uploadImagesUsingCustomFunction(v.options.imageUploadFunction,S.target.files):v.uploadImages(S.target.files,o,p),C.removeEventListener("change",b)}C.addEventListener("change",b)};Te.prototype.uploadImage=function(o,p,v){var C=this;p=p||function(T){yc(C,T)};function b(g){C.updateStatusBar("upload-image",g),setTimeout(function(){C.updateStatusBar("upload-image",C.options.imageTexts.sbInit)},1e4),v&&typeof v=="function"&&v(g),C.options.errorCallback(g)}function S(g){var T=C.options.imageTexts.sizeUnits.split(",");return g.replace("#image_name#",o.name).replace("#image_size#",Fi(o.size,T)).replace("#image_max_size#",Fi(C.options.imageMaxSize,T))}if(o.size>this.options.imageMaxSize){b(S(this.options.errorMessages.fileTooLarge));return}var s=new FormData;s.append("image",o),C.options.imageCSRFToken&&!C.options.imageCSRFHeader&&s.append(C.options.imageCSRFName,C.options.imageCSRFToken);var h=new XMLHttpRequest;h.upload.onprogress=function(g){if(g.lengthComputable){var T=""+Math.round(g.loaded*100/g.total);C.updateStatusBar("upload-image",C.options.imageTexts.sbProgress.replace("#file_name#",o.name).replace("#progress#",T))}},h.open("POST",this.options.imageUploadEndpoint),C.options.imageCSRFToken&&C.options.imageCSRFHeader&&h.setRequestHeader(C.options.imageCSRFName,C.options.imageCSRFToken),h.onload=function(){try{var g=JSON.parse(this.responseText)}catch{console.error("EasyMDE: The server did not return a valid json."),b(S(C.options.errorMessages.importError));return}this.status===200&&g&&!g.error&&g.data&&g.data.filePath?p((C.options.imagePathAbsolute?"":window.location.origin+"/")+g.data.filePath):g.error&&g.error in C.options.errorMessages?b(S(C.options.errorMessages[g.error])):g.error?b(S(g.error)):(console.error("EasyMDE: Received an unexpected response after uploading the image."+this.status+" ("+this.statusText+")"),b(S(C.options.errorMessages.importError)))},h.onerror=function(g){console.error("EasyMDE: An unexpected error occurred when trying to upload the image."+g.target.status+" ("+g.target.statusText+")"),b(C.options.errorMessages.importError)},h.send(s)};Te.prototype.uploadImageUsingCustomFunction=function(o,p){var v=this;function C(s){yc(v,s)}function b(s){var h=S(s);v.updateStatusBar("upload-image",h),setTimeout(function(){v.updateStatusBar("upload-image",v.options.imageTexts.sbInit)},1e4),v.options.errorCallback(h)}function S(s){var h=v.options.imageTexts.sizeUnits.split(",");return s.replace("#image_name#",p.name).replace("#image_size#",Fi(p.size,h)).replace("#image_max_size#",Fi(v.options.imageMaxSize,h))}o.apply(this,[p,C,b])};Te.prototype.setPreviewMaxHeight=function(){var o=this.codemirror,p=o.getWrapperElement(),v=p.nextSibling,C=parseInt(window.getComputedStyle(p).paddingTop),b=parseInt(window.getComputedStyle(p).borderTopWidth),S=parseInt(this.options.maxHeight),s=S+C*2+b*2,h=s.toString()+"px";v.style.height=h};Te.prototype.createSideBySide=function(){var o=this.codemirror,p=o.getWrapperElement(),v=p.nextSibling;if(!v||!v.classList.contains("editor-preview-side")){if(v=document.createElement("div"),v.className="editor-preview-side",this.options.previewClass)if(Array.isArray(this.options.previewClass))for(var C=0;C{try{let z=k[k.length-1];if(z.origin==="+input"){let M="(https://)",w=z.text[z.text.length-1];if(w.endsWith(M)&&w!=="[]"+M){let W=z.from,E=z.to,G=z.text.length>1?0:W.ch;setTimeout(()=>{d.setSelection({line:E.line,ch:G+w.lastIndexOf("(")+1},{line:E.line,ch:G+w.lastIndexOf(")")})},25)}}}catch{}}),this.editor.codemirror.on("change",Alpine.debounce(()=>{this.editor&&(this.state=this.editor.value(),p&&this.$wire.call("$refresh"))},C??300)),v&&this.editor.codemirror.on("blur",()=>this.$wire.call("$refresh")),this.$watch("state",()=>{this.editor&&(this.editor.codemirror.hasFocus()||Alpine.raw(this.editor).value(this.state??""))}),h&&h(this)},destroy:function(){this.editor.cleanup(),this.editor=null},getToolbar:function(){let d=[];return y.includes("bold")&&d.push({name:"bold",action:EasyMDE.toggleBold,title:T.toolbar_buttons?.bold}),y.includes("italic")&&d.push({name:"italic",action:EasyMDE.toggleItalic,title:T.toolbar_buttons?.italic}),y.includes("strike")&&d.push({name:"strikethrough",action:EasyMDE.toggleStrikethrough,title:T.toolbar_buttons?.strike}),y.includes("link")&&d.push({name:"link",action:EasyMDE.drawLink,title:T.toolbar_buttons?.link}),["bold","italic","strike","link"].some(k=>y.includes(k))&&["heading"].some(k=>y.includes(k))&&d.push("|"),y.includes("heading")&&d.push({name:"heading",action:EasyMDE.toggleHeadingSmaller,title:T.toolbar_buttons?.heading}),["heading"].some(k=>y.includes(k))&&["blockquote","codeBlock","bulletList","orderedList"].some(k=>y.includes(k))&&d.push("|"),y.includes("blockquote")&&d.push({name:"quote",action:EasyMDE.toggleBlockquote,title:T.toolbar_buttons?.blockquote}),y.includes("codeBlock")&&d.push({name:"code",action:EasyMDE.toggleCodeBlock,title:T.toolbar_buttons?.code_block}),y.includes("bulletList")&&d.push({name:"unordered-list",action:EasyMDE.toggleUnorderedList,title:T.toolbar_buttons?.bullet_list}),y.includes("orderedList")&&d.push({name:"ordered-list",action:EasyMDE.toggleOrderedList,title:T.toolbar_buttons?.ordered_list}),["blockquote","codeBlock","bulletList","orderedList"].some(k=>y.includes(k))&&["table","attachFiles"].some(k=>y.includes(k))&&d.push("|"),y.includes("table")&&d.push({name:"table",action:EasyMDE.drawTable,title:T.toolbar_buttons?.table}),y.includes("attachFiles")&&d.push({name:"upload-image",action:EasyMDE.drawUploadedImage,title:T.toolbar_buttons?.attach_files}),["table","attachFiles"].some(k=>y.includes(k))&&["undo","redo"].some(k=>y.includes(k))&&d.push("|"),y.includes("undo")&&d.push({name:"undo",action:EasyMDE.undo,title:T.toolbar_buttons?.undo}),y.includes("redo")&&d.push({name:"redo",action:EasyMDE.redo,title:T.toolbar_buttons?.redo}),d}}}export{Kd as default}; diff --git a/public/js/filament/forms/components/rich-editor.js b/public/js/filament/forms/components/rich-editor.js deleted file mode 100644 index a07fd6c..0000000 --- a/public/js/filament/forms/components/rich-editor.js +++ /dev/null @@ -1,150 +0,0 @@ -var po="2.1.15",Rt="[data-trix-attachment]",mi={preview:{presentation:"gallery",caption:{name:!0,size:!0}},file:{caption:{size:!0}}},U={default:{tagName:"div",parse:!1},quote:{tagName:"blockquote",nestable:!0},heading1:{tagName:"h1",terminal:!0,breakOnReturn:!0,group:!1},code:{tagName:"pre",terminal:!0,htmlAttributes:["language"],text:{plaintext:!0}},bulletList:{tagName:"ul",parse:!1},bullet:{tagName:"li",listAttribute:"bulletList",group:!1,nestable:!0,test(i){return Gi(i.parentNode)===U[this.listAttribute].tagName}},numberList:{tagName:"ol",parse:!1},number:{tagName:"li",listAttribute:"numberList",group:!1,nestable:!0,test(i){return Gi(i.parentNode)===U[this.listAttribute].tagName}},attachmentGallery:{tagName:"div",exclusive:!0,terminal:!0,parse:!1,group:!1}},Gi=i=>{var t;return i==null||(t=i.tagName)===null||t===void 0?void 0:t.toLowerCase()},Yi=navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i),Sn=Yi&&parseInt(Yi[1]),xe={composesExistingText:/Android.*Chrome/.test(navigator.userAgent),recentAndroid:Sn&&Sn>12,samsungAndroid:Sn&&navigator.userAgent.match(/Android.*SM-/),forcesObjectResizing:/Trident.*rv:11/.test(navigator.userAgent),supportsInputEvents:typeof InputEvent<"u"&&["data","getTargetRanges","inputType"].every(i=>i in InputEvent.prototype)},Lr={ADD_ATTR:["language"],SAFE_FOR_XML:!1,RETURN_DOM:!0},m={attachFiles:"Attach Files",bold:"Bold",bullets:"Bullets",byte:"Byte",bytes:"Bytes",captionPlaceholder:"Add a caption\u2026",code:"Code",heading1:"Heading",indent:"Increase Level",italic:"Italic",link:"Link",numbers:"Numbers",outdent:"Decrease Level",quote:"Quote",redo:"Redo",remove:"Remove",strike:"Strikethrough",undo:"Undo",unlink:"Unlink",url:"URL",urlPlaceholder:"Enter a URL\u2026",GB:"GB",KB:"KB",MB:"MB",PB:"PB",TB:"TB"},fo=[m.bytes,m.KB,m.MB,m.GB,m.TB,m.PB],Dr={prefix:"IEC",precision:2,formatter(i){switch(i){case 0:return"0 ".concat(m.bytes);case 1:return"1 ".concat(m.byte);default:let t;this.prefix==="SI"?t=1e3:this.prefix==="IEC"&&(t=1024);let e=Math.floor(Math.log(i)/Math.log(t)),n=(i/Math.pow(t,e)).toFixed(this.precision).replace(/0*$/,"").replace(/\.$/,"");return"".concat(n," ").concat(fo[e])}}},ln="\uFEFF",ft="\xA0",Nr=function(i){for(let t in i){let e=i[t];this[t]=e}return this},pi=document.documentElement,bo=pi.matches,S=function(i){let{onElement:t,matchingSelector:e,withCallback:n,inPhase:r,preventDefault:o,times:s}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},l=t||pi,c=e,u=r==="capturing",d=function(C){s!=null&&--s==0&&d.destroy();let T=vt(C.target,{matchingSelector:c});T!=null&&(n?.call(T,C,T),o&&C.preventDefault())};return d.destroy=()=>l.removeEventListener(i,d,u),l.addEventListener(i,d,u),d},de=function(i){let{onElement:t,bubbles:e,cancelable:n,attributes:r}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},o=t??pi;e=e!==!1,n=n!==!1;let s=document.createEvent("Events");return s.initEvent(i,e,n),r!=null&&Nr.call(s,r),o.dispatchEvent(s)},Ir=function(i,t){if(i?.nodeType===1)return bo.call(i,t)},vt=function(i){let{matchingSelector:t,untilNode:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};for(;i&&i.nodeType!==Node.ELEMENT_NODE;)i=i.parentNode;if(i!=null){if(t==null)return i;if(i.closest&&e==null)return i.closest(t);for(;i&&i!==e;){if(Ir(i,t))return i;i=i.parentNode}}},fi=i=>document.activeElement!==i&&kt(i,document.activeElement),kt=function(i,t){if(i&&t)for(;t;){if(t===i)return!0;t=t.parentNode}},kn=function(i){var t;if((t=i)===null||t===void 0||!t.parentNode)return;let e=0;for(i=i.previousSibling;i;)e++,i=i.previousSibling;return e},At=i=>{var t;return i==null||(t=i.parentNode)===null||t===void 0?void 0:t.removeChild(i)},je=function(i){let{onlyNodesOfType:t,usingFilter:e,expandEntityReferences:n}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=(()=>{switch(t){case"element":return NodeFilter.SHOW_ELEMENT;case"text":return NodeFilter.SHOW_TEXT;case"comment":return NodeFilter.SHOW_COMMENT;default:return NodeFilter.SHOW_ALL}})();return document.createTreeWalker(i,r,e??null,n===!0)},W=i=>{var t;return i==null||(t=i.tagName)===null||t===void 0?void 0:t.toLowerCase()},p=function(i){let t,e,n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};typeof i=="object"?(n=i,i=n.tagName):n={attributes:n};let r=document.createElement(i);if(n.editable!=null&&(n.attributes==null&&(n.attributes={}),n.attributes.contenteditable=n.editable),n.attributes)for(t in n.attributes)e=n.attributes[t],r.setAttribute(t,e);if(n.style)for(t in n.style)e=n.style[t],r.style[t]=e;if(n.data)for(t in n.data)e=n.data[t],r.dataset[t]=e;return n.className&&n.className.split(" ").forEach(o=>{r.classList.add(o)}),n.textContent&&(r.textContent=n.textContent),n.childNodes&&[].concat(n.childNodes).forEach(o=>{r.appendChild(o)}),r},re,ge=function(){if(re!=null)return re;re=[];for(let i in U){let t=U[i];t.tagName&&re.push(t.tagName)}return re},Rn=i=>Vt(i?.firstChild),$i=function(i){let{strict:t}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{strict:!0};return t?Vt(i):Vt(i)||!Vt(i.firstChild)&&function(e){return ge().includes(W(e))&&!ge().includes(W(e.firstChild))}(i)},Vt=i=>vo(i)&&i?.data==="block",vo=i=>i?.nodeType===Node.COMMENT_NODE,zt=function(i){let{name:t}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(i)return me(i)?i.data===ln?!t||i.parentNode.dataset.trixCursorTarget===t:void 0:zt(i.firstChild)},Tt=i=>Ir(i,Rt),Or=i=>me(i)&&i?.data==="",me=i=>i?.nodeType===Node.TEXT_NODE,bi={level2Enabled:!0,getLevel(){return this.level2Enabled&&xe.supportsInputEvents?2:0},pickFiles(i){let t=p("input",{type:"file",multiple:!0,hidden:!0,id:this.fileInputId});t.addEventListener("change",()=>{i(t.files),At(t)}),At(document.getElementById(this.fileInputId)),document.body.appendChild(t),t.click()}},Me={removeBlankTableCells:!1,tableCellSeparator:" | ",tableRowSeparator:` -`},Dt={bold:{tagName:"strong",inheritable:!0,parser(i){let t=window.getComputedStyle(i);return t.fontWeight==="bold"||t.fontWeight>=600}},italic:{tagName:"em",inheritable:!0,parser:i=>window.getComputedStyle(i).fontStyle==="italic"},href:{groupTagName:"a",parser(i){let t="a:not(".concat(Rt,")"),e=i.closest(t);if(e)return e.getAttribute("href")}},strike:{tagName:"del",inheritable:!0},frozen:{style:{backgroundColor:"highlight"}}},Fr={getDefaultHTML:()=>`
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
`)},$n={interval:5e3},Ce=Object.freeze({__proto__:null,attachments:mi,blockAttributes:U,browser:xe,css:{attachment:"attachment",attachmentCaption:"attachment__caption",attachmentCaptionEditor:"attachment__caption-editor",attachmentMetadata:"attachment__metadata",attachmentMetadataContainer:"attachment__metadata-container",attachmentName:"attachment__name",attachmentProgress:"attachment__progress",attachmentSize:"attachment__size",attachmentToolbar:"attachment__toolbar",attachmentGallery:"attachment-gallery"},dompurify:Lr,fileSize:Dr,input:bi,keyNames:{8:"backspace",9:"tab",13:"return",27:"escape",37:"left",39:"right",46:"delete",68:"d",72:"h",79:"o"},lang:m,parser:Me,textAttributes:Dt,toolbar:Fr,undo:$n}),R=class{static proxyMethod(t){let{name:e,toMethod:n,toProperty:r,optional:o}=Ao(t);this.prototype[e]=function(){let s,l;var c,u;return n?l=o?(c=this[n])===null||c===void 0?void 0:c.call(this):this[n]():r&&(l=this[r]),o?(s=(u=l)===null||u===void 0?void 0:u[e],s?Xi.call(s,l,arguments):void 0):(s=l[e],Xi.call(s,l,arguments))}}},Ao=function(i){let t=i.match(yo);if(!t)throw new Error("can't parse @proxyMethod expression: ".concat(i));let e={name:t[4]};return t[2]!=null?e.toMethod=t[1]:e.toProperty=t[1],t[3]!=null&&(e.optional=!0),e},{apply:Xi}=Function.prototype,yo=new RegExp("^(.+?)(\\(\\))?(\\?)?\\.(.+?)$"),Tn,wn,Ln,Nt=class extends R{static box(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"";return t instanceof this?t:this.fromUCS2String(t?.toString())}static fromUCS2String(t){return new this(t,Xn(t))}static fromCodepoints(t){return new this(Zn(t),t)}constructor(t,e){super(...arguments),this.ucs2String=t,this.codepoints=e,this.length=this.codepoints.length,this.ucs2Length=this.ucs2String.length}offsetToUCS2Offset(t){return Zn(this.codepoints.slice(0,Math.max(0,t))).length}offsetFromUCS2Offset(t){return Xn(this.ucs2String.slice(0,Math.max(0,t))).length}slice(){return this.constructor.fromCodepoints(this.codepoints.slice(...arguments))}charAt(t){return this.slice(t,t+1)}isEqualTo(t){return this.constructor.box(t).ucs2String===this.ucs2String}toJSON(){return this.ucs2String}getCacheKey(){return this.ucs2String}toString(){return this.ucs2String}},xo=((Tn=Array.from)===null||Tn===void 0?void 0:Tn.call(Array,"\u{1F47C}").length)===1,Co=((wn=" ".codePointAt)===null||wn===void 0?void 0:wn.call(" ",0))!=null,Eo=((Ln=String.fromCodePoint)===null||Ln===void 0?void 0:Ln.call(String,32,128124))===" \u{1F47C}",Xn,Zn;Xn=xo&&Co?i=>Array.from(i).map(t=>t.codePointAt(0)):function(i){let t=[],e=0,{length:n}=i;for(;eString.fromCodePoint(...Array.from(i||[])):function(i){return(()=>{let t=[];return Array.from(i).forEach(e=>{let n="";e>65535&&(e-=65536,n+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t.push(n+String.fromCharCode(e))}),t})().join("")};var So=0,ht=class extends R{static fromJSONString(t){return this.fromJSON(JSON.parse(t))}constructor(){super(...arguments),this.id=++So}hasSameConstructorAs(t){return this.constructor===t?.constructor}isEqualTo(t){return this===t}inspect(){let t=[],e=this.contentsForInspection()||{};for(let n in e){let r=e[n];t.push("".concat(n,"=").concat(r))}return"#<".concat(this.constructor.name,":").concat(this.id).concat(t.length?" ".concat(t.join(", ")):"",">")}contentsForInspection(){}toJSONString(){return JSON.stringify(this)}toUTF16String(){return Nt.box(this)}getCacheKey(){return this.id.toString()}},It=function(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];if(i.length!==t.length)return!1;for(let e=0;e1?e-1:0),r=1;r(Dn||(Dn=wo().concat(To())),Dn),L=i=>U[i],To=()=>(Nn||(Nn=Object.keys(U)),Nn),ti=i=>Dt[i],wo=()=>(In||(In=Object.keys(Dt)),In),Pr=function(i,t){Lo(i).textContent=t.replace(/%t/g,i)},Lo=function(i){let t=document.createElement("style");t.setAttribute("type","text/css"),t.setAttribute("data-tag-name",i.toLowerCase());let e=Do();return e&&t.setAttribute("nonce",e),document.head.insertBefore(t,document.head.firstChild),t},Do=function(){let i=Zi("trix-csp-nonce")||Zi("csp-nonce");if(i){let{nonce:t,content:e}=i;return t==""?e:t}},Zi=i=>document.head.querySelector("meta[name=".concat(i,"]")),Qi={"application/x-trix-feature-detection":"test"},Mr=function(i){let t=i.getData("text/plain"),e=i.getData("text/html");if(!t||!e)return t?.length;{let{body:n}=new DOMParser().parseFromString(e,"text/html");if(n.textContent===t)return!n.querySelector("*")}},Br=/Mac|^iP/.test(navigator.platform)?i=>i.metaKey:i=>i.ctrlKey,Ai=i=>setTimeout(i,1),_r=function(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t={};for(let e in i){let n=i[e];t[e]=n}return t},Zt=function(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(Object.keys(i).length!==Object.keys(t).length)return!1;for(let e in i)if(i[e]!==t[e])return!1;return!0},y=function(i){if(i!=null)return Array.isArray(i)||(i=[i,i]),[tr(i[0]),tr(i[1]!=null?i[1]:i[0])]},ut=function(i){if(i==null)return;let[t,e]=y(i);return ei(t,e)},We=function(i,t){if(i==null||t==null)return;let[e,n]=y(i),[r,o]=y(t);return ei(e,r)&&ei(n,o)},tr=function(i){return typeof i=="number"?i:_r(i)},ei=function(i,t){return typeof i=="number"?i===t:Zt(i,t)},Ue=class extends R{constructor(){super(...arguments),this.update=this.update.bind(this),this.selectionManagers=[]}start(){this.started||(this.started=!0,document.addEventListener("selectionchange",this.update,!0))}stop(){if(this.started)return this.started=!1,document.removeEventListener("selectionchange",this.update,!0)}registerSelectionManager(t){if(!this.selectionManagers.includes(t))return this.selectionManagers.push(t),this.start()}unregisterSelectionManager(t){if(this.selectionManagers=this.selectionManagers.filter(e=>e!==t),this.selectionManagers.length===0)return this.stop()}notifySelectionManagersOfSelectionChange(){return this.selectionManagers.map(t=>t.selectionDidChange())}update(){this.notifySelectionManagersOfSelectionChange()}reset(){this.update()}},Ot=new Ue,jr=function(){let i=window.getSelection();if(i.rangeCount>0)return i},pe=function(){var i;let t=(i=jr())===null||i===void 0?void 0:i.getRangeAt(0);if(t&&!No(t))return t},Wr=function(i){let t=window.getSelection();return t.removeAllRanges(),t.addRange(i),Ot.update()},No=i=>er(i.startContainer)||er(i.endContainer),er=i=>!Object.getPrototypeOf(i),he=i=>i.replace(new RegExp("".concat(ln),"g"),"").replace(new RegExp("".concat(ft),"g")," "),yi=new RegExp("[^\\S".concat(ft,"]")),xi=i=>i.replace(new RegExp("".concat(yi.source),"g")," ").replace(/\ {2,}/g," "),nr=function(i,t){if(i.isEqualTo(t))return["",""];let e=On(i,t),{length:n}=e.utf16String,r;if(n){let{offset:o}=e,s=i.codepoints.slice(0,o).concat(i.codepoints.slice(o+n));r=On(t,Nt.fromCodepoints(s))}else r=On(t,i);return[e.utf16String.toString(),r.utf16String.toString()]},On=function(i,t){let e=0,n=i.length,r=t.length;for(;ee+1&&i.charAt(n-1).isEqualTo(t.charAt(r-1));)n--,r--;return{utf16String:i.slice(e,n),offset:e}},X=class i extends ht{static fromCommonAttributesOfObjects(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];if(!t.length)return new this;let e=oe(t[0]),n=e.getKeys();return t.slice(1).forEach(r=>{n=e.getKeysCommonToHash(oe(r)),e=e.slice(n)}),e}static box(t){return oe(t)}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};super(...arguments),this.values=Be(t)}add(t,e){return this.merge(Io(t,e))}remove(t){return new i(Be(this.values,t))}get(t){return this.values[t]}has(t){return t in this.values}merge(t){return new i(Oo(this.values,Fo(t)))}slice(t){let e={};return Array.from(t).forEach(n=>{this.has(n)&&(e[n]=this.values[n])}),new i(e)}getKeys(){return Object.keys(this.values)}getKeysCommonToHash(t){return t=oe(t),this.getKeys().filter(e=>this.values[e]===t.values[e])}isEqualTo(t){return It(this.toArray(),oe(t).toArray())}isEmpty(){return this.getKeys().length===0}toArray(){if(!this.array){let t=[];for(let e in this.values){let n=this.values[e];t.push(t.push(e,n))}this.array=t.slice(0)}return this.array}toObject(){return Be(this.values)}toJSON(){return this.toObject()}contentsForInspection(){return{values:JSON.stringify(this.values)}}},Io=function(i,t){let e={};return e[i]=t,e},Oo=function(i,t){let e=Be(i);for(let n in t){let r=t[n];e[n]=r}return e},Be=function(i,t){let e={};return Object.keys(i).sort().forEach(n=>{n!==t&&(e[n]=i[n])}),e},oe=function(i){return i instanceof X?i:new X(i)},Fo=function(i){return i instanceof X?i.values:i},be=class{static groupObjects(){let t,e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],{depth:n,asTree:r}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};r&&n==null&&(n=0);let o=[];return Array.from(e).forEach(s=>{var l;if(t){var c,u,d;if((c=s.canBeGrouped)!==null&&c!==void 0&&c.call(s,n)&&(u=(d=t[t.length-1]).canBeGroupedWith)!==null&&u!==void 0&&u.call(d,s,n))return void t.push(s);o.push(new this(t,{depth:n,asTree:r})),t=null}(l=s.canBeGrouped)!==null&&l!==void 0&&l.call(s,n)?t=[s]:o.push(s)}),t&&o.push(new this(t,{depth:n,asTree:r})),o}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],{depth:e,asTree:n}=arguments.length>1?arguments[1]:void 0;this.objects=t,n&&(this.depth=e,this.objects=this.constructor.groupObjects(this.objects,{asTree:n,depth:this.depth+1}))}getObjects(){return this.objects}getDepth(){return this.depth}getCacheKey(){let t=["objectGroup"];return Array.from(this.getObjects()).forEach(e=>{t.push(e.getCacheKey())}),t.join("/")}},ni=class extends R{constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];super(...arguments),this.objects={},Array.from(t).forEach(e=>{let n=JSON.stringify(e);this.objects[n]==null&&(this.objects[n]=e)})}find(t){let e=JSON.stringify(t);return this.objects[e]}},ii=class{constructor(t){this.reset(t)}add(t){let e=ir(t);this.elements[e]=t}remove(t){let e=ir(t),n=this.elements[e];if(n)return delete this.elements[e],n}reset(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];return this.elements={},Array.from(t).forEach(e=>{this.add(e)}),t}},ir=i=>i.dataset.trixStoreKey,Ht=class extends R{isPerforming(){return this.performing===!0}hasPerformed(){return this.performed===!0}hasSucceeded(){return this.performed&&this.succeeded}hasFailed(){return this.performed&&!this.succeeded}getPromise(){return this.promise||(this.promise=new Promise((t,e)=>(this.performing=!0,this.perform((n,r)=>{this.succeeded=n,this.performing=!1,this.performed=!0,this.succeeded?t(r):e(r)})))),this.promise}perform(t){return t(!1)}release(){var t,e;(t=this.promise)===null||t===void 0||(e=t.cancel)===null||e===void 0||e.call(t),this.promise=null,this.performing=null,this.performed=null,this.succeeded=null}};Ht.proxyMethod("getPromise().then"),Ht.proxyMethod("getPromise().catch");var dt=class extends R{constructor(t){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};super(...arguments),this.object=t,this.options=e,this.childViews=[],this.rootView=this}getNodes(){return this.nodes||(this.nodes=this.createNodes()),this.nodes.map(t=>t.cloneNode(!0))}invalidate(){var t;return this.nodes=null,this.childViews=[],(t=this.parentView)===null||t===void 0?void 0:t.invalidate()}invalidateViewForObject(t){var e;return(e=this.findViewForObject(t))===null||e===void 0?void 0:e.invalidate()}findOrCreateCachedChildView(t,e,n){let r=this.getCachedViewForObject(e);return r?this.recordChildView(r):(r=this.createChildView(...arguments),this.cacheViewForObject(r,e)),r}createChildView(t,e){let n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};e instanceof be&&(n.viewClass=t,t=ri);let r=new t(e,n);return this.recordChildView(r)}recordChildView(t){return t.parentView=this,t.rootView=this.rootView,this.childViews.push(t),t}getAllChildViews(){let t=[];return this.childViews.forEach(e=>{t.push(e),t=t.concat(e.getAllChildViews())}),t}findElement(){return this.findElementForObject(this.object)}findElementForObject(t){let e=t?.id;if(e)return this.rootView.element.querySelector("[data-trix-id='".concat(e,"']"))}findViewForObject(t){for(let e of this.getAllChildViews())if(e.object===t)return e}getViewCache(){return this.rootView!==this?this.rootView.getViewCache():this.isViewCachingEnabled()?(this.viewCache||(this.viewCache={}),this.viewCache):void 0}isViewCachingEnabled(){return this.shouldCacheViews!==!1}enableViewCaching(){this.shouldCacheViews=!0}disableViewCaching(){this.shouldCacheViews=!1}getCachedViewForObject(t){var e;return(e=this.getViewCache())===null||e===void 0?void 0:e[t.getCacheKey()]}cacheViewForObject(t,e){let n=this.getViewCache();n&&(n[e.getCacheKey()]=t)}garbageCollectCachedViews(){let t=this.getViewCache();if(t){let e=this.getAllChildViews().concat(this).map(n=>n.object.getCacheKey());for(let n in t)e.includes(n)||delete t[n]}}},ri=class extends dt{constructor(){super(...arguments),this.objectGroup=this.object,this.viewClass=this.options.viewClass,delete this.options.viewClass}getChildViews(){return this.childViews.length||Array.from(this.objectGroup.getObjects()).forEach(t=>{this.findOrCreateCachedChildView(this.viewClass,t,this.options)}),this.childViews}createNodes(){let t=this.createContainerElement();return this.getChildViews().forEach(e=>{Array.from(e.getNodes()).forEach(n=>{t.appendChild(n)})}),[t]}createContainerElement(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.objectGroup.getDepth();return this.getChildViews()[0].createContainerElement(t)}};var{entries:Ur,setPrototypeOf:rr,isFrozen:Po,getPrototypeOf:Mo,getOwnPropertyDescriptor:Bo}=Object,{freeze:z,seal:G,create:Vr}=Object,{apply:oi,construct:si}=typeof Reflect<"u"&&Reflect;z||(z=function(i){return i}),G||(G=function(i){return i}),oi||(oi=function(i,t,e){return i.apply(t,e)}),si||(si=function(i,t){return new i(...t)});var Ne=H(Array.prototype.forEach),_o=H(Array.prototype.lastIndexOf),or=H(Array.prototype.pop),se=H(Array.prototype.push),jo=H(Array.prototype.splice),_e=H(String.prototype.toLowerCase),Fn=H(String.prototype.toString),sr=H(String.prototype.match),ae=H(String.prototype.replace),Wo=H(String.prototype.indexOf),Uo=H(String.prototype.trim),Y=H(Object.prototype.hasOwnProperty),j=H(RegExp.prototype.test),le=(ar=TypeError,function(){for(var i=arguments.length,t=new Array(i),e=0;e1?e-1:0),r=1;r2&&arguments[2]!==void 0?arguments[2]:_e;rr&&rr(i,null);let n=t.length;for(;n--;){let r=t[n];if(typeof r=="string"){let o=e(r);o!==r&&(Po(t)||(t[n]=o),r=o)}i[r]=!0}return i}function Vo(i){for(let t=0;t/gm),Ko=G(/\$\{[\w\W]*/gm),Go=G(/^data-[\-\w.\u00B7-\uFFFF]+$/),Yo=G(/^aria-[\-\w]+$/),zr=G(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),$o=G(/^(?:\w+script|data):/i),Xo=G(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Hr=G(/^html$/i),Zo=G(/^[a-z][.\w]*(-[.\w]+)+$/i),dr=Object.freeze({__proto__:null,ARIA_ATTR:Yo,ATTR_WHITESPACE:Xo,CUSTOM_ELEMENT:Zo,DATA_ATTR:Go,DOCTYPE_NAME:Hr,ERB_EXPR:Jo,IS_ALLOWED_URI:zr,IS_SCRIPT_OR_DATA:$o,MUSTACHE_EXPR:qo,TMPLIT_EXPR:Ko}),Qo=1,ts=3,es=7,ns=8,is=9,rs=function(){return typeof window>"u"?null:window},Ve=function i(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:rs(),e=a=>i(a);if(e.version="3.2.5",e.removed=[],!t||!t.document||t.document.nodeType!==is||!t.Element)return e.isSupported=!1,e;let{document:n}=t,r=n,o=r.currentScript,{DocumentFragment:s,HTMLTemplateElement:l,Node:c,Element:u,NodeFilter:d,NamedNodeMap:C=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:T,DOMParser:J,trustedTypes:Q}=t,M=u.prototype,mt=ce(M,"cloneNode"),yt=ce(M,"remove"),Qt=ce(M,"nextSibling"),te=ce(M,"childNodes"),F=ce(M,"parentNode");if(typeof l=="function"){let a=n.createElement("template");a.content&&a.content.ownerDocument&&(n=a.content.ownerDocument)}let k,rt="",{implementation:xt,createNodeIterator:eo,createDocumentFragment:no,getElementsByTagName:io}=n,{importNode:ro}=r,B={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};e.isSupported=typeof Ur=="function"&&typeof F=="function"&&xt&&xt.createHTMLDocument!==void 0;let{MUSTACHE_EXPR:un,ERB_EXPR:hn,TMPLIT_EXPR:dn,DATA_ATTR:oo,ARIA_ATTR:so,IS_SCRIPT_OR_DATA:ao,ATTR_WHITESPACE:Ei,CUSTOM_ELEMENT:lo}=dr,{IS_ALLOWED_URI:Si}=dr,N=null,ki=b({},[...lr,...Pn,...Mn,...Bn,...cr]),O=null,Ri=b({},[...ur,..._n,...hr,...Ie]),w=Object.seal(Vr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ee=null,gn=null,Ti=!0,mn=!0,wi=!1,Li=!0,Pt=!1,pn=!0,Ct=!1,fn=!1,bn=!1,Mt=!1,Ee=!1,Se=!1,Di=!0,Ni=!1,vn=!0,ne=!1,Bt={},_t=null,Ii=b({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Oi=null,Fi=b({},["audio","video","img","source","image","track"]),An=null,Pi=b({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ke="http://www.w3.org/1998/Math/MathML",Re="http://www.w3.org/2000/svg",ot="http://www.w3.org/1999/xhtml",jt=ot,yn=!1,xn=null,co=b({},[ke,Re,ot],Fn),Te=b({},["mi","mo","mn","ms","mtext"]),we=b({},["annotation-xml"]),uo=b({},["title","style","font","a","script"]),ie=null,ho=["application/xhtml+xml","text/html"],I=null,Wt=null,go=n.createElement("form"),Mi=function(a){return a instanceof RegExp||a instanceof Function},Cn=function(){let a=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!Wt||Wt!==a){if(a&&typeof a=="object"||(a={}),a=St(a),ie=ho.indexOf(a.PARSER_MEDIA_TYPE)===-1?"text/html":a.PARSER_MEDIA_TYPE,I=ie==="application/xhtml+xml"?Fn:_e,N=Y(a,"ALLOWED_TAGS")?b({},a.ALLOWED_TAGS,I):ki,O=Y(a,"ALLOWED_ATTR")?b({},a.ALLOWED_ATTR,I):Ri,xn=Y(a,"ALLOWED_NAMESPACES")?b({},a.ALLOWED_NAMESPACES,Fn):co,An=Y(a,"ADD_URI_SAFE_ATTR")?b(St(Pi),a.ADD_URI_SAFE_ATTR,I):Pi,Oi=Y(a,"ADD_DATA_URI_TAGS")?b(St(Fi),a.ADD_DATA_URI_TAGS,I):Fi,_t=Y(a,"FORBID_CONTENTS")?b({},a.FORBID_CONTENTS,I):Ii,ee=Y(a,"FORBID_TAGS")?b({},a.FORBID_TAGS,I):{},gn=Y(a,"FORBID_ATTR")?b({},a.FORBID_ATTR,I):{},Bt=!!Y(a,"USE_PROFILES")&&a.USE_PROFILES,Ti=a.ALLOW_ARIA_ATTR!==!1,mn=a.ALLOW_DATA_ATTR!==!1,wi=a.ALLOW_UNKNOWN_PROTOCOLS||!1,Li=a.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Pt=a.SAFE_FOR_TEMPLATES||!1,pn=a.SAFE_FOR_XML!==!1,Ct=a.WHOLE_DOCUMENT||!1,Mt=a.RETURN_DOM||!1,Ee=a.RETURN_DOM_FRAGMENT||!1,Se=a.RETURN_TRUSTED_TYPE||!1,bn=a.FORCE_BODY||!1,Di=a.SANITIZE_DOM!==!1,Ni=a.SANITIZE_NAMED_PROPS||!1,vn=a.KEEP_CONTENT!==!1,ne=a.IN_PLACE||!1,Si=a.ALLOWED_URI_REGEXP||zr,jt=a.NAMESPACE||ot,Te=a.MATHML_TEXT_INTEGRATION_POINTS||Te,we=a.HTML_INTEGRATION_POINTS||we,w=a.CUSTOM_ELEMENT_HANDLING||{},a.CUSTOM_ELEMENT_HANDLING&&Mi(a.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(w.tagNameCheck=a.CUSTOM_ELEMENT_HANDLING.tagNameCheck),a.CUSTOM_ELEMENT_HANDLING&&Mi(a.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(w.attributeNameCheck=a.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),a.CUSTOM_ELEMENT_HANDLING&&typeof a.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(w.allowCustomizedBuiltInElements=a.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Pt&&(mn=!1),Ee&&(Mt=!0),Bt&&(N=b({},cr),O=[],Bt.html===!0&&(b(N,lr),b(O,ur)),Bt.svg===!0&&(b(N,Pn),b(O,_n),b(O,Ie)),Bt.svgFilters===!0&&(b(N,Mn),b(O,_n),b(O,Ie)),Bt.mathMl===!0&&(b(N,Bn),b(O,hr),b(O,Ie))),a.ADD_TAGS&&(N===ki&&(N=St(N)),b(N,a.ADD_TAGS,I)),a.ADD_ATTR&&(O===Ri&&(O=St(O)),b(O,a.ADD_ATTR,I)),a.ADD_URI_SAFE_ATTR&&b(An,a.ADD_URI_SAFE_ATTR,I),a.FORBID_CONTENTS&&(_t===Ii&&(_t=St(_t)),b(_t,a.FORBID_CONTENTS,I)),vn&&(N["#text"]=!0),Ct&&b(N,["html","head","body"]),N.table&&(b(N,["tbody"]),delete ee.tbody),a.TRUSTED_TYPES_POLICY){if(typeof a.TRUSTED_TYPES_POLICY.createHTML!="function")throw le('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof a.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw le('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');k=a.TRUSTED_TYPES_POLICY,rt=k.createHTML("")}else k===void 0&&(k=function(g,h){if(typeof g!="object"||typeof g.createPolicy!="function")return null;let v=null,A="data-tt-policy-suffix";h&&h.hasAttribute(A)&&(v=h.getAttribute(A));let f="dompurify"+(v?"#"+v:"");try{return g.createPolicy(f,{createHTML:D=>D,createScriptURL:D=>D})}catch{return console.warn("TrustedTypes policy "+f+" could not be created."),null}}(Q,o)),k!==null&&typeof rt=="string"&&(rt=k.createHTML(""));z&&z(a),Wt=a}},Bi=b({},[...Pn,...Mn,...zo]),_i=b({},[...Bn,...Ho]),tt=function(a){se(e.removed,{element:a});try{F(a).removeChild(a)}catch{yt(a)}},Le=function(a,g){try{se(e.removed,{attribute:g.getAttributeNode(a),from:g})}catch{se(e.removed,{attribute:null,from:g})}if(g.removeAttribute(a),a==="is")if(Mt||Ee)try{tt(g)}catch{}else try{g.setAttribute(a,"")}catch{}},ji=function(a){let g=null,h=null;if(bn)a=""+a;else{let f=sr(a,/^[\r\n\t ]+/);h=f&&f[0]}ie==="application/xhtml+xml"&&jt===ot&&(a=''+a+"");let v=k?k.createHTML(a):a;if(jt===ot)try{g=new J().parseFromString(v,ie)}catch{}if(!g||!g.documentElement){g=xt.createDocument(jt,"template",null);try{g.documentElement.innerHTML=yn?rt:v}catch{}}let A=g.body||g.documentElement;return a&&h&&A.insertBefore(n.createTextNode(h),A.childNodes[0]||null),jt===ot?io.call(g,Ct?"html":"body")[0]:Ct?g.documentElement:A},Wi=function(a){return eo.call(a.ownerDocument||a,a,d.SHOW_ELEMENT|d.SHOW_COMMENT|d.SHOW_TEXT|d.SHOW_PROCESSING_INSTRUCTION|d.SHOW_CDATA_SECTION,null)},En=function(a){return a instanceof T&&(typeof a.nodeName!="string"||typeof a.textContent!="string"||typeof a.removeChild!="function"||!(a.attributes instanceof C)||typeof a.removeAttribute!="function"||typeof a.setAttribute!="function"||typeof a.namespaceURI!="string"||typeof a.insertBefore!="function"||typeof a.hasChildNodes!="function")},Ui=function(a){return typeof c=="function"&&a instanceof c};function st(a,g,h){Ne(a,v=>{v.call(e,g,h,Wt)})}let Vi=function(a){let g=null;if(st(B.beforeSanitizeElements,a,null),En(a))return tt(a),!0;let h=I(a.nodeName);if(st(B.uponSanitizeElement,a,{tagName:h,allowedTags:N}),a.hasChildNodes()&&!Ui(a.firstElementChild)&&j(/<[/\w!]/g,a.innerHTML)&&j(/<[/\w!]/g,a.textContent)||a.nodeType===es||pn&&a.nodeType===ns&&j(/<[/\w]/g,a.data))return tt(a),!0;if(!N[h]||ee[h]){if(!ee[h]&&Hi(h)&&(w.tagNameCheck instanceof RegExp&&j(w.tagNameCheck,h)||w.tagNameCheck instanceof Function&&w.tagNameCheck(h)))return!1;if(vn&&!_t[h]){let v=F(a)||a.parentNode,A=te(a)||a.childNodes;if(A&&v)for(let f=A.length-1;f>=0;--f){let D=mt(A[f],!0);D.__removalCount=(a.__removalCount||0)+1,v.insertBefore(D,Qt(a))}}return tt(a),!0}return a instanceof u&&!function(v){let A=F(v);A&&A.tagName||(A={namespaceURI:jt,tagName:"template"});let f=_e(v.tagName),D=_e(A.tagName);return!!xn[v.namespaceURI]&&(v.namespaceURI===Re?A.namespaceURI===ot?f==="svg":A.namespaceURI===ke?f==="svg"&&(D==="annotation-xml"||Te[D]):!!Bi[f]:v.namespaceURI===ke?A.namespaceURI===ot?f==="math":A.namespaceURI===Re?f==="math"&&we[D]:!!_i[f]:v.namespaceURI===ot?!(A.namespaceURI===Re&&!we[D])&&!(A.namespaceURI===ke&&!Te[D])&&!_i[f]&&(uo[f]||!Bi[f]):!(ie!=="application/xhtml+xml"||!xn[v.namespaceURI]))}(a)?(tt(a),!0):h!=="noscript"&&h!=="noembed"&&h!=="noframes"||!j(/<\/no(script|embed|frames)/i,a.innerHTML)?(Pt&&a.nodeType===ts&&(g=a.textContent,Ne([un,hn,dn],v=>{g=ae(g,v," ")}),a.textContent!==g&&(se(e.removed,{element:a.cloneNode()}),a.textContent=g)),st(B.afterSanitizeElements,a,null),!1):(tt(a),!0)},zi=function(a,g,h){if(Di&&(g==="id"||g==="name")&&(h in n||h in go))return!1;if(!(mn&&!gn[g]&&j(oo,g))){if(!(Ti&&j(so,g))){if(!O[g]||gn[g]){if(!(Hi(a)&&(w.tagNameCheck instanceof RegExp&&j(w.tagNameCheck,a)||w.tagNameCheck instanceof Function&&w.tagNameCheck(a))&&(w.attributeNameCheck instanceof RegExp&&j(w.attributeNameCheck,g)||w.attributeNameCheck instanceof Function&&w.attributeNameCheck(g))||g==="is"&&w.allowCustomizedBuiltInElements&&(w.tagNameCheck instanceof RegExp&&j(w.tagNameCheck,h)||w.tagNameCheck instanceof Function&&w.tagNameCheck(h))))return!1}else if(!An[g]){if(!j(Si,ae(h,Ei,""))){if((g!=="src"&&g!=="xlink:href"&&g!=="href"||a==="script"||Wo(h,"data:")!==0||!Oi[a])&&!(wi&&!j(ao,ae(h,Ei,"")))){if(h)return!1}}}}}return!0},Hi=function(a){return a!=="annotation-xml"&&sr(a,lo)},qi=function(a){st(B.beforeSanitizeAttributes,a,null);let{attributes:g}=a;if(!g||En(a))return;let h={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:O,forceKeepAttr:void 0},v=g.length;for(;v--;){let A=g[v],{name:f,namespaceURI:D,value:at}=A,et=I(f),_=f==="value"?at:Uo(at);if(h.attrName=et,h.attrValue=_,h.keepAttr=!0,h.forceKeepAttr=void 0,st(B.uponSanitizeAttribute,a,h),_=h.attrValue,!Ni||et!=="id"&&et!=="name"||(Le(f,a),_="user-content-"+_),pn&&j(/((--!?|])>)|<\/(style|title)/i,_)){Le(f,a);continue}if(h.forceKeepAttr||(Le(f,a),!h.keepAttr))continue;if(!Li&&j(/\/>/i,_)){Le(f,a);continue}Pt&&Ne([un,hn,dn],Ki=>{_=ae(_,Ki," ")});let Ji=I(a.nodeName);if(zi(Ji,et,_)){if(k&&typeof Q=="object"&&typeof Q.getAttributeType=="function"&&!D)switch(Q.getAttributeType(Ji,et)){case"TrustedHTML":_=k.createHTML(_);break;case"TrustedScriptURL":_=k.createScriptURL(_)}try{D?a.setAttributeNS(D,f,_):a.setAttribute(f,_),En(a)?tt(a):or(e.removed)}catch{}}}st(B.afterSanitizeAttributes,a,null)},mo=function a(g){let h=null,v=Wi(g);for(st(B.beforeSanitizeShadowDOM,g,null);h=v.nextNode();)st(B.uponSanitizeShadowNode,h,null),Vi(h),qi(h),h.content instanceof s&&a(h.content);st(B.afterSanitizeShadowDOM,g,null)};return e.sanitize=function(a){let g=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},h=null,v=null,A=null,f=null;if(yn=!a,yn&&(a=""),typeof a!="string"&&!Ui(a)){if(typeof a.toString!="function")throw le("toString is not a function");if(typeof(a=a.toString())!="string")throw le("dirty is not a string, aborting")}if(!e.isSupported)return a;if(fn||Cn(g),e.removed=[],typeof a=="string"&&(ne=!1),ne){if(a.nodeName){let et=I(a.nodeName);if(!N[et]||ee[et])throw le("root node is forbidden and cannot be sanitized in-place")}}else if(a instanceof c)h=ji(""),v=h.ownerDocument.importNode(a,!0),v.nodeType===Qo&&v.nodeName==="BODY"||v.nodeName==="HTML"?h=v:h.appendChild(v);else{if(!Mt&&!Pt&&!Ct&&a.indexOf("<")===-1)return k&&Se?k.createHTML(a):a;if(h=ji(a),!h)return Mt?null:Se?rt:""}h&&bn&&tt(h.firstChild);let D=Wi(ne?a:h);for(;A=D.nextNode();)Vi(A),qi(A),A.content instanceof s&&mo(A.content);if(ne)return a;if(Mt){if(Ee)for(f=no.call(h.ownerDocument);h.firstChild;)f.appendChild(h.firstChild);else f=h;return(O.shadowroot||O.shadowrootmode)&&(f=ro.call(r,f,!0)),f}let at=Ct?h.outerHTML:h.innerHTML;return Ct&&N["!doctype"]&&h.ownerDocument&&h.ownerDocument.doctype&&h.ownerDocument.doctype.name&&j(Hr,h.ownerDocument.doctype.name)&&(at=" -`+at),Pt&&Ne([un,hn,dn],et=>{at=ae(at,et," ")}),k&&Se?k.createHTML(at):at},e.setConfig=function(){Cn(arguments.length>0&&arguments[0]!==void 0?arguments[0]:{}),fn=!0},e.clearConfig=function(){Wt=null,fn=!1},e.isValidAttribute=function(a,g,h){Wt||Cn({});let v=I(a),A=I(g);return zi(v,A,h)},e.addHook=function(a,g){typeof g=="function"&&se(B[a],g)},e.removeHook=function(a,g){if(g!==void 0){let h=_o(B[a],g);return h===-1?void 0:jo(B[a],h,1)[0]}return or(B[a])},e.removeHooks=function(a){B[a]=[]},e.removeAllHooks=function(){B={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},e}();Ve.addHook("uponSanitizeAttribute",function(i,t){/^data-trix-/.test(t.attrName)&&(t.forceKeepAttr=!0)});var os="style href src width height language class".split(" "),ss="javascript:".split(" "),as="script iframe form noscript".split(" "),qt=class extends R{static setHTML(t,e,n){let r=new this(e,n).sanitize(),o=r.getHTML?r.getHTML():r.outerHTML;t.innerHTML=o}static sanitize(t,e){let n=new this(t,e);return n.sanitize(),n}constructor(t){let{allowedAttributes:e,forbiddenProtocols:n,forbiddenElements:r,purifyOptions:o}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};super(...arguments),this.allowedAttributes=e||os,this.forbiddenProtocols=n||ss,this.forbiddenElements=r||as,this.purifyOptions=o||{},this.body=ls(t)}sanitize(){this.sanitizeElements(),this.normalizeListElementNesting();let t=Object.assign({},Lr,this.purifyOptions);return Ve.setConfig(t),this.body=Ve.sanitize(this.body),this.body}getHTML(){return this.body.innerHTML}getBody(){return this.body}sanitizeElements(){let t=je(this.body),e=[];for(;t.nextNode();){let n=t.currentNode;switch(n.nodeType){case Node.ELEMENT_NODE:this.elementIsRemovable(n)?e.push(n):this.sanitizeElement(n);break;case Node.COMMENT_NODE:e.push(n)}}return e.forEach(n=>At(n)),this.body}sanitizeElement(t){return t.hasAttribute("href")&&this.forbiddenProtocols.includes(t.protocol)&&t.removeAttribute("href"),Array.from(t.attributes).forEach(e=>{let{name:n}=e;this.allowedAttributes.includes(n)||n.indexOf("data-trix")===0||t.removeAttribute(n)}),t}normalizeListElementNesting(){return Array.from(this.body.querySelectorAll("ul,ol")).forEach(t=>{let e=t.previousElementSibling;e&&W(e)==="li"&&e.appendChild(t)}),this.body}elementIsRemovable(t){if(t?.nodeType===Node.ELEMENT_NODE)return this.elementIsForbidden(t)||this.elementIsntSerializable(t)}elementIsForbidden(t){return this.forbiddenElements.includes(W(t))}elementIsntSerializable(t){return t.getAttribute("data-trix-serialize")==="false"&&!Tt(t)}},ls=function(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"";i=i.replace(/<\/html[^>]*>[^]*$/i,"");let t=document.implementation.createHTMLDocument("");return t.documentElement.innerHTML=i,Array.from(t.head.querySelectorAll("style")).forEach(e=>{t.body.appendChild(e)}),t.body},{css:pt}=Ce,ve=class extends dt{constructor(){super(...arguments),this.attachment=this.object,this.attachment.uploadProgressDelegate=this,this.attachmentPiece=this.options.piece}createContentNodes(){return[]}createNodes(){let t,e=t=p({tagName:"figure",className:this.getClassName(),data:this.getData(),editable:!1}),n=this.getHref();return n&&(t=p({tagName:"a",editable:!1,attributes:{href:n,tabindex:-1}}),e.appendChild(t)),this.attachment.hasContent()?qt.setHTML(t,this.attachment.getContent()):this.createContentNodes().forEach(r=>{t.appendChild(r)}),t.appendChild(this.createCaptionElement()),this.attachment.isPending()&&(this.progressElement=p({tagName:"progress",attributes:{class:pt.attachmentProgress,value:this.attachment.getUploadProgress(),max:100},data:{trixMutable:!0,trixStoreKey:["progressElement",this.attachment.id].join("/")}}),e.appendChild(this.progressElement)),[gr("left"),e,gr("right")]}createCaptionElement(){let t=p({tagName:"figcaption",className:pt.attachmentCaption}),e=this.attachmentPiece.getCaption();if(e)t.classList.add("".concat(pt.attachmentCaption,"--edited")),t.textContent=e;else{let n,r,o=this.getCaptionConfig();if(o.name&&(n=this.attachment.getFilename()),o.size&&(r=this.attachment.getFormattedFilesize()),n){let s=p({tagName:"span",className:pt.attachmentName,textContent:n});t.appendChild(s)}if(r){n&&t.appendChild(document.createTextNode(" "));let s=p({tagName:"span",className:pt.attachmentSize,textContent:r});t.appendChild(s)}}return t}getClassName(){let t=[pt.attachment,"".concat(pt.attachment,"--").concat(this.attachment.getType())],e=this.attachment.getExtension();return e&&t.push("".concat(pt.attachment,"--").concat(e)),t.join(" ")}getData(){let t={trixAttachment:JSON.stringify(this.attachment),trixContentType:this.attachment.getContentType(),trixId:this.attachment.id},{attributes:e}=this.attachmentPiece;return e.isEmpty()||(t.trixAttributes=JSON.stringify(e)),this.attachment.isPending()&&(t.trixSerialize=!1),t}getHref(){if(!cs(this.attachment.getContent(),"a"))return this.attachment.getHref()}getCaptionConfig(){var t;let e=this.attachment.getType(),n=_r((t=mi[e])===null||t===void 0?void 0:t.caption);return e==="file"&&(n.name=!0),n}findProgressElement(){var t;return(t=this.findElement())===null||t===void 0?void 0:t.querySelector("progress")}attachmentDidChangeUploadProgress(){let t=this.attachment.getUploadProgress(),e=this.findProgressElement();e&&(e.value=t)}},gr=i=>p({tagName:"span",textContent:ln,data:{trixCursorTarget:i,trixSerialize:!1}}),cs=function(i,t){let e=p("div");return qt.setHTML(e,i||""),e.querySelector(t)},ze=class extends ve{constructor(){super(...arguments),this.attachment.previewDelegate=this}createContentNodes(){return this.image=p({tagName:"img",attributes:{src:""},data:{trixMutable:!0}}),this.refresh(this.image),[this.image]}createCaptionElement(){let t=super.createCaptionElement(...arguments);return t.textContent||t.setAttribute("data-trix-placeholder",m.captionPlaceholder),t}refresh(t){var e;if(t||(t=(e=this.findElement())===null||e===void 0?void 0:e.querySelector("img")),t)return this.updateAttributesForImage(t)}updateAttributesForImage(t){let e=this.attachment.getURL(),n=this.attachment.getPreviewURL();if(t.src=n||e,n===e)t.removeAttribute("data-trix-serialized-attributes");else{let l=JSON.stringify({src:e});t.setAttribute("data-trix-serialized-attributes",l)}let r=this.attachment.getWidth(),o=this.attachment.getHeight();r!=null&&(t.width=r),o!=null&&(t.height=o);let s=["imageElement",this.attachment.id,t.src,t.width,t.height].join("/");t.dataset.trixStoreKey=s}attachmentDidChangeAttributes(){return this.refresh(this.image),this.refresh()}},He=class extends dt{constructor(){super(...arguments),this.piece=this.object,this.attributes=this.piece.getAttributes(),this.textConfig=this.options.textConfig,this.context=this.options.context,this.piece.attachment?this.attachment=this.piece.attachment:this.string=this.piece.toString()}createNodes(){let t=this.attachment?this.createAttachmentNodes():this.createStringNodes(),e=this.createElement();if(e){let n=function(r){for(;(o=r)!==null&&o!==void 0&&o.firstElementChild;){var o;r=r.firstElementChild}return r}(e);Array.from(t).forEach(r=>{n.appendChild(r)}),t=[e]}return t}createAttachmentNodes(){let t=this.attachment.isPreviewable()?ze:ve;return this.createChildView(t,this.piece.attachment,{piece:this.piece}).getNodes()}createStringNodes(){var t;if((t=this.textConfig)!==null&&t!==void 0&&t.plaintext)return[document.createTextNode(this.string)];{let e=[],n=this.string.split(` -`);for(let r=0;r0){let s=p("br");e.push(s)}if(o.length){let s=document.createTextNode(this.preserveSpaces(o));e.push(s)}}return e}}createElement(){let t,e,n,r={};for(e in this.attributes){n=this.attributes[e];let s=ti(e);if(s){if(s.tagName){var o;let l=p(s.tagName);o?(o.appendChild(l),o=l):t=o=l}if(s.styleProperty&&(r[s.styleProperty]=n),s.style)for(e in s.style)n=s.style[e],r[e]=n}}if(Object.keys(r).length)for(e in t||(t=p("span")),r)n=r[e],t.style[e]=n;return t}createContainerElement(){for(let t in this.attributes){let e=this.attributes[t],n=ti(t);if(n&&n.groupTagName){let r={};return r[t]=e,p(n.groupTagName,r)}}}preserveSpaces(t){return this.context.isLast&&(t=t.replace(/\ $/,ft)),t=t.replace(/(\S)\ {3}(\S)/g,"$1 ".concat(ft," $2")).replace(/\ {2}/g,"".concat(ft," ")).replace(/\ {2}/g," ".concat(ft)),(this.context.isFirst||this.context.followsWhitespace)&&(t=t.replace(/^\ /,ft)),t}},qe=class extends dt{constructor(){super(...arguments),this.text=this.object,this.textConfig=this.options.textConfig}createNodes(){let t=[],e=be.groupObjects(this.getPieces()),n=e.length-1;for(let o=0;o!t.hasAttribute("blockBreak"))}},us=i=>/\s$/.test(i?.toString()),{css:mr}=Ce,Je=class extends dt{constructor(){super(...arguments),this.block=this.object,this.attributes=this.block.getAttributes()}createNodes(){let t=[document.createComment("block")];if(this.block.isEmpty())t.push(p("br"));else{var e;let n=(e=L(this.block.getLastAttribute()))===null||e===void 0?void 0:e.text,r=this.findOrCreateCachedChildView(qe,this.block.text,{textConfig:n});t.push(...Array.from(r.getNodes()||[])),this.shouldAddExtraNewlineElement()&&t.push(p("br"))}if(this.attributes.length)return t;{let n,{tagName:r}=U.default;this.block.isRTL()&&(n={dir:"rtl"});let o=p({tagName:r,attributes:n});return t.forEach(s=>o.appendChild(s)),[o]}}createContainerElement(t){let e={},n,r=this.attributes[t],{tagName:o,htmlAttributes:s=[]}=L(r);if(t===0&&this.block.isRTL()&&Object.assign(e,{dir:"rtl"}),r==="attachmentGallery"){let l=this.block.getBlockBreakPosition();n="".concat(mr.attachmentGallery," ").concat(mr.attachmentGallery,"--").concat(l)}return Object.entries(this.block.htmlAttributes).forEach(l=>{let[c,u]=l;s.includes(c)&&(e[c]=u)}),p({tagName:o,className:n,attributes:e})}shouldAddExtraNewlineElement(){return/\n\n$/.test(this.block.toString())}},Jt=class extends dt{static render(t){let e=p("div"),n=new this(t,{element:e});return n.render(),n.sync(),e}constructor(){super(...arguments),this.element=this.options.element,this.elementStore=new ii,this.setDocument(this.object)}setDocument(t){t.isEqualTo(this.document)||(this.document=this.object=t)}render(){if(this.childViews=[],this.shadowElement=p("div"),!this.document.isEmpty()){let t=be.groupObjects(this.document.getBlocks(),{asTree:!0});Array.from(t).forEach(e=>{let n=this.findOrCreateCachedChildView(Je,e);Array.from(n.getNodes()).map(r=>this.shadowElement.appendChild(r))})}}isSynced(){return hs(this.shadowElement,this.element)}sync(){let t=this.createDocumentFragmentForSync();for(;this.element.lastChild;)this.element.removeChild(this.element.lastChild);return this.element.appendChild(t),this.didSync()}didSync(){return this.elementStore.reset(pr(this.element)),Ai(()=>this.garbageCollectCachedViews())}createDocumentFragmentForSync(){let t=document.createDocumentFragment();return Array.from(this.shadowElement.childNodes).forEach(e=>{t.appendChild(e.cloneNode(!0))}),Array.from(pr(t)).forEach(e=>{let n=this.elementStore.remove(e);n&&e.parentNode.replaceChild(n,e)}),t}},pr=i=>i.querySelectorAll("[data-trix-store-key]"),hs=(i,t)=>fr(i.innerHTML)===fr(t.innerHTML),fr=i=>i.replace(/ /g," ");function Oe(i){var t,e;function n(o,s){try{var l=i[o](s),c=l.value,u=c instanceof ds;Promise.resolve(u?c.v:c).then(function(d){if(u){var C=o==="return"?"return":"next";if(!c.k||d.done)return n(C,d);d=i[C](d).value}r(l.done?"return":"normal",d)},function(d){n("throw",d)})}catch(d){r("throw",d)}}function r(o,s){switch(o){case"return":t.resolve({value:s,done:!0});break;case"throw":t.reject(s);break;default:t.resolve({value:s,done:!1})}(t=t.next)?n(t.key,t.arg):e=null}this._invoke=function(o,s){return new Promise(function(l,c){var u={key:o,arg:s,resolve:l,reject:c,next:null};e?e=e.next=u:(t=e=u,n(o,s))})},typeof i.return!="function"&&(this.return=void 0)}function ds(i,t){this.v=i,this.k=t}function V(i,t,e){return(t=gs(t))in i?Object.defineProperty(i,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):i[t]=e,i}function gs(i){var t=function(e,n){if(typeof e!="object"||e===null)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var o=r.call(e,n||"default");if(typeof o!="object")return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return(n==="string"?String:Number)(e)}(i,"string");return typeof t=="symbol"?t:String(t)}Oe.prototype[typeof Symbol=="function"&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},Oe.prototype.next=function(i){return this._invoke("next",i)},Oe.prototype.throw=function(i){return this._invoke("throw",i)},Oe.prototype.return=function(i){return this._invoke("return",i)};function x(i,t){return ms(i,qr(i,t,"get"))}function Ci(i,t,e){return ps(i,qr(i,t,"set"),e),e}function qr(i,t,e){if(!t.has(i))throw new TypeError("attempted to "+e+" private field on non-instance");return t.get(i)}function ms(i,t){return t.get?t.get.call(i):t.value}function ps(i,t,e){if(t.set)t.set.call(i,e);else{if(!t.writable)throw new TypeError("attempted to set read only private field");t.value=e}}function Fe(i,t,e){if(!t.has(i))throw new TypeError("attempted to get private field on non-instance");return e}function Jr(i,t){if(t.has(i))throw new TypeError("Cannot initialize the same private elements twice on an object")}function fe(i,t,e){Jr(i,t),t.set(i,e)}var gt=class extends ht{static registerType(t,e){e.type=t,this.types[t]=e}static fromJSON(t){let e=this.types[t.type];if(e)return e.fromJSON(t)}constructor(t){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};super(...arguments),this.attributes=X.box(e)}copyWithAttributes(t){return new this.constructor(this.getValue(),t)}copyWithAdditionalAttributes(t){return this.copyWithAttributes(this.attributes.merge(t))}copyWithoutAttribute(t){return this.copyWithAttributes(this.attributes.remove(t))}copy(){return this.copyWithAttributes(this.attributes)}getAttribute(t){return this.attributes.get(t)}getAttributesHash(){return this.attributes}getAttributes(){return this.attributes.toObject()}hasAttribute(t){return this.attributes.has(t)}hasSameStringValueAsPiece(t){return t&&this.toString()===t.toString()}hasSameAttributesAsPiece(t){return t&&(this.attributes===t.attributes||this.attributes.isEqualTo(t.attributes))}isBlockBreak(){return!1}isEqualTo(t){return super.isEqualTo(...arguments)||this.hasSameConstructorAs(t)&&this.hasSameStringValueAsPiece(t)&&this.hasSameAttributesAsPiece(t)}isEmpty(){return this.length===0}isSerializable(){return!0}toJSON(){return{type:this.constructor.type,attributes:this.getAttributes()}}contentsForInspection(){return{type:this.constructor.type,attributes:this.attributes.inspect()}}canBeGrouped(){return this.hasAttribute("href")}canBeGroupedWith(t){return this.getAttribute("href")===t.getAttribute("href")}getLength(){return this.length}canBeConsolidatedWith(t){return!1}};V(gt,"types",{});var Ke=class extends Ht{constructor(t){super(...arguments),this.url=t}perform(t){let e=new Image;e.onload=()=>(e.width=this.width=e.naturalWidth,e.height=this.height=e.naturalHeight,t(!0,e)),e.onerror=()=>t(!1),e.src=this.url}},Kt=class i extends ht{static attachmentForFile(t){let e=new this(this.attributesForFile(t));return e.setFile(t),e}static attributesForFile(t){return new X({filename:t.name,filesize:t.size,contentType:t.type})}static fromJSON(t){return new this(t)}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};super(t),this.releaseFile=this.releaseFile.bind(this),this.attributes=X.box(t),this.didChangeAttributes()}getAttribute(t){return this.attributes.get(t)}hasAttribute(t){return this.attributes.has(t)}getAttributes(){return this.attributes.toObject()}setAttributes(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},e=this.attributes.merge(t);var n,r,o,s;if(!this.attributes.isEqualTo(e))return this.attributes=e,this.didChangeAttributes(),(n=this.previewDelegate)===null||n===void 0||(r=n.attachmentDidChangeAttributes)===null||r===void 0||r.call(n,this),(o=this.delegate)===null||o===void 0||(s=o.attachmentDidChangeAttributes)===null||s===void 0?void 0:s.call(o,this)}didChangeAttributes(){if(this.isPreviewable())return this.preloadURL()}isPending(){return this.file!=null&&!(this.getURL()||this.getHref())}isPreviewable(){return this.attributes.has("previewable")?this.attributes.get("previewable"):i.previewablePattern.test(this.getContentType())}getType(){return this.hasContent()?"content":this.isPreviewable()?"preview":"file"}getURL(){return this.attributes.get("url")}getHref(){return this.attributes.get("href")}getFilename(){return this.attributes.get("filename")||""}getFilesize(){return this.attributes.get("filesize")}getFormattedFilesize(){let t=this.attributes.get("filesize");return typeof t=="number"?Dr.formatter(t):""}getExtension(){var t;return(t=this.getFilename().match(/\.(\w+)$/))===null||t===void 0?void 0:t[1].toLowerCase()}getContentType(){return this.attributes.get("contentType")}hasContent(){return this.attributes.has("content")}getContent(){return this.attributes.get("content")}getWidth(){return this.attributes.get("width")}getHeight(){return this.attributes.get("height")}getFile(){return this.file}setFile(t){if(this.file=t,this.isPreviewable())return this.preloadFile()}releaseFile(){this.releasePreloadedFile(),this.file=null}getUploadProgress(){return this.uploadProgress!=null?this.uploadProgress:0}setUploadProgress(t){var e,n;if(this.uploadProgress!==t)return this.uploadProgress=t,(e=this.uploadProgressDelegate)===null||e===void 0||(n=e.attachmentDidChangeUploadProgress)===null||n===void 0?void 0:n.call(e,this)}toJSON(){return this.getAttributes()}getCacheKey(){return[super.getCacheKey(...arguments),this.attributes.getCacheKey(),this.getPreviewURL()].join("/")}getPreviewURL(){return this.previewURL||this.preloadingURL}setPreviewURL(t){var e,n,r,o;if(t!==this.getPreviewURL())return this.previewURL=t,(e=this.previewDelegate)===null||e===void 0||(n=e.attachmentDidChangeAttributes)===null||n===void 0||n.call(e,this),(r=this.delegate)===null||r===void 0||(o=r.attachmentDidChangePreviewURL)===null||o===void 0?void 0:o.call(r,this)}preloadURL(){return this.preload(this.getURL(),this.releaseFile)}preloadFile(){if(this.file)return this.fileObjectURL=URL.createObjectURL(this.file),this.preload(this.fileObjectURL)}releasePreloadedFile(){this.fileObjectURL&&(URL.revokeObjectURL(this.fileObjectURL),this.fileObjectURL=null)}preload(t,e){if(t&&t!==this.getPreviewURL())return this.preloadingURL=t,new Ke(t).then(n=>{let{width:r,height:o}=n;return this.getWidth()&&this.getHeight()||this.setAttributes({width:r,height:o}),this.preloadingURL=null,this.setPreviewURL(t),e?.()}).catch(()=>(this.preloadingURL=null,e?.()))}};V(Kt,"previewablePattern",/^image(\/(gif|png|webp|jpe?g)|$)/);var Gt=class i extends gt{static fromJSON(t){return new this(Kt.fromJSON(t.attachment),t.attributes)}constructor(t){super(...arguments),this.attachment=t,this.length=1,this.ensureAttachmentExclusivelyHasAttribute("href"),this.attachment.hasContent()||this.removeProhibitedAttributes()}ensureAttachmentExclusivelyHasAttribute(t){this.hasAttribute(t)&&(this.attachment.hasAttribute(t)||this.attachment.setAttributes(this.attributes.slice([t])),this.attributes=this.attributes.remove(t))}removeProhibitedAttributes(){let t=this.attributes.slice(i.permittedAttributes);t.isEqualTo(this.attributes)||(this.attributes=t)}getValue(){return this.attachment}isSerializable(){return!this.attachment.isPending()}getCaption(){return this.attributes.get("caption")||""}isEqualTo(t){var e;return super.isEqualTo(t)&&this.attachment.id===(t==null||(e=t.attachment)===null||e===void 0?void 0:e.id)}toString(){return"\uFFFC"}toJSON(){let t=super.toJSON(...arguments);return t.attachment=this.attachment,t}getCacheKey(){return[super.getCacheKey(...arguments),this.attachment.getCacheKey()].join("/")}toConsole(){return JSON.stringify(this.toString())}};V(Gt,"permittedAttributes",["caption","presentation"]),gt.registerType("attachment",Gt);var Ae=class extends gt{static fromJSON(t){return new this(t.string,t.attributes)}constructor(t){super(...arguments),this.string=(e=>e.replace(/\r\n?/g,` -`))(t),this.length=this.string.length}getValue(){return this.string}toString(){return this.string.toString()}isBlockBreak(){return this.toString()===` -`&&this.getAttribute("blockBreak")===!0}toJSON(){let t=super.toJSON(...arguments);return t.string=this.string,t}canBeConsolidatedWith(t){return t&&this.hasSameConstructorAs(t)&&this.hasSameAttributesAsPiece(t)}consolidateWith(t){return new this.constructor(this.toString()+t.toString(),this.attributes)}splitAtOffset(t){let e,n;return t===0?(e=null,n=this):t===this.length?(e=this,n=null):(e=new this.constructor(this.string.slice(0,t),this.attributes),n=new this.constructor(this.string.slice(t),this.attributes)),[e,n]}toConsole(){let{string:t}=this;return t.length>15&&(t=t.slice(0,14)+"\u2026"),JSON.stringify(t.toString())}};gt.registerType("string",Ae);var Yt=class extends ht{static box(t){return t instanceof this?t:new this(t)}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];super(...arguments),this.objects=t.slice(0),this.length=this.objects.length}indexOf(t){return this.objects.indexOf(t)}splice(){for(var t=arguments.length,e=new Array(t),n=0;nt(e,n))}insertObjectAtIndex(t,e){return this.splice(e,0,t)}insertSplittableListAtIndex(t,e){return this.splice(e,0,...t.objects)}insertSplittableListAtPosition(t,e){let[n,r]=this.splitObjectAtPosition(e);return new this.constructor(n).insertSplittableListAtIndex(t,r)}editObjectAtIndex(t,e){return this.replaceObjectAtIndex(e(this.objects[t]),t)}replaceObjectAtIndex(t,e){return this.splice(e,1,t)}removeObjectAtIndex(t){return this.splice(t,1)}getObjectAtIndex(t){return this.objects[t]}getSplittableListInRange(t){let[e,n,r]=this.splitObjectsAtRange(t);return new this.constructor(e.slice(n,r+1))}selectSplittableList(t){let e=this.objects.filter(n=>t(n));return new this.constructor(e)}removeObjectsInRange(t){let[e,n,r]=this.splitObjectsAtRange(t);return new this.constructor(e).splice(n,r-n+1)}transformObjectsInRange(t,e){let[n,r,o]=this.splitObjectsAtRange(t),s=n.map((l,c)=>r<=c&&c<=o?e(l):l);return new this.constructor(s)}splitObjectsAtRange(t){let e,[n,r,o]=this.splitObjectAtPosition(bs(t));return[n,e]=new this.constructor(n).splitObjectAtPosition(vs(t)+o),[n,r,e-1]}getObjectAtPosition(t){let{index:e}=this.findIndexAndOffsetAtPosition(t);return this.objects[e]}splitObjectAtPosition(t){let e,n,{index:r,offset:o}=this.findIndexAndOffsetAtPosition(t),s=this.objects.slice(0);if(r!=null)if(o===0)e=r,n=0;else{let l=this.getObjectAtIndex(r),[c,u]=l.splitAtOffset(o);s.splice(r,1,c,u),e=r+1,n=c.getLength()-o}else e=s.length,n=0;return[s,e,n]}consolidate(){let t=[],e=this.objects[0];return this.objects.slice(1).forEach(n=>{var r,o;(r=(o=e).canBeConsolidatedWith)!==null&&r!==void 0&&r.call(o,n)?e=e.consolidateWith(n):(t.push(e),e=n)}),e&&t.push(e),new this.constructor(t)}consolidateFromIndexToIndex(t,e){let n=this.objects.slice(0).slice(t,e+1),r=new this.constructor(n).consolidate().toArray();return this.splice(t,n.length,...r)}findIndexAndOffsetAtPosition(t){let e,n=0;for(e=0;ethis.endPosition+=t.getLength())),this.endPosition}toString(){return this.objects.join("")}toArray(){return this.objects.slice(0)}toJSON(){return this.toArray()}isEqualTo(t){return super.isEqualTo(...arguments)||fs(this.objects,t?.objects)}contentsForInspection(){return{objects:"[".concat(this.objects.map(t=>t.inspect()).join(", "),"]")}}},fs=function(i){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];if(i.length!==t.length)return!1;let e=!0;for(let n=0;ni[0],vs=i=>i[1],K=class extends ht{static textForAttachmentWithAttributes(t,e){return new this([new Gt(t,e)])}static textForStringWithAttributes(t,e){return new this([new Ae(t,e)])}static fromJSON(t){return new this(Array.from(t).map(e=>gt.fromJSON(e)))}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];super(...arguments);let e=t.filter(n=>!n.isEmpty());this.pieceList=new Yt(e)}copy(){return this.copyWithPieceList(this.pieceList)}copyWithPieceList(t){return new this.constructor(t.consolidate().toArray())}copyUsingObjectMap(t){let e=this.getPieces().map(n=>t.find(n)||n);return new this.constructor(e)}appendText(t){return this.insertTextAtPosition(t,this.getLength())}insertTextAtPosition(t,e){return this.copyWithPieceList(this.pieceList.insertSplittableListAtPosition(t.pieceList,e))}removeTextAtRange(t){return this.copyWithPieceList(this.pieceList.removeObjectsInRange(t))}replaceTextAtRange(t,e){return this.removeTextAtRange(e).insertTextAtPosition(t,e[0])}moveTextFromRangeToPosition(t,e){if(t[0]<=e&&e<=t[1])return;let n=this.getTextAtRange(t),r=n.getLength();return t[0]n.copyWithAdditionalAttributes(t)))}removeAttributeAtRange(t,e){return this.copyWithPieceList(this.pieceList.transformObjectsInRange(e,n=>n.copyWithoutAttribute(t)))}setAttributesAtRange(t,e){return this.copyWithPieceList(this.pieceList.transformObjectsInRange(e,n=>n.copyWithAttributes(t)))}getAttributesAtPosition(t){var e;return((e=this.pieceList.getObjectAtPosition(t))===null||e===void 0?void 0:e.getAttributes())||{}}getCommonAttributes(){let t=Array.from(this.pieceList.toArray()).map(e=>e.getAttributes());return X.fromCommonAttributesOfObjects(t).toObject()}getCommonAttributesAtRange(t){return this.getTextAtRange(t).getCommonAttributes()||{}}getExpandedRangeForAttributeAtOffset(t,e){let n,r=n=e,o=this.getLength();for(;r>0&&this.getCommonAttributesAtRange([r-1,n])[t];)r--;for(;n!!t.attachment)}getAttachments(){return this.getAttachmentPieces().map(t=>t.attachment)}getAttachmentAndPositionById(t){let e=0;for(let r of this.pieceList.toArray()){var n;if(((n=r.attachment)===null||n===void 0?void 0:n.id)===t)return{attachment:r.attachment,position:e};e+=r.length}return{attachment:null,position:null}}getAttachmentById(t){let{attachment:e}=this.getAttachmentAndPositionById(t);return e}getRangeOfAttachment(t){let e=this.getAttachmentAndPositionById(t.id),n=e.position;if(t=e.attachment)return[n,n+1]}updateAttributesForAttachment(t,e){let n=this.getRangeOfAttachment(e);return n?this.addAttributesAtRange(t,n):this}getLength(){return this.pieceList.getEndPosition()}isEmpty(){return this.getLength()===0}isEqualTo(t){var e;return super.isEqualTo(t)||(t==null||(e=t.pieceList)===null||e===void 0?void 0:e.isEqualTo(this.pieceList))}isBlockBreak(){return this.getLength()===1&&this.pieceList.getObjectAtIndex(0).isBlockBreak()}eachPiece(t){return this.pieceList.eachObject(t)}getPieces(){return this.pieceList.toArray()}getPieceAtPosition(t){return this.pieceList.getObjectAtPosition(t)}contentsForInspection(){return{pieceList:this.pieceList.inspect()}}toSerializableText(){let t=this.pieceList.selectSplittableList(e=>e.isSerializable());return this.copyWithPieceList(t)}toString(){return this.pieceList.toString()}toJSON(){return this.pieceList.toJSON()}toConsole(){return JSON.stringify(this.pieceList.toArray().map(t=>JSON.parse(t.toConsole())))}getDirection(){return Ro(this.toString())}isRTL(){return this.getDirection()==="rtl"}},bt=class i extends ht{static fromJSON(t){return new this(K.fromJSON(t.text),t.attributes,t.htmlAttributes)}constructor(t,e,n){super(...arguments),this.text=As(t||new K),this.attributes=e||[],this.htmlAttributes=n||{}}isEmpty(){return this.text.isBlockBreak()}isEqualTo(t){return!!super.isEqualTo(t)||this.text.isEqualTo(t?.text)&&It(this.attributes,t?.attributes)&&Zt(this.htmlAttributes,t?.htmlAttributes)}copyWithText(t){return new i(t,this.attributes,this.htmlAttributes)}copyWithoutText(){return this.copyWithText(null)}copyWithAttributes(t){return new i(this.text,t,this.htmlAttributes)}copyWithoutAttributes(){return this.copyWithAttributes(null)}copyUsingObjectMap(t){let e=t.find(this.text);return e?this.copyWithText(e):this.copyWithText(this.text.copyUsingObjectMap(t))}addAttribute(t){let e=this.attributes.concat(br(t));return this.copyWithAttributes(e)}addHTMLAttribute(t,e){let n=Object.assign({},this.htmlAttributes,{[t]:e});return new i(this.text,this.attributes,n)}removeAttribute(t){let{listAttribute:e}=L(t),n=Ar(Ar(this.attributes,t),e);return this.copyWithAttributes(n)}removeLastAttribute(){return this.removeAttribute(this.getLastAttribute())}getLastAttribute(){return vr(this.attributes)}getAttributes(){return this.attributes.slice(0)}getAttributeLevel(){return this.attributes.length}getAttributeAtLevel(t){return this.attributes[t-1]}hasAttribute(t){return this.attributes.includes(t)}hasAttributes(){return this.getAttributeLevel()>0}getLastNestableAttribute(){return vr(this.getNestableAttributes())}getNestableAttributes(){return this.attributes.filter(t=>L(t).nestable)}getNestingLevel(){return this.getNestableAttributes().length}decreaseNestingLevel(){let t=this.getLastNestableAttribute();return t?this.removeAttribute(t):this}increaseNestingLevel(){let t=this.getLastNestableAttribute();if(t){let e=this.attributes.lastIndexOf(t),n=vi(this.attributes,e+1,0,...br(t));return this.copyWithAttributes(n)}return this}getListItemAttributes(){return this.attributes.filter(t=>L(t).listAttribute)}isListItem(){var t;return(t=L(this.getLastAttribute()))===null||t===void 0?void 0:t.listAttribute}isTerminalBlock(){var t;return(t=L(this.getLastAttribute()))===null||t===void 0?void 0:t.terminal}breaksOnReturn(){var t;return(t=L(this.getLastAttribute()))===null||t===void 0?void 0:t.breakOnReturn}findLineBreakInDirectionFromPosition(t,e){let n=this.toString(),r;switch(t){case"forward":r=n.indexOf(` -`,e);break;case"backward":r=n.slice(0,e).lastIndexOf(` -`)}if(r!==-1)return r}contentsForInspection(){return{text:this.text.inspect(),attributes:this.attributes}}toString(){return this.text.toString()}toJSON(){return{text:this.text,attributes:this.attributes,htmlAttributes:this.htmlAttributes}}getDirection(){return this.text.getDirection()}isRTL(){return this.text.isRTL()}getLength(){return this.text.getLength()}canBeConsolidatedWith(t){return!this.hasAttributes()&&!t.hasAttributes()&&this.getDirection()===t.getDirection()}consolidateWith(t){let e=K.textForStringWithAttributes(` -`),n=this.getTextWithoutBlockBreak().appendText(e);return this.copyWithText(n.appendText(t.text))}splitAtOffset(t){let e,n;return t===0?(e=null,n=this):t===this.getLength()?(e=this,n=null):(e=this.copyWithText(this.text.getTextAtRange([0,t])),n=this.copyWithText(this.text.getTextAtRange([t,this.getLength()]))),[e,n]}getBlockBreakPosition(){return this.text.getLength()-1}getTextWithoutBlockBreak(){return Kr(this.text)?this.text.getTextAtRange([0,this.getBlockBreakPosition()]):this.text.copy()}canBeGrouped(t){return this.attributes[t]}canBeGroupedWith(t,e){let n=t.getAttributes(),r=n[e],o=this.attributes[e];return o===r&&!(L(o).group===!1&&!(()=>{if(!De){De=[];for(let s in U){let{listAttribute:l}=U[s];l!=null&&De.push(l)}}return De})().includes(n[e+1]))&&(this.getDirection()===t.getDirection()||t.isEmpty())}},As=function(i){return i=ys(i),i=Cs(i)},ys=function(i){let t=!1,e=i.getPieces(),n=e.slice(0,e.length-1),r=e[e.length-1];return r?(n=n.map(o=>o.isBlockBreak()?(t=!0,Es(o)):o),t?new K([...n,r]):i):i},xs=K.textForStringWithAttributes(` -`,{blockBreak:!0}),Cs=function(i){return Kr(i)?i:i.appendText(xs)},Kr=function(i){let t=i.getLength();return t===0?!1:i.getTextAtRange([t-1,t]).isBlockBreak()},Es=i=>i.copyWithoutAttribute("blockBreak"),br=function(i){let{listAttribute:t}=L(i);return t?[t,i]:[i]},vr=i=>i.slice(-1)[0],Ar=function(i,t){let e=i.lastIndexOf(t);return e===-1?i:vi(i,e,1)},q=class extends ht{static fromJSON(t){return new this(Array.from(t).map(e=>bt.fromJSON(e)))}static fromString(t,e){let n=K.textForStringWithAttributes(t,e);return new this([new bt(n)])}constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];super(...arguments),t.length===0&&(t=[new bt]),this.blockList=Yt.box(t)}isEmpty(){let t=this.getBlockAtIndex(0);return this.blockList.length===1&&t.isEmpty()&&!t.hasAttributes()}copy(){let t=(arguments.length>0&&arguments[0]!==void 0?arguments[0]:{}).consolidateBlocks?this.blockList.consolidate().toArray():this.blockList.toArray();return new this.constructor(t)}copyUsingObjectsFromDocument(t){let e=new ni(t.getObjects());return this.copyUsingObjectMap(e)}copyUsingObjectMap(t){let e=this.getBlocks().map(n=>t.find(n)||n.copyUsingObjectMap(t));return new this.constructor(e)}copyWithBaseBlockAttributes(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],e=this.getBlocks().map(n=>{let r=t.concat(n.getAttributes());return n.copyWithAttributes(r)});return new this.constructor(e)}replaceBlock(t,e){let n=this.blockList.indexOf(t);return n===-1?this:new this.constructor(this.blockList.replaceObjectAtIndex(e,n))}insertDocumentAtRange(t,e){let{blockList:n}=t;e=y(e);let[r]=e,{index:o,offset:s}=this.locationFromPosition(r),l=this,c=this.getBlockAtPosition(r);return ut(e)&&c.isEmpty()&&!c.hasAttributes()?l=new this.constructor(l.blockList.removeObjectAtIndex(o)):c.getBlockBreakPosition()===s&&r++,l=l.removeTextAtRange(e),new this.constructor(l.blockList.insertSplittableListAtPosition(n,r))}mergeDocumentAtRange(t,e){let n,r;e=y(e);let[o]=e,s=this.locationFromPosition(o),l=this.getBlockAtIndex(s.index).getAttributes(),c=t.getBaseBlockAttributes(),u=l.slice(-c.length);if(It(c,u)){let T=l.slice(0,-c.length);n=t.copyWithBaseBlockAttributes(T)}else n=t.copy({consolidateBlocks:!0}).copyWithBaseBlockAttributes(l);let d=n.getBlockCount(),C=n.getBlockAtIndex(0);if(It(l,C.getAttributes())){let T=C.getTextWithoutBlockBreak();if(r=this.insertTextAtRange(T,e),d>1){n=new this.constructor(n.getBlocks().slice(1));let J=o+T.getLength();r=r.insertDocumentAtRange(n,J)}}else r=this.insertDocumentAtRange(n,e);return r}insertTextAtRange(t,e){e=y(e);let[n]=e,{index:r,offset:o}=this.locationFromPosition(n),s=this.removeTextAtRange(e);return new this.constructor(s.blockList.editObjectAtIndex(r,l=>l.copyWithText(l.text.insertTextAtPosition(t,o))))}removeTextAtRange(t){let e;t=y(t);let[n,r]=t;if(ut(t))return this;let[o,s]=Array.from(this.locationRangeFromRange(t)),l=o.index,c=o.offset,u=this.getBlockAtIndex(l),d=s.index,C=s.offset,T=this.getBlockAtIndex(d);if(r-n==1&&u.getBlockBreakPosition()===c&&T.getBlockBreakPosition()!==C&&T.text.getStringAtPosition(C)===` -`)e=this.blockList.editObjectAtIndex(d,J=>J.copyWithText(J.text.removeTextAtRange([C,C+1])));else{let J,Q=u.text.getTextAtRange([0,c]),M=T.text.getTextAtRange([C,T.getLength()]),mt=Q.appendText(M);J=l!==d&&c===0&&u.getAttributeLevel()>=T.getAttributeLevel()?T.copyWithText(mt):u.copyWithText(mt);let yt=d+1-l;e=this.blockList.splice(l,yt,J)}return new this.constructor(e)}moveTextFromRangeToPosition(t,e){let n;t=y(t);let[r,o]=t;if(r<=e&&e<=o)return this;let s=this.getDocumentAtRange(t),l=this.removeTextAtRange(t),c=rr=r.editObjectAtIndex(l,function(){return L(t)?o.addAttribute(t,e):s[0]===s[1]?o:o.copyWithText(o.text.addAttributeAtRange(t,e,s))})),new this.constructor(r)}addAttribute(t,e){let{blockList:n}=this;return this.eachBlock((r,o)=>n=n.editObjectAtIndex(o,()=>r.addAttribute(t,e))),new this.constructor(n)}removeAttributeAtRange(t,e){let{blockList:n}=this;return this.eachBlockAtRange(e,function(r,o,s){L(t)?n=n.editObjectAtIndex(s,()=>r.removeAttribute(t)):o[0]!==o[1]&&(n=n.editObjectAtIndex(s,()=>r.copyWithText(r.text.removeAttributeAtRange(t,o))))}),new this.constructor(n)}updateAttributesForAttachment(t,e){let n=this.getRangeOfAttachment(e),[r]=Array.from(n),{index:o}=this.locationFromPosition(r),s=this.getTextAtIndex(o);return new this.constructor(this.blockList.editObjectAtIndex(o,l=>l.copyWithText(s.updateAttributesForAttachment(t,e))))}removeAttributeForAttachment(t,e){let n=this.getRangeOfAttachment(e);return this.removeAttributeAtRange(t,n)}setHTMLAttributeAtPosition(t,e,n){let r=this.getBlockAtPosition(t),o=r.addHTMLAttribute(e,n);return this.replaceBlock(r,o)}insertBlockBreakAtRange(t){let e;t=y(t);let[n]=t,{offset:r}=this.locationFromPosition(n),o=this.removeTextAtRange(t);return r===0&&(e=[new bt]),new this.constructor(o.blockList.insertSplittableListAtPosition(new Yt(e),n))}applyBlockAttributeAtRange(t,e,n){let r=this.expandRangeToLineBreaksAndSplitBlocks(n),o=r.document;n=r.range;let s=L(t);if(s.listAttribute){o=o.removeLastListAttributeAtRange(n,{exceptAttributeName:t});let l=o.convertLineBreaksToBlockBreaksInRange(n);o=l.document,n=l.range}else o=s.exclusive?o.removeBlockAttributesAtRange(n):s.terminal?o.removeLastTerminalAttributeAtRange(n):o.consolidateBlocksAtRange(n);return o.addAttributeAtRange(t,e,n)}removeLastListAttributeAtRange(t){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},{blockList:n}=this;return this.eachBlockAtRange(t,function(r,o,s){let l=r.getLastAttribute();l&&L(l).listAttribute&&l!==e.exceptAttributeName&&(n=n.editObjectAtIndex(s,()=>r.removeAttribute(l)))}),new this.constructor(n)}removeLastTerminalAttributeAtRange(t){let{blockList:e}=this;return this.eachBlockAtRange(t,function(n,r,o){let s=n.getLastAttribute();s&&L(s).terminal&&(e=e.editObjectAtIndex(o,()=>n.removeAttribute(s)))}),new this.constructor(e)}removeBlockAttributesAtRange(t){let{blockList:e}=this;return this.eachBlockAtRange(t,function(n,r,o){n.hasAttributes()&&(e=e.editObjectAtIndex(o,()=>n.copyWithoutAttributes()))}),new this.constructor(e)}expandRangeToLineBreaksAndSplitBlocks(t){let e;t=y(t);let[n,r]=t,o=this.locationFromPosition(n),s=this.locationFromPosition(r),l=this,c=l.getBlockAtIndex(o.index);if(o.offset=c.findLineBreakInDirectionFromPosition("backward",o.offset),o.offset!=null&&(e=l.positionFromLocation(o),l=l.insertBlockBreakAtRange([e,e+1]),s.index+=1,s.offset-=l.getBlockAtIndex(o.index).getLength(),o.index+=1),o.offset=0,s.offset===0&&s.index>o.index)s.index-=1,s.offset=l.getBlockAtIndex(s.index).getBlockBreakPosition();else{let u=l.getBlockAtIndex(s.index);u.text.getStringAtRange([s.offset-1,s.offset])===` -`?s.offset-=1:s.offset=u.findLineBreakInDirectionFromPosition("forward",s.offset),s.offset!==u.getBlockBreakPosition()&&(e=l.positionFromLocation(s),l=l.insertBlockBreakAtRange([e,e+1]))}return n=l.positionFromLocation(o),r=l.positionFromLocation(s),{document:l,range:t=y([n,r])}}convertLineBreaksToBlockBreaksInRange(t){t=y(t);let[e]=t,n=this.getStringAtRange(t).slice(0,-1),r=this;return n.replace(/.*?\n/g,function(o){e+=o.length,r=r.insertBlockBreakAtRange([e-1,e])}),{document:r,range:t}}consolidateBlocksAtRange(t){t=y(t);let[e,n]=t,r=this.locationFromPosition(e).index,o=this.locationFromPosition(n).index;return new this.constructor(this.blockList.consolidateFromIndexToIndex(r,o))}getDocumentAtRange(t){t=y(t);let e=this.blockList.getSplittableListInRange(t).toArray();return new this.constructor(e)}getStringAtRange(t){let e,n=t=y(t);return n[n.length-1]!==this.getLength()&&(e=-1),this.getDocumentAtRange(t).toString().slice(0,e)}getBlockAtIndex(t){return this.blockList.getObjectAtIndex(t)}getBlockAtPosition(t){let{index:e}=this.locationFromPosition(t);return this.getBlockAtIndex(e)}getTextAtIndex(t){var e;return(e=this.getBlockAtIndex(t))===null||e===void 0?void 0:e.text}getTextAtPosition(t){let{index:e}=this.locationFromPosition(t);return this.getTextAtIndex(e)}getPieceAtPosition(t){let{index:e,offset:n}=this.locationFromPosition(t);return this.getTextAtIndex(e).getPieceAtPosition(n)}getCharacterAtPosition(t){let{index:e,offset:n}=this.locationFromPosition(t);return this.getTextAtIndex(e).getStringAtRange([n,n+1])}getLength(){return this.blockList.getEndPosition()}getBlocks(){return this.blockList.toArray()}getBlockCount(){return this.blockList.length}getEditCount(){return this.editCount}eachBlock(t){return this.blockList.eachObject(t)}eachBlockAtRange(t,e){let n,r;t=y(t);let[o,s]=t,l=this.locationFromPosition(o),c=this.locationFromPosition(s);if(l.index===c.index)return n=this.getBlockAtIndex(l.index),r=[l.offset,c.offset],e(n,r,l.index);for(let u=l.index;u<=c.index;u++)if(n=this.getBlockAtIndex(u),n){switch(u){case l.index:r=[l.offset,n.text.getLength()];break;case c.index:r=[0,c.offset];break;default:r=[0,n.text.getLength()]}e(n,r,u)}}getCommonAttributesAtRange(t){t=y(t);let[e]=t;if(ut(t))return this.getCommonAttributesAtPosition(e);{let n=[],r=[];return this.eachBlockAtRange(t,function(o,s){if(s[0]!==s[1])return n.push(o.text.getCommonAttributesAtRange(s)),r.push(yr(o))}),X.fromCommonAttributesOfObjects(n).merge(X.fromCommonAttributesOfObjects(r)).toObject()}}getCommonAttributesAtPosition(t){let e,n,{index:r,offset:o}=this.locationFromPosition(t),s=this.getBlockAtIndex(r);if(!s)return{};let l=yr(s),c=s.text.getAttributesAtPosition(o),u=s.text.getAttributesAtPosition(o-1),d=Object.keys(Dt).filter(C=>Dt[C].inheritable);for(e in u)n=u[e],(n===c[e]||d.includes(e))&&(l[e]=n);return l}getRangeOfCommonAttributeAtPosition(t,e){let{index:n,offset:r}=this.locationFromPosition(e),o=this.getTextAtIndex(n),[s,l]=Array.from(o.getExpandedRangeForAttributeAtOffset(t,r)),c=this.positionFromLocation({index:n,offset:s}),u=this.positionFromLocation({index:n,offset:l});return y([c,u])}getBaseBlockAttributes(){let t=this.getBlockAtIndex(0).getAttributes();for(let e=1;e{let o=[];for(let s=0;s{let{text:n}=e;return t=t.concat(n.getAttachmentPieces())}),t}getAttachments(){return this.getAttachmentPieces().map(t=>t.attachment)}getRangeOfAttachment(t){let e=0,n=this.blockList.toArray();for(let r=0;r{let o=r.getLength();r.hasAttribute(t)&&n.push([e,e+o]),e+=o}),n}findRangesForTextAttribute(t){let{withValue:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=0,r=[],o=[];return this.getPieces().forEach(s=>{let l=s.getLength();(function(c){return e?c.getAttribute(t)===e:c.hasAttribute(t)})(s)&&(r[1]===n?r[1]=n+l:o.push(r=[n,n+l])),n+=l}),o}locationFromPosition(t){let e=this.blockList.findIndexAndOffsetAtPosition(Math.max(0,t));if(e.index!=null)return e;{let n=this.getBlocks();return{index:n.length-1,offset:n[n.length-1].getLength()}}}positionFromLocation(t){return this.blockList.findPositionAtIndexAndOffset(t.index,t.offset)}locationRangeFromPosition(t){return y(this.locationFromPosition(t))}locationRangeFromRange(t){if(!(t=y(t)))return;let[e,n]=Array.from(t),r=this.locationFromPosition(e),o=this.locationFromPosition(n);return y([r,o])}rangeFromLocationRange(t){let e;t=y(t);let n=this.positionFromLocation(t[0]);return ut(t)||(e=this.positionFromLocation(t[1])),y([n,e])}isEqualTo(t){return this.blockList.isEqualTo(t?.blockList)}getTexts(){return this.getBlocks().map(t=>t.text)}getPieces(){let t=[];return Array.from(this.getTexts()).forEach(e=>{t.push(...Array.from(e.getPieces()||[]))}),t}getObjects(){return this.getBlocks().concat(this.getTexts()).concat(this.getPieces())}toSerializableDocument(){let t=[];return this.blockList.eachObject(e=>t.push(e.copyWithText(e.text.toSerializableText()))),new this.constructor(t)}toString(){return this.blockList.toString()}toJSON(){return this.blockList.toJSON()}toConsole(){return JSON.stringify(this.blockList.toArray().map(t=>JSON.parse(t.text.toConsole())))}},yr=function(i){let t={},e=i.getLastAttribute();return e&&(t[e]=!0),t},jn=function(i){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};return{string:i=he(i),attributes:t,type:"string"}},xr=(i,t)=>{try{return JSON.parse(i.getAttribute("data-trix-".concat(t)))}catch{return{}}},Ft=class extends R{static parse(t,e){let n=new this(t,e);return n.parse(),n}constructor(t){let{referenceElement:e,purifyOptions:n}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};super(...arguments),this.html=t,this.referenceElement=e,this.purifyOptions=n,this.blocks=[],this.blockElements=[],this.processedElements=[]}getDocument(){return q.fromJSON(this.blocks)}parse(){try{this.createHiddenContainer(),qt.setHTML(this.containerElement,this.html,{purifyOptions:this.purifyOptions});let t=je(this.containerElement,{usingFilter:ks});for(;t.nextNode();)this.processNode(t.currentNode);return this.translateBlockElementMarginsToNewlines()}finally{this.removeHiddenContainer()}}createHiddenContainer(){return this.referenceElement?(this.containerElement=this.referenceElement.cloneNode(!1),this.containerElement.removeAttribute("id"),this.containerElement.setAttribute("data-trix-internal",""),this.containerElement.style.display="none",this.referenceElement.parentNode.insertBefore(this.containerElement,this.referenceElement.nextSibling)):(this.containerElement=p({tagName:"div",style:{display:"none"}}),document.body.appendChild(this.containerElement))}removeHiddenContainer(){return At(this.containerElement)}processNode(t){switch(t.nodeType){case Node.TEXT_NODE:if(!this.isInsignificantTextNode(t))return this.appendBlockForTextNode(t),this.processTextNode(t);break;case Node.ELEMENT_NODE:return this.appendBlockForElement(t),this.processElement(t)}}appendBlockForTextNode(t){let e=t.parentNode;if(e===this.currentBlockElement&&this.isBlockElement(t.previousSibling))return this.appendStringWithAttributes(` -`);if(e===this.containerElement||this.isBlockElement(e)){var n;let r=this.getBlockAttributes(e),o=this.getBlockHTMLAttributes(e);It(r,(n=this.currentBlock)===null||n===void 0?void 0:n.attributes)||(this.currentBlock=this.appendBlockForAttributesWithElement(r,e,o),this.currentBlockElement=e)}}appendBlockForElement(t){let e=this.isBlockElement(t),n=kt(this.currentBlockElement,t);if(e&&!this.isBlockElement(t.firstChild)){if(!this.isInsignificantTextNode(t.firstChild)||!this.isBlockElement(t.firstElementChild)){let r=this.getBlockAttributes(t),o=this.getBlockHTMLAttributes(t);if(t.firstChild){if(n&&It(r,this.currentBlock.attributes))return this.appendStringWithAttributes(` -`);this.currentBlock=this.appendBlockForAttributesWithElement(r,t,o),this.currentBlockElement=t}}}else if(this.currentBlockElement&&!n&&!e){let r=this.findParentBlockElement(t);if(r)return this.appendBlockForElement(r);this.currentBlock=this.appendEmptyBlock(),this.currentBlockElement=null}}findParentBlockElement(t){let{parentElement:e}=t;for(;e&&e!==this.containerElement;){if(this.isBlockElement(e)&&this.blockElements.includes(e))return e;e=e.parentElement}return null}processTextNode(t){let e=t.data;var n;return Cr(t.parentNode)||(e=xi(e),Gr((n=t.previousSibling)===null||n===void 0?void 0:n.textContent)&&(e=Rs(e))),this.appendStringWithAttributes(e,this.getTextAttributes(t.parentNode))}processElement(t){let e;if(Tt(t)){if(e=xr(t,"attachment"),Object.keys(e).length){let n=this.getTextAttributes(t);this.appendAttachmentWithAttributes(e,n),t.innerHTML=""}return this.processedElements.push(t)}switch(W(t)){case"br":return this.isExtraBR(t)||this.isBlockElement(t.nextSibling)||this.appendStringWithAttributes(` -`,this.getTextAttributes(t)),this.processedElements.push(t);case"img":e={url:t.getAttribute("src"),contentType:"image"};let n=(r=>{let o=r.getAttribute("width"),s=r.getAttribute("height"),l={};return o&&(l.width=parseInt(o,10)),s&&(l.height=parseInt(s,10)),l})(t);for(let r in n){let o=n[r];e[r]=o}return this.appendAttachmentWithAttributes(e,this.getTextAttributes(t)),this.processedElements.push(t);case"tr":if(this.needsTableSeparator(t))return this.appendStringWithAttributes(Me.tableRowSeparator);break;case"td":if(this.needsTableSeparator(t))return this.appendStringWithAttributes(Me.tableCellSeparator)}}appendBlockForAttributesWithElement(t,e){let n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};this.blockElements.push(e);let r=function(){return{text:[],attributes:arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},htmlAttributes:arguments.length>1&&arguments[1]!==void 0?arguments[1]:{}}}(t,n);return this.blocks.push(r),r}appendEmptyBlock(){return this.appendBlockForAttributesWithElement([],null)}appendStringWithAttributes(t,e){return this.appendPiece(jn(t,e))}appendAttachmentWithAttributes(t,e){return this.appendPiece(function(n){return{attachment:n,attributes:arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},type:"attachment"}}(t,e))}appendPiece(t){return this.blocks.length===0&&this.appendEmptyBlock(),this.blocks[this.blocks.length-1].text.push(t)}appendStringToTextAtIndex(t,e){let{text:n}=this.blocks[e],r=n[n.length-1];if(r?.type!=="string")return n.push(jn(t));r.string+=t}prependStringToTextAtIndex(t,e){let{text:n}=this.blocks[e],r=n[0];if(r?.type!=="string")return n.unshift(jn(t));r.string=t+r.string}getTextAttributes(t){let e,n={};for(let r in Dt){let o=Dt[r];if(o.tagName&&vt(t,{matchingSelector:o.tagName,untilNode:this.containerElement}))n[r]=!0;else if(o.parser){if(e=o.parser(t),e){let s=!1;for(let l of this.findBlockElementAncestors(t))if(o.parser(l)===e){s=!0;break}s||(n[r]=e)}}else o.styleProperty&&(e=t.style[o.styleProperty],e&&(n[r]=e))}if(Tt(t)){let r=xr(t,"attributes");for(let o in r)e=r[o],n[o]=e}return n}getBlockAttributes(t){let e=[];for(;t&&t!==this.containerElement;){for(let r in U){let o=U[r];var n;o.parse!==!1&&W(t)===o.tagName&&((n=o.test)!==null&&n!==void 0&&n.call(o,t)||!o.test)&&(e.push(r),o.listAttribute&&e.push(o.listAttribute))}t=t.parentNode}return e.reverse()}getBlockHTMLAttributes(t){let e={},n=Object.values(U).find(r=>r.tagName===W(t));return(n?.htmlAttributes||[]).forEach(r=>{t.hasAttribute(r)&&(e[r]=t.getAttribute(r))}),e}findBlockElementAncestors(t){let e=[];for(;t&&t!==this.containerElement;){let n=W(t);ge().includes(n)&&e.push(t),t=t.parentNode}return e}isBlockElement(t){if(t?.nodeType===Node.ELEMENT_NODE&&!Tt(t)&&!vt(t,{matchingSelector:"td",untilNode:this.containerElement}))return ge().includes(W(t))||window.getComputedStyle(t).display==="block"}isInsignificantTextNode(t){if(t?.nodeType!==Node.TEXT_NODE||!Ts(t.data))return;let{parentNode:e,previousSibling:n,nextSibling:r}=t;return Ss(e.previousSibling)&&!this.isBlockElement(e.previousSibling)||Cr(e)?void 0:!n||this.isBlockElement(n)||!r||this.isBlockElement(r)}isExtraBR(t){return W(t)==="br"&&this.isBlockElement(t.parentNode)&&t.parentNode.lastChild===t}needsTableSeparator(t){if(Me.removeBlankTableCells){var e;let n=(e=t.previousSibling)===null||e===void 0?void 0:e.textContent;return n&&/\S/.test(n)}return t.previousSibling}translateBlockElementMarginsToNewlines(){let t=this.getMarginOfDefaultBlockElement();for(let e=0;e2*t.top&&this.prependStringToTextAtIndex(` -`,e),n.bottom>2*t.bottom&&this.appendStringToTextAtIndex(` -`,e))}}getMarginOfBlockElementAtIndex(t){let e=this.blockElements[t];if(e&&e.textContent&&!ge().includes(W(e))&&!this.processedElements.includes(e))return Er(e)}getMarginOfDefaultBlockElement(){let t=p(U.default.tagName);return this.containerElement.appendChild(t),Er(t)}},Cr=function(i){let{whiteSpace:t}=window.getComputedStyle(i);return["pre","pre-wrap","pre-line"].includes(t)},Ss=i=>i&&!Gr(i.textContent),Er=function(i){let t=window.getComputedStyle(i);if(t.display==="block")return{top:parseInt(t.marginTop),bottom:parseInt(t.marginBottom)}},ks=function(i){return W(i)==="style"?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},Rs=i=>i.replace(new RegExp("^".concat(yi.source,"+")),""),Ts=i=>new RegExp("^".concat(yi.source,"*$")).test(i),Gr=i=>/\s$/.test(i),ws=["contenteditable","data-trix-id","data-trix-store-key","data-trix-mutable","data-trix-placeholder","tabindex"],ai="data-trix-serialized-attributes",Ls="[".concat(ai,"]"),Ds=new RegExp("","g"),Ns={"application/json":function(i){let t;if(i instanceof q)t=i;else{if(!(i instanceof HTMLElement))throw new Error("unserializable object");t=Ft.parse(i.innerHTML).getDocument()}return t.toSerializableDocument().toJSONString()},"text/html":function(i){let t;if(i instanceof q)t=Jt.render(i);else{if(!(i instanceof HTMLElement))throw new Error("unserializable object");t=i.cloneNode(!0)}return Array.from(t.querySelectorAll("[data-trix-serialize=false]")).forEach(e=>{At(e)}),ws.forEach(e=>{Array.from(t.querySelectorAll("[".concat(e,"]"))).forEach(n=>{n.removeAttribute(e)})}),Array.from(t.querySelectorAll(Ls)).forEach(e=>{try{let n=JSON.parse(e.getAttribute(ai));e.removeAttribute(ai);for(let r in n){let o=n[r];e.setAttribute(r,o)}}catch{}}),t.innerHTML.replace(Ds,"")}},Is=Object.freeze({__proto__:null}),E=class extends R{constructor(t,e){super(...arguments),this.attachmentManager=t,this.attachment=e,this.id=this.attachment.id,this.file=this.attachment.file}remove(){return this.attachmentManager.requestRemovalOfAttachment(this.attachment)}};E.proxyMethod("attachment.getAttribute"),E.proxyMethod("attachment.hasAttribute"),E.proxyMethod("attachment.setAttribute"),E.proxyMethod("attachment.getAttributes"),E.proxyMethod("attachment.setAttributes"),E.proxyMethod("attachment.isPending"),E.proxyMethod("attachment.isPreviewable"),E.proxyMethod("attachment.getURL"),E.proxyMethod("attachment.getHref"),E.proxyMethod("attachment.getFilename"),E.proxyMethod("attachment.getFilesize"),E.proxyMethod("attachment.getFormattedFilesize"),E.proxyMethod("attachment.getExtension"),E.proxyMethod("attachment.getContentType"),E.proxyMethod("attachment.getFile"),E.proxyMethod("attachment.setFile"),E.proxyMethod("attachment.releaseFile"),E.proxyMethod("attachment.getUploadProgress"),E.proxyMethod("attachment.setUploadProgress");var Ge=class extends R{constructor(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];super(...arguments),this.managedAttachments={},Array.from(t).forEach(e=>{this.manageAttachment(e)})}getAttachments(){let t=[];for(let e in this.managedAttachments){let n=this.managedAttachments[e];t.push(n)}return t}manageAttachment(t){return this.managedAttachments[t.id]||(this.managedAttachments[t.id]=new E(this,t)),this.managedAttachments[t.id]}attachmentIsManaged(t){return t.id in this.managedAttachments}requestRemovalOfAttachment(t){var e,n;if(this.attachmentIsManaged(t))return(e=this.delegate)===null||e===void 0||(n=e.attachmentManagerDidRequestRemovalOfAttachment)===null||n===void 0?void 0:n.call(e,t)}unmanageAttachment(t){let e=this.managedAttachments[t.id];return delete this.managedAttachments[t.id],e}},Ye=class{constructor(t){this.composition=t,this.document=this.composition.document;let e=this.composition.getSelectedRange();this.startPosition=e[0],this.endPosition=e[1],this.startLocation=this.document.locationFromPosition(this.startPosition),this.endLocation=this.document.locationFromPosition(this.endPosition),this.block=this.document.getBlockAtIndex(this.endLocation.index),this.breaksOnReturn=this.block.breaksOnReturn(),this.previousCharacter=this.block.text.getStringAtPosition(this.endLocation.offset-1),this.nextCharacter=this.block.text.getStringAtPosition(this.endLocation.offset)}shouldInsertBlockBreak(){return this.block.hasAttributes()&&this.block.isListItem()&&!this.block.isEmpty()?this.startLocation.offset!==0:this.breaksOnReturn&&this.nextCharacter!==` -`}shouldBreakFormattedBlock(){return this.block.hasAttributes()&&!this.block.isListItem()&&(this.breaksOnReturn&&this.nextCharacter===` -`||this.previousCharacter===` -`)}shouldDecreaseListLevel(){return this.block.hasAttributes()&&this.block.isListItem()&&this.block.isEmpty()}shouldPrependListItem(){return this.block.isListItem()&&this.startLocation.offset===0&&!this.block.isEmpty()}shouldRemoveLastBlockAttribute(){return this.block.hasAttributes()&&!this.block.isListItem()&&this.block.isEmpty()}},it=class extends R{constructor(){super(...arguments),this.document=new q,this.attachments=[],this.currentAttributes={},this.revision=0}setDocument(t){var e,n;if(!t.isEqualTo(this.document))return this.document=t,this.refreshAttachments(),this.revision++,(e=this.delegate)===null||e===void 0||(n=e.compositionDidChangeDocument)===null||n===void 0?void 0:n.call(e,t)}getSnapshot(){return{document:this.document,selectedRange:this.getSelectedRange()}}loadSnapshot(t){var e,n,r,o;let{document:s,selectedRange:l}=t;return(e=this.delegate)===null||e===void 0||(n=e.compositionWillLoadSnapshot)===null||n===void 0||n.call(e),this.setDocument(s??new q),this.setSelection(l??[0,0]),(r=this.delegate)===null||r===void 0||(o=r.compositionDidLoadSnapshot)===null||o===void 0?void 0:o.call(r)}insertText(t){let{updatePosition:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{updatePosition:!0},n=this.getSelectedRange();this.setDocument(this.document.insertTextAtRange(t,n));let r=n[0],o=r+t.getLength();return e&&this.setSelection(o),this.notifyDelegateOfInsertionAtRange([r,o])}insertBlock(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:new bt,e=new q([t]);return this.insertDocument(e)}insertDocument(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:new q,e=this.getSelectedRange();this.setDocument(this.document.insertDocumentAtRange(t,e));let n=e[0],r=n+t.getLength();return this.setSelection(r),this.notifyDelegateOfInsertionAtRange([n,r])}insertString(t,e){let n=this.getCurrentTextAttributes(),r=K.textForStringWithAttributes(t,n);return this.insertText(r,e)}insertBlockBreak(){let t=this.getSelectedRange();this.setDocument(this.document.insertBlockBreakAtRange(t));let e=t[0],n=e+1;return this.setSelection(n),this.notifyDelegateOfInsertionAtRange([e,n])}insertLineBreak(){let t=new Ye(this);if(t.shouldDecreaseListLevel())return this.decreaseListLevel(),this.setSelection(t.startPosition);if(t.shouldPrependListItem()){let e=new q([t.block.copyWithoutText()]);return this.insertDocument(e)}return t.shouldInsertBlockBreak()?this.insertBlockBreak():t.shouldRemoveLastBlockAttribute()?this.removeLastBlockAttribute():t.shouldBreakFormattedBlock()?this.breakFormattedBlock(t):this.insertString(` -`)}insertHTML(t){let e=Ft.parse(t,{purifyOptions:{SAFE_FOR_XML:!0}}).getDocument(),n=this.getSelectedRange();this.setDocument(this.document.mergeDocumentAtRange(e,n));let r=n[0],o=r+e.getLength()-1;return this.setSelection(o),this.notifyDelegateOfInsertionAtRange([r,o])}replaceHTML(t){let e=Ft.parse(t).getDocument().copyUsingObjectsFromDocument(this.document),n=this.getLocationRange({strict:!1}),r=this.document.rangeFromLocationRange(n);return this.setDocument(e),this.setSelection(r)}insertFile(t){return this.insertFiles([t])}insertFiles(t){let e=[];return Array.from(t).forEach(n=>{var r;if((r=this.delegate)!==null&&r!==void 0&&r.compositionShouldAcceptFile(n)){let o=Kt.attachmentForFile(n);e.push(o)}}),this.insertAttachments(e)}insertAttachment(t){return this.insertAttachments([t])}insertAttachments(t){let e=new K;return Array.from(t).forEach(n=>{var r;let o=n.getType(),s=(r=mi[o])===null||r===void 0?void 0:r.presentation,l=this.getCurrentTextAttributes();s&&(l.presentation=s);let c=K.textForAttachmentWithAttributes(n,l);e=e.appendText(c)}),this.insertText(e)}shouldManageDeletingInDirection(t){let e=this.getLocationRange();if(ut(e)){if(t==="backward"&&e[0].offset===0||this.shouldManageMovingCursorInDirection(t))return!0}else if(e[0].index!==e[1].index)return!0;return!1}deleteInDirection(t){let e,n,r,{length:o}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},s=this.getLocationRange(),l=this.getSelectedRange(),c=ut(l);if(c?n=t==="backward"&&s[0].offset===0:r=s[0].index!==s[1].index,n&&this.canDecreaseBlockAttributeLevel()){let u=this.getBlock();if(u.isListItem()?this.decreaseListLevel():this.decreaseBlockAttributeLevel(),this.setSelection(l[0]),u.isEmpty())return!1}return c&&(l=this.getExpandedRangeInDirection(t,{length:o}),t==="backward"&&(e=this.getAttachmentAtRange(l))),e?(this.editAttachment(e),!1):(this.setDocument(this.document.removeTextAtRange(l)),this.setSelection(l[0]),!n&&!r&&void 0)}moveTextFromRange(t){let[e]=Array.from(this.getSelectedRange());return this.setDocument(this.document.moveTextFromRangeToPosition(t,e)),this.setSelection(e)}removeAttachment(t){let e=this.document.getRangeOfAttachment(t);if(e)return this.stopEditingAttachment(),this.setDocument(this.document.removeTextAtRange(e)),this.setSelection(e[0])}removeLastBlockAttribute(){let[t,e]=Array.from(this.getSelectedRange()),n=this.document.getBlockAtPosition(e);return this.removeCurrentAttribute(n.getLastAttribute()),this.setSelection(t)}insertPlaceholder(){return this.placeholderPosition=this.getPosition(),this.insertString(" ")}selectPlaceholder(){if(this.placeholderPosition!=null)return this.setSelectedRange([this.placeholderPosition,this.placeholderPosition+1]),this.getSelectedRange()}forgetPlaceholder(){this.placeholderPosition=null}hasCurrentAttribute(t){let e=this.currentAttributes[t];return e!=null&&e!==!1}toggleCurrentAttribute(t){let e=!this.currentAttributes[t];return e?this.setCurrentAttribute(t,e):this.removeCurrentAttribute(t)}canSetCurrentAttribute(t){return L(t)?this.canSetCurrentBlockAttribute(t):this.canSetCurrentTextAttribute(t)}canSetCurrentTextAttribute(t){let e=this.getSelectedDocument();if(e){for(let n of Array.from(e.getAttachments()))if(!n.hasContent())return!1;return!0}}canSetCurrentBlockAttribute(t){let e=this.getBlock();if(e)return!e.isTerminalBlock()}setCurrentAttribute(t,e){return L(t)?this.setBlockAttribute(t,e):(this.setTextAttribute(t,e),this.currentAttributes[t]=e,this.notifyDelegateOfCurrentAttributesChange())}setHTMLAtributeAtPosition(t,e,n){var r;let o=this.document.getBlockAtPosition(t),s=(r=L(o.getLastAttribute()))===null||r===void 0?void 0:r.htmlAttributes;if(o&&s!=null&&s.includes(e)){let l=this.document.setHTMLAttributeAtPosition(t,e,n);this.setDocument(l)}}setTextAttribute(t,e){let n=this.getSelectedRange();if(!n)return;let[r,o]=Array.from(n);if(r!==o)return this.setDocument(this.document.addAttributeAtRange(t,e,n));if(t==="href"){let s=K.textForStringWithAttributes(e,{href:e});return this.insertText(s)}}setBlockAttribute(t,e){let n=this.getSelectedRange();if(this.canSetCurrentAttribute(t))return this.setDocument(this.document.applyBlockAttributeAtRange(t,e,n)),this.setSelection(n)}removeCurrentAttribute(t){return L(t)?(this.removeBlockAttribute(t),this.updateCurrentAttributes()):(this.removeTextAttribute(t),delete this.currentAttributes[t],this.notifyDelegateOfCurrentAttributesChange())}removeTextAttribute(t){let e=this.getSelectedRange();if(e)return this.setDocument(this.document.removeAttributeAtRange(t,e))}removeBlockAttribute(t){let e=this.getSelectedRange();if(e)return this.setDocument(this.document.removeAttributeAtRange(t,e))}canDecreaseNestingLevel(){var t;return((t=this.getBlock())===null||t===void 0?void 0:t.getNestingLevel())>0}canIncreaseNestingLevel(){var t;let e=this.getBlock();if(e){if((t=L(e.getLastNestableAttribute()))===null||t===void 0||!t.listAttribute)return e.getNestingLevel()>0;{let n=this.getPreviousBlock();if(n)return function(){let r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];return It((arguments.length>0&&arguments[0]!==void 0?arguments[0]:[]).slice(0,r.length),r)}(n.getListItemAttributes(),e.getListItemAttributes())}}}decreaseNestingLevel(){let t=this.getBlock();if(t)return this.setDocument(this.document.replaceBlock(t,t.decreaseNestingLevel()))}increaseNestingLevel(){let t=this.getBlock();if(t)return this.setDocument(this.document.replaceBlock(t,t.increaseNestingLevel()))}canDecreaseBlockAttributeLevel(){var t;return((t=this.getBlock())===null||t===void 0?void 0:t.getAttributeLevel())>0}decreaseBlockAttributeLevel(){var t;let e=(t=this.getBlock())===null||t===void 0?void 0:t.getLastAttribute();if(e)return this.removeCurrentAttribute(e)}decreaseListLevel(){let[t]=Array.from(this.getSelectedRange()),{index:e}=this.document.locationFromPosition(t),n=e,r=this.getBlock().getAttributeLevel(),o=this.document.getBlockAtIndex(n+1);for(;o&&o.isListItem()&&!(o.getAttributeLevel()<=r);)n++,o=this.document.getBlockAtIndex(n+1);t=this.document.positionFromLocation({index:e,offset:0});let s=this.document.positionFromLocation({index:n,offset:0});return this.setDocument(this.document.removeLastListAttributeAtRange([t,s]))}updateCurrentAttributes(){let t=this.getSelectedRange({ignoreLock:!0});if(t){let e=this.document.getCommonAttributesAtRange(t);if(Array.from(Qn()).forEach(n=>{e[n]||this.canSetCurrentAttribute(n)||(e[n]=!1)}),!Zt(e,this.currentAttributes))return this.currentAttributes=e,this.notifyDelegateOfCurrentAttributesChange()}}getCurrentAttributes(){return Nr.call({},this.currentAttributes)}getCurrentTextAttributes(){let t={};for(let e in this.currentAttributes){let n=this.currentAttributes[e];n!==!1&&ti(e)&&(t[e]=n)}return t}freezeSelection(){return this.setCurrentAttribute("frozen",!0)}thawSelection(){return this.removeCurrentAttribute("frozen")}hasFrozenSelection(){return this.hasCurrentAttribute("frozen")}setSelection(t){var e;let n=this.document.locationRangeFromRange(t);return(e=this.delegate)===null||e===void 0?void 0:e.compositionDidRequestChangingSelectionToLocationRange(n)}getSelectedRange(){let t=this.getLocationRange();if(t)return this.document.rangeFromLocationRange(t)}setSelectedRange(t){let e=this.document.locationRangeFromRange(t);return this.getSelectionManager().setLocationRange(e)}getPosition(){let t=this.getLocationRange();if(t)return this.document.positionFromLocation(t[0])}getLocationRange(t){return this.targetLocationRange?this.targetLocationRange:this.getSelectionManager().getLocationRange(t)||y({index:0,offset:0})}withTargetLocationRange(t,e){let n;this.targetLocationRange=t;try{n=e()}finally{this.targetLocationRange=null}return n}withTargetRange(t,e){let n=this.document.locationRangeFromRange(t);return this.withTargetLocationRange(n,e)}withTargetDOMRange(t,e){let n=this.createLocationRangeFromDOMRange(t,{strict:!1});return this.withTargetLocationRange(n,e)}getExpandedRangeInDirection(t){let{length:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},[n,r]=Array.from(this.getSelectedRange());return t==="backward"?e?n-=e:n=this.translateUTF16PositionFromOffset(n,-1):e?r+=e:r=this.translateUTF16PositionFromOffset(r,1),y([n,r])}shouldManageMovingCursorInDirection(t){if(this.editingAttachment)return!0;let e=this.getExpandedRangeInDirection(t);return this.getAttachmentAtRange(e)!=null}moveCursorInDirection(t){let e,n;if(this.editingAttachment)n=this.document.getRangeOfAttachment(this.editingAttachment);else{let r=this.getSelectedRange();n=this.getExpandedRangeInDirection(t),e=!We(r,n)}if(t==="backward"?this.setSelectedRange(n[0]):this.setSelectedRange(n[1]),e){let r=this.getAttachmentAtRange(n);if(r)return this.editAttachment(r)}}expandSelectionInDirection(t){let{length:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=this.getExpandedRangeInDirection(t,{length:e});return this.setSelectedRange(n)}expandSelectionForEditing(){if(this.hasCurrentAttribute("href"))return this.expandSelectionAroundCommonAttribute("href")}expandSelectionAroundCommonAttribute(t){let e=this.getPosition(),n=this.document.getRangeOfCommonAttributeAtPosition(t,e);return this.setSelectedRange(n)}selectionContainsAttachments(){var t;return((t=this.getSelectedAttachments())===null||t===void 0?void 0:t.length)>0}selectionIsInCursorTarget(){return this.editingAttachment||this.positionIsCursorTarget(this.getPosition())}positionIsCursorTarget(t){let e=this.document.locationFromPosition(t);if(e)return this.locationIsCursorTarget(e)}positionIsBlockBreak(t){var e;return(e=this.document.getPieceAtPosition(t))===null||e===void 0?void 0:e.isBlockBreak()}getSelectedDocument(){let t=this.getSelectedRange();if(t)return this.document.getDocumentAtRange(t)}getSelectedAttachments(){var t;return(t=this.getSelectedDocument())===null||t===void 0?void 0:t.getAttachments()}getAttachments(){return this.attachments.slice(0)}refreshAttachments(){let t=this.document.getAttachments(),{added:e,removed:n}=function(){let r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],o=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],s=[],l=[],c=new Set;r.forEach(d=>{c.add(d)});let u=new Set;return o.forEach(d=>{u.add(d),c.has(d)||s.push(d)}),r.forEach(d=>{u.has(d)||l.push(d)}),{added:s,removed:l}}(this.attachments,t);return this.attachments=t,Array.from(n).forEach(r=>{var o,s;r.delegate=null,(o=this.delegate)===null||o===void 0||(s=o.compositionDidRemoveAttachment)===null||s===void 0||s.call(o,r)}),(()=>{let r=[];return Array.from(e).forEach(o=>{var s,l;o.delegate=this,r.push((s=this.delegate)===null||s===void 0||(l=s.compositionDidAddAttachment)===null||l===void 0?void 0:l.call(s,o))}),r})()}attachmentDidChangeAttributes(t){var e,n;return this.revision++,(e=this.delegate)===null||e===void 0||(n=e.compositionDidEditAttachment)===null||n===void 0?void 0:n.call(e,t)}attachmentDidChangePreviewURL(t){var e,n;return this.revision++,(e=this.delegate)===null||e===void 0||(n=e.compositionDidChangeAttachmentPreviewURL)===null||n===void 0?void 0:n.call(e,t)}editAttachment(t,e){var n,r;if(t!==this.editingAttachment)return this.stopEditingAttachment(),this.editingAttachment=t,(n=this.delegate)===null||n===void 0||(r=n.compositionDidStartEditingAttachment)===null||r===void 0?void 0:r.call(n,this.editingAttachment,e)}stopEditingAttachment(){var t,e;this.editingAttachment&&((t=this.delegate)===null||t===void 0||(e=t.compositionDidStopEditingAttachment)===null||e===void 0||e.call(t,this.editingAttachment),this.editingAttachment=null)}updateAttributesForAttachment(t,e){return this.setDocument(this.document.updateAttributesForAttachment(t,e))}removeAttributeForAttachment(t,e){return this.setDocument(this.document.removeAttributeForAttachment(t,e))}breakFormattedBlock(t){let{document:e}=t,{block:n}=t,r=t.startPosition,o=[r-1,r];n.getBlockBreakPosition()===t.startLocation.offset?(n.breaksOnReturn()&&t.nextCharacter===` -`?r+=1:e=e.removeTextAtRange(o),o=[r,r]):t.nextCharacter===` -`?t.previousCharacter===` -`?o=[r-1,r+1]:(o=[r,r+1],r+=1):t.startLocation.offset-1!=0&&(r+=1);let s=new q([n.removeLastAttribute().copyWithoutText()]);return this.setDocument(e.insertDocumentAtRange(s,o)),this.setSelection(r)}getPreviousBlock(){let t=this.getLocationRange();if(t){let{index:e}=t[0];if(e>0)return this.document.getBlockAtIndex(e-1)}}getBlock(){let t=this.getLocationRange();if(t)return this.document.getBlockAtIndex(t[0].index)}getAttachmentAtRange(t){let e=this.document.getDocumentAtRange(t);if(e.toString()==="".concat("\uFFFC",` -`))return e.getAttachments()[0]}notifyDelegateOfCurrentAttributesChange(){var t,e;return(t=this.delegate)===null||t===void 0||(e=t.compositionDidChangeCurrentAttributes)===null||e===void 0?void 0:e.call(t,this.currentAttributes)}notifyDelegateOfInsertionAtRange(t){var e,n;return(e=this.delegate)===null||e===void 0||(n=e.compositionDidPerformInsertionAtRange)===null||n===void 0?void 0:n.call(e,t)}translateUTF16PositionFromOffset(t,e){let n=this.document.toUTF16String(),r=n.offsetFromUCS2Offset(t);return n.offsetToUCS2Offset(r+e)}};it.proxyMethod("getSelectionManager().getPointRange"),it.proxyMethod("getSelectionManager().setLocationRangeFromPointRange"),it.proxyMethod("getSelectionManager().createLocationRangeFromDOMRange"),it.proxyMethod("getSelectionManager().locationIsCursorTarget"),it.proxyMethod("getSelectionManager().selectionIsExpanded"),it.proxyMethod("delegate?.getSelectionManager");var ye=class extends R{constructor(t){super(...arguments),this.composition=t,this.undoEntries=[],this.redoEntries=[]}recordUndoEntry(t){let{context:e,consolidatable:n}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=this.undoEntries.slice(-1)[0];if(!n||!Os(r,t,e)){let o=this.createEntry({description:t,context:e});this.undoEntries.push(o),this.redoEntries=[]}}undo(){let t=this.undoEntries.pop();if(t){let e=this.createEntry(t);return this.redoEntries.push(e),this.composition.loadSnapshot(t.snapshot)}}redo(){let t=this.redoEntries.pop();if(t){let e=this.createEntry(t);return this.undoEntries.push(e),this.composition.loadSnapshot(t.snapshot)}}canUndo(){return this.undoEntries.length>0}canRedo(){return this.redoEntries.length>0}createEntry(){let{description:t,context:e}=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return{description:t?.toString(),context:JSON.stringify(e),snapshot:this.composition.getSnapshot()}}},Os=(i,t,e)=>i?.description===t?.toString()&&i?.context===JSON.stringify(e),Wn="attachmentGallery",$e=class{constructor(t){this.document=t.document,this.selectedRange=t.selectedRange}perform(){return this.removeBlockAttribute(),this.applyBlockAttribute()}getSnapshot(){return{document:this.document,selectedRange:this.selectedRange}}removeBlockAttribute(){return this.findRangesOfBlocks().map(t=>this.document=this.document.removeAttributeAtRange(Wn,t))}applyBlockAttribute(){let t=0;this.findRangesOfPieces().forEach(e=>{e[1]-e[0]>1&&(e[0]+=t,e[1]+=t,this.document.getCharacterAtPosition(e[1])!==` -`&&(this.document=this.document.insertBlockBreakAtRange(e[1]),e[1]0&&arguments[0]!==void 0?arguments[0]:"",e=Ft.parse(t,{referenceElement:this.element}).getDocument();return this.loadDocument(e)}loadJSON(t){let{document:e,selectedRange:n}=t;return e=q.fromJSON(e),this.loadSnapshot({document:e,selectedRange:n})}loadSnapshot(t){return this.undoManager=new ye(this.composition),this.composition.loadSnapshot(t)}getDocument(){return this.composition.document}getSelectedDocument(){return this.composition.getSelectedDocument()}getSnapshot(){return this.composition.getSnapshot()}toJSON(){return this.getSnapshot()}deleteInDirection(t){return this.composition.deleteInDirection(t)}insertAttachment(t){return this.composition.insertAttachment(t)}insertAttachments(t){return this.composition.insertAttachments(t)}insertDocument(t){return this.composition.insertDocument(t)}insertFile(t){return this.composition.insertFile(t)}insertFiles(t){return this.composition.insertFiles(t)}insertHTML(t){return this.composition.insertHTML(t)}insertString(t){return this.composition.insertString(t)}insertText(t){return this.composition.insertText(t)}insertLineBreak(){return this.composition.insertLineBreak()}getSelectedRange(){return this.composition.getSelectedRange()}getPosition(){return this.composition.getPosition()}getClientRectAtPosition(t){let e=this.getDocument().locationRangeFromRange([t,t+1]);return this.selectionManager.getClientRectAtLocationRange(e)}expandSelectionInDirection(t){return this.composition.expandSelectionInDirection(t)}moveCursorInDirection(t){return this.composition.moveCursorInDirection(t)}setSelectedRange(t){return this.composition.setSelectedRange(t)}activateAttribute(t){let e=!(arguments.length>1&&arguments[1]!==void 0)||arguments[1];return this.composition.setCurrentAttribute(t,e)}attributeIsActive(t){return this.composition.hasCurrentAttribute(t)}canActivateAttribute(t){return this.composition.canSetCurrentAttribute(t)}deactivateAttribute(t){return this.composition.removeCurrentAttribute(t)}setHTMLAtributeAtPosition(t,e,n){this.composition.setHTMLAtributeAtPosition(t,e,n)}canDecreaseNestingLevel(){return this.composition.canDecreaseNestingLevel()}canIncreaseNestingLevel(){return this.composition.canIncreaseNestingLevel()}decreaseNestingLevel(){if(this.canDecreaseNestingLevel())return this.composition.decreaseNestingLevel()}increaseNestingLevel(){if(this.canIncreaseNestingLevel())return this.composition.increaseNestingLevel()}canRedo(){return this.undoManager.canRedo()}canUndo(){return this.undoManager.canUndo()}recordUndoEntry(t){let{context:e,consolidatable:n}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};return this.undoManager.recordUndoEntry(t,{context:e,consolidatable:n})}redo(){if(this.canRedo())return this.undoManager.redo()}undo(){if(this.canUndo())return this.undoManager.undo()}},Ze=class{constructor(t){this.element=t}findLocationFromContainerAndOffset(t,e){let{strict:n}=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{strict:!0},r=0,o=!1,s={index:0,offset:0},l=this.findAttachmentElementParentForNode(t);l&&(t=l.parentNode,e=kn(l));let c=je(this.element,{usingFilter:$r});for(;c.nextNode();){let u=c.currentNode;if(u===t&&me(t)){zt(u)||(s.offset+=e);break}if(u.parentNode===t){if(r++===e)break}else if(!kt(t,u)&&r>0)break;$i(u,{strict:n})?(o&&s.index++,s.offset=0,o=!0):s.offset+=Un(u)}return s}findContainerAndOffsetFromLocation(t){let e,n;if(t.index===0&&t.offset===0){for(e=this.element,n=0;e.firstChild;)if(e=e.firstChild,Rn(e)){n=1;break}return[e,n]}let[r,o]=this.findNodeAndOffsetFromLocation(t);if(r){if(me(r))Un(r)===0?(e=r.parentNode.parentNode,n=kn(r.parentNode),zt(r,{name:"right"})&&n++):(e=r,n=t.offset-o);else{if(e=r.parentNode,!$i(r.previousSibling)&&!Rn(e))for(;r===e.lastChild&&(r=e,e=e.parentNode,!Rn(e)););n=kn(r),t.offset!==0&&n++}return[e,n]}}findNodeAndOffsetFromLocation(t){let e,n,r=0;for(let o of this.getSignificantNodesForIndex(t.index)){let s=Un(o);if(t.offset<=r+s)if(me(o)){if(e=o,n=r,t.offset===n&&zt(e))break}else e||(e=o,n=r);if(r+=s,r>t.offset)break}return[e,n]}findAttachmentElementParentForNode(t){for(;t&&t!==this.element;){if(Tt(t))return t;t=t.parentNode}}getSignificantNodesForIndex(t){let e=[],n=je(this.element,{usingFilter:Ps}),r=!1;for(;n.nextNode();){let s=n.currentNode;var o;if(Vt(s)){if(o!=null?o++:o=0,o===t)r=!0;else if(r)break}else r&&e.push(s)}return e}},Un=function(i){return i.nodeType===Node.TEXT_NODE?zt(i)?0:i.textContent.length:W(i)==="br"||Tt(i)?1:0},Ps=function(i){return Ms(i)===NodeFilter.FILTER_ACCEPT?$r(i):NodeFilter.FILTER_REJECT},Ms=function(i){return Or(i)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},$r=function(i){return Tt(i.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},Qe=class{createDOMRangeFromPoint(t){let e,{x:n,y:r}=t;if(document.caretPositionFromPoint){let{offsetNode:o,offset:s}=document.caretPositionFromPoint(n,r);return e=document.createRange(),e.setStart(o,s),e}if(document.caretRangeFromPoint)return document.caretRangeFromPoint(n,r);if(document.body.createTextRange){let o=pe();try{let s=document.body.createTextRange();s.moveToPoint(n,r),s.select()}catch{}return e=pe(),Wr(o),e}}getClientRectsForDOMRange(t){let e=Array.from(t.getClientRects());return[e[0],e[e.length-1]]}},ct=class extends R{constructor(t){super(...arguments),this.didMouseDown=this.didMouseDown.bind(this),this.selectionDidChange=this.selectionDidChange.bind(this),this.element=t,this.locationMapper=new Ze(this.element),this.pointMapper=new Qe,this.lockCount=0,S("mousedown",{onElement:this.element,withCallback:this.didMouseDown})}getLocationRange(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return t.strict===!1?this.createLocationRangeFromDOMRange(pe()):t.ignoreLock?this.currentLocationRange:this.lockedLocationRange?this.lockedLocationRange:this.currentLocationRange}setLocationRange(t){if(this.lockedLocationRange)return;t=y(t);let e=this.createDOMRangeFromLocationRange(t);e&&(Wr(e),this.updateCurrentLocationRange(t))}setLocationRangeFromPointRange(t){t=y(t);let e=this.getLocationAtPoint(t[0]),n=this.getLocationAtPoint(t[1]);this.setLocationRange([e,n])}getClientRectAtLocationRange(t){let e=this.createDOMRangeFromLocationRange(t);if(e)return this.getClientRectsForDOMRange(e)[1]}locationIsCursorTarget(t){let e=Array.from(this.findNodeAndOffsetFromLocation(t))[0];return zt(e)}lock(){this.lockCount++==0&&(this.updateCurrentLocationRange(),this.lockedLocationRange=this.getLocationRange())}unlock(){if(--this.lockCount==0){let{lockedLocationRange:t}=this;if(this.lockedLocationRange=null,t!=null)return this.setLocationRange(t)}}clearSelection(){var t;return(t=jr())===null||t===void 0?void 0:t.removeAllRanges()}selectionIsCollapsed(){var t;return((t=pe())===null||t===void 0?void 0:t.collapsed)===!0}selectionIsExpanded(){return!this.selectionIsCollapsed()}createLocationRangeFromDOMRange(t,e){if(t==null||!this.domRangeWithinElement(t))return;let n=this.findLocationFromContainerAndOffset(t.startContainer,t.startOffset,e);if(!n)return;let r=t.collapsed?void 0:this.findLocationFromContainerAndOffset(t.endContainer,t.endOffset,e);return y([n,r])}didMouseDown(){return this.pauseTemporarily()}pauseTemporarily(){let t;this.paused=!0;let e=()=>{if(this.paused=!1,clearTimeout(n),Array.from(t).forEach(r=>{r.destroy()}),kt(document,this.element))return this.selectionDidChange()},n=setTimeout(e,200);t=["mousemove","keydown"].map(r=>S(r,{onElement:document,withCallback:e}))}selectionDidChange(){if(!this.paused&&!fi(this.element))return this.updateCurrentLocationRange()}updateCurrentLocationRange(t){var e,n;if((t??(t=this.createLocationRangeFromDOMRange(pe())))&&!We(t,this.currentLocationRange))return this.currentLocationRange=t,(e=this.delegate)===null||e===void 0||(n=e.locationRangeDidChange)===null||n===void 0?void 0:n.call(e,this.currentLocationRange.slice(0))}createDOMRangeFromLocationRange(t){let e=this.findContainerAndOffsetFromLocation(t[0]),n=ut(t)?e:this.findContainerAndOffsetFromLocation(t[1])||e;if(e!=null&&n!=null){let r=document.createRange();return r.setStart(...Array.from(e||[])),r.setEnd(...Array.from(n||[])),r}}getLocationAtPoint(t){let e=this.createDOMRangeFromPoint(t);var n;if(e)return(n=this.createLocationRangeFromDOMRange(e))===null||n===void 0?void 0:n[0]}domRangeWithinElement(t){return t.collapsed?kt(this.element,t.startContainer):kt(this.element,t.startContainer)&&kt(this.element,t.endContainer)}};ct.proxyMethod("locationMapper.findLocationFromContainerAndOffset"),ct.proxyMethod("locationMapper.findContainerAndOffsetFromLocation"),ct.proxyMethod("locationMapper.findNodeAndOffsetFromLocation"),ct.proxyMethod("pointMapper.createDOMRangeFromPoint"),ct.proxyMethod("pointMapper.getClientRectsForDOMRange");var Xr=Object.freeze({__proto__:null,Attachment:Kt,AttachmentManager:Ge,AttachmentPiece:Gt,Block:bt,Composition:it,Document:q,Editor:Xe,HTMLParser:Ft,HTMLSanitizer:qt,LineBreakInsertion:Ye,LocationMapper:Ze,ManagedAttachment:E,Piece:gt,PointMapper:Qe,SelectionManager:ct,SplittableList:Yt,StringPiece:Ae,Text:K,UndoManager:ye}),Bs=Object.freeze({__proto__:null,ObjectView:dt,AttachmentView:ve,BlockView:Je,DocumentView:Jt,PieceView:He,PreviewableAttachmentView:ze,TextView:qe}),{lang:Vn,css:Et,keyNames:_s}=Ce,zn=function(i){return function(){let t=i.apply(this,arguments);t.do(),this.undos||(this.undos=[]),this.undos.push(t.undo)}},tn=class extends R{constructor(t,e,n){let r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{};super(...arguments),V(this,"makeElementMutable",zn(()=>({do:()=>{this.element.dataset.trixMutable=!0},undo:()=>delete this.element.dataset.trixMutable}))),V(this,"addToolbar",zn(()=>{let o=p({tagName:"div",className:Et.attachmentToolbar,data:{trixMutable:!0},childNodes:p({tagName:"div",className:"trix-button-row",childNodes:p({tagName:"span",className:"trix-button-group trix-button-group--actions",childNodes:p({tagName:"button",className:"trix-button trix-button--remove",textContent:Vn.remove,attributes:{title:Vn.remove},data:{trixAction:"remove"}})})})});return this.attachment.isPreviewable()&&o.appendChild(p({tagName:"div",className:Et.attachmentMetadataContainer,childNodes:p({tagName:"span",className:Et.attachmentMetadata,childNodes:[p({tagName:"span",className:Et.attachmentName,textContent:this.attachment.getFilename(),attributes:{title:this.attachment.getFilename()}}),p({tagName:"span",className:Et.attachmentSize,textContent:this.attachment.getFormattedFilesize()})]})})),S("click",{onElement:o,withCallback:this.didClickToolbar}),S("click",{onElement:o,matchingSelector:"[data-trix-action]",withCallback:this.didClickActionButton}),de("trix-attachment-before-toolbar",{onElement:this.element,attributes:{toolbar:o,attachment:this.attachment}}),{do:()=>this.element.appendChild(o),undo:()=>At(o)}})),V(this,"installCaptionEditor",zn(()=>{let o=p({tagName:"textarea",className:Et.attachmentCaptionEditor,attributes:{placeholder:Vn.captionPlaceholder},data:{trixMutable:!0}});o.value=this.attachmentPiece.getCaption();let s=o.cloneNode();s.classList.add("trix-autoresize-clone"),s.tabIndex=-1;let l=function(){s.value=o.value,o.style.height=s.scrollHeight+"px"};S("input",{onElement:o,withCallback:l}),S("input",{onElement:o,withCallback:this.didInputCaption}),S("keydown",{onElement:o,withCallback:this.didKeyDownCaption}),S("change",{onElement:o,withCallback:this.didChangeCaption}),S("blur",{onElement:o,withCallback:this.didBlurCaption});let c=this.element.querySelector("figcaption"),u=c.cloneNode();return{do:()=>{if(c.style.display="none",u.appendChild(o),u.appendChild(s),u.classList.add("".concat(Et.attachmentCaption,"--editing")),c.parentElement.insertBefore(u,c),l(),this.options.editCaption)return Ai(()=>o.focus())},undo(){At(u),c.style.display=null}}})),this.didClickToolbar=this.didClickToolbar.bind(this),this.didClickActionButton=this.didClickActionButton.bind(this),this.didKeyDownCaption=this.didKeyDownCaption.bind(this),this.didInputCaption=this.didInputCaption.bind(this),this.didChangeCaption=this.didChangeCaption.bind(this),this.didBlurCaption=this.didBlurCaption.bind(this),this.attachmentPiece=t,this.element=e,this.container=n,this.options=r,this.attachment=this.attachmentPiece.attachment,W(this.element)==="a"&&(this.element=this.element.firstChild),this.install()}install(){this.makeElementMutable(),this.addToolbar(),this.attachment.isPreviewable()&&this.installCaptionEditor()}uninstall(){var t;let e=this.undos.pop();for(this.savePendingCaption();e;)e(),e=this.undos.pop();(t=this.delegate)===null||t===void 0||t.didUninstallAttachmentEditor(this)}savePendingCaption(){if(this.pendingCaption!=null){let o=this.pendingCaption;var t,e,n,r;this.pendingCaption=null,o?(t=this.delegate)===null||t===void 0||(e=t.attachmentEditorDidRequestUpdatingAttributesForAttachment)===null||e===void 0||e.call(t,{caption:o},this.attachment):(n=this.delegate)===null||n===void 0||(r=n.attachmentEditorDidRequestRemovingAttributeForAttachment)===null||r===void 0||r.call(n,"caption",this.attachment)}}didClickToolbar(t){return t.preventDefault(),t.stopPropagation()}didClickActionButton(t){var e;if(t.target.getAttribute("data-trix-action")==="remove")return(e=this.delegate)===null||e===void 0?void 0:e.attachmentEditorDidRequestRemovalOfAttachment(this.attachment)}didKeyDownCaption(t){var e,n;if(_s[t.keyCode]==="return")return t.preventDefault(),this.savePendingCaption(),(e=this.delegate)===null||e===void 0||(n=e.attachmentEditorDidRequestDeselectingAttachment)===null||n===void 0?void 0:n.call(e,this.attachment)}didInputCaption(t){this.pendingCaption=t.target.value.replace(/\s/g," ").trim()}didChangeCaption(t){return this.savePendingCaption()}didBlurCaption(t){return this.savePendingCaption()}},en=class extends R{constructor(t,e){super(...arguments),this.didFocus=this.didFocus.bind(this),this.didBlur=this.didBlur.bind(this),this.didClickAttachment=this.didClickAttachment.bind(this),this.element=t,this.composition=e,this.documentView=new Jt(this.composition.document,{element:this.element}),S("focus",{onElement:this.element,withCallback:this.didFocus}),S("blur",{onElement:this.element,withCallback:this.didBlur}),S("click",{onElement:this.element,matchingSelector:"a[contenteditable=false]",preventDefault:!0}),S("mousedown",{onElement:this.element,matchingSelector:Rt,withCallback:this.didClickAttachment}),S("click",{onElement:this.element,matchingSelector:"a".concat(Rt),preventDefault:!0})}didFocus(t){var e;let n=()=>{var r,o;if(!this.focused)return this.focused=!0,(r=this.delegate)===null||r===void 0||(o=r.compositionControllerDidFocus)===null||o===void 0?void 0:o.call(r)};return((e=this.blurPromise)===null||e===void 0?void 0:e.then(n))||n()}didBlur(t){this.blurPromise=new Promise(e=>Ai(()=>{var n,r;return fi(this.element)||(this.focused=null,(n=this.delegate)===null||n===void 0||(r=n.compositionControllerDidBlur)===null||r===void 0||r.call(n)),this.blurPromise=null,e()}))}didClickAttachment(t,e){var n,r;let o=this.findAttachmentForElement(e),s=!!vt(t.target,{matchingSelector:"figcaption"});return(n=this.delegate)===null||n===void 0||(r=n.compositionControllerDidSelectAttachment)===null||r===void 0?void 0:r.call(n,o,{editCaption:s})}getSerializableElement(){return this.isEditingAttachment()?this.documentView.shadowElement:this.element}render(){var t,e,n,r,o,s;return this.revision!==this.composition.revision&&(this.documentView.setDocument(this.composition.document),this.documentView.render(),this.revision=this.composition.revision),this.canSyncDocumentView()&&!this.documentView.isSynced()&&((n=this.delegate)===null||n===void 0||(r=n.compositionControllerWillSyncDocumentView)===null||r===void 0||r.call(n),this.documentView.sync(),(o=this.delegate)===null||o===void 0||(s=o.compositionControllerDidSyncDocumentView)===null||s===void 0||s.call(o)),(t=this.delegate)===null||t===void 0||(e=t.compositionControllerDidRender)===null||e===void 0?void 0:e.call(t)}rerenderViewForObject(t){return this.invalidateViewForObject(t),this.render()}invalidateViewForObject(t){return this.documentView.invalidateViewForObject(t)}isViewCachingEnabled(){return this.documentView.isViewCachingEnabled()}enableViewCaching(){return this.documentView.enableViewCaching()}disableViewCaching(){return this.documentView.disableViewCaching()}refreshViewCache(){return this.documentView.garbageCollectCachedViews()}isEditingAttachment(){return!!this.attachmentEditor}installAttachmentEditorForAttachment(t,e){var n;if(((n=this.attachmentEditor)===null||n===void 0?void 0:n.attachment)===t)return;let r=this.documentView.findElementForObject(t);if(!r)return;this.uninstallAttachmentEditor();let o=this.composition.document.getAttachmentPieceForAttachment(t);this.attachmentEditor=new tn(o,r,this.element,e),this.attachmentEditor.delegate=this}uninstallAttachmentEditor(){var t;return(t=this.attachmentEditor)===null||t===void 0?void 0:t.uninstall()}didUninstallAttachmentEditor(){return this.attachmentEditor=null,this.render()}attachmentEditorDidRequestUpdatingAttributesForAttachment(t,e){var n,r;return(n=this.delegate)===null||n===void 0||(r=n.compositionControllerWillUpdateAttachment)===null||r===void 0||r.call(n,e),this.composition.updateAttributesForAttachment(t,e)}attachmentEditorDidRequestRemovingAttributeForAttachment(t,e){var n,r;return(n=this.delegate)===null||n===void 0||(r=n.compositionControllerWillUpdateAttachment)===null||r===void 0||r.call(n,e),this.composition.removeAttributeForAttachment(t,e)}attachmentEditorDidRequestRemovalOfAttachment(t){var e,n;return(e=this.delegate)===null||e===void 0||(n=e.compositionControllerDidRequestRemovalOfAttachment)===null||n===void 0?void 0:n.call(e,t)}attachmentEditorDidRequestDeselectingAttachment(t){var e,n;return(e=this.delegate)===null||e===void 0||(n=e.compositionControllerDidRequestDeselectingAttachment)===null||n===void 0?void 0:n.call(e,t)}canSyncDocumentView(){return!this.isEditingAttachment()}findAttachmentForElement(t){return this.composition.document.getAttachmentById(parseInt(t.dataset.trixId,10))}},nn=class extends R{},Zr="data-trix-mutable",js="[".concat(Zr,"]"),Ws={attributes:!0,childList:!0,characterData:!0,characterDataOldValue:!0,subtree:!0},rn=class extends R{constructor(t){super(t),this.didMutate=this.didMutate.bind(this),this.element=t,this.observer=new window.MutationObserver(this.didMutate),this.start()}start(){return this.reset(),this.observer.observe(this.element,Ws)}stop(){return this.observer.disconnect()}didMutate(t){var e,n;if(this.mutations.push(...Array.from(this.findSignificantMutations(t)||[])),this.mutations.length)return(e=this.delegate)===null||e===void 0||(n=e.elementDidMutate)===null||n===void 0||n.call(e,this.getMutationSummary()),this.reset()}reset(){this.mutations=[]}findSignificantMutations(t){return t.filter(e=>this.mutationIsSignificant(e))}mutationIsSignificant(t){if(this.nodeIsMutable(t.target))return!1;for(let e of Array.from(this.nodesModifiedByMutation(t)))if(this.nodeIsSignificant(e))return!0;return!1}nodeIsSignificant(t){return t!==this.element&&!this.nodeIsMutable(t)&&!Or(t)}nodeIsMutable(t){return vt(t,{matchingSelector:js})}nodesModifiedByMutation(t){let e=[];switch(t.type){case"attributes":t.attributeName!==Zr&&e.push(t.target);break;case"characterData":e.push(t.target.parentNode),e.push(t.target);break;case"childList":e.push(...Array.from(t.addedNodes||[])),e.push(...Array.from(t.removedNodes||[]))}return e}getMutationSummary(){return this.getTextMutationSummary()}getTextMutationSummary(){let{additions:t,deletions:e}=this.getTextChangesFromCharacterData(),n=this.getTextChangesFromChildList();Array.from(n.additions).forEach(l=>{Array.from(t).includes(l)||t.push(l)}),e.push(...Array.from(n.deletions||[]));let r={},o=t.join("");o&&(r.textAdded=o);let s=e.join("");return s&&(r.textDeleted=s),r}getMutationsByType(t){return Array.from(this.mutations).filter(e=>e.type===t)}getTextChangesFromChildList(){let t,e,n=[],r=[];Array.from(this.getMutationsByType("childList")).forEach(l=>{n.push(...Array.from(l.addedNodes||[])),r.push(...Array.from(l.removedNodes||[]))}),n.length===0&&r.length===1&&Vt(r[0])?(t=[],e=[` -`]):(t=li(n),e=li(r));let o=t.filter((l,c)=>l!==e[c]).map(he),s=e.filter((l,c)=>l!==t[c]).map(he);return{additions:o,deletions:s}}getTextChangesFromCharacterData(){let t,e,n=this.getMutationsByType("characterData");if(n.length){let r=n[0],o=n[n.length-1],s=function(l,c){let u,d;return l=Nt.box(l),(c=Nt.box(c)).length0&&arguments[0]!==void 0?arguments[0]:[],t=[];for(let e of Array.from(i))switch(e.nodeType){case Node.TEXT_NODE:t.push(e.data);break;case Node.ELEMENT_NODE:W(e)==="br"?t.push(` -`):t.push(...Array.from(li(e.childNodes)||[]))}return t},on=class extends Ht{constructor(t){super(...arguments),this.file=t}perform(t){let e=new FileReader;return e.onerror=()=>t(!1),e.onload=()=>{e.onerror=null;try{e.abort()}catch{}return t(!0,this.file)},e.readAsArrayBuffer(this.file)}},ci=class{constructor(t){this.element=t}shouldIgnore(t){return!!xe.samsungAndroid&&(this.previousEvent=this.event,this.event=t,this.checkSamsungKeyboardBuggyModeStart(),this.checkSamsungKeyboardBuggyModeEnd(),this.buggyMode)}checkSamsungKeyboardBuggyModeStart(){this.insertingLongTextAfterUnidentifiedChar()&&Us(this.element.innerText,this.event.data)&&(this.buggyMode=!0,this.event.preventDefault())}checkSamsungKeyboardBuggyModeEnd(){this.buggyMode&&this.event.inputType!=="insertText"&&(this.buggyMode=!1)}insertingLongTextAfterUnidentifiedChar(){var t;return this.isBeforeInputInsertText()&&this.previousEventWasUnidentifiedKeydown()&&((t=this.event.data)===null||t===void 0?void 0:t.length)>50}isBeforeInputInsertText(){return this.event.type==="beforeinput"&&this.event.inputType==="insertText"}previousEventWasUnidentifiedKeydown(){var t,e;return((t=this.previousEvent)===null||t===void 0?void 0:t.type)==="keydown"&&((e=this.previousEvent)===null||e===void 0?void 0:e.key)==="Unidentified"}},Us=(i,t)=>Sr(i)===Sr(t),Vs=new RegExp("(".concat("\uFFFC","|").concat(ln,"|").concat(ft,"|\\s)+"),"g"),Sr=i=>i.replace(Vs," ").trim(),$t=class extends R{constructor(t){super(...arguments),this.element=t,this.mutationObserver=new rn(this.element),this.mutationObserver.delegate=this,this.flakyKeyboardDetector=new ci(this.element);for(let e in this.constructor.events)S(e,{onElement:this.element,withCallback:this.handlerFor(e)})}elementDidMutate(t){}editorWillSyncDocumentView(){return this.mutationObserver.stop()}editorDidSyncDocumentView(){return this.mutationObserver.start()}requestRender(){var t,e;return(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidRequestRender)===null||e===void 0?void 0:e.call(t)}requestReparse(){var t,e;return(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidRequestReparse)===null||e===void 0||e.call(t),this.requestRender()}attachFiles(t){let e=Array.from(t).map(n=>new on(n));return Promise.all(e).then(n=>{this.handleInput(function(){var r,o;return(r=this.delegate)===null||r===void 0||r.inputControllerWillAttachFiles(),(o=this.responder)===null||o===void 0||o.insertFiles(n),this.requestRender()})})}handlerFor(t){return e=>{e.defaultPrevented||this.handleInput(()=>{if(!fi(this.element)){if(this.flakyKeyboardDetector.shouldIgnore(e))return;this.eventName=t,this.constructor.events[t].call(this,e)}})}}handleInput(t){try{var e;(e=this.delegate)===null||e===void 0||e.inputControllerWillHandleInput(),t.call(this)}finally{var n;(n=this.delegate)===null||n===void 0||n.inputControllerDidHandleInput()}}createLinkHTML(t,e){let n=document.createElement("a");return n.href=t,n.textContent=e||t,n.outerHTML}},Hn;V($t,"events",{});var{browser:zs,keyNames:Qr}=Ce,Hs=0,$=class extends $t{constructor(){super(...arguments),this.resetInputSummary()}setInputSummary(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.inputSummary.eventName=this.eventName;for(let e in t){let n=t[e];this.inputSummary[e]=n}return this.inputSummary}resetInputSummary(){this.inputSummary={}}reset(){return this.resetInputSummary(),Ot.reset()}elementDidMutate(t){var e,n;return this.isComposing()?(e=this.delegate)===null||e===void 0||(n=e.inputControllerDidAllowUnhandledInput)===null||n===void 0?void 0:n.call(e):this.handleInput(function(){return this.mutationIsSignificant(t)&&(this.mutationIsExpected(t)?this.requestRender():this.requestReparse()),this.reset()})}mutationIsExpected(t){let{textAdded:e,textDeleted:n}=t;if(this.inputSummary.preferDocument)return!0;let r=e!=null?e===this.inputSummary.textAdded:!this.inputSummary.textAdded,o=n!=null?this.inputSummary.didDelete:!this.inputSummary.didDelete,s=[` -`,` -`].includes(e)&&!r,l=n===` -`&&!o;if(s&&!l||l&&!s){let u=this.getSelectedRange();if(u){var c;let d=s?e.replace(/\n$/,"").length||-1:e?.length||1;if((c=this.responder)!==null&&c!==void 0&&c.positionIsBlockBreak(u[1]+d))return!0}}return r&&o}mutationIsSignificant(t){var e;let n=Object.keys(t).length>0,r=((e=this.compositionInput)===null||e===void 0?void 0:e.getEndData())==="";return n||!r}getCompositionInput(){if(this.isComposing())return this.compositionInput;this.compositionInput=new nt(this)}isComposing(){return this.compositionInput&&!this.compositionInput.isEnded()}deleteInDirection(t,e){var n;return((n=this.responder)===null||n===void 0?void 0:n.deleteInDirection(t))!==!1?this.setInputSummary({didDelete:!0}):e?(e.preventDefault(),this.requestRender()):void 0}serializeSelectionToDataTransfer(t){var e;if(!function(r){if(r==null||!r.setData)return!1;for(let o in Qi){let s=Qi[o];try{if(r.setData(o,s),!r.getData(o)===s)return!1}catch{return!1}}return!0}(t))return;let n=(e=this.responder)===null||e===void 0?void 0:e.getSelectedDocument().toSerializableDocument();return t.setData("application/x-trix-document",JSON.stringify(n)),t.setData("text/html",Jt.render(n).innerHTML),t.setData("text/plain",n.toString().replace(/\n$/,"")),!0}canAcceptDataTransfer(t){let e={};return Array.from(t?.types||[]).forEach(n=>{e[n]=!0}),e.Files||e["application/x-trix-document"]||e["text/html"]||e["text/plain"]}getPastedHTMLUsingHiddenElement(t){let e=this.getSelectedRange(),n={position:"absolute",left:"".concat(window.pageXOffset,"px"),top:"".concat(window.pageYOffset,"px"),opacity:0},r=p({style:n,tagName:"div",editable:!0});return document.body.appendChild(r),r.focus(),requestAnimationFrame(()=>{let o=r.innerHTML;return At(r),this.setSelectedRange(e),t(o)})}};V($,"events",{keydown(i){this.isComposing()||this.resetInputSummary(),this.inputSummary.didInput=!0;let t=Qr[i.keyCode];if(t){var e;let r=this.keys;["ctrl","alt","shift","meta"].forEach(o=>{var s;i["".concat(o,"Key")]&&(o==="ctrl"&&(o="control"),r=(s=r)===null||s===void 0?void 0:s[o])}),((e=r)===null||e===void 0?void 0:e[t])!=null&&(this.setInputSummary({keyName:t}),Ot.reset(),r[t].call(this,i))}if(Br(i)){let r=String.fromCharCode(i.keyCode).toLowerCase();if(r){var n;let o=["alt","shift"].map(s=>{if(i["".concat(s,"Key")])return s}).filter(s=>s);o.push(r),(n=this.delegate)!==null&&n!==void 0&&n.inputControllerDidReceiveKeyboardCommand(o)&&i.preventDefault()}}},keypress(i){if(this.inputSummary.eventName!=null||i.metaKey||i.ctrlKey&&!i.altKey)return;let t=Ks(i);var e,n;return t?((e=this.delegate)===null||e===void 0||e.inputControllerWillPerformTyping(),(n=this.responder)===null||n===void 0||n.insertString(t),this.setInputSummary({textAdded:t,didDelete:this.selectionIsExpanded()})):void 0},textInput(i){let{data:t}=i,{textAdded:e}=this.inputSummary;if(e&&e!==t&&e.toUpperCase()===t){var n;let r=this.getSelectedRange();return this.setSelectedRange([r[0],r[1]+e.length]),(n=this.responder)===null||n===void 0||n.insertString(t),this.setInputSummary({textAdded:t}),this.setSelectedRange(r)}},dragenter(i){i.preventDefault()},dragstart(i){var t,e;return this.serializeSelectionToDataTransfer(i.dataTransfer),this.draggedRange=this.getSelectedRange(),(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidStartDrag)===null||e===void 0?void 0:e.call(t)},dragover(i){if(this.draggedRange||this.canAcceptDataTransfer(i.dataTransfer)){i.preventDefault();let n={x:i.clientX,y:i.clientY};var t,e;if(!Zt(n,this.draggingPoint))return this.draggingPoint=n,(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidReceiveDragOverPoint)===null||e===void 0?void 0:e.call(t,this.draggingPoint)}},dragend(i){var t,e;(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidCancelDrag)===null||e===void 0||e.call(t),this.draggedRange=null,this.draggingPoint=null},drop(i){var t,e;i.preventDefault();let n=(t=i.dataTransfer)===null||t===void 0?void 0:t.files,r=i.dataTransfer.getData("application/x-trix-document"),o={x:i.clientX,y:i.clientY};if((e=this.responder)===null||e===void 0||e.setLocationRangeFromPointRange(o),n!=null&&n.length)this.attachFiles(n);else if(this.draggedRange){var s,l;(s=this.delegate)===null||s===void 0||s.inputControllerWillMoveText(),(l=this.responder)===null||l===void 0||l.moveTextFromRange(this.draggedRange),this.draggedRange=null,this.requestRender()}else if(r){var c;let u=q.fromJSONString(r);(c=this.responder)===null||c===void 0||c.insertDocument(u),this.requestRender()}this.draggedRange=null,this.draggingPoint=null},cut(i){var t,e;if((t=this.responder)!==null&&t!==void 0&&t.selectionIsExpanded()&&(this.serializeSelectionToDataTransfer(i.clipboardData)&&i.preventDefault(),(e=this.delegate)===null||e===void 0||e.inputControllerWillCutText(),this.deleteInDirection("backward"),i.defaultPrevented))return this.requestRender()},copy(i){var t;(t=this.responder)!==null&&t!==void 0&&t.selectionIsExpanded()&&this.serializeSelectionToDataTransfer(i.clipboardData)&&i.preventDefault()},paste(i){let t=i.clipboardData||i.testClipboardData,e={clipboard:t};if(!t||Gs(i))return void this.getPastedHTMLUsingHiddenElement(F=>{var k,rt,xt;return e.type="text/html",e.html=F,(k=this.delegate)===null||k===void 0||k.inputControllerWillPaste(e),(rt=this.responder)===null||rt===void 0||rt.insertHTML(e.html),this.requestRender(),(xt=this.delegate)===null||xt===void 0?void 0:xt.inputControllerDidPaste(e)});let n=t.getData("URL"),r=t.getData("text/html"),o=t.getData("public.url-name");if(n){var s,l,c;let F;e.type="text/html",F=o?xi(o).trim():n,e.html=this.createLinkHTML(n,F),(s=this.delegate)===null||s===void 0||s.inputControllerWillPaste(e),this.setInputSummary({textAdded:F,didDelete:this.selectionIsExpanded()}),(l=this.responder)===null||l===void 0||l.insertHTML(e.html),this.requestRender(),(c=this.delegate)===null||c===void 0||c.inputControllerDidPaste(e)}else if(Mr(t)){var u,d,C;e.type="text/plain",e.string=t.getData("text/plain"),(u=this.delegate)===null||u===void 0||u.inputControllerWillPaste(e),this.setInputSummary({textAdded:e.string,didDelete:this.selectionIsExpanded()}),(d=this.responder)===null||d===void 0||d.insertString(e.string),this.requestRender(),(C=this.delegate)===null||C===void 0||C.inputControllerDidPaste(e)}else if(r){var T,J,Q;e.type="text/html",e.html=r,(T=this.delegate)===null||T===void 0||T.inputControllerWillPaste(e),(J=this.responder)===null||J===void 0||J.insertHTML(e.html),this.requestRender(),(Q=this.delegate)===null||Q===void 0||Q.inputControllerDidPaste(e)}else if(Array.from(t.types).includes("Files")){var M,mt;let F=(M=t.items)===null||M===void 0||(M=M[0])===null||M===void 0||(mt=M.getAsFile)===null||mt===void 0?void 0:mt.call(M);if(F){var yt,Qt,te;let k=qs(F);!F.name&&k&&(F.name="pasted-file-".concat(++Hs,".").concat(k)),e.type="File",e.file=F,(yt=this.delegate)===null||yt===void 0||yt.inputControllerWillAttachFiles(),(Qt=this.responder)===null||Qt===void 0||Qt.insertFile(e.file),this.requestRender(),(te=this.delegate)===null||te===void 0||te.inputControllerDidPaste(e)}}i.preventDefault()},compositionstart(i){return this.getCompositionInput().start(i.data)},compositionupdate(i){return this.getCompositionInput().update(i.data)},compositionend(i){return this.getCompositionInput().end(i.data)},beforeinput(i){this.inputSummary.didInput=!0},input(i){return this.inputSummary.didInput=!0,i.stopPropagation()}}),V($,"keys",{backspace(i){var t;return(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),this.deleteInDirection("backward",i)},delete(i){var t;return(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),this.deleteInDirection("forward",i)},return(i){var t,e;return this.setInputSummary({preferDocument:!0}),(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),(e=this.responder)===null||e===void 0?void 0:e.insertLineBreak()},tab(i){var t,e;(t=this.responder)!==null&&t!==void 0&&t.canIncreaseNestingLevel()&&((e=this.responder)===null||e===void 0||e.increaseNestingLevel(),this.requestRender(),i.preventDefault())},left(i){var t;if(this.selectionIsInCursorTarget())return i.preventDefault(),(t=this.responder)===null||t===void 0?void 0:t.moveCursorInDirection("backward")},right(i){var t;if(this.selectionIsInCursorTarget())return i.preventDefault(),(t=this.responder)===null||t===void 0?void 0:t.moveCursorInDirection("forward")},control:{d(i){var t;return(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),this.deleteInDirection("forward",i)},h(i){var t;return(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),this.deleteInDirection("backward",i)},o(i){var t,e;return i.preventDefault(),(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),(e=this.responder)===null||e===void 0||e.insertString(` -`,{updatePosition:!1}),this.requestRender()}},shift:{return(i){var t,e;(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),(e=this.responder)===null||e===void 0||e.insertString(` -`),this.requestRender(),i.preventDefault()},tab(i){var t,e;(t=this.responder)!==null&&t!==void 0&&t.canDecreaseNestingLevel()&&((e=this.responder)===null||e===void 0||e.decreaseNestingLevel(),this.requestRender(),i.preventDefault())},left(i){if(this.selectionIsInCursorTarget())return i.preventDefault(),this.expandSelectionInDirection("backward")},right(i){if(this.selectionIsInCursorTarget())return i.preventDefault(),this.expandSelectionInDirection("forward")}},alt:{backspace(i){var t;return this.setInputSummary({preferDocument:!1}),(t=this.delegate)===null||t===void 0?void 0:t.inputControllerWillPerformTyping()}},meta:{backspace(i){var t;return this.setInputSummary({preferDocument:!1}),(t=this.delegate)===null||t===void 0?void 0:t.inputControllerWillPerformTyping()}}}),$.proxyMethod("responder?.getSelectedRange"),$.proxyMethod("responder?.setSelectedRange"),$.proxyMethod("responder?.expandSelectionInDirection"),$.proxyMethod("responder?.selectionIsInCursorTarget"),$.proxyMethod("responder?.selectionIsExpanded");var qs=i=>{var t;return(t=i.type)===null||t===void 0||(t=t.match(/\/(\w+)$/))===null||t===void 0?void 0:t[1]},Js=!((Hn=" ".codePointAt)===null||Hn===void 0||!Hn.call(" ",0)),Ks=function(i){if(i.key&&Js&&i.key.codePointAt(0)===i.keyCode)return i.key;{let t;if(i.which===null?t=i.keyCode:i.which!==0&&i.charCode!==0&&(t=i.charCode),t!=null&&Qr[t]!=="escape")return Nt.fromCodepoints([t]).toString()}},Gs=function(i){let t=i.clipboardData;if(t){if(t.types.includes("text/html")){for(let e of t.types){let n=/^CorePasteboardFlavorType/.test(e),r=/^dyn\./.test(e)&&t.getData(e);if(n||r)return!0}return!1}{let e=t.types.includes("com.apple.webarchive"),n=t.types.includes("com.apple.flat-rtfd");return e||n}}},nt=class extends R{constructor(t){super(...arguments),this.inputController=t,this.responder=this.inputController.responder,this.delegate=this.inputController.delegate,this.inputSummary=this.inputController.inputSummary,this.data={}}start(t){if(this.data.start=t,this.isSignificant()){var e,n;this.inputSummary.eventName==="keypress"&&this.inputSummary.textAdded&&((n=this.responder)===null||n===void 0||n.deleteInDirection("left")),this.selectionIsExpanded()||(this.insertPlaceholder(),this.requestRender()),this.range=(e=this.responder)===null||e===void 0?void 0:e.getSelectedRange()}}update(t){if(this.data.update=t,this.isSignificant()){let e=this.selectPlaceholder();e&&(this.forgetPlaceholder(),this.range=e)}}end(t){return this.data.end=t,this.isSignificant()?(this.forgetPlaceholder(),this.canApplyToDocument()?(this.setInputSummary({preferDocument:!0,didInput:!1}),(e=this.delegate)===null||e===void 0||e.inputControllerWillPerformTyping(),(n=this.responder)===null||n===void 0||n.setSelectedRange(this.range),(r=this.responder)===null||r===void 0||r.insertString(this.data.end),(o=this.responder)===null||o===void 0?void 0:o.setSelectedRange(this.range[0]+this.data.end.length)):this.data.start!=null||this.data.update!=null?(this.requestReparse(),this.inputController.reset()):void 0):this.inputController.reset();var e,n,r,o}getEndData(){return this.data.end}isEnded(){return this.getEndData()!=null}isSignificant(){return!zs.composesExistingText||this.inputSummary.didInput}canApplyToDocument(){var t,e;return((t=this.data.start)===null||t===void 0?void 0:t.length)===0&&((e=this.data.end)===null||e===void 0?void 0:e.length)>0&&this.range}};nt.proxyMethod("inputController.setInputSummary"),nt.proxyMethod("inputController.requestRender"),nt.proxyMethod("inputController.requestReparse"),nt.proxyMethod("responder?.selectionIsExpanded"),nt.proxyMethod("responder?.insertPlaceholder"),nt.proxyMethod("responder?.selectPlaceholder"),nt.proxyMethod("responder?.forgetPlaceholder");var wt=class extends $t{constructor(){super(...arguments),this.render=this.render.bind(this)}elementDidMutate(){return this.scheduledRender?this.composing?(t=this.delegate)===null||t===void 0||(e=t.inputControllerDidAllowUnhandledInput)===null||e===void 0?void 0:e.call(t):void 0:this.reparse();var t,e}scheduleRender(){return this.scheduledRender?this.scheduledRender:this.scheduledRender=requestAnimationFrame(this.render)}render(){var t,e;cancelAnimationFrame(this.scheduledRender),this.scheduledRender=null,this.composing||(e=this.delegate)===null||e===void 0||e.render(),(t=this.afterRender)===null||t===void 0||t.call(this),this.afterRender=null}reparse(){var t;return(t=this.delegate)===null||t===void 0?void 0:t.reparse()}insertString(){var t;let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0;return(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),this.withTargetDOMRange(function(){var r;return(r=this.responder)===null||r===void 0?void 0:r.insertString(e,n)})}toggleAttributeIfSupported(t){var e;if(Qn().includes(t))return(e=this.delegate)===null||e===void 0||e.inputControllerWillPerformFormatting(t),this.withTargetDOMRange(function(){var n;return(n=this.responder)===null||n===void 0?void 0:n.toggleCurrentAttribute(t)})}activateAttributeIfSupported(t,e){var n;if(Qn().includes(t))return(n=this.delegate)===null||n===void 0||n.inputControllerWillPerformFormatting(t),this.withTargetDOMRange(function(){var r;return(r=this.responder)===null||r===void 0?void 0:r.setCurrentAttribute(t,e)})}deleteInDirection(t){let{recordUndoEntry:e}=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{recordUndoEntry:!0};var n;e&&((n=this.delegate)===null||n===void 0||n.inputControllerWillPerformTyping());let r=()=>{var s;return(s=this.responder)===null||s===void 0?void 0:s.deleteInDirection(t)},o=this.getTargetDOMRange({minLength:this.composing?1:2});return o?this.withTargetDOMRange(o,r):r()}withTargetDOMRange(t,e){var n;return typeof t=="function"&&(e=t,t=this.getTargetDOMRange()),t?(n=this.responder)===null||n===void 0?void 0:n.withTargetDOMRange(t,e.bind(this)):(Ot.reset(),e.call(this))}getTargetDOMRange(){var t,e;let{minLength:n}=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{minLength:0},r=(t=(e=this.event).getTargetRanges)===null||t===void 0?void 0:t.call(e);if(r&&r.length){let o=Ys(r[0]);if(n===0||o.toString().length>=n)return o}}withEvent(t,e){let n;this.event=t;try{n=e.call(this)}finally{this.event=null}return n}};V(wt,"events",{keydown(i){if(Br(i)){var t;let e=Zs(i);(t=this.delegate)!==null&&t!==void 0&&t.inputControllerDidReceiveKeyboardCommand(e)&&i.preventDefault()}else{let e=i.key;i.altKey&&(e+="+Alt"),i.shiftKey&&(e+="+Shift");let n=this.constructor.keys[e];if(n)return this.withEvent(i,n)}},paste(i){var t;let e,n=(t=i.clipboardData)===null||t===void 0?void 0:t.getData("URL");return to(i)?(i.preventDefault(),this.attachFiles(i.clipboardData.files)):Xs(i)?(i.preventDefault(),e={type:"text/plain",string:i.clipboardData.getData("text/plain")},(r=this.delegate)===null||r===void 0||r.inputControllerWillPaste(e),(o=this.responder)===null||o===void 0||o.insertString(e.string),this.render(),(s=this.delegate)===null||s===void 0?void 0:s.inputControllerDidPaste(e)):n?(i.preventDefault(),e={type:"text/html",html:this.createLinkHTML(n)},(l=this.delegate)===null||l===void 0||l.inputControllerWillPaste(e),(c=this.responder)===null||c===void 0||c.insertHTML(e.html),this.render(),(u=this.delegate)===null||u===void 0?void 0:u.inputControllerDidPaste(e)):void 0;var r,o,s,l,c,u},beforeinput(i){let t=this.constructor.inputTypes[i.inputType],e=(n=i,!(!/iPhone|iPad/.test(navigator.userAgent)||n.inputType&&n.inputType!=="insertParagraph"));var n;t&&(this.withEvent(i,t),e||this.scheduleRender()),e&&this.render()},input(i){Ot.reset()},dragstart(i){var t,e;(t=this.responder)!==null&&t!==void 0&&t.selectionContainsAttachments()&&(i.dataTransfer.setData("application/x-trix-dragging",!0),this.dragging={range:(e=this.responder)===null||e===void 0?void 0:e.getSelectedRange(),point:Jn(i)})},dragenter(i){qn(i)&&i.preventDefault()},dragover(i){if(this.dragging){i.preventDefault();let e=Jn(i);var t;if(!Zt(e,this.dragging.point))return this.dragging.point=e,(t=this.responder)===null||t===void 0?void 0:t.setLocationRangeFromPointRange(e)}else qn(i)&&i.preventDefault()},drop(i){var t,e;if(this.dragging)return i.preventDefault(),(t=this.delegate)===null||t===void 0||t.inputControllerWillMoveText(),(e=this.responder)===null||e===void 0||e.moveTextFromRange(this.dragging.range),this.dragging=null,this.scheduleRender();if(qn(i)){var n;i.preventDefault();let r=Jn(i);return(n=this.responder)===null||n===void 0||n.setLocationRangeFromPointRange(r),this.attachFiles(i.dataTransfer.files)}},dragend(){var i;this.dragging&&((i=this.responder)===null||i===void 0||i.setSelectedRange(this.dragging.range),this.dragging=null)},compositionend(i){this.composing&&(this.composing=!1,xe.recentAndroid||this.scheduleRender())}}),V(wt,"keys",{ArrowLeft(){var i,t;if((i=this.responder)!==null&&i!==void 0&&i.shouldManageMovingCursorInDirection("backward"))return this.event.preventDefault(),(t=this.responder)===null||t===void 0?void 0:t.moveCursorInDirection("backward")},ArrowRight(){var i,t;if((i=this.responder)!==null&&i!==void 0&&i.shouldManageMovingCursorInDirection("forward"))return this.event.preventDefault(),(t=this.responder)===null||t===void 0?void 0:t.moveCursorInDirection("forward")},Backspace(){var i,t,e;if((i=this.responder)!==null&&i!==void 0&&i.shouldManageDeletingInDirection("backward"))return this.event.preventDefault(),(t=this.delegate)===null||t===void 0||t.inputControllerWillPerformTyping(),(e=this.responder)===null||e===void 0||e.deleteInDirection("backward"),this.render()},Tab(){var i,t;if((i=this.responder)!==null&&i!==void 0&&i.canIncreaseNestingLevel())return this.event.preventDefault(),(t=this.responder)===null||t===void 0||t.increaseNestingLevel(),this.render()},"Tab+Shift"(){var i,t;if((i=this.responder)!==null&&i!==void 0&&i.canDecreaseNestingLevel())return this.event.preventDefault(),(t=this.responder)===null||t===void 0||t.decreaseNestingLevel(),this.render()}}),V(wt,"inputTypes",{deleteByComposition(){return this.deleteInDirection("backward",{recordUndoEntry:!1})},deleteByCut(){return this.deleteInDirection("backward")},deleteByDrag(){return this.event.preventDefault(),this.withTargetDOMRange(function(){var i;this.deleteByDragRange=(i=this.responder)===null||i===void 0?void 0:i.getSelectedRange()})},deleteCompositionText(){return this.deleteInDirection("backward",{recordUndoEntry:!1})},deleteContent(){return this.deleteInDirection("backward")},deleteContentBackward(){return this.deleteInDirection("backward")},deleteContentForward(){return this.deleteInDirection("forward")},deleteEntireSoftLine(){return this.deleteInDirection("forward")},deleteHardLineBackward(){return this.deleteInDirection("backward")},deleteHardLineForward(){return this.deleteInDirection("forward")},deleteSoftLineBackward(){return this.deleteInDirection("backward")},deleteSoftLineForward(){return this.deleteInDirection("forward")},deleteWordBackward(){return this.deleteInDirection("backward")},deleteWordForward(){return this.deleteInDirection("forward")},formatBackColor(){return this.activateAttributeIfSupported("backgroundColor",this.event.data)},formatBold(){return this.toggleAttributeIfSupported("bold")},formatFontColor(){return this.activateAttributeIfSupported("color",this.event.data)},formatFontName(){return this.activateAttributeIfSupported("font",this.event.data)},formatIndent(){var i;if((i=this.responder)!==null&&i!==void 0&&i.canIncreaseNestingLevel())return this.withTargetDOMRange(function(){var t;return(t=this.responder)===null||t===void 0?void 0:t.increaseNestingLevel()})},formatItalic(){return this.toggleAttributeIfSupported("italic")},formatJustifyCenter(){return this.toggleAttributeIfSupported("justifyCenter")},formatJustifyFull(){return this.toggleAttributeIfSupported("justifyFull")},formatJustifyLeft(){return this.toggleAttributeIfSupported("justifyLeft")},formatJustifyRight(){return this.toggleAttributeIfSupported("justifyRight")},formatOutdent(){var i;if((i=this.responder)!==null&&i!==void 0&&i.canDecreaseNestingLevel())return this.withTargetDOMRange(function(){var t;return(t=this.responder)===null||t===void 0?void 0:t.decreaseNestingLevel()})},formatRemove(){this.withTargetDOMRange(function(){for(let e in(i=this.responder)===null||i===void 0?void 0:i.getCurrentAttributes()){var i,t;(t=this.responder)===null||t===void 0||t.removeCurrentAttribute(e)}})},formatSetBlockTextDirection(){return this.activateAttributeIfSupported("blockDir",this.event.data)},formatSetInlineTextDirection(){return this.activateAttributeIfSupported("textDir",this.event.data)},formatStrikeThrough(){return this.toggleAttributeIfSupported("strike")},formatSubscript(){return this.toggleAttributeIfSupported("sub")},formatSuperscript(){return this.toggleAttributeIfSupported("sup")},formatUnderline(){return this.toggleAttributeIfSupported("underline")},historyRedo(){var i;return(i=this.delegate)===null||i===void 0?void 0:i.inputControllerWillPerformRedo()},historyUndo(){var i;return(i=this.delegate)===null||i===void 0?void 0:i.inputControllerWillPerformUndo()},insertCompositionText(){return this.composing=!0,this.insertString(this.event.data)},insertFromComposition(){return this.composing=!1,this.insertString(this.event.data)},insertFromDrop(){let i=this.deleteByDragRange;var t;if(i)return this.deleteByDragRange=null,(t=this.delegate)===null||t===void 0||t.inputControllerWillMoveText(),this.withTargetDOMRange(function(){var e;return(e=this.responder)===null||e===void 0?void 0:e.moveTextFromRange(i)})},insertFromPaste(){let{dataTransfer:i}=this.event,t={dataTransfer:i},e=i.getData("URL"),n=i.getData("text/html");if(e){var r;let c;this.event.preventDefault(),t.type="text/html";let u=i.getData("public.url-name");c=u?xi(u).trim():e,t.html=this.createLinkHTML(e,c),(r=this.delegate)===null||r===void 0||r.inputControllerWillPaste(t),this.withTargetDOMRange(function(){var d;return(d=this.responder)===null||d===void 0?void 0:d.insertHTML(t.html)}),this.afterRender=()=>{var d;return(d=this.delegate)===null||d===void 0?void 0:d.inputControllerDidPaste(t)}}else if(Mr(i)){var o;t.type="text/plain",t.string=i.getData("text/plain"),(o=this.delegate)===null||o===void 0||o.inputControllerWillPaste(t),this.withTargetDOMRange(function(){var c;return(c=this.responder)===null||c===void 0?void 0:c.insertString(t.string)}),this.afterRender=()=>{var c;return(c=this.delegate)===null||c===void 0?void 0:c.inputControllerDidPaste(t)}}else if($s(this.event)){var s;t.type="File",t.file=i.files[0],(s=this.delegate)===null||s===void 0||s.inputControllerWillPaste(t),this.withTargetDOMRange(function(){var c;return(c=this.responder)===null||c===void 0?void 0:c.insertFile(t.file)}),this.afterRender=()=>{var c;return(c=this.delegate)===null||c===void 0?void 0:c.inputControllerDidPaste(t)}}else if(n){var l;this.event.preventDefault(),t.type="text/html",t.html=n,(l=this.delegate)===null||l===void 0||l.inputControllerWillPaste(t),this.withTargetDOMRange(function(){var c;return(c=this.responder)===null||c===void 0?void 0:c.insertHTML(t.html)}),this.afterRender=()=>{var c;return(c=this.delegate)===null||c===void 0?void 0:c.inputControllerDidPaste(t)}}},insertFromYank(){return this.insertString(this.event.data)},insertLineBreak(){return this.insertString(` -`)},insertLink(){return this.activateAttributeIfSupported("href",this.event.data)},insertOrderedList(){return this.toggleAttributeIfSupported("number")},insertParagraph(){var i;return(i=this.delegate)===null||i===void 0||i.inputControllerWillPerformTyping(),this.withTargetDOMRange(function(){var t;return(t=this.responder)===null||t===void 0?void 0:t.insertLineBreak()})},insertReplacementText(){let i=this.event.dataTransfer.getData("text/plain"),t=this.event.getTargetRanges()[0];this.withTargetDOMRange(t,()=>{this.insertString(i,{updatePosition:!1})})},insertText(){var i;return this.insertString(this.event.data||((i=this.event.dataTransfer)===null||i===void 0?void 0:i.getData("text/plain")))},insertTranspose(){return this.insertString(this.event.data)},insertUnorderedList(){return this.toggleAttributeIfSupported("bullet")}});var Ys=function(i){let t=document.createRange();return t.setStart(i.startContainer,i.startOffset),t.setEnd(i.endContainer,i.endOffset),t},qn=i=>{var t;return Array.from(((t=i.dataTransfer)===null||t===void 0?void 0:t.types)||[]).includes("Files")},$s=i=>{var t;return((t=i.dataTransfer.files)===null||t===void 0?void 0:t[0])&&!to(i)&&!(e=>{let{dataTransfer:n}=e;return n.types.includes("Files")&&n.types.includes("text/html")&&n.getData("text/html").includes("urn:schemas-microsoft-com:office:office")})(i)},to=function(i){let t=i.clipboardData;if(t)return Array.from(t.types).filter(e=>e.match(/file/i)).length===t.types.length&&t.files.length>=1},Xs=function(i){let t=i.clipboardData;if(t)return t.types.includes("text/plain")&&t.types.length===1},Zs=function(i){let t=[];return i.altKey&&t.push("alt"),i.shiftKey&&t.push("shift"),t.push(i.key),t},Jn=i=>({x:i.clientX,y:i.clientY}),ui="[data-trix-attribute]",hi="[data-trix-action]",Qs="".concat(ui,", ").concat(hi),cn="[data-trix-dialog]",ta="".concat(cn,"[data-trix-active]"),ea="".concat(cn," [data-trix-method]"),kr="".concat(cn," [data-trix-input]"),Rr=(i,t)=>(t||(t=Ut(i)),i.querySelector("[data-trix-input][name='".concat(t,"']"))),Tr=i=>i.getAttribute("data-trix-action"),Ut=i=>i.getAttribute("data-trix-attribute")||i.getAttribute("data-trix-dialog-attribute"),sn=class extends R{constructor(t){super(t),this.didClickActionButton=this.didClickActionButton.bind(this),this.didClickAttributeButton=this.didClickAttributeButton.bind(this),this.didClickDialogButton=this.didClickDialogButton.bind(this),this.didKeyDownDialogInput=this.didKeyDownDialogInput.bind(this),this.element=t,this.attributes={},this.actions={},this.resetDialogInputs(),S("mousedown",{onElement:this.element,matchingSelector:hi,withCallback:this.didClickActionButton}),S("mousedown",{onElement:this.element,matchingSelector:ui,withCallback:this.didClickAttributeButton}),S("click",{onElement:this.element,matchingSelector:Qs,preventDefault:!0}),S("click",{onElement:this.element,matchingSelector:ea,withCallback:this.didClickDialogButton}),S("keydown",{onElement:this.element,matchingSelector:kr,withCallback:this.didKeyDownDialogInput})}didClickActionButton(t,e){var n;(n=this.delegate)===null||n===void 0||n.toolbarDidClickButton(),t.preventDefault();let r=Tr(e);return this.getDialog(r)?this.toggleDialog(r):(o=this.delegate)===null||o===void 0?void 0:o.toolbarDidInvokeAction(r,e);var o}didClickAttributeButton(t,e){var n;(n=this.delegate)===null||n===void 0||n.toolbarDidClickButton(),t.preventDefault();let r=Ut(e);var o;return this.getDialog(r)?this.toggleDialog(r):(o=this.delegate)===null||o===void 0||o.toolbarDidToggleAttribute(r),this.refreshAttributeButtons()}didClickDialogButton(t,e){let n=vt(e,{matchingSelector:cn});return this[e.getAttribute("data-trix-method")].call(this,n)}didKeyDownDialogInput(t,e){if(t.keyCode===13){t.preventDefault();let n=e.getAttribute("name"),r=this.getDialog(n);this.setAttribute(r)}if(t.keyCode===27)return t.preventDefault(),this.hideDialog()}updateActions(t){return this.actions=t,this.refreshActionButtons()}refreshActionButtons(){return this.eachActionButton((t,e)=>{t.disabled=this.actions[e]===!1})}eachActionButton(t){return Array.from(this.element.querySelectorAll(hi)).map(e=>t(e,Tr(e)))}updateAttributes(t){return this.attributes=t,this.refreshAttributeButtons()}refreshAttributeButtons(){return this.eachAttributeButton((t,e)=>(t.disabled=this.attributes[e]===!1,this.attributes[e]||this.dialogIsVisible(e)?(t.setAttribute("data-trix-active",""),t.classList.add("trix-active")):(t.removeAttribute("data-trix-active"),t.classList.remove("trix-active"))))}eachAttributeButton(t){return Array.from(this.element.querySelectorAll(ui)).map(e=>t(e,Ut(e)))}applyKeyboardCommand(t){let e=JSON.stringify(t.sort());for(let n of Array.from(this.element.querySelectorAll("[data-trix-key]"))){let r=n.getAttribute("data-trix-key").split("+");if(JSON.stringify(r.sort())===e)return de("mousedown",{onElement:n}),!0}return!1}dialogIsVisible(t){let e=this.getDialog(t);if(e)return e.hasAttribute("data-trix-active")}toggleDialog(t){return this.dialogIsVisible(t)?this.hideDialog():this.showDialog(t)}showDialog(t){var e,n;this.hideDialog(),(e=this.delegate)===null||e===void 0||e.toolbarWillShowDialog();let r=this.getDialog(t);r.setAttribute("data-trix-active",""),r.classList.add("trix-active"),Array.from(r.querySelectorAll("input[disabled]")).forEach(s=>{s.removeAttribute("disabled")});let o=Ut(r);if(o){let s=Rr(r,t);s&&(s.value=this.attributes[o]||"",s.select())}return(n=this.delegate)===null||n===void 0?void 0:n.toolbarDidShowDialog(t)}setAttribute(t){var e;let n=Ut(t),r=Rr(t,n);return!r.willValidate||(r.setCustomValidity(""),r.checkValidity()&&this.isSafeAttribute(r))?((e=this.delegate)===null||e===void 0||e.toolbarDidUpdateAttribute(n,r.value),this.hideDialog()):(r.setCustomValidity("Invalid value"),r.setAttribute("data-trix-validate",""),r.classList.add("trix-validate"),r.focus())}isSafeAttribute(t){return!t.hasAttribute("data-trix-validate-href")||Ve.isValidAttribute("a","href",t.value)}removeAttribute(t){var e;let n=Ut(t);return(e=this.delegate)===null||e===void 0||e.toolbarDidRemoveAttribute(n),this.hideDialog()}hideDialog(){let t=this.element.querySelector(ta);var e;if(t)return t.removeAttribute("data-trix-active"),t.classList.remove("trix-active"),this.resetDialogInputs(),(e=this.delegate)===null||e===void 0?void 0:e.toolbarDidHideDialog((n=>n.getAttribute("data-trix-dialog"))(t))}resetDialogInputs(){Array.from(this.element.querySelectorAll(kr)).forEach(t=>{t.setAttribute("disabled","disabled"),t.removeAttribute("data-trix-validate"),t.classList.remove("trix-validate")})}getDialog(t){return this.element.querySelector("[data-trix-dialog=".concat(t,"]"))}},Lt=class extends nn{constructor(t){let{editorElement:e,document:n,html:r}=t;super(...arguments),this.editorElement=e,this.selectionManager=new ct(this.editorElement),this.selectionManager.delegate=this,this.composition=new it,this.composition.delegate=this,this.attachmentManager=new Ge(this.composition.getAttachments()),this.attachmentManager.delegate=this,this.inputController=bi.getLevel()===2?new wt(this.editorElement):new $(this.editorElement),this.inputController.delegate=this,this.inputController.responder=this.composition,this.compositionController=new en(this.editorElement,this.composition),this.compositionController.delegate=this,this.toolbarController=new sn(this.editorElement.toolbarElement),this.toolbarController.delegate=this,this.editor=new Xe(this.composition,this.selectionManager,this.editorElement),n?this.editor.loadDocument(n):this.editor.loadHTML(r)}registerSelectionManager(){return Ot.registerSelectionManager(this.selectionManager)}unregisterSelectionManager(){return Ot.unregisterSelectionManager(this.selectionManager)}render(){return this.compositionController.render()}reparse(){return this.composition.replaceHTML(this.editorElement.innerHTML)}compositionDidChangeDocument(t){if(this.notifyEditorElement("document-change"),!this.handlingInput)return this.render()}compositionDidChangeCurrentAttributes(t){return this.currentAttributes=t,this.toolbarController.updateAttributes(this.currentAttributes),this.updateCurrentActions(),this.notifyEditorElement("attributes-change",{attributes:this.currentAttributes})}compositionDidPerformInsertionAtRange(t){this.pasting&&(this.pastedRange=t)}compositionShouldAcceptFile(t){return this.notifyEditorElement("file-accept",{file:t})}compositionDidAddAttachment(t){let e=this.attachmentManager.manageAttachment(t);return this.notifyEditorElement("attachment-add",{attachment:e})}compositionDidEditAttachment(t){this.compositionController.rerenderViewForObject(t);let e=this.attachmentManager.manageAttachment(t);return this.notifyEditorElement("attachment-edit",{attachment:e}),this.notifyEditorElement("change")}compositionDidChangeAttachmentPreviewURL(t){return this.compositionController.invalidateViewForObject(t),this.notifyEditorElement("change")}compositionDidRemoveAttachment(t){let e=this.attachmentManager.unmanageAttachment(t);return this.notifyEditorElement("attachment-remove",{attachment:e})}compositionDidStartEditingAttachment(t,e){return this.attachmentLocationRange=this.composition.document.getLocationRangeOfAttachment(t),this.compositionController.installAttachmentEditorForAttachment(t,e),this.selectionManager.setLocationRange(this.attachmentLocationRange)}compositionDidStopEditingAttachment(t){this.compositionController.uninstallAttachmentEditor(),this.attachmentLocationRange=null}compositionDidRequestChangingSelectionToLocationRange(t){if(!this.loadingSnapshot||this.isFocused())return this.requestedLocationRange=t,this.compositionRevisionWhenLocationRangeRequested=this.composition.revision,this.handlingInput?void 0:this.render()}compositionWillLoadSnapshot(){this.loadingSnapshot=!0}compositionDidLoadSnapshot(){this.compositionController.refreshViewCache(),this.render(),this.loadingSnapshot=!1}getSelectionManager(){return this.selectionManager}attachmentManagerDidRequestRemovalOfAttachment(t){return this.removeAttachment(t)}compositionControllerWillSyncDocumentView(){return this.inputController.editorWillSyncDocumentView(),this.selectionManager.lock(),this.selectionManager.clearSelection()}compositionControllerDidSyncDocumentView(){return this.inputController.editorDidSyncDocumentView(),this.selectionManager.unlock(),this.updateCurrentActions(),this.notifyEditorElement("sync")}compositionControllerDidRender(){this.requestedLocationRange&&(this.compositionRevisionWhenLocationRangeRequested===this.composition.revision&&this.selectionManager.setLocationRange(this.requestedLocationRange),this.requestedLocationRange=null,this.compositionRevisionWhenLocationRangeRequested=null),this.renderedCompositionRevision!==this.composition.revision&&(this.runEditorFilters(),this.composition.updateCurrentAttributes(),this.notifyEditorElement("render")),this.renderedCompositionRevision=this.composition.revision}compositionControllerDidFocus(){return this.isFocusedInvisibly()&&this.setLocationRange({index:0,offset:0}),this.toolbarController.hideDialog(),this.notifyEditorElement("focus")}compositionControllerDidBlur(){return this.notifyEditorElement("blur")}compositionControllerDidSelectAttachment(t,e){return this.toolbarController.hideDialog(),this.composition.editAttachment(t,e)}compositionControllerDidRequestDeselectingAttachment(t){let e=this.attachmentLocationRange||this.composition.document.getLocationRangeOfAttachment(t);return this.selectionManager.setLocationRange(e[1])}compositionControllerWillUpdateAttachment(t){return this.editor.recordUndoEntry("Edit Attachment",{context:t.id,consolidatable:!0})}compositionControllerDidRequestRemovalOfAttachment(t){return this.removeAttachment(t)}inputControllerWillHandleInput(){this.handlingInput=!0,this.requestedRender=!1}inputControllerDidRequestRender(){this.requestedRender=!0}inputControllerDidHandleInput(){if(this.handlingInput=!1,this.requestedRender)return this.requestedRender=!1,this.render()}inputControllerDidAllowUnhandledInput(){return this.notifyEditorElement("change")}inputControllerDidRequestReparse(){return this.reparse()}inputControllerWillPerformTyping(){return this.recordTypingUndoEntry()}inputControllerWillPerformFormatting(t){return this.recordFormattingUndoEntry(t)}inputControllerWillCutText(){return this.editor.recordUndoEntry("Cut")}inputControllerWillPaste(t){return this.editor.recordUndoEntry("Paste"),this.pasting=!0,this.notifyEditorElement("before-paste",{paste:t})}inputControllerDidPaste(t){return t.range=this.pastedRange,this.pastedRange=null,this.pasting=null,this.notifyEditorElement("paste",{paste:t})}inputControllerWillMoveText(){return this.editor.recordUndoEntry("Move")}inputControllerWillAttachFiles(){return this.editor.recordUndoEntry("Drop Files")}inputControllerWillPerformUndo(){return this.editor.undo()}inputControllerWillPerformRedo(){return this.editor.redo()}inputControllerDidReceiveKeyboardCommand(t){return this.toolbarController.applyKeyboardCommand(t)}inputControllerDidStartDrag(){this.locationRangeBeforeDrag=this.selectionManager.getLocationRange()}inputControllerDidReceiveDragOverPoint(t){return this.selectionManager.setLocationRangeFromPointRange(t)}inputControllerDidCancelDrag(){this.selectionManager.setLocationRange(this.locationRangeBeforeDrag),this.locationRangeBeforeDrag=null}locationRangeDidChange(t){return this.composition.updateCurrentAttributes(),this.updateCurrentActions(),this.attachmentLocationRange&&!We(this.attachmentLocationRange,t)&&this.composition.stopEditingAttachment(),this.notifyEditorElement("selection-change")}toolbarDidClickButton(){if(!this.getLocationRange())return this.setLocationRange({index:0,offset:0})}toolbarDidInvokeAction(t,e){return this.invokeAction(t,e)}toolbarDidToggleAttribute(t){if(this.recordFormattingUndoEntry(t),this.composition.toggleCurrentAttribute(t),this.render(),!this.selectionFrozen)return this.editorElement.focus()}toolbarDidUpdateAttribute(t,e){if(this.recordFormattingUndoEntry(t),this.composition.setCurrentAttribute(t,e),this.render(),!this.selectionFrozen)return this.editorElement.focus()}toolbarDidRemoveAttribute(t){if(this.recordFormattingUndoEntry(t),this.composition.removeCurrentAttribute(t),this.render(),!this.selectionFrozen)return this.editorElement.focus()}toolbarWillShowDialog(t){return this.composition.expandSelectionForEditing(),this.freezeSelection()}toolbarDidShowDialog(t){return this.notifyEditorElement("toolbar-dialog-show",{dialogName:t})}toolbarDidHideDialog(t){return this.thawSelection(),this.editorElement.focus(),this.notifyEditorElement("toolbar-dialog-hide",{dialogName:t})}freezeSelection(){if(!this.selectionFrozen)return this.selectionManager.lock(),this.composition.freezeSelection(),this.selectionFrozen=!0,this.render()}thawSelection(){if(this.selectionFrozen)return this.composition.thawSelection(),this.selectionManager.unlock(),this.selectionFrozen=!1,this.render()}canInvokeAction(t){return!!this.actionIsExternal(t)||!((e=this.actions[t])===null||e===void 0||(e=e.test)===null||e===void 0||!e.call(this));var e}invokeAction(t,e){return this.actionIsExternal(t)?this.notifyEditorElement("action-invoke",{actionName:t,invokingElement:e}):(n=this.actions[t])===null||n===void 0||(n=n.perform)===null||n===void 0?void 0:n.call(this);var n}actionIsExternal(t){return/^x-./.test(t)}getCurrentActions(){let t={};for(let e in this.actions)t[e]=this.canInvokeAction(e);return t}updateCurrentActions(){let t=this.getCurrentActions();if(!Zt(t,this.currentActions))return this.currentActions=t,this.toolbarController.updateActions(this.currentActions),this.notifyEditorElement("actions-change",{actions:this.currentActions})}runEditorFilters(){let t=this.composition.getSnapshot();if(Array.from(this.editor.filters).forEach(r=>{let{document:o,selectedRange:s}=t;t=r.call(this.editor,t)||{},t.document||(t.document=o),t.selectedRange||(t.selectedRange=s)}),e=t,n=this.composition.getSnapshot(),!We(e.selectedRange,n.selectedRange)||!e.document.isEqualTo(n.document))return this.composition.loadSnapshot(t);var e,n}updateInputElement(){let t=function(e,n){let r=Ns[n];if(r)return r(e);throw new Error("unknown content type: ".concat(n))}(this.compositionController.getSerializableElement(),"text/html");return this.editorElement.setFormValue(t)}notifyEditorElement(t,e){switch(t){case"document-change":this.documentChangedSinceLastRender=!0;break;case"render":this.documentChangedSinceLastRender&&(this.documentChangedSinceLastRender=!1,this.notifyEditorElement("change"));break;case"change":case"attachment-add":case"attachment-edit":case"attachment-remove":this.updateInputElement()}return this.editorElement.notify(t,e)}removeAttachment(t){return this.editor.recordUndoEntry("Delete Attachment"),this.composition.removeAttachment(t),this.render()}recordFormattingUndoEntry(t){let e=L(t),n=this.selectionManager.getLocationRange();if(e||!ut(n))return this.editor.recordUndoEntry("Formatting",{context:this.getUndoContext(),consolidatable:!0})}recordTypingUndoEntry(){return this.editor.recordUndoEntry("Typing",{context:this.getUndoContext(this.currentAttributes),consolidatable:!0})}getUndoContext(){for(var t=arguments.length,e=new Array(t),n=0;n0?Math.floor(new Date().getTime()/$n.interval):0}isFocused(){var t;return this.editorElement===((t=this.editorElement.ownerDocument)===null||t===void 0?void 0:t.activeElement)}isFocusedInvisibly(){return this.isFocused()&&!this.getLocationRange()}get actions(){return this.constructor.actions}};V(Lt,"actions",{undo:{test(){return this.editor.canUndo()},perform(){return this.editor.undo()}},redo:{test(){return this.editor.canRedo()},perform(){return this.editor.redo()}},link:{test(){return this.editor.canActivateAttribute("href")}},increaseNestingLevel:{test(){return this.editor.canIncreaseNestingLevel()},perform(){return this.editor.increaseNestingLevel()&&this.render()}},decreaseNestingLevel:{test(){return this.editor.canDecreaseNestingLevel()},perform(){return this.editor.decreaseNestingLevel()&&this.render()}},attachFiles:{test:()=>!0,perform(){return bi.pickFiles(this.editor.insertFiles)}}}),Lt.proxyMethod("getSelectionManager().setLocationRange"),Lt.proxyMethod("getSelectionManager().getLocationRange");var na=Object.freeze({__proto__:null,AttachmentEditorController:tn,CompositionController:en,Controller:nn,EditorController:Lt,InputController:$t,Level0InputController:$,Level2InputController:wt,ToolbarController:sn}),ia=Object.freeze({__proto__:null,MutationObserver:rn,SelectionChangeObserver:Ue}),ra=Object.freeze({__proto__:null,FileVerificationOperation:on,ImagePreloadOperation:Ke});Pr("trix-toolbar",`%t { - display: block; -} - -%t { - white-space: nowrap; -} - -%t [data-trix-dialog] { - display: none; -} - -%t [data-trix-dialog][data-trix-active] { - display: block; -} - -%t [data-trix-dialog] [data-trix-validate]:invalid { - background-color: #ffdddd; -}`);var an=class extends HTMLElement{connectedCallback(){this.innerHTML===""&&(this.innerHTML=Fr.getDefaultHTML())}},oa=0,sa=function(i){if(!i.hasAttribute("contenteditable"))return i.setAttribute("contenteditable",""),function(t){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};return e.times=1,S(t,e)}("focus",{onElement:i,withCallback:()=>aa(i)})},aa=function(i){return la(i),ca(i)},la=function(i){var t,e;if((t=(e=document).queryCommandSupported)!==null&&t!==void 0&&t.call(e,"enableObjectResizing"))return document.execCommand("enableObjectResizing",!1,!1),S("mscontrolselect",{onElement:i,preventDefault:!0})},ca=function(i){var t,e;if((t=(e=document).queryCommandSupported)!==null&&t!==void 0&&t.call(e,"DefaultParagraphSeparator")){let{tagName:n}=U.default;if(["div","p"].includes(n))return document.execCommand("DefaultParagraphSeparator",!1,n)}},wr=xe.forcesObjectResizing?{display:"inline",width:"auto"}:{display:"inline-block",width:"1px"};Pr("trix-editor",`%t { - display: block; -} - -%t:empty::before { - content: attr(placeholder); - color: graytext; - cursor: text; - pointer-events: none; - white-space: pre-line; -} - -%t a[contenteditable=false] { - cursor: text; -} - -%t img { - max-width: 100%; - height: auto; -} - -%t `.concat(Rt,` figcaption textarea { - resize: none; -} - -%t `).concat(Rt,` figcaption textarea.trix-autoresize-clone { - position: absolute; - left: -9999px; - max-height: 0px; -} - -%t `).concat(Rt,` figcaption[data-trix-placeholder]:empty::before { - content: attr(data-trix-placeholder); - color: graytext; -} - -%t [data-trix-cursor-target] { - display: `).concat(wr.display,` !important; - width: `).concat(wr.width,` !important; - padding: 0 !important; - margin: 0 !important; - border: none !important; -} - -%t [data-trix-cursor-target=left] { - vertical-align: top !important; - margin-left: -1px !important; -} - -%t [data-trix-cursor-target=right] { - vertical-align: bottom !important; - margin-right: -1px !important; -}`));var lt=new WeakMap,ue=new WeakSet,di=class{constructor(t){var e,n;Jr(e=this,n=ue),n.add(e),fe(this,lt,{writable:!0,value:void 0}),this.element=t,Ci(this,lt,t.attachInternals())}connectedCallback(){Fe(this,ue,Pe).call(this)}disconnectedCallback(){}get labels(){return x(this,lt).labels}get disabled(){var t;return(t=this.element.inputElement)===null||t===void 0?void 0:t.disabled}set disabled(t){this.element.toggleAttribute("disabled",t)}get required(){return this.element.hasAttribute("required")}set required(t){this.element.toggleAttribute("required",t),Fe(this,ue,Pe).call(this)}get validity(){return x(this,lt).validity}get validationMessage(){return x(this,lt).validationMessage}get willValidate(){return x(this,lt).willValidate}setFormValue(t){Fe(this,ue,Pe).call(this)}checkValidity(){return x(this,lt).checkValidity()}reportValidity(){return x(this,lt).reportValidity()}setCustomValidity(t){Fe(this,ue,Pe).call(this,t)}};function Pe(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",{required:t,value:e}=this.element,n=t&&!e,r=!!i,o=p("input",{required:t}),s=i||o.validationMessage;x(this,lt).setValidity({valueMissing:n,customError:r},s)}var Kn=new WeakMap,Gn=new WeakMap,Yn=new WeakMap,gi=class{constructor(t){fe(this,Kn,{writable:!0,value:void 0}),fe(this,Gn,{writable:!0,value:e=>{e.defaultPrevented||e.target===this.element.form&&this.element.reset()}}),fe(this,Yn,{writable:!0,value:e=>{if(e.defaultPrevented||this.element.contains(e.target))return;let n=vt(e.target,{matchingSelector:"label"});n&&Array.from(this.labels).includes(n)&&this.element.focus()}}),this.element=t}connectedCallback(){Ci(this,Kn,function(t){if(t.hasAttribute("aria-label")||t.hasAttribute("aria-labelledby"))return;let e=function(){let n=Array.from(t.labels).map(o=>{if(!o.contains(t))return o.textContent}).filter(o=>o),r=n.join(" ");return r?t.setAttribute("aria-label",r):t.removeAttribute("aria-label")};return e(),S("focus",{onElement:t,withCallback:e})}(this.element)),window.addEventListener("reset",x(this,Gn),!1),window.addEventListener("click",x(this,Yn),!1)}disconnectedCallback(){var t;(t=x(this,Kn))===null||t===void 0||t.destroy(),window.removeEventListener("reset",x(this,Gn),!1),window.removeEventListener("click",x(this,Yn),!1)}get labels(){let t=[];this.element.id&&this.element.ownerDocument&&t.push(...Array.from(this.element.ownerDocument.querySelectorAll("label[for='".concat(this.element.id,"']"))||[]));let e=vt(this.element,{matchingSelector:"label"});return e&&[this.element,null].includes(e.control)&&t.push(e),t}get disabled(){return console.warn("This browser does not support the [disabled] attribute for trix-editor elements."),!1}set disabled(t){console.warn("This browser does not support the [disabled] attribute for trix-editor elements.")}get required(){return console.warn("This browser does not support the [required] attribute for trix-editor elements."),!1}set required(t){console.warn("This browser does not support the [required] attribute for trix-editor elements.")}get validity(){return console.warn("This browser does not support the validity property for trix-editor elements."),null}get validationMessage(){return console.warn("This browser does not support the validationMessage property for trix-editor elements."),""}get willValidate(){return console.warn("This browser does not support the willValidate property for trix-editor elements."),!1}setFormValue(t){}checkValidity(){return console.warn("This browser does not support checkValidity() for trix-editor elements."),!0}reportValidity(){return console.warn("This browser does not support reportValidity() for trix-editor elements."),!0}setCustomValidity(t){console.warn("This browser does not support setCustomValidity(validationMessage) for trix-editor elements.")}},P=new WeakMap,Xt=class extends HTMLElement{constructor(){super(),fe(this,P,{writable:!0,value:void 0}),Ci(this,P,this.constructor.formAssociated?new di(this):new gi(this))}get trixId(){return this.hasAttribute("trix-id")?this.getAttribute("trix-id"):(this.setAttribute("trix-id",++oa),this.trixId)}get labels(){return x(this,P).labels}get disabled(){return x(this,P).disabled}set disabled(t){x(this,P).disabled=t}get required(){return x(this,P).required}set required(t){x(this,P).required=t}get validity(){return x(this,P).validity}get validationMessage(){return x(this,P).validationMessage}get willValidate(){return x(this,P).willValidate}get type(){return this.localName}get toolbarElement(){var t;if(this.hasAttribute("toolbar"))return(t=this.ownerDocument)===null||t===void 0?void 0:t.getElementById(this.getAttribute("toolbar"));if(this.parentNode){let e="trix-toolbar-".concat(this.trixId);return this.setAttribute("toolbar",e),this.internalToolbar=p("trix-toolbar",{id:e}),this.parentNode.insertBefore(this.internalToolbar,this),this.internalToolbar}}get form(){var t;return(t=this.inputElement)===null||t===void 0?void 0:t.form}get inputElement(){var t;if(this.hasAttribute("input"))return(t=this.ownerDocument)===null||t===void 0?void 0:t.getElementById(this.getAttribute("input"));if(this.parentNode){let e="trix-input-".concat(this.trixId);this.setAttribute("input",e);let n=p("input",{type:"hidden",id:e});return this.parentNode.insertBefore(n,this.nextElementSibling),n}}get editor(){var t;return(t=this.editorController)===null||t===void 0?void 0:t.editor}get name(){var t;return(t=this.inputElement)===null||t===void 0?void 0:t.name}get value(){var t;return(t=this.inputElement)===null||t===void 0?void 0:t.value}set value(t){var e;this.defaultValue=t,(e=this.editor)===null||e===void 0||e.loadHTML(this.defaultValue)}attributeChangedCallback(t,e,n){t==="connected"&&this.isConnected&&e!=null&&e!==n&&requestAnimationFrame(()=>this.reconnect())}notify(t,e){if(this.editorController)return de("trix-".concat(t),{onElement:this,attributes:e})}setFormValue(t){this.inputElement&&(this.inputElement.value=t,x(this,P).setFormValue(t))}connectedCallback(){this.hasAttribute("data-trix-internal")||(sa(this),function(t){t.hasAttribute("role")||t.setAttribute("role","textbox")}(this),this.editorController||(de("trix-before-initialize",{onElement:this}),this.editorController=new Lt({editorElement:this,html:this.defaultValue=this.value}),requestAnimationFrame(()=>de("trix-initialize",{onElement:this}))),this.editorController.registerSelectionManager(),x(this,P).connectedCallback(),this.toggleAttribute("connected",!0),function(t){!document.querySelector(":focus")&&t.hasAttribute("autofocus")&&document.querySelector("[autofocus]")===t&&t.focus()}(this))}disconnectedCallback(){var t;(t=this.editorController)===null||t===void 0||t.unregisterSelectionManager(),x(this,P).disconnectedCallback(),this.toggleAttribute("connected",!1)}reconnect(){this.removeInternalToolbar(),this.disconnectedCallback(),this.connectedCallback()}removeInternalToolbar(){var t;(t=this.internalToolbar)===null||t===void 0||t.remove(),this.internalToolbar=null}checkValidity(){return x(this,P).checkValidity()}reportValidity(){return x(this,P).reportValidity()}setCustomValidity(t){x(this,P).setCustomValidity(t)}formDisabledCallback(t){this.inputElement&&(this.inputElement.disabled=t),this.toggleAttribute("contenteditable",!t)}formResetCallback(){this.reset()}reset(){this.value=this.defaultValue}};V(Xt,"formAssociated","ElementInternals"in window),V(Xt,"observedAttributes",["connected"]);var Z={VERSION:po,config:Ce,core:Is,models:Xr,views:Bs,controllers:na,observers:ia,operations:ra,elements:Object.freeze({__proto__:null,TrixEditorElement:Xt,TrixToolbarElement:an}),filters:Object.freeze({__proto__:null,Filter:$e,attachmentGalleryFilter:Yr})};Object.assign(Z,Xr),window.Trix=Z,setTimeout(function(){customElements.get("trix-toolbar")||customElements.define("trix-toolbar",an),customElements.get("trix-editor")||customElements.define("trix-editor",Xt)},0);Z.config.blockAttributes.default.tagName="p";Z.config.blockAttributes.default.breakOnReturn=!0;Z.config.blockAttributes.heading={tagName:"h2",terminal:!0,breakOnReturn:!0,group:!1};Z.config.blockAttributes.subHeading={tagName:"h3",terminal:!0,breakOnReturn:!0,group:!1};Z.config.textAttributes.underline={style:{textDecoration:"underline"},inheritable:!0,parser:i=>window.getComputedStyle(i).textDecoration.includes("underline")};Z.Block.prototype.breaksOnReturn=function(){let i=this.getLastAttribute();return Z.config.blockAttributes[i||"default"]?.breakOnReturn??!1};Z.LineBreakInsertion.prototype.shouldInsertBlockBreak=function(){return this.block.hasAttributes()&&this.block.isListItem()&&!this.block.isEmpty()?this.startLocation.offset>0:this.shouldBreakFormattedBlock()?!1:this.breaksOnReturn};function ua({state:i}){return{state:i,init:function(){this.$refs.trixValue.value=this.state,this.$refs.trix.editor?.loadHTML(this.state??""),this.$watch("state",()=>{document.activeElement!==this.$refs.trix&&(this.$refs.trixValue.value=this.state,this.$refs.trix.editor?.loadHTML(this.state??""))})}}}export{ua as default}; -/*! Bundled license information: - -trix/dist/trix.esm.min.js: - (*! @license DOMPurify 3.2.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.5/LICENSE *) -*/ diff --git a/public/js/filament/forms/components/select.js b/public/js/filament/forms/components/select.js deleted file mode 100644 index fdea5da..0000000 --- a/public/js/filament/forms/components/select.js +++ /dev/null @@ -1,6 +0,0 @@ -var lt=Object.create;var Ge=Object.defineProperty;var ct=Object.getOwnPropertyDescriptor;var ut=Object.getOwnPropertyNames;var ht=Object.getPrototypeOf,dt=Object.prototype.hasOwnProperty;var ft=(se,ie)=>()=>(ie||se((ie={exports:{}}).exports,ie),ie.exports);var pt=(se,ie,X,me)=>{if(ie&&typeof ie=="object"||typeof ie=="function")for(let j of ut(ie))!dt.call(se,j)&&j!==X&&Ge(se,j,{get:()=>ie[j],enumerable:!(me=ct(ie,j))||me.enumerable});return se};var mt=(se,ie,X)=>(X=se!=null?lt(ht(se)):{},pt(ie||!se||!se.__esModule?Ge(X,"default",{value:se,enumerable:!0}):X,se));var $e=ft((Ae,Ye)=>{(function(ie,X){typeof Ae=="object"&&typeof Ye=="object"?Ye.exports=X():typeof define=="function"&&define.amd?define([],X):typeof Ae=="object"?Ae.Choices=X():ie.Choices=X()})(window,function(){return function(){"use strict";var se={282:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0}),i.clearChoices=i.activateChoices=i.filterChoices=i.addChoice=void 0;var _=b(883),h=function(c){var l=c.value,O=c.label,L=c.id,y=c.groupId,D=c.disabled,k=c.elementId,Q=c.customProperties,Z=c.placeholder,ne=c.keyCode;return{type:_.ACTION_TYPES.ADD_CHOICE,value:l,label:O,id:L,groupId:y,disabled:D,elementId:k,customProperties:Q,placeholder:Z,keyCode:ne}};i.addChoice=h;var d=function(c){return{type:_.ACTION_TYPES.FILTER_CHOICES,results:c}};i.filterChoices=d;var a=function(c){return c===void 0&&(c=!0),{type:_.ACTION_TYPES.ACTIVATE_CHOICES,active:c}};i.activateChoices=a;var r=function(){return{type:_.ACTION_TYPES.CLEAR_CHOICES}};i.clearChoices=r},783:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0}),i.addGroup=void 0;var _=b(883),h=function(d){var a=d.value,r=d.id,c=d.active,l=d.disabled;return{type:_.ACTION_TYPES.ADD_GROUP,value:a,id:r,active:c,disabled:l}};i.addGroup=h},464:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0}),i.highlightItem=i.removeItem=i.addItem=void 0;var _=b(883),h=function(r){var c=r.value,l=r.label,O=r.id,L=r.choiceId,y=r.groupId,D=r.customProperties,k=r.placeholder,Q=r.keyCode;return{type:_.ACTION_TYPES.ADD_ITEM,value:c,label:l,id:O,choiceId:L,groupId:y,customProperties:D,placeholder:k,keyCode:Q}};i.addItem=h;var d=function(r,c){return{type:_.ACTION_TYPES.REMOVE_ITEM,id:r,choiceId:c}};i.removeItem=d;var a=function(r,c){return{type:_.ACTION_TYPES.HIGHLIGHT_ITEM,id:r,highlighted:c}};i.highlightItem=a},137:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0}),i.setIsLoading=i.resetTo=i.clearAll=void 0;var _=b(883),h=function(){return{type:_.ACTION_TYPES.CLEAR_ALL}};i.clearAll=h;var d=function(r){return{type:_.ACTION_TYPES.RESET_TO,state:r}};i.resetTo=d;var a=function(r){return{type:_.ACTION_TYPES.SET_IS_LOADING,isLoading:r}};i.setIsLoading=a},373:function(j,i,b){var _=this&&this.__spreadArray||function(g,e,t){if(t||arguments.length===2)for(var n=0,s=e.length,v;n=0?this._store.getGroupById(v):null;return this._store.dispatch((0,l.highlightItem)(n,!0)),t&&this.passedElement.triggerEvent(y.EVENTS.highlightItem,{id:n,value:M,label:f,groupValue:u&&u.value?u.value:null}),this},g.prototype.unhighlightItem=function(e){if(!e||!e.id)return this;var t=e.id,n=e.groupId,s=n===void 0?-1:n,v=e.value,P=v===void 0?"":v,M=e.label,K=M===void 0?"":M,f=s>=0?this._store.getGroupById(s):null;return this._store.dispatch((0,l.highlightItem)(t,!1)),this.passedElement.triggerEvent(y.EVENTS.highlightItem,{id:t,value:P,label:K,groupValue:f&&f.value?f.value:null}),this},g.prototype.highlightAll=function(){var e=this;return this._store.items.forEach(function(t){return e.highlightItem(t)}),this},g.prototype.unhighlightAll=function(){var e=this;return this._store.items.forEach(function(t){return e.unhighlightItem(t)}),this},g.prototype.removeActiveItemsByValue=function(e){var t=this;return this._store.activeItems.filter(function(n){return n.value===e}).forEach(function(n){return t._removeItem(n)}),this},g.prototype.removeActiveItems=function(e){var t=this;return this._store.activeItems.filter(function(n){var s=n.id;return s!==e}).forEach(function(n){return t._removeItem(n)}),this},g.prototype.removeHighlightedItems=function(e){var t=this;return e===void 0&&(e=!1),this._store.highlightedActiveItems.forEach(function(n){t._removeItem(n),e&&t._triggerChange(n.value)}),this},g.prototype.showDropdown=function(e){var t=this;return this.dropdown.isActive?this:(requestAnimationFrame(function(){t.dropdown.show(),t.containerOuter.open(t.dropdown.distanceFromTopWindow),!e&&t._canSearch&&t.input.focus(),t.passedElement.triggerEvent(y.EVENTS.showDropdown,{})}),this)},g.prototype.hideDropdown=function(e){var t=this;return this.dropdown.isActive?(requestAnimationFrame(function(){t.dropdown.hide(),t.containerOuter.close(),!e&&t._canSearch&&(t.input.removeActiveDescendant(),t.input.blur()),t.passedElement.triggerEvent(y.EVENTS.hideDropdown,{})}),this):this},g.prototype.getValue=function(e){e===void 0&&(e=!1);var t=this._store.activeItems.reduce(function(n,s){var v=e?s.value:s;return n.push(v),n},[]);return this._isSelectOneElement?t[0]:t},g.prototype.setValue=function(e){var t=this;return this.initialised?(e.forEach(function(n){return t._setChoiceOrItem(n)}),this):this},g.prototype.setChoiceByValue=function(e){var t=this;if(!this.initialised||this._isTextElement)return this;var n=Array.isArray(e)?e:[e];return n.forEach(function(s){return t._findAndSelectChoiceByValue(s)}),this},g.prototype.setChoices=function(e,t,n,s){var v=this;if(e===void 0&&(e=[]),t===void 0&&(t="value"),n===void 0&&(n="label"),s===void 0&&(s=!1),!this.initialised)throw new ReferenceError("setChoices was called on a non-initialized instance of Choices");if(!this._isSelectElement)throw new TypeError("setChoices can't be used with INPUT based Choices");if(typeof t!="string"||!t)throw new TypeError("value parameter must be a name of 'value' field in passed objects");if(s&&this.clearChoices(),typeof e=="function"){var P=e(this);if(typeof Promise=="function"&&P instanceof Promise)return new Promise(function(M){return requestAnimationFrame(M)}).then(function(){return v._handleLoadingState(!0)}).then(function(){return P}).then(function(M){return v.setChoices(M,t,n,s)}).catch(function(M){v.config.silent||console.error(M)}).then(function(){return v._handleLoadingState(!1)}).then(function(){return v});if(!Array.isArray(P))throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof P));return this.setChoices(P,t,n,!1)}if(!Array.isArray(e))throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices");return this.containerOuter.removeLoadingState(),this._startLoading(),e.forEach(function(M){if(M.choices)v._addGroup({id:M.id?parseInt("".concat(M.id),10):null,group:M,valueKey:t,labelKey:n});else{var K=M;v._addChoice({value:K[t],label:K[n],isSelected:!!K.selected,isDisabled:!!K.disabled,placeholder:!!K.placeholder,customProperties:K.customProperties})}}),this._stopLoading(),this},g.prototype.clearChoices=function(){return this._store.dispatch((0,r.clearChoices)()),this},g.prototype.clearStore=function(){return this._store.dispatch((0,O.clearAll)()),this},g.prototype.clearInput=function(){var e=!this._isSelectOneElement;return this.input.clear(e),!this._isTextElement&&this._canSearch&&(this._isSearching=!1,this._store.dispatch((0,r.activateChoices)(!0))),this},g.prototype._render=function(){if(!this._store.isLoading()){this._currentState=this._store.state;var e=this._currentState.choices!==this._prevState.choices||this._currentState.groups!==this._prevState.groups||this._currentState.items!==this._prevState.items,t=this._isSelectElement,n=this._currentState.items!==this._prevState.items;e&&(t&&this._renderChoices(),n&&this._renderItems(),this._prevState=this._currentState)}},g.prototype._renderChoices=function(){var e=this,t=this._store,n=t.activeGroups,s=t.activeChoices,v=document.createDocumentFragment();if(this.choiceList.clear(),this.config.resetScrollPosition&&requestAnimationFrame(function(){return e.choiceList.scrollToTop()}),n.length>=1&&!this._isSearching){var P=s.filter(function(C){return C.placeholder===!0&&C.groupId===-1});P.length>=1&&(v=this._createChoicesFragment(P,v)),v=this._createGroupsFragment(n,s,v)}else s.length>=1&&(v=this._createChoicesFragment(s,v));if(v.childNodes&&v.childNodes.length>0){var M=this._store.activeItems,K=this._canAddItem(M,this.input.value);if(K.response)this.choiceList.append(v),this._highlightChoice();else{var f=this._getTemplate("notice",K.notice);this.choiceList.append(f)}}else{var u=void 0,f=void 0;this._isSearching?(f=typeof this.config.noResultsText=="function"?this.config.noResultsText():this.config.noResultsText,u=this._getTemplate("notice",f,"no-results")):(f=typeof this.config.noChoicesText=="function"?this.config.noChoicesText():this.config.noChoicesText,u=this._getTemplate("notice",f,"no-choices")),this.choiceList.append(u)}},g.prototype._renderItems=function(){var e=this._store.activeItems||[];this.itemList.clear();var t=this._createItemsFragment(e);t.childNodes&&this.itemList.append(t)},g.prototype._createGroupsFragment=function(e,t,n){var s=this;n===void 0&&(n=document.createDocumentFragment());var v=function(P){return t.filter(function(M){return s._isSelectOneElement?M.groupId===P.id:M.groupId===P.id&&(s.config.renderSelectedChoices==="always"||!M.selected)})};return this.config.shouldSort&&e.sort(this.config.sorter),e.forEach(function(P){var M=v(P);if(M.length>=1){var K=s._getTemplate("choiceGroup",P);n.appendChild(K),s._createChoicesFragment(M,n,!0)}}),n},g.prototype._createChoicesFragment=function(e,t,n){var s=this;t===void 0&&(t=document.createDocumentFragment()),n===void 0&&(n=!1);var v=this.config,P=v.renderSelectedChoices,M=v.searchResultLimit,K=v.renderChoiceLimit,f=this._isSearching?k.sortByScore:this.config.sorter,u=function(z){var ee=P==="auto"?s._isSelectOneElement||!z.selected:!0;if(ee){var ae=s._getTemplate("choice",z,s.config.itemSelectText);t.appendChild(ae)}},C=e;P==="auto"&&!this._isSelectOneElement&&(C=e.filter(function(z){return!z.selected}));var Y=C.reduce(function(z,ee){return ee.placeholder?z.placeholderChoices.push(ee):z.normalChoices.push(ee),z},{placeholderChoices:[],normalChoices:[]}),V=Y.placeholderChoices,U=Y.normalChoices;(this.config.shouldSort||this._isSearching)&&U.sort(f);var $=C.length,W=this._isSelectOneElement?_(_([],V,!0),U,!0):U;this._isSearching?$=M:K&&K>0&&!n&&($=K);for(var J=0;J<$;J+=1)W[J]&&u(W[J]);return t},g.prototype._createItemsFragment=function(e,t){var n=this;t===void 0&&(t=document.createDocumentFragment());var s=this.config,v=s.shouldSortItems,P=s.sorter,M=s.removeItemButton;v&&!this._isSelectOneElement&&e.sort(P),this._isTextElement?this.passedElement.value=e.map(function(f){var u=f.value;return u}).join(this.config.delimiter):this.passedElement.options=e;var K=function(f){var u=n._getTemplate("item",f,M);t.appendChild(u)};return e.forEach(K),t},g.prototype._triggerChange=function(e){e!=null&&this.passedElement.triggerEvent(y.EVENTS.change,{value:e})},g.prototype._selectPlaceholderChoice=function(e){this._addItem({value:e.value,label:e.label,choiceId:e.id,groupId:e.groupId,placeholder:e.placeholder}),this._triggerChange(e.value)},g.prototype._handleButtonAction=function(e,t){if(!(!e||!t||!this.config.removeItems||!this.config.removeItemButton)){var n=t.parentNode&&t.parentNode.dataset.id,s=n&&e.find(function(v){return v.id===parseInt(n,10)});s&&(this._removeItem(s),this._triggerChange(s.value),this._isSelectOneElement&&this._store.placeholderChoice&&this._selectPlaceholderChoice(this._store.placeholderChoice))}},g.prototype._handleItemAction=function(e,t,n){var s=this;if(n===void 0&&(n=!1),!(!e||!t||!this.config.removeItems||this._isSelectOneElement)){var v=t.dataset.id;e.forEach(function(P){P.id===parseInt("".concat(v),10)&&!P.highlighted?s.highlightItem(P):!n&&P.highlighted&&s.unhighlightItem(P)}),this.input.focus()}},g.prototype._handleChoiceAction=function(e,t){if(!(!e||!t)){var n=t.dataset.id,s=n&&this._store.getChoiceById(n);if(s){var v=e[0]&&e[0].keyCode?e[0].keyCode:void 0,P=this.dropdown.isActive;if(s.keyCode=v,this.passedElement.triggerEvent(y.EVENTS.choice,{choice:s}),!s.selected&&!s.disabled){var M=this._canAddItem(e,s.value);M.response&&(this._addItem({value:s.value,label:s.label,choiceId:s.id,groupId:s.groupId,customProperties:s.customProperties,placeholder:s.placeholder,keyCode:s.keyCode}),this._triggerChange(s.value))}this.clearInput(),P&&this._isSelectOneElement&&(this.hideDropdown(!0),this.containerOuter.focus())}}},g.prototype._handleBackspace=function(e){if(!(!this.config.removeItems||!e)){var t=e[e.length-1],n=e.some(function(s){return s.highlighted});this.config.editItems&&!n&&t?(this.input.value=t.value,this.input.setWidth(),this._removeItem(t),this._triggerChange(t.value)):(n||this.highlightItem(t,!1),this.removeHighlightedItems(!0))}},g.prototype._startLoading=function(){this._store.dispatch((0,O.setIsLoading)(!0))},g.prototype._stopLoading=function(){this._store.dispatch((0,O.setIsLoading)(!1))},g.prototype._handleLoadingState=function(e){e===void 0&&(e=!0);var t=this.itemList.getChild(".".concat(this.config.classNames.placeholder));e?(this.disable(),this.containerOuter.addLoadingState(),this._isSelectOneElement?t?t.innerHTML=this.config.loadingText:(t=this._getTemplate("placeholder",this.config.loadingText),t&&this.itemList.append(t)):this.input.placeholder=this.config.loadingText):(this.enable(),this.containerOuter.removeLoadingState(),this._isSelectOneElement?t&&(t.innerHTML=this._placeholderValue||""):this.input.placeholder=this._placeholderValue||"")},g.prototype._handleSearch=function(e){if(this.input.isFocussed){var t=this._store.choices,n=this.config,s=n.searchFloor,v=n.searchChoices,P=t.some(function(K){return!K.active});if(e!==null&&typeof e<"u"&&e.length>=s){var M=v?this._searchChoices(e):0;this.passedElement.triggerEvent(y.EVENTS.search,{value:e,resultCount:M})}else P&&(this._isSearching=!1,this._store.dispatch((0,r.activateChoices)(!0)))}},g.prototype._canAddItem=function(e,t){var n=!0,s=typeof this.config.addItemText=="function"?this.config.addItemText(t):this.config.addItemText;if(!this._isSelectOneElement){var v=(0,k.existsInArray)(e,t);this.config.maxItemCount>0&&this.config.maxItemCount<=e.length&&(n=!1,s=typeof this.config.maxItemText=="function"?this.config.maxItemText(this.config.maxItemCount):this.config.maxItemText),!this.config.duplicateItemsAllowed&&v&&n&&(n=!1,s=typeof this.config.uniqueItemText=="function"?this.config.uniqueItemText(t):this.config.uniqueItemText),this._isTextElement&&this.config.addItems&&n&&typeof this.config.addItemFilter=="function"&&!this.config.addItemFilter(t)&&(n=!1,s=typeof this.config.customAddItemText=="function"?this.config.customAddItemText(t):this.config.customAddItemText)}return{response:n,notice:s}},g.prototype._searchChoices=function(e){var t=typeof e=="string"?e.trim():e,n=typeof this._currentValue=="string"?this._currentValue.trim():this._currentValue;if(t.length<1&&t==="".concat(n," "))return 0;var s=this._store.searchableChoices,v=t,P=Object.assign(this.config.fuseOptions,{keys:_([],this.config.searchFields,!0),includeMatches:!0}),M=new a.default(s,P),K=M.search(v);return this._currentValue=t,this._highlightPosition=0,this._isSearching=!0,this._store.dispatch((0,r.filterChoices)(K)),K.length},g.prototype._addEventListeners=function(){var e=document.documentElement;e.addEventListener("touchend",this._onTouchEnd,!0),this.containerOuter.element.addEventListener("keydown",this._onKeyDown,!0),this.containerOuter.element.addEventListener("mousedown",this._onMouseDown,!0),e.addEventListener("click",this._onClick,{passive:!0}),e.addEventListener("touchmove",this._onTouchMove,{passive:!0}),this.dropdown.element.addEventListener("mouseover",this._onMouseOver,{passive:!0}),this._isSelectOneElement&&(this.containerOuter.element.addEventListener("focus",this._onFocus,{passive:!0}),this.containerOuter.element.addEventListener("blur",this._onBlur,{passive:!0})),this.input.element.addEventListener("keyup",this._onKeyUp,{passive:!0}),this.input.element.addEventListener("focus",this._onFocus,{passive:!0}),this.input.element.addEventListener("blur",this._onBlur,{passive:!0}),this.input.element.form&&this.input.element.form.addEventListener("reset",this._onFormReset,{passive:!0}),this.input.addEventListeners()},g.prototype._removeEventListeners=function(){var e=document.documentElement;e.removeEventListener("touchend",this._onTouchEnd,!0),this.containerOuter.element.removeEventListener("keydown",this._onKeyDown,!0),this.containerOuter.element.removeEventListener("mousedown",this._onMouseDown,!0),e.removeEventListener("click",this._onClick),e.removeEventListener("touchmove",this._onTouchMove),this.dropdown.element.removeEventListener("mouseover",this._onMouseOver),this._isSelectOneElement&&(this.containerOuter.element.removeEventListener("focus",this._onFocus),this.containerOuter.element.removeEventListener("blur",this._onBlur)),this.input.element.removeEventListener("keyup",this._onKeyUp),this.input.element.removeEventListener("focus",this._onFocus),this.input.element.removeEventListener("blur",this._onBlur),this.input.element.form&&this.input.element.form.removeEventListener("reset",this._onFormReset),this.input.removeEventListeners()},g.prototype._onKeyDown=function(e){var t=e.keyCode,n=this._store.activeItems,s=this.input.isFocussed,v=this.dropdown.isActive,P=this.itemList.hasChildren(),M=String.fromCharCode(t),K=/[^\x00-\x1F]/.test(M),f=y.KEY_CODES.BACK_KEY,u=y.KEY_CODES.DELETE_KEY,C=y.KEY_CODES.ENTER_KEY,Y=y.KEY_CODES.A_KEY,V=y.KEY_CODES.ESC_KEY,U=y.KEY_CODES.UP_KEY,$=y.KEY_CODES.DOWN_KEY,W=y.KEY_CODES.PAGE_UP_KEY,J=y.KEY_CODES.PAGE_DOWN_KEY;switch(!this._isTextElement&&!v&&K&&(this.showDropdown(),this.input.isFocussed||(this.input.value+=e.key.toLowerCase())),t){case Y:return this._onSelectKey(e,P);case C:return this._onEnterKey(e,n,v);case V:return this._onEscapeKey(v);case U:case W:case $:case J:return this._onDirectionKey(e,v);case u:case f:return this._onDeleteKey(e,n,s);default:}},g.prototype._onKeyUp=function(e){var t=e.target,n=e.keyCode,s=this.input.value,v=this._store.activeItems,P=this._canAddItem(v,s),M=y.KEY_CODES.BACK_KEY,K=y.KEY_CODES.DELETE_KEY;if(this._isTextElement){var f=P.notice&&s;if(f){var u=this._getTemplate("notice",P.notice);this.dropdown.element.innerHTML=u.outerHTML,this.showDropdown(!0)}else this.hideDropdown(!0)}else{var C=n===M||n===K,Y=C&&t&&!t.value,V=!this._isTextElement&&this._isSearching,U=this._canSearch&&P.response;Y&&V?(this._isSearching=!1,this._store.dispatch((0,r.activateChoices)(!0))):U&&this._handleSearch(this.input.rawValue)}this._canSearch=this.config.searchEnabled},g.prototype._onSelectKey=function(e,t){var n=e.ctrlKey,s=e.metaKey,v=n||s;if(v&&t){this._canSearch=!1;var P=this.config.removeItems&&!this.input.value&&this.input.element===document.activeElement;P&&this.highlightAll()}},g.prototype._onEnterKey=function(e,t,n){var s=e.target,v=y.KEY_CODES.ENTER_KEY,P=s&&s.hasAttribute("data-button");if(this._isTextElement&&s&&s.value){var M=this.input.value,K=this._canAddItem(t,M);K.response&&(this.hideDropdown(!0),this._addItem({value:M}),this._triggerChange(M),this.clearInput())}if(P&&(this._handleButtonAction(t,s),e.preventDefault()),n){var f=this.dropdown.getChild(".".concat(this.config.classNames.highlightedState));f&&(t[0]&&(t[0].keyCode=v),this._handleChoiceAction(t,f)),e.preventDefault()}else this._isSelectOneElement&&(this.showDropdown(),e.preventDefault())},g.prototype._onEscapeKey=function(e){e&&(this.hideDropdown(!0),this.containerOuter.focus())},g.prototype._onDirectionKey=function(e,t){var n=e.keyCode,s=e.metaKey,v=y.KEY_CODES.DOWN_KEY,P=y.KEY_CODES.PAGE_UP_KEY,M=y.KEY_CODES.PAGE_DOWN_KEY;if(t||this._isSelectOneElement){this.showDropdown(),this._canSearch=!1;var K=n===v||n===M?1:-1,f=s||n===M||n===P,u="[data-choice-selectable]",C=void 0;if(f)K>0?C=this.dropdown.element.querySelector("".concat(u,":last-of-type")):C=this.dropdown.element.querySelector(u);else{var Y=this.dropdown.element.querySelector(".".concat(this.config.classNames.highlightedState));Y?C=(0,k.getAdjacentEl)(Y,u,K):C=this.dropdown.element.querySelector(u)}C&&((0,k.isScrolledIntoView)(C,this.choiceList.element,K)||this.choiceList.scrollToChildElement(C,K),this._highlightChoice(C)),e.preventDefault()}},g.prototype._onDeleteKey=function(e,t,n){var s=e.target;!this._isSelectOneElement&&!s.value&&n&&(this._handleBackspace(t),e.preventDefault())},g.prototype._onTouchMove=function(){this._wasTap&&(this._wasTap=!1)},g.prototype._onTouchEnd=function(e){var t=(e||e.touches[0]).target,n=this._wasTap&&this.containerOuter.element.contains(t);if(n){var s=t===this.containerOuter.element||t===this.containerInner.element;s&&(this._isTextElement?this.input.focus():this._isSelectMultipleElement&&this.showDropdown()),e.stopPropagation()}this._wasTap=!0},g.prototype._onMouseDown=function(e){var t=e.target;if(t instanceof HTMLElement){if(E&&this.choiceList.element.contains(t)){var n=this.choiceList.element.firstElementChild,s=this._direction==="ltr"?e.offsetX>=n.offsetWidth:e.offsetX0;s&&this.unhighlightAll(),this.containerOuter.removeFocusState(),this.hideDropdown(!0)}},g.prototype._onFocus=function(e){var t,n=this,s=e.target,v=s&&this.containerOuter.element.contains(s);if(v){var P=(t={},t[y.TEXT_TYPE]=function(){s===n.input.element&&n.containerOuter.addFocusState()},t[y.SELECT_ONE_TYPE]=function(){n.containerOuter.addFocusState(),s===n.input.element&&n.showDropdown(!0)},t[y.SELECT_MULTIPLE_TYPE]=function(){s===n.input.element&&(n.showDropdown(!0),n.containerOuter.addFocusState())},t);P[this.passedElement.element.type]()}},g.prototype._onBlur=function(e){var t,n=this,s=e.target,v=s&&this.containerOuter.element.contains(s);if(v&&!this._isScrollingOnIe){var P=this._store.activeItems,M=P.some(function(f){return f.highlighted}),K=(t={},t[y.TEXT_TYPE]=function(){s===n.input.element&&(n.containerOuter.removeFocusState(),M&&n.unhighlightAll(),n.hideDropdown(!0))},t[y.SELECT_ONE_TYPE]=function(){n.containerOuter.removeFocusState(),(s===n.input.element||s===n.containerOuter.element&&!n._canSearch)&&n.hideDropdown(!0)},t[y.SELECT_MULTIPLE_TYPE]=function(){s===n.input.element&&(n.containerOuter.removeFocusState(),n.hideDropdown(!0),M&&n.unhighlightAll())},t);K[this.passedElement.element.type]()}else this._isScrollingOnIe=!1,this.input.element.focus()},g.prototype._onFormReset=function(){this._store.dispatch((0,O.resetTo)(this._initialState))},g.prototype._highlightChoice=function(e){var t=this;e===void 0&&(e=null);var n=Array.from(this.dropdown.element.querySelectorAll("[data-choice-selectable]"));if(n.length){var s=e,v=Array.from(this.dropdown.element.querySelectorAll(".".concat(this.config.classNames.highlightedState)));v.forEach(function(P){P.classList.remove(t.config.classNames.highlightedState),P.setAttribute("aria-selected","false")}),s?this._highlightPosition=n.indexOf(s):(n.length>this._highlightPosition?s=n[this._highlightPosition]:s=n[n.length-1],s||(s=n[0])),s.classList.add(this.config.classNames.highlightedState),s.setAttribute("aria-selected","true"),this.passedElement.triggerEvent(y.EVENTS.highlightChoice,{el:s}),this.dropdown.isActive&&(this.input.setActiveDescendant(s.id),this.containerOuter.setActiveDescendant(s.id))}},g.prototype._addItem=function(e){var t=e.value,n=e.label,s=n===void 0?null:n,v=e.choiceId,P=v===void 0?-1:v,M=e.groupId,K=M===void 0?-1:M,f=e.customProperties,u=f===void 0?{}:f,C=e.placeholder,Y=C===void 0?!1:C,V=e.keyCode,U=V===void 0?-1:V,$=typeof t=="string"?t.trim():t,W=this._store.items,J=s||$,z=P||-1,ee=K>=0?this._store.getGroupById(K):null,ae=W?W.length+1:1;this.config.prependValue&&($=this.config.prependValue+$.toString()),this.config.appendValue&&($+=this.config.appendValue.toString()),this._store.dispatch((0,l.addItem)({value:$,label:J,id:ae,choiceId:z,groupId:K,customProperties:u,placeholder:Y,keyCode:U})),this._isSelectOneElement&&this.removeActiveItems(ae),this.passedElement.triggerEvent(y.EVENTS.addItem,{id:ae,value:$,label:J,customProperties:u,groupValue:ee&&ee.value?ee.value:null,keyCode:U})},g.prototype._removeItem=function(e){var t=e.id,n=e.value,s=e.label,v=e.customProperties,P=e.choiceId,M=e.groupId,K=M&&M>=0?this._store.getGroupById(M):null;!t||!P||(this._store.dispatch((0,l.removeItem)(t,P)),this.passedElement.triggerEvent(y.EVENTS.removeItem,{id:t,value:n,label:s,customProperties:v,groupValue:K&&K.value?K.value:null}))},g.prototype._addChoice=function(e){var t=e.value,n=e.label,s=n===void 0?null:n,v=e.isSelected,P=v===void 0?!1:v,M=e.isDisabled,K=M===void 0?!1:M,f=e.groupId,u=f===void 0?-1:f,C=e.customProperties,Y=C===void 0?{}:C,V=e.placeholder,U=V===void 0?!1:V,$=e.keyCode,W=$===void 0?-1:$;if(!(typeof t>"u"||t===null)){var J=this._store.choices,z=s||t,ee=J?J.length+1:1,ae="".concat(this._baseId,"-").concat(this._idNames.itemChoice,"-").concat(ee);this._store.dispatch((0,r.addChoice)({id:ee,groupId:u,elementId:ae,value:t,label:z,disabled:K,customProperties:Y,placeholder:U,keyCode:W})),P&&this._addItem({value:t,label:z,choiceId:ee,customProperties:Y,placeholder:U,keyCode:W})}},g.prototype._addGroup=function(e){var t=this,n=e.group,s=e.id,v=e.valueKey,P=v===void 0?"value":v,M=e.labelKey,K=M===void 0?"label":M,f=(0,k.isType)("Object",n)?n.choices:Array.from(n.getElementsByTagName("OPTION")),u=s||Math.floor(new Date().valueOf()*Math.random()),C=n.disabled?n.disabled:!1;if(f){this._store.dispatch((0,c.addGroup)({value:n.label,id:u,active:!0,disabled:C}));var Y=function(V){var U=V.disabled||V.parentNode&&V.parentNode.disabled;t._addChoice({value:V[P],label:(0,k.isType)("Object",V)?V[K]:V.innerHTML,isSelected:V.selected,isDisabled:U,groupId:u,customProperties:V.customProperties,placeholder:V.placeholder})};f.forEach(Y)}else this._store.dispatch((0,c.addGroup)({value:n.label,id:n.id,active:!1,disabled:n.disabled}))},g.prototype._getTemplate=function(e){for(var t,n=[],s=1;s0?this.element.scrollTop+y-O:a.offsetTop;requestAnimationFrame(function(){c._animateScroll(D,r)})}},d.prototype._scrollDown=function(a,r,c){var l=(c-a)/r,O=l>1?l:1;this.element.scrollTop=a+O},d.prototype._scrollUp=function(a,r,c){var l=(a-c)/r,O=l>1?l:1;this.element.scrollTop=a-O},d.prototype._animateScroll=function(a,r){var c=this,l=_.SCROLLING_SPEED,O=this.element.scrollTop,L=!1;r>0?(this._scrollDown(O,l,a),Oa&&(L=!0)),L&&requestAnimationFrame(function(){c._animateScroll(a,r)})},d}();i.default=h},730:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0});var _=b(799),h=function(){function d(a){var r=a.element,c=a.classNames;if(this.element=r,this.classNames=c,!(r instanceof HTMLInputElement)&&!(r instanceof HTMLSelectElement))throw new TypeError("Invalid element passed");this.isDisabled=!1}return Object.defineProperty(d.prototype,"isActive",{get:function(){return this.element.dataset.choice==="active"},enumerable:!1,configurable:!0}),Object.defineProperty(d.prototype,"dir",{get:function(){return this.element.dir},enumerable:!1,configurable:!0}),Object.defineProperty(d.prototype,"value",{get:function(){return this.element.value},set:function(a){this.element.value=a},enumerable:!1,configurable:!0}),d.prototype.conceal=function(){this.element.classList.add(this.classNames.input),this.element.hidden=!0,this.element.tabIndex=-1;var a=this.element.getAttribute("style");a&&this.element.setAttribute("data-choice-orig-style",a),this.element.setAttribute("data-choice","active")},d.prototype.reveal=function(){this.element.classList.remove(this.classNames.input),this.element.hidden=!1,this.element.removeAttribute("tabindex");var a=this.element.getAttribute("data-choice-orig-style");a?(this.element.removeAttribute("data-choice-orig-style"),this.element.setAttribute("style",a)):this.element.removeAttribute("style"),this.element.removeAttribute("data-choice"),this.element.value=this.element.value},d.prototype.enable=function(){this.element.removeAttribute("disabled"),this.element.disabled=!1,this.isDisabled=!1},d.prototype.disable=function(){this.element.setAttribute("disabled",""),this.element.disabled=!0,this.isDisabled=!0},d.prototype.triggerEvent=function(a,r){(0,_.dispatchEvent)(this.element,a,r)},d}();i.default=h},541:function(j,i,b){var _=this&&this.__extends||function(){var r=function(c,l){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(O,L){O.__proto__=L}||function(O,L){for(var y in L)Object.prototype.hasOwnProperty.call(L,y)&&(O[y]=L[y])},r(c,l)};return function(c,l){if(typeof l!="function"&&l!==null)throw new TypeError("Class extends value "+String(l)+" is not a constructor or null");r(c,l);function O(){this.constructor=c}c.prototype=l===null?Object.create(l):(O.prototype=l.prototype,new O)}}(),h=this&&this.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(i,"__esModule",{value:!0});var d=h(b(730)),a=function(r){_(c,r);function c(l){var O=l.element,L=l.classNames,y=l.delimiter,D=r.call(this,{element:O,classNames:L})||this;return D.delimiter=y,D}return Object.defineProperty(c.prototype,"value",{get:function(){return this.element.value},set:function(l){this.element.setAttribute("value",l),this.element.value=l},enumerable:!1,configurable:!0}),c}(d.default);i.default=a},982:function(j,i,b){var _=this&&this.__extends||function(){var r=function(c,l){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(O,L){O.__proto__=L}||function(O,L){for(var y in L)Object.prototype.hasOwnProperty.call(L,y)&&(O[y]=L[y])},r(c,l)};return function(c,l){if(typeof l!="function"&&l!==null)throw new TypeError("Class extends value "+String(l)+" is not a constructor or null");r(c,l);function O(){this.constructor=c}c.prototype=l===null?Object.create(l):(O.prototype=l.prototype,new O)}}(),h=this&&this.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(i,"__esModule",{value:!0});var d=h(b(730)),a=function(r){_(c,r);function c(l){var O=l.element,L=l.classNames,y=l.template,D=r.call(this,{element:O,classNames:L})||this;return D.template=y,D}return Object.defineProperty(c.prototype,"placeholderOption",{get:function(){return this.element.querySelector('option[value=""]')||this.element.querySelector("option[placeholder]")},enumerable:!1,configurable:!0}),Object.defineProperty(c.prototype,"optionGroups",{get:function(){return Array.from(this.element.getElementsByTagName("OPTGROUP"))},enumerable:!1,configurable:!0}),Object.defineProperty(c.prototype,"options",{get:function(){return Array.from(this.element.options)},set:function(l){var O=this,L=document.createDocumentFragment(),y=function(D){var k=O.template(D);L.appendChild(k)};l.forEach(function(D){return y(D)}),this.appendDocFragment(L)},enumerable:!1,configurable:!0}),c.prototype.appendDocFragment=function(l){this.element.innerHTML="",this.element.appendChild(l)},c}(d.default);i.default=a},883:function(j,i){Object.defineProperty(i,"__esModule",{value:!0}),i.SCROLLING_SPEED=i.SELECT_MULTIPLE_TYPE=i.SELECT_ONE_TYPE=i.TEXT_TYPE=i.KEY_CODES=i.ACTION_TYPES=i.EVENTS=void 0,i.EVENTS={showDropdown:"showDropdown",hideDropdown:"hideDropdown",change:"change",choice:"choice",search:"search",addItem:"addItem",removeItem:"removeItem",highlightItem:"highlightItem",highlightChoice:"highlightChoice",unhighlightItem:"unhighlightItem"},i.ACTION_TYPES={ADD_CHOICE:"ADD_CHOICE",FILTER_CHOICES:"FILTER_CHOICES",ACTIVATE_CHOICES:"ACTIVATE_CHOICES",CLEAR_CHOICES:"CLEAR_CHOICES",ADD_GROUP:"ADD_GROUP",ADD_ITEM:"ADD_ITEM",REMOVE_ITEM:"REMOVE_ITEM",HIGHLIGHT_ITEM:"HIGHLIGHT_ITEM",CLEAR_ALL:"CLEAR_ALL",RESET_TO:"RESET_TO",SET_IS_LOADING:"SET_IS_LOADING"},i.KEY_CODES={BACK_KEY:46,DELETE_KEY:8,ENTER_KEY:13,A_KEY:65,ESC_KEY:27,UP_KEY:38,DOWN_KEY:40,PAGE_UP_KEY:33,PAGE_DOWN_KEY:34},i.TEXT_TYPE="text",i.SELECT_ONE_TYPE="select-one",i.SELECT_MULTIPLE_TYPE="select-multiple",i.SCROLLING_SPEED=4},789:function(j,i,b){Object.defineProperty(i,"__esModule",{value:!0}),i.DEFAULT_CONFIG=i.DEFAULT_CLASSNAMES=void 0;var _=b(799);i.DEFAULT_CLASSNAMES={containerOuter:"choices",containerInner:"choices__inner",input:"choices__input",inputCloned:"choices__input--cloned",list:"choices__list",listItems:"choices__list--multiple",listSingle:"choices__list--single",listDropdown:"choices__list--dropdown",item:"choices__item",itemSelectable:"choices__item--selectable",itemDisabled:"choices__item--disabled",itemChoice:"choices__item--choice",placeholder:"choices__placeholder",group:"choices__group",groupHeading:"choices__heading",button:"choices__button",activeState:"is-active",focusState:"is-focused",openState:"is-open",disabledState:"is-disabled",highlightedState:"is-highlighted",selectedState:"is-selected",flippedState:"is-flipped",loadingState:"is-loading",noResults:"has-no-results",noChoices:"has-no-choices"},i.DEFAULT_CONFIG={items:[],choices:[],silent:!1,renderChoiceLimit:-1,maxItemCount:-1,addItems:!0,addItemFilter:null,removeItems:!0,removeItemButton:!1,editItems:!1,allowHTML:!0,duplicateItemsAllowed:!0,delimiter:",",paste:!0,searchEnabled:!0,searchChoices:!0,searchFloor:1,searchResultLimit:4,searchFields:["label","value"],position:"auto",resetScrollPosition:!0,shouldSort:!0,shouldSortItems:!1,sorter:_.sortByAlpha,placeholder:!0,placeholderValue:null,searchPlaceholderValue:null,prependValue:null,appendValue:null,renderSelectedChoices:"auto",loadingText:"Loading...",noResultsText:"No results found",noChoicesText:"No choices to choose from",itemSelectText:"Press to select",uniqueItemText:"Only unique values can be added",customAddItemText:"Only values matching specific conditions can be added",addItemText:function(h){return'Press Enter to add "'.concat((0,_.sanitise)(h),'"')},maxItemText:function(h){return"Only ".concat(h," values can be added")},valueComparer:function(h,d){return h===d},fuseOptions:{includeScore:!0},labelId:"",callbackOnInit:null,callbackOnCreateTemplates:null,classNames:i.DEFAULT_CLASSNAMES}},18:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},978:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},948:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},359:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},285:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},533:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},187:function(j,i,b){var _=this&&this.__createBinding||(Object.create?function(d,a,r,c){c===void 0&&(c=r);var l=Object.getOwnPropertyDescriptor(a,r);(!l||("get"in l?!a.__esModule:l.writable||l.configurable))&&(l={enumerable:!0,get:function(){return a[r]}}),Object.defineProperty(d,c,l)}:function(d,a,r,c){c===void 0&&(c=r),d[c]=a[r]}),h=this&&this.__exportStar||function(d,a){for(var r in d)r!=="default"&&!Object.prototype.hasOwnProperty.call(a,r)&&_(a,d,r)};Object.defineProperty(i,"__esModule",{value:!0}),h(b(18),i),h(b(978),i),h(b(948),i),h(b(359),i),h(b(285),i),h(b(533),i),h(b(287),i),h(b(132),i),h(b(837),i),h(b(598),i),h(b(369),i),h(b(37),i),h(b(47),i),h(b(923),i),h(b(876),i)},287:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},132:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},837:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},598:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},37:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},369:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},47:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},923:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},876:function(j,i){Object.defineProperty(i,"__esModule",{value:!0})},799:function(j,i){Object.defineProperty(i,"__esModule",{value:!0}),i.parseCustomProperties=i.diff=i.cloneObject=i.existsInArray=i.dispatchEvent=i.sortByScore=i.sortByAlpha=i.strToEl=i.sanitise=i.isScrolledIntoView=i.getAdjacentEl=i.wrap=i.isType=i.getType=i.generateId=i.generateChars=i.getRandomNumber=void 0;var b=function(E,w){return Math.floor(Math.random()*(w-E)+E)};i.getRandomNumber=b;var _=function(E){return Array.from({length:E},function(){return(0,i.getRandomNumber)(0,36).toString(36)}).join("")};i.generateChars=_;var h=function(E,w){var N=E.id||E.name&&"".concat(E.name,"-").concat((0,i.generateChars)(2))||(0,i.generateChars)(4);return N=N.replace(/(:|\.|\[|\]|,)/g,""),N="".concat(w,"-").concat(N),N};i.generateId=h;var d=function(E){return Object.prototype.toString.call(E).slice(8,-1)};i.getType=d;var a=function(E,w){return w!=null&&(0,i.getType)(w)===E};i.isType=a;var r=function(E,w){return w===void 0&&(w=document.createElement("div")),E.parentNode&&(E.nextSibling?E.parentNode.insertBefore(w,E.nextSibling):E.parentNode.appendChild(w)),w.appendChild(E)};i.wrap=r;var c=function(E,w,N){N===void 0&&(N=1);for(var g="".concat(N>0?"next":"previous","ElementSibling"),e=E[g];e;){if(e.matches(w))return e;e=e[g]}return e};i.getAdjacentEl=c;var l=function(E,w,N){if(N===void 0&&(N=1),!E)return!1;var g;return N>0?g=w.scrollTop+w.offsetHeight>=E.offsetTop+E.offsetHeight:g=E.offsetTop>=w.scrollTop,g};i.isScrolledIntoView=l;var O=function(E){return typeof E!="string"?E:E.replace(/&/g,"&").replace(/>/g,">").replace(/-1?h.map(function(y){var D=y;return D.id===parseInt("".concat(c.choiceId),10)&&(D.selected=!0),D}):h}case"REMOVE_ITEM":{var l=d;return l.choiceId&&l.choiceId>-1?h.map(function(y){var D=y;return D.id===parseInt("".concat(l.choiceId),10)&&(D.selected=!1),D}):h}case"FILTER_CHOICES":{var O=d;return h.map(function(y){var D=y;return D.active=O.results.some(function(k){var Q=k.item,Z=k.score;return Q.id===D.id?(D.score=Z,!0):!1}),D})}case"ACTIVATE_CHOICES":{var L=d;return h.map(function(y){var D=y;return D.active=L.active,D})}case"CLEAR_CHOICES":return i.defaultState;default:return h}}i.default=_},871:function(j,i){var b=this&&this.__spreadArray||function(h,d,a){if(a||arguments.length===2)for(var r=0,c=d.length,l;r0?"treeitem":"option"),Object.assign(t.dataset,{choice:"",id:Q,value:Z,selectText:d}),N?(t.classList.add(D),t.dataset.choiceDisabled="",t.setAttribute("aria-disabled","true")):(t.classList.add(L),t.dataset.choiceSelectable=""),t},input:function(_,h){var d=_.classNames,a=d.input,r=d.inputCloned,c=Object.assign(document.createElement("input"),{type:"search",name:"search_terms",className:"".concat(a," ").concat(r),autocomplete:"off",autocapitalize:"off",spellcheck:!1});return c.setAttribute("role","textbox"),c.setAttribute("aria-autocomplete","list"),c.setAttribute("aria-label",h),c},dropdown:function(_){var h=_.classNames,d=h.list,a=h.listDropdown,r=document.createElement("div");return r.classList.add(d,a),r.setAttribute("aria-expanded","false"),r},notice:function(_,h,d){var a,r=_.allowHTML,c=_.classNames,l=c.item,O=c.itemChoice,L=c.noResults,y=c.noChoices;d===void 0&&(d="");var D=[l,O];return d==="no-choices"?D.push(y):d==="no-results"&&D.push(L),Object.assign(document.createElement("div"),(a={},a[r?"innerHTML":"innerText"]=h,a.className=D.join(" "),a))},option:function(_){var h=_.label,d=_.value,a=_.customProperties,r=_.active,c=_.disabled,l=new Option(h,d,!1,r);return a&&(l.dataset.customProperties="".concat(a)),l.disabled=!!c,l}};i.default=b},996:function(j){var i=function(w){return b(w)&&!_(w)};function b(E){return!!E&&typeof E=="object"}function _(E){var w=Object.prototype.toString.call(E);return w==="[object RegExp]"||w==="[object Date]"||a(E)}var h=typeof Symbol=="function"&&Symbol.for,d=h?Symbol.for("react.element"):60103;function a(E){return E.$$typeof===d}function r(E){return Array.isArray(E)?[]:{}}function c(E,w){return w.clone!==!1&&w.isMergeableObject(E)?Z(r(E),E,w):E}function l(E,w,N){return E.concat(w).map(function(g){return c(g,N)})}function O(E,w){if(!w.customMerge)return Z;var N=w.customMerge(E);return typeof N=="function"?N:Z}function L(E){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(E).filter(function(w){return E.propertyIsEnumerable(w)}):[]}function y(E){return Object.keys(E).concat(L(E))}function D(E,w){try{return w in E}catch{return!1}}function k(E,w){return D(E,w)&&!(Object.hasOwnProperty.call(E,w)&&Object.propertyIsEnumerable.call(E,w))}function Q(E,w,N){var g={};return N.isMergeableObject(E)&&y(E).forEach(function(e){g[e]=c(E[e],N)}),y(w).forEach(function(e){k(E,e)||(D(E,e)&&N.isMergeableObject(w[e])?g[e]=O(e,N)(E[e],w[e],N):g[e]=c(w[e],N))}),g}function Z(E,w,N){N=N||{},N.arrayMerge=N.arrayMerge||l,N.isMergeableObject=N.isMergeableObject||i,N.cloneUnlessOtherwiseSpecified=c;var g=Array.isArray(w),e=Array.isArray(E),t=g===e;return t?g?N.arrayMerge(E,w,N):Q(E,w,N):c(w,N)}Z.all=function(w,N){if(!Array.isArray(w))throw new Error("first argument should be an array");return w.reduce(function(g,e){return Z(g,e,N)},{})};var ne=Z;j.exports=ne},221:function(j,i,b){b.r(i),b.d(i,{default:function(){return Se}});function _(p){return Array.isArray?Array.isArray(p):k(p)==="[object Array]"}let h=1/0;function d(p){if(typeof p=="string")return p;let o=p+"";return o=="0"&&1/p==-h?"-0":o}function a(p){return p==null?"":d(p)}function r(p){return typeof p=="string"}function c(p){return typeof p=="number"}function l(p){return p===!0||p===!1||L(p)&&k(p)=="[object Boolean]"}function O(p){return typeof p=="object"}function L(p){return O(p)&&p!==null}function y(p){return p!=null}function D(p){return!p.trim().length}function k(p){return p==null?p===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(p)}let Q="Extended search is not available",Z="Incorrect 'index' type",ne=p=>`Invalid value for key ${p}`,E=p=>`Pattern length exceeds max of ${p}.`,w=p=>`Missing ${p} property in key`,N=p=>`Property 'weight' in key '${p}' must be a positive integer`,g=Object.prototype.hasOwnProperty;class e{constructor(o){this._keys=[],this._keyMap={};let m=0;o.forEach(S=>{let I=t(S);m+=I.weight,this._keys.push(I),this._keyMap[I.id]=I,m+=I.weight}),this._keys.forEach(S=>{S.weight/=m})}get(o){return this._keyMap[o]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function t(p){let o=null,m=null,S=null,I=1,T=null;if(r(p)||_(p))S=p,o=n(p),m=s(p);else{if(!g.call(p,"name"))throw new Error(w("name"));let A=p.name;if(S=A,g.call(p,"weight")&&(I=p.weight,I<=0))throw new Error(N(A));o=n(A),m=s(A),T=p.getFn}return{path:o,id:m,weight:I,src:S,getFn:T}}function n(p){return _(p)?p:p.split(".")}function s(p){return _(p)?p.join("."):p}function v(p,o){let m=[],S=!1,I=(T,A,R)=>{if(y(T))if(!A[R])m.push(T);else{let F=A[R],H=T[F];if(!y(H))return;if(R===A.length-1&&(r(H)||c(H)||l(H)))m.push(a(H));else if(_(H)){S=!0;for(let B=0,x=H.length;Bp.score===o.score?p.idx{this._keysMap[m.id]=S})}create(){this.isCreated||!this.docs.length||(this.isCreated=!0,r(this.docs[0])?this.docs.forEach((o,m)=>{this._addString(o,m)}):this.docs.forEach((o,m)=>{this._addObject(o,m)}),this.norm.clear())}add(o){let m=this.size();r(o)?this._addString(o,m):this._addObject(o,m)}removeAt(o){this.records.splice(o,1);for(let m=o,S=this.size();m{let A=I.getFn?I.getFn(o):this.getFn(o,I.path);if(y(A)){if(_(A)){let R=[],F=[{nestedArrIndex:-1,value:A}];for(;F.length;){let{nestedArrIndex:H,value:B}=F.pop();if(y(B))if(r(B)&&!D(B)){let x={v:B,i:H,n:this.norm.get(B)};R.push(x)}else _(B)&&B.forEach((x,G)=>{F.push({nestedArrIndex:G,value:x})})}S.$[T]=R}else if(r(A)&&!D(A)){let R={v:A,n:this.norm.get(A)};S.$[T]=R}}}),this.records.push(S)}toJSON(){return{keys:this.keys,records:this.records}}}function U(p,o,{getFn:m=u.getFn,fieldNormWeight:S=u.fieldNormWeight}={}){let I=new V({getFn:m,fieldNormWeight:S});return I.setKeys(p.map(t)),I.setSources(o),I.create(),I}function $(p,{getFn:o=u.getFn,fieldNormWeight:m=u.fieldNormWeight}={}){let{keys:S,records:I}=p,T=new V({getFn:o,fieldNormWeight:m});return T.setKeys(S),T.setIndexRecords(I),T}function W(p,{errors:o=0,currentLocation:m=0,expectedLocation:S=0,distance:I=u.distance,ignoreLocation:T=u.ignoreLocation}={}){let A=o/p.length;if(T)return A;let R=Math.abs(S-m);return I?A+R/I:R?1:A}function J(p=[],o=u.minMatchCharLength){let m=[],S=-1,I=-1,T=0;for(let A=p.length;T=o&&m.push([S,I]),S=-1)}return p[T-1]&&T-S>=o&&m.push([S,T-1]),m}let z=32;function ee(p,o,m,{location:S=u.location,distance:I=u.distance,threshold:T=u.threshold,findAllMatches:A=u.findAllMatches,minMatchCharLength:R=u.minMatchCharLength,includeMatches:F=u.includeMatches,ignoreLocation:H=u.ignoreLocation}={}){if(o.length>z)throw new Error(E(z));let B=o.length,x=p.length,G=Math.max(0,Math.min(S,x)),q=T,re=G,ue=R>1||F,Ee=ue?Array(x):[],ve;for(;(ve=p.indexOf(o,re))>-1;){let he=W(o,{currentLocation:ve,expectedLocation:G,distance:I,ignoreLocation:H});if(q=Math.min(he,q),re=ve+B,ue){let ge=0;for(;ge=Ue;fe-=1){let Le=fe-1,We=m[p.charAt(Le)];if(ue&&(Ee[Le]=+!!We),Oe[fe]=(Oe[fe+1]<<1|1)&We,he&&(Oe[fe]|=(Ie[fe+1]|Ie[fe])<<1|1|Ie[fe+1]),Oe[fe]&at&&(be=W(o,{errors:he,currentLocation:Le,expectedLocation:G,distance:I,ignoreLocation:H}),be<=q)){if(q=be,re=Le,re<=G)break;Ue=Math.max(1,2*G-re)}}if(W(o,{errors:he+1,currentLocation:G,expectedLocation:G,distance:I,ignoreLocation:H})>q)break;Ie=Oe}let Ke={isMatch:re>=0,score:Math.max(.001,be)};if(ue){let he=J(Ee,R);he.length?F&&(Ke.indices=he):Ke.isMatch=!1}return Ke}function ae(p){let o={};for(let m=0,S=p.length;m{this.chunks.push({pattern:G,alphabet:ae(G),startIndex:q})},x=this.pattern.length;if(x>z){let G=0,q=x%z,re=x-q;for(;G{let{isMatch:ve,score:Ie,indices:be}=ee(o,re,ue,{location:I+Ee,distance:T,threshold:A,findAllMatches:R,minMatchCharLength:F,includeMatches:S,ignoreLocation:H});ve&&(G=!0),x+=Ie,ve&&be&&(B=[...B,...be])});let q={isMatch:G,score:G?x/this.chunks.length:1};return G&&S&&(q.indices=B),q}}class le{constructor(o){this.pattern=o}static isMultiMatch(o){return _e(o,this.multiRegex)}static isSingleMatch(o){return _e(o,this.singleRegex)}search(){}}function _e(p,o){let m=p.match(o);return m?m[1]:null}class te extends le{constructor(o){super(o)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(o){let m=o===this.pattern;return{isMatch:m,score:m?0:1,indices:[0,this.pattern.length-1]}}}class de extends le{constructor(o){super(o)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(o){let S=o.indexOf(this.pattern)===-1;return{isMatch:S,score:S?0:1,indices:[0,o.length-1]}}}class pe extends le{constructor(o){super(o)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(o){let m=o.startsWith(this.pattern);return{isMatch:m,score:m?0:1,indices:[0,this.pattern.length-1]}}}class oe extends le{constructor(o){super(o)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(o){let m=!o.startsWith(this.pattern);return{isMatch:m,score:m?0:1,indices:[0,o.length-1]}}}class Te extends le{constructor(o){super(o)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(o){let m=o.endsWith(this.pattern);return{isMatch:m,score:m?0:1,indices:[o.length-this.pattern.length,o.length-1]}}}class Pe extends le{constructor(o){super(o)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(o){let m=!o.endsWith(this.pattern);return{isMatch:m,score:m?0:1,indices:[0,o.length-1]}}}class He extends le{constructor(o,{location:m=u.location,threshold:S=u.threshold,distance:I=u.distance,includeMatches:T=u.includeMatches,findAllMatches:A=u.findAllMatches,minMatchCharLength:R=u.minMatchCharLength,isCaseSensitive:F=u.isCaseSensitive,ignoreLocation:H=u.ignoreLocation}={}){super(o),this._bitapSearch=new ce(o,{location:m,threshold:S,distance:I,includeMatches:T,findAllMatches:A,minMatchCharLength:R,isCaseSensitive:F,ignoreLocation:H})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(o){return this._bitapSearch.searchIn(o)}}class Be extends le{constructor(o){super(o)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(o){let m=0,S,I=[],T=this.pattern.length;for(;(S=o.indexOf(this.pattern,m))>-1;)m=S+T,I.push([S,m-1]);let A=!!I.length;return{isMatch:A,score:A?0:1,indices:I}}}let Me=[te,Be,pe,oe,Pe,Te,de,He],Ve=Me.length,Xe=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,Je="|";function Qe(p,o={}){return p.split(Je).map(m=>{let S=m.trim().split(Xe).filter(T=>T&&!!T.trim()),I=[];for(let T=0,A=S.length;T!!(p[Ce.AND]||p[Ce.OR]),tt=p=>!!p[je.PATH],it=p=>!_(p)&&O(p)&&!Re(p),ke=p=>({[Ce.AND]:Object.keys(p).map(o=>({[o]:p[o]}))});function xe(p,o,{auto:m=!0}={}){let S=I=>{let T=Object.keys(I),A=tt(I);if(!A&&T.length>1&&!Re(I))return S(ke(I));if(it(I)){let F=A?I[je.PATH]:T[0],H=A?I[je.PATTERN]:I[F];if(!r(H))throw new Error(ne(F));let B={keyId:s(F),pattern:H};return m&&(B.searcher=Ne(H,o)),B}let R={children:[],operator:T[0]};return T.forEach(F=>{let H=I[F];_(H)&&H.forEach(B=>{R.children.push(S(B))})}),R};return Re(p)||(p=ke(p)),S(p)}function nt(p,{ignoreFieldNorm:o=u.ignoreFieldNorm}){p.forEach(m=>{let S=1;m.matches.forEach(({key:I,norm:T,score:A})=>{let R=I?I.weight:null;S*=Math.pow(A===0&&R?Number.EPSILON:A,(R||1)*(o?1:T))}),m.score=S})}function rt(p,o){let m=p.matches;o.matches=[],y(m)&&m.forEach(S=>{if(!y(S.indices)||!S.indices.length)return;let{indices:I,value:T}=S,A={indices:I,value:T};S.key&&(A.key=S.key.src),S.idx>-1&&(A.refIndex=S.idx),o.matches.push(A)})}function st(p,o){o.score=p.score}function ot(p,o,{includeMatches:m=u.includeMatches,includeScore:S=u.includeScore}={}){let I=[];return m&&I.push(rt),S&&I.push(st),p.map(T=>{let{idx:A}=T,R={item:o[A],refIndex:A};return I.length&&I.forEach(F=>{F(T,R)}),R})}class Se{constructor(o,m={},S){this.options={...u,...m},this.options.useExtendedSearch,this._keyStore=new e(this.options.keys),this.setCollection(o,S)}setCollection(o,m){if(this._docs=o,m&&!(m instanceof V))throw new Error(Z);this._myIndex=m||U(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(o){y(o)&&(this._docs.push(o),this._myIndex.add(o))}remove(o=()=>!1){let m=[];for(let S=0,I=this._docs.length;S-1&&(F=F.slice(0,m)),ot(F,this._docs,{includeMatches:S,includeScore:I})}_searchStringList(o){let m=Ne(o,this.options),{records:S}=this._myIndex,I=[];return S.forEach(({v:T,i:A,n:R})=>{if(!y(T))return;let{isMatch:F,score:H,indices:B}=m.searchIn(T);F&&I.push({item:T,idx:A,matches:[{score:H,value:T,norm:R,indices:B}]})}),I}_searchLogical(o){let m=xe(o,this.options),S=(R,F,H)=>{if(!R.children){let{keyId:x,searcher:G}=R,q=this._findMatches({key:this._keyStore.get(x),value:this._myIndex.getValueForItemAtKeyId(F,x),searcher:G});return q&&q.length?[{idx:H,item:F,matches:q}]:[]}let B=[];for(let x=0,G=R.children.length;x{if(y(R)){let H=S(m,R,F);H.length&&(T[F]||(T[F]={idx:F,item:R,matches:[]},A.push(T[F])),H.forEach(({matches:B})=>{T[F].matches.push(...B)}))}}),A}_searchObjectList(o){let m=Ne(o,this.options),{keys:S,records:I}=this._myIndex,T=[];return I.forEach(({$:A,i:R})=>{if(!y(A))return;let F=[];S.forEach((H,B)=>{F.push(...this._findMatches({key:H,value:A[B],searcher:m}))}),F.length&&T.push({idx:R,item:A,matches:F})}),T}_findMatches({key:o,value:m,searcher:S}){if(!y(m))return[];let I=[];if(_(m))m.forEach(({v:T,i:A,n:R})=>{if(!y(T))return;let{isMatch:F,score:H,indices:B}=S.searchIn(T);F&&I.push({score:H,key:o,value:T,idx:A,norm:R,indices:B})});else{let{v:T,n:A}=m,{isMatch:R,score:F,indices:H}=S.searchIn(T);R&&I.push({score:F,key:o,value:T,norm:A,indices:H})}return I}}Se.version="6.6.2",Se.createIndex=U,Se.parseIndex=$,Se.config=u,Se.parseQuery=xe,et(qe)},791:function(j,i,b){b.r(i),b.d(i,{__DO_NOT_USE__ActionTypes:function(){return y},applyMiddleware:function(){return M},bindActionCreators:function(){return v},combineReducers:function(){return n},compose:function(){return P},createStore:function(){return w},legacy_createStore:function(){return N}});function _(f){"@babel/helpers - typeof";return _=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(u){return typeof u}:function(u){return u&&typeof Symbol=="function"&&u.constructor===Symbol&&u!==Symbol.prototype?"symbol":typeof u},_(f)}function h(f,u){if(_(f)!=="object"||f===null)return f;var C=f[Symbol.toPrimitive];if(C!==void 0){var Y=C.call(f,u||"default");if(_(Y)!=="object")return Y;throw new TypeError("@@toPrimitive must return a primitive value.")}return(u==="string"?String:Number)(f)}function d(f){var u=h(f,"string");return _(u)==="symbol"?u:String(u)}function a(f,u,C){return u=d(u),u in f?Object.defineProperty(f,u,{value:C,enumerable:!0,configurable:!0,writable:!0}):f[u]=C,f}function r(f,u){var C=Object.keys(f);if(Object.getOwnPropertySymbols){var Y=Object.getOwnPropertySymbols(f);u&&(Y=Y.filter(function(V){return Object.getOwnPropertyDescriptor(f,V).enumerable})),C.push.apply(C,Y)}return C}function c(f){for(var u=1;u"u"&&(C=u,u=void 0),typeof C<"u"){if(typeof C!="function")throw new Error(l(1));return C(w)(f,u)}if(typeof f!="function")throw new Error(l(2));var V=f,U=u,$=[],W=$,J=!1;function z(){W===$&&(W=$.slice())}function ee(){if(J)throw new Error(l(3));return U}function ae(te){if(typeof te!="function")throw new Error(l(4));if(J)throw new Error(l(5));var de=!0;return z(),W.push(te),function(){if(de){if(J)throw new Error(l(6));de=!1,z();var oe=W.indexOf(te);W.splice(oe,1),$=null}}}function ce(te){if(!D(te))throw new Error(l(7));if(typeof te.type>"u")throw new Error(l(8));if(J)throw new Error(l(9));try{J=!0,U=V(U,te)}finally{J=!1}for(var de=$=W,pe=0;pe0)return"Unexpected "+($.length>1?"keys":"key")+" "+('"'+$.join('", "')+'" found in '+U+". ")+"Expected to find one of the known reducer keys instead: "+('"'+V.join('", "')+'". Unexpected keys will be ignored.')}function t(f){Object.keys(f).forEach(function(u){var C=f[u],Y=C(void 0,{type:y.INIT});if(typeof Y>"u")throw new Error(l(12));if(typeof C(void 0,{type:y.PROBE_UNKNOWN_ACTION()})>"u")throw new Error(l(13))})}function n(f){for(var u=Object.keys(f),C={},Y=0;Y"u"){var Te=ee&&ee.type;throw new Error(l(14))}le[te]=oe,ce=ce||oe!==pe}return ce=ce||U.length!==Object.keys(z).length,ce?le:z}}function s(f,u){return function(){return u(f.apply(this,arguments))}}function v(f,u){if(typeof f=="function")return s(f,u);if(typeof f!="object"||f===null)throw new Error(l(16));var C={};for(var Y in f){var V=f[Y];typeof V=="function"&&(C[Y]=s(V,u))}return C}function P(){for(var f=arguments.length,u=new Array(f),C=0;Cwindow.pluralize(O,e,{count:e}),noChoicesText:E,noResultsText:L,placeholderValue:k,position:Q??"auto",removeItemButton:se,renderChoiceLimit:D,searchEnabled:h,searchFields:w??["label"],searchPlaceholderValue:E,searchResultLimit:D,shouldSort:!1,searchFloor:a?0:1}),await this.refreshChoices({withInitialOptions:!0}),[null,void 0,""].includes(this.state)||this.select.setChoiceByValue(this.formatState(this.state)),this.refreshPlaceholder(),b&&this.select.showDropdown(),this.$refs.input.addEventListener("change",()=>{this.refreshPlaceholder(),!this.isStateBeingUpdated&&(this.isStateBeingUpdated=!0,this.state=this.select.getValue(!0)??null,this.$nextTick(()=>this.isStateBeingUpdated=!1))}),d&&this.$refs.input.addEventListener("showDropdown",async()=>{this.select.clearChoices(),await this.select.setChoices([{label:c,value:"",disabled:!0}]),await this.refreshChoices()}),a&&(this.$refs.input.addEventListener("search",async e=>{let t=e.detail.value?.trim();this.isSearching=!0,this.select.clearChoices(),await this.select.setChoices([{label:[null,void 0,""].includes(t)?c:ne,value:"",disabled:!0}])}),this.$refs.input.addEventListener("search",Alpine.debounce(async e=>{await this.refreshChoices({search:e.detail.value?.trim()}),this.isSearching=!1},Z))),_||window.addEventListener("filament-forms::select.refreshSelectedOptionLabel",async e=>{e.detail.livewireId===r&&e.detail.statePath===g&&await this.refreshChoices({withInitialOptions:!1})}),this.$watch("state",async()=>{this.select&&(this.refreshPlaceholder(),!this.isStateBeingUpdated&&await this.refreshChoices({withInitialOptions:!d}))})},destroy:function(){this.select.destroy(),this.select=null},refreshChoices:async function(e={}){let t=await this.getChoices(e);this.select&&(this.select.clearStore(),this.refreshPlaceholder(),this.setChoices(t),[null,void 0,""].includes(this.state)||this.select.setChoiceByValue(this.formatState(this.state)))},setChoices:function(e){this.select.setChoices(e,"value","label",!0)},getChoices:async function(e={}){let t=await this.getExistingOptions(e);return t.concat(await this.getMissingOptions(t))},getExistingOptions:async function({search:e,withInitialOptions:t}){if(t)return y;let n=[];return e!==""&&e!==null&&e!==void 0?n=await i(e):n=await j(),n.map(s=>s.choices?(s.choices=s.choices.map(v=>(v.selected=Array.isArray(this.state)?this.state.includes(v.value):this.state===v.value,v)),s):(s.selected=Array.isArray(this.state)?this.state.includes(s.value):this.state===s.value,s))},refreshPlaceholder:function(){_||(this.select._renderItems(),[null,void 0,""].includes(this.state)&&(this.$el.querySelector(".choices__list--single").innerHTML=`
${k??""}
`))},formatState:function(e){return _?(e??[]).map(t=>t?.toString()):e?.toString()},getMissingOptions:async function(e){let t=this.formatState(this.state);if([null,void 0,"",[],{}].includes(t))return{};let n=new Set;return e.forEach(s=>{if(s.choices){s.choices.forEach(v=>n.add(v.value));return}n.add(s.value)}),_?t.every(s=>n.has(s))?{}:(await me()).filter(s=>!n.has(s.value)).map(s=>(s.selected=!0,s)):n.has(t)?n:[{label:await X(),value:t,selected:!0}]}}}export{vt as default}; -/*! Bundled license information: - -choices.js/public/assets/scripts/choices.js: - (*! choices.js v10.2.0 | © 2022 Josh Johnson | https://github.com/jshjohnson/Choices#readme *) -*/ diff --git a/public/js/filament/forms/components/tags-input.js b/public/js/filament/forms/components/tags-input.js deleted file mode 100644 index 6a2aa30..0000000 --- a/public/js/filament/forms/components/tags-input.js +++ /dev/null @@ -1 +0,0 @@ -function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; diff --git a/public/js/filament/forms/components/textarea.js b/public/js/filament/forms/components/textarea.js deleted file mode 100644 index 4fda241..0000000 --- a/public/js/filament/forms/components/textarea.js +++ /dev/null @@ -1 +0,0 @@ -function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; diff --git a/public/js/filament/notifications/notifications.js b/public/js/filament/notifications/notifications.js deleted file mode 100644 index 7ce3063..0000000 --- a/public/js/filament/notifications/notifications.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{var O=Object.create;var N=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var d=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports);var j=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Y(t))!W.call(i,n)&&n!==e&&N(i,n,{get:()=>t[n],enumerable:!(s=V(t,n))||s.enumerable});return i};var J=(i,t,e)=>(e=i!=null?O(H(i)):{},j(t||!i||!i.__esModule?N(e,"default",{value:i,enumerable:!0}):e,i));var S=d((ut,_)=>{var v,g=typeof global<"u"&&(global.crypto||global.msCrypto);g&&g.getRandomValues&&(y=new Uint8Array(16),v=function(){return g.getRandomValues(y),y});var y;v||(T=new Array(16),v=function(){for(var i=0,t;i<16;i++)(i&3)===0&&(t=Math.random()*4294967296),T[i]=t>>>((i&3)<<3)&255;return T});var T;_.exports=v});var C=d((ct,U)=>{var P=[];for(f=0;f<256;++f)P[f]=(f+256).toString(16).substr(1);var f;function K(i,t){var e=t||0,s=P;return s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]}U.exports=K});var R=d((lt,b)=>{var Q=S(),X=C(),a=Q(),Z=[a[0]|1,a[1],a[2],a[3],a[4],a[5]],F=(a[6]<<8|a[7])&16383,D=0,A=0;function tt(i,t,e){var s=t&&e||0,n=t||[];i=i||{};var r=i.clockseq!==void 0?i.clockseq:F,o=i.msecs!==void 0?i.msecs:new Date().getTime(),h=i.nsecs!==void 0?i.nsecs:A+1,l=o-D+(h-A)/1e4;if(l<0&&i.clockseq===void 0&&(r=r+1&16383),(l<0||o>D)&&i.nsecs===void 0&&(h=0),h>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");D=o,A=h,F=r,o+=122192928e5;var c=((o&268435455)*1e4+h)%4294967296;n[s++]=c>>>24&255,n[s++]=c>>>16&255,n[s++]=c>>>8&255,n[s++]=c&255;var u=o/4294967296*1e4&268435455;n[s++]=u>>>8&255,n[s++]=u&255,n[s++]=u>>>24&15|16,n[s++]=u>>>16&255,n[s++]=r>>>8|128,n[s++]=r&255;for(var $=i.node||Z,m=0;m<6;++m)n[s+m]=$[m];return t||X(n)}b.exports=tt});var I=d((dt,G)=>{var it=S(),et=C();function st(i,t,e){var s=t&&e||0;typeof i=="string"&&(t=i=="binary"?new Array(16):null,i=null),i=i||{};var n=i.random||(i.rng||it)();if(n[6]=n[6]&15|64,n[8]=n[8]&63|128,t)for(var r=0;r<16;++r)t[s+r]=n[r];return t||et(n)}G.exports=st});var z=d((ft,M)=>{var nt=R(),L=I(),E=L;E.v1=nt;E.v4=L;M.exports=E});function k(i,t=()=>{}){let e=!1;return function(){e?t.apply(this,arguments):(e=!0,i.apply(this,arguments))}}var q=i=>{i.data("notificationComponent",({notification:t})=>({isShown:!1,computedStyle:null,transitionDuration:null,transitionEasing:null,init:function(){this.computedStyle=window.getComputedStyle(this.$el),this.transitionDuration=parseFloat(this.computedStyle.transitionDuration)*1e3,this.transitionEasing=this.computedStyle.transitionTimingFunction,this.configureTransitions(),this.configureAnimations(),t.duration&&t.duration!=="persistent"&&setTimeout(()=>{if(!this.$el.matches(":hover")){this.close();return}this.$el.addEventListener("mouseleave",()=>this.close())},t.duration),this.isShown=!0},configureTransitions:function(){let e=this.computedStyle.display,s=()=>{i.mutateDom(()=>{this.$el.style.setProperty("display",e),this.$el.style.setProperty("visibility","visible")}),this.$el._x_isShown=!0},n=()=>{i.mutateDom(()=>{this.$el._x_isShown?this.$el.style.setProperty("visibility","hidden"):this.$el.style.setProperty("display","none")})},r=k(o=>o?s():n(),o=>{this.$el._x_toggleAndCascadeWithTransitions(this.$el,o,s,n)});i.effect(()=>r(this.isShown))},configureAnimations:function(){let e;Livewire.hook("commit",({component:s,commit:n,succeed:r,fail:o,respond:h})=>{s.snapshot.data.isFilamentNotificationsComponent&&requestAnimationFrame(()=>{let l=()=>this.$el.getBoundingClientRect().top,c=l();h(()=>{e=()=>{this.isShown&&this.$el.animate([{transform:`translateY(${c-l()}px)`},{transform:"translateY(0px)"}],{duration:this.transitionDuration,easing:this.transitionEasing})},this.$el.getAnimations().forEach(u=>u.finish())}),r(({snapshot:u,effect:$})=>{e()})})})},close:function(){this.isShown=!1,setTimeout(()=>window.dispatchEvent(new CustomEvent("notificationClosed",{detail:{id:t.id}})),this.transitionDuration)},markAsRead:function(){window.dispatchEvent(new CustomEvent("markedNotificationAsRead",{detail:{id:t.id}}))},markAsUnread:function(){window.dispatchEvent(new CustomEvent("markedNotificationAsUnread",{detail:{id:t.id}}))}}))};var B=J(z(),1),p=class{constructor(){return this.id((0,B.v4)()),this}id(t){return this.id=t,this}title(t){return this.title=t,this}body(t){return this.body=t,this}actions(t){return this.actions=t,this}status(t){return this.status=t,this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconColor(t){return this.iconColor=t,this}duration(t){return this.duration=t,this}seconds(t){return this.duration(t*1e3),this}persistent(){return this.duration("persistent"),this}danger(){return this.status("danger"),this}info(){return this.status("info"),this}success(){return this.status("success"),this}warning(){return this.status("warning"),this}view(t){return this.view=t,this}viewData(t){return this.viewData=t,this}send(){return window.dispatchEvent(new CustomEvent("notificationSent",{detail:{notification:this}})),this}},w=class{constructor(t){return this.name(t),this}name(t){return this.name=t,this}color(t){return this.color=t,this}dispatch(t,e){return this.event(t),this.eventData(e),this}dispatchSelf(t,e){return this.dispatch(t,e),this.dispatchDirection="self",this}dispatchTo(t,e,s){return this.dispatch(e,s),this.dispatchDirection="to",this.dispatchToComponent=t,this}emit(t,e){return this.dispatch(t,e),this}emitSelf(t,e){return this.dispatchSelf(t,e),this}emitTo(t,e,s){return this.dispatchTo(t,e,s),this}dispatchDirection(t){return this.dispatchDirection=t,this}dispatchToComponent(t){return this.dispatchToComponent=t,this}event(t){return this.event=t,this}eventData(t){return this.eventData=t,this}extraAttributes(t){return this.extraAttributes=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}outlined(t=!0){return this.isOutlined=t,this}disabled(t=!0){return this.isDisabled=t,this}label(t){return this.label=t,this}close(t=!0){return this.shouldClose=t,this}openUrlInNewTab(t=!0){return this.shouldOpenUrlInNewTab=t,this}size(t){return this.size=t,this}url(t){return this.url=t,this}view(t){return this.view=t,this}button(){return this.view("filament-actions::button-action"),this}grouped(){return this.view("filament-actions::grouped-action"),this}link(){return this.view("filament-actions::link-action"),this}},x=class{constructor(t){return this.actions(t),this}actions(t){return this.actions=t.map(e=>e.grouped()),this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}label(t){return this.label=t,this}tooltip(t){return this.tooltip=t,this}};window.FilamentNotificationAction=w;window.FilamentNotificationActionGroup=x;window.FilamentNotification=p;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(q)});})(); diff --git a/public/js/filament/support/async-alpine.js b/public/js/filament/support/async-alpine.js deleted file mode 100644 index 048f75c..0000000 --- a/public/js/filament/support/async-alpine.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{(()=>{var d=Object.defineProperty,m=t=>d(t,"__esModule",{value:!0}),f=(t,e)=>{m(t);for(var i in e)d(t,i,{get:e[i],enumerable:!0})},o={};f(o,{eager:()=>g,event:()=>w,idle:()=>y,media:()=>b,visible:()=>E});var c=()=>!0,g=c,v=({component:t,argument:e})=>new Promise(i=>{if(e)window.addEventListener(e,()=>i(),{once:!0});else{let n=a=>{a.detail.id===t.id&&(window.removeEventListener("async-alpine:load",n),i())};window.addEventListener("async-alpine:load",n)}}),w=v,x=()=>new Promise(t=>{"requestIdleCallback"in window?window.requestIdleCallback(t):setTimeout(t,200)}),y=x,A=({argument:t})=>new Promise(e=>{if(!t)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),e();let i=window.matchMedia(`(${t})`);i.matches?e():i.addEventListener("change",e,{once:!0})}),b=A,$=({component:t,argument:e})=>new Promise(i=>{let n=e||"0px 0px 0px 0px",a=new IntersectionObserver(r=>{r[0].isIntersecting&&(a.disconnect(),i())},{rootMargin:n});a.observe(t.el)}),E=$;function P(t){let e=q(t),i=u(e);return i.type==="method"?{type:"expression",operator:"&&",parameters:[i]}:i}function q(t){let e=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,i=[],n;for(;(n=e.exec(t))!==null;){let[,a,r,s]=n;if(a!==void 0)i.push({type:"parenthesis",value:a});else if(r!==void 0)i.push({type:"operator",value:r==="|"?"&&":r});else{let p={type:"method",method:s.trim()};s.includes("(")&&(p.method=s.substring(0,s.indexOf("(")).trim(),p.argument=s.substring(s.indexOf("(")+1,s.indexOf(")"))),s.method==="immediate"&&(s.method="eager"),i.push(p)}}return i}function u(t){let e=h(t);for(;t.length>0&&(t[0].value==="&&"||t[0].value==="|"||t[0].value==="||");){let i=t.shift().value,n=h(t);e.type==="expression"&&e.operator===i?e.parameters.push(n):e={type:"expression",operator:i,parameters:[e,n]}}return e}function h(t){if(t[0].value==="("){t.shift();let e=u(t);return t[0].value===")"&&t.shift(),e}else return t.shift()}var _="__internal_",l={Alpine:null,_options:{prefix:"ax-",alpinePrefix:"x-",root:"load",inline:"load-src",defaultStrategy:"eager"},_alias:!1,_data:{},_realIndex:0,get _index(){return this._realIndex++},init(t,e={}){return this.Alpine=t,this._options={...this._options,...e},this},start(){return this._processInline(),this._setupComponents(),this._mutations(),this},data(t,e=!1){return this._data[t]={loaded:!1,download:e},this},url(t,e){!t||!e||(this._data[t]||this.data(t),this._data[t].download=()=>import(this._parseUrl(e)))},alias(t){this._alias=t},_processInline(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.inline}]`);for(let e of t)this._inlineElement(e)},_inlineElement(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`),i=t.getAttribute(`${this._options.prefix}${this._options.inline}`);if(!e||!i)return;let n=this._parseName(e);this.url(n,i)},_setupComponents(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.root}]`);for(let e of t)this._setupComponent(e)},_setupComponent(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`);t.setAttribute(`${this._options.alpinePrefix}ignore`,"");let i=this._parseName(e),n=t.getAttribute(`${this._options.prefix}${this._options.root}`)||this._options.defaultStrategy;this._componentStrategy({name:i,strategy:n,el:t,id:t.id||this._index})},async _componentStrategy(t){let e=P(t.strategy);await this._generateRequirements(t,e),await this._download(t.name),this._activate(t)},_generateRequirements(t,e){if(e.type==="expression"){if(e.operator==="&&")return Promise.all(e.parameters.map(i=>this._generateRequirements(t,i)));if(e.operator==="||")return Promise.any(e.parameters.map(i=>this._generateRequirements(t,i)))}return o[e.method]?o[e.method]({component:t,argument:e.argument}):!1},async _download(t){if(t.startsWith(_)||(this._handleAlias(t),!this._data[t]||this._data[t].loaded))return;let e=await this._getModule(t);this.Alpine.data(t,e),this._data[t].loaded=!0},async _getModule(t){if(!this._data[t])return;let e=await this._data[t].download(t);return typeof e=="function"?e:e[t]||e.default||Object.values(e)[0]||!1},_activate(t){this.Alpine.destroyTree(t.el),t.el.removeAttribute(`${this._options.alpinePrefix}ignore`),t.el._x_ignore=!1,this.Alpine.initTree(t.el)},_mutations(){new MutationObserver(t=>{for(let e of t)if(e.addedNodes)for(let i of e.addedNodes)i.nodeType===1&&(i.hasAttribute(`${this._options.prefix}${this._options.root}`)&&this._mutationEl(i),i.querySelectorAll(`[${this._options.prefix}${this._options.root}]`).forEach(n=>this._mutationEl(n)))}).observe(document,{attributes:!0,childList:!0,subtree:!0})},_mutationEl(t){t.hasAttribute(`${this._options.prefix}${this._options.inline}`)&&this._inlineElement(t),this._setupComponent(t)},_handleAlias(t){if(!(!this._alias||this._data[t])){if(typeof this._alias=="function"){this.data(t,this._alias);return}this.url(t,this._alias.replaceAll("[name]",t))}},_parseName(t){return(t||"").split(/[({]/g)[0]||`${_}${this._index}`},_parseUrl(t){return new RegExp("^(?:[a-z+]+:)?//","i").test(t)?t:new URL(t,document.baseURI).href}};document.addEventListener("alpine:init",()=>{window.AsyncAlpine=l,l.init(Alpine,window.AsyncAlpineOptions||{}),document.dispatchEvent(new CustomEvent("async-alpine:init")),l.start()})})();})(); diff --git a/public/js/filament/support/support.js b/public/js/filament/support/support.js deleted file mode 100644 index 9b2b2dc..0000000 --- a/public/js/filament/support/support.js +++ /dev/null @@ -1,46 +0,0 @@ -(()=>{var Vo=Object.create;var Pi=Object.defineProperty;var Yo=Object.getOwnPropertyDescriptor;var Xo=Object.getOwnPropertyNames;var qo=Object.getPrototypeOf,Go=Object.prototype.hasOwnProperty;var Jr=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Ko=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Xo(e))!Go.call(t,i)&&i!==r&&Pi(t,i,{get:()=>e[i],enumerable:!(n=Yo(e,i))||n.enumerable});return t};var Jo=(t,e,r)=>(r=t!=null?Vo(qo(t)):{},Ko(e||!t||!t.__esModule?Pi(r,"default",{value:t,enumerable:!0}):r,t));var po=Jr(()=>{});var ho=Jr(()=>{});var vo=Jr((js,yr)=>{(function(){"use strict";var t="input is invalid type",e="finalize already called",r=typeof window=="object",n=r?window:{};n.JS_MD5_NO_WINDOW&&(r=!1);var i=!r&&typeof self=="object",o=!n.JS_MD5_NO_NODE_JS&&typeof process=="object"&&process.versions&&process.versions.node;o?n=global:i&&(n=self);var a=!n.JS_MD5_NO_COMMON_JS&&typeof yr=="object"&&yr.exports,d=typeof define=="function"&&define.amd,f=!n.JS_MD5_NO_ARRAY_BUFFER&&typeof ArrayBuffer<"u",u="0123456789abcdef".split(""),w=[128,32768,8388608,-2147483648],m=[0,8,16,24],E=["hex","array","digest","buffer","arrayBuffer","base64"],O="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),S=[],M;if(f){var I=new ArrayBuffer(68);M=new Uint8Array(I),S=new Uint32Array(I)}var $=Array.isArray;(n.JS_MD5_NO_NODE_JS||!$)&&($=function(l){return Object.prototype.toString.call(l)==="[object Array]"});var A=ArrayBuffer.isView;f&&(n.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW||!A)&&(A=function(l){return typeof l=="object"&&l.buffer&&l.buffer.constructor===ArrayBuffer});var k=function(l){var h=typeof l;if(h==="string")return[l,!0];if(h!=="object"||l===null)throw new Error(t);if(f&&l.constructor===ArrayBuffer)return[new Uint8Array(l),!1];if(!$(l)&&!A(l))throw new Error(t);return[l,!1]},Y=function(l){return function(h){return new X(!0).update(h)[l]()}},nt=function(){var l=Y("hex");o&&(l=J(l)),l.create=function(){return new X},l.update=function(p){return l.create().update(p)};for(var h=0;h>>6,Vt[P++]=128|p&63):p<55296||p>=57344?(Vt[P++]=224|p>>>12,Vt[P++]=128|p>>>6&63,Vt[P++]=128|p&63):(p=65536+((p&1023)<<10|l.charCodeAt(++j)&1023),Vt[P++]=240|p>>>18,Vt[P++]=128|p>>>12&63,Vt[P++]=128|p>>>6&63,Vt[P++]=128|p&63);else for(P=this.start;j>>2]|=p<>>2]|=(192|p>>>6)<>>2]|=(128|p&63)<=57344?(Q[P>>>2]|=(224|p>>>12)<>>2]|=(128|p>>>6&63)<>>2]|=(128|p&63)<>>2]|=(240|p>>>18)<>>2]|=(128|p>>>12&63)<>>2]|=(128|p>>>6&63)<>>2]|=(128|p&63)<>>2]|=l[j]<=64?(this.start=P-64,this.hash(),this.hashed=!0):this.start=P}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this},X.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var l=this.blocks,h=this.lastByteIndex;l[h>>>2]|=w[h&3],h>=56&&(this.hashed||this.hash(),l[0]=l[16],l[16]=l[1]=l[2]=l[3]=l[4]=l[5]=l[6]=l[7]=l[8]=l[9]=l[10]=l[11]=l[12]=l[13]=l[14]=l[15]=0),l[14]=this.bytes<<3,l[15]=this.hBytes<<3|this.bytes>>>29,this.hash()}},X.prototype.hash=function(){var l,h,v,p,j,P,R=this.blocks;this.first?(l=R[0]-680876937,l=(l<<7|l>>>25)-271733879<<0,p=(-1732584194^l&2004318071)+R[1]-117830708,p=(p<<12|p>>>20)+l<<0,v=(-271733879^p&(l^-271733879))+R[2]-1126478375,v=(v<<17|v>>>15)+p<<0,h=(l^v&(p^l))+R[3]-1316259209,h=(h<<22|h>>>10)+v<<0):(l=this.h0,h=this.h1,v=this.h2,p=this.h3,l+=(p^h&(v^p))+R[0]-680876936,l=(l<<7|l>>>25)+h<<0,p+=(v^l&(h^v))+R[1]-389564586,p=(p<<12|p>>>20)+l<<0,v+=(h^p&(l^h))+R[2]+606105819,v=(v<<17|v>>>15)+p<<0,h+=(l^v&(p^l))+R[3]-1044525330,h=(h<<22|h>>>10)+v<<0),l+=(p^h&(v^p))+R[4]-176418897,l=(l<<7|l>>>25)+h<<0,p+=(v^l&(h^v))+R[5]+1200080426,p=(p<<12|p>>>20)+l<<0,v+=(h^p&(l^h))+R[6]-1473231341,v=(v<<17|v>>>15)+p<<0,h+=(l^v&(p^l))+R[7]-45705983,h=(h<<22|h>>>10)+v<<0,l+=(p^h&(v^p))+R[8]+1770035416,l=(l<<7|l>>>25)+h<<0,p+=(v^l&(h^v))+R[9]-1958414417,p=(p<<12|p>>>20)+l<<0,v+=(h^p&(l^h))+R[10]-42063,v=(v<<17|v>>>15)+p<<0,h+=(l^v&(p^l))+R[11]-1990404162,h=(h<<22|h>>>10)+v<<0,l+=(p^h&(v^p))+R[12]+1804603682,l=(l<<7|l>>>25)+h<<0,p+=(v^l&(h^v))+R[13]-40341101,p=(p<<12|p>>>20)+l<<0,v+=(h^p&(l^h))+R[14]-1502002290,v=(v<<17|v>>>15)+p<<0,h+=(l^v&(p^l))+R[15]+1236535329,h=(h<<22|h>>>10)+v<<0,l+=(v^p&(h^v))+R[1]-165796510,l=(l<<5|l>>>27)+h<<0,p+=(h^v&(l^h))+R[6]-1069501632,p=(p<<9|p>>>23)+l<<0,v+=(l^h&(p^l))+R[11]+643717713,v=(v<<14|v>>>18)+p<<0,h+=(p^l&(v^p))+R[0]-373897302,h=(h<<20|h>>>12)+v<<0,l+=(v^p&(h^v))+R[5]-701558691,l=(l<<5|l>>>27)+h<<0,p+=(h^v&(l^h))+R[10]+38016083,p=(p<<9|p>>>23)+l<<0,v+=(l^h&(p^l))+R[15]-660478335,v=(v<<14|v>>>18)+p<<0,h+=(p^l&(v^p))+R[4]-405537848,h=(h<<20|h>>>12)+v<<0,l+=(v^p&(h^v))+R[9]+568446438,l=(l<<5|l>>>27)+h<<0,p+=(h^v&(l^h))+R[14]-1019803690,p=(p<<9|p>>>23)+l<<0,v+=(l^h&(p^l))+R[3]-187363961,v=(v<<14|v>>>18)+p<<0,h+=(p^l&(v^p))+R[8]+1163531501,h=(h<<20|h>>>12)+v<<0,l+=(v^p&(h^v))+R[13]-1444681467,l=(l<<5|l>>>27)+h<<0,p+=(h^v&(l^h))+R[2]-51403784,p=(p<<9|p>>>23)+l<<0,v+=(l^h&(p^l))+R[7]+1735328473,v=(v<<14|v>>>18)+p<<0,h+=(p^l&(v^p))+R[12]-1926607734,h=(h<<20|h>>>12)+v<<0,j=h^v,l+=(j^p)+R[5]-378558,l=(l<<4|l>>>28)+h<<0,p+=(j^l)+R[8]-2022574463,p=(p<<11|p>>>21)+l<<0,P=p^l,v+=(P^h)+R[11]+1839030562,v=(v<<16|v>>>16)+p<<0,h+=(P^v)+R[14]-35309556,h=(h<<23|h>>>9)+v<<0,j=h^v,l+=(j^p)+R[1]-1530992060,l=(l<<4|l>>>28)+h<<0,p+=(j^l)+R[4]+1272893353,p=(p<<11|p>>>21)+l<<0,P=p^l,v+=(P^h)+R[7]-155497632,v=(v<<16|v>>>16)+p<<0,h+=(P^v)+R[10]-1094730640,h=(h<<23|h>>>9)+v<<0,j=h^v,l+=(j^p)+R[13]+681279174,l=(l<<4|l>>>28)+h<<0,p+=(j^l)+R[0]-358537222,p=(p<<11|p>>>21)+l<<0,P=p^l,v+=(P^h)+R[3]-722521979,v=(v<<16|v>>>16)+p<<0,h+=(P^v)+R[6]+76029189,h=(h<<23|h>>>9)+v<<0,j=h^v,l+=(j^p)+R[9]-640364487,l=(l<<4|l>>>28)+h<<0,p+=(j^l)+R[12]-421815835,p=(p<<11|p>>>21)+l<<0,P=p^l,v+=(P^h)+R[15]+530742520,v=(v<<16|v>>>16)+p<<0,h+=(P^v)+R[2]-995338651,h=(h<<23|h>>>9)+v<<0,l+=(v^(h|~p))+R[0]-198630844,l=(l<<6|l>>>26)+h<<0,p+=(h^(l|~v))+R[7]+1126891415,p=(p<<10|p>>>22)+l<<0,v+=(l^(p|~h))+R[14]-1416354905,v=(v<<15|v>>>17)+p<<0,h+=(p^(v|~l))+R[5]-57434055,h=(h<<21|h>>>11)+v<<0,l+=(v^(h|~p))+R[12]+1700485571,l=(l<<6|l>>>26)+h<<0,p+=(h^(l|~v))+R[3]-1894986606,p=(p<<10|p>>>22)+l<<0,v+=(l^(p|~h))+R[10]-1051523,v=(v<<15|v>>>17)+p<<0,h+=(p^(v|~l))+R[1]-2054922799,h=(h<<21|h>>>11)+v<<0,l+=(v^(h|~p))+R[8]+1873313359,l=(l<<6|l>>>26)+h<<0,p+=(h^(l|~v))+R[15]-30611744,p=(p<<10|p>>>22)+l<<0,v+=(l^(p|~h))+R[6]-1560198380,v=(v<<15|v>>>17)+p<<0,h+=(p^(v|~l))+R[13]+1309151649,h=(h<<21|h>>>11)+v<<0,l+=(v^(h|~p))+R[4]-145523070,l=(l<<6|l>>>26)+h<<0,p+=(h^(l|~v))+R[11]-1120210379,p=(p<<10|p>>>22)+l<<0,v+=(l^(p|~h))+R[2]+718787259,v=(v<<15|v>>>17)+p<<0,h+=(p^(v|~l))+R[9]-343485551,h=(h<<21|h>>>11)+v<<0,this.first?(this.h0=l+1732584193<<0,this.h1=h-271733879<<0,this.h2=v-1732584194<<0,this.h3=p+271733878<<0,this.first=!1):(this.h0=this.h0+l<<0,this.h1=this.h1+h<<0,this.h2=this.h2+v<<0,this.h3=this.h3+p<<0)},X.prototype.hex=function(){this.finalize();var l=this.h0,h=this.h1,v=this.h2,p=this.h3;return u[l>>>4&15]+u[l&15]+u[l>>>12&15]+u[l>>>8&15]+u[l>>>20&15]+u[l>>>16&15]+u[l>>>28&15]+u[l>>>24&15]+u[h>>>4&15]+u[h&15]+u[h>>>12&15]+u[h>>>8&15]+u[h>>>20&15]+u[h>>>16&15]+u[h>>>28&15]+u[h>>>24&15]+u[v>>>4&15]+u[v&15]+u[v>>>12&15]+u[v>>>8&15]+u[v>>>20&15]+u[v>>>16&15]+u[v>>>28&15]+u[v>>>24&15]+u[p>>>4&15]+u[p&15]+u[p>>>12&15]+u[p>>>8&15]+u[p>>>20&15]+u[p>>>16&15]+u[p>>>28&15]+u[p>>>24&15]},X.prototype.toString=X.prototype.hex,X.prototype.digest=function(){this.finalize();var l=this.h0,h=this.h1,v=this.h2,p=this.h3;return[l&255,l>>>8&255,l>>>16&255,l>>>24&255,h&255,h>>>8&255,h>>>16&255,h>>>24&255,v&255,v>>>8&255,v>>>16&255,v>>>24&255,p&255,p>>>8&255,p>>>16&255,p>>>24&255]},X.prototype.array=X.prototype.digest,X.prototype.arrayBuffer=function(){this.finalize();var l=new ArrayBuffer(16),h=new Uint32Array(l);return h[0]=this.h0,h[1]=this.h1,h[2]=this.h2,h[3]=this.h3,l},X.prototype.buffer=X.prototype.arrayBuffer,X.prototype.base64=function(){for(var l,h,v,p="",j=this.array(),P=0;P<15;)l=j[P++],h=j[P++],v=j[P++],p+=O[l>>>2]+O[(l<<4|h>>>4)&63]+O[(h<<2|v>>>6)&63]+O[v&63];return l=j[P],p+=O[l>>>2]+O[l<<4&63]+"==",p};function Z(l,h){var v,p=k(l);if(l=p[0],p[1]){var j=[],P=l.length,R=0,Q;for(v=0;v>>6,j[R++]=128|Q&63):Q<55296||Q>=57344?(j[R++]=224|Q>>>12,j[R++]=128|Q>>>6&63,j[R++]=128|Q&63):(Q=65536+((Q&1023)<<10|l.charCodeAt(++v)&1023),j[R++]=240|Q>>>18,j[R++]=128|Q>>>12&63,j[R++]=128|Q>>>6&63,j[R++]=128|Q&63);l=j}l.length>64&&(l=new X(!0).update(l).array());var Vt=[],Re=[];for(v=0;v<64;++v){var ze=l[v]||0;Vt[v]=92^ze,Re[v]=54^ze}X.call(this,h),this.update(Re),this.oKeyPad=Vt,this.inner=!0,this.sharedMemory=h}Z.prototype=new X,Z.prototype.finalize=function(){if(X.prototype.finalize.call(this),this.inner){this.inner=!1;var l=this.array();X.call(this,this.sharedMemory),this.update(this.oKeyPad),this.update(l),X.prototype.finalize.call(this)}};var mt=nt();mt.md5=mt,mt.md5.hmac=dt(),a?yr.exports=mt:(n.md5=mt,d&&define(function(){return mt}))})()});var $i=["top","right","bottom","left"],Mi=["start","end"],Ri=$i.reduce((t,e)=>t.concat(e,e+"-"+Mi[0],e+"-"+Mi[1]),[]),Ee=Math.min,ee=Math.max,hr=Math.round,pr=Math.floor,nn=t=>({x:t,y:t}),Zo={left:"right",right:"left",bottom:"top",top:"bottom"},Qo={start:"end",end:"start"};function Zr(t,e,r){return ee(t,Ee(e,r))}function je(t,e){return typeof t=="function"?t(e):t}function pe(t){return t.split("-")[0]}function xe(t){return t.split("-")[1]}function Wi(t){return t==="x"?"y":"x"}function Qr(t){return t==="y"?"height":"width"}function Pn(t){return["top","bottom"].includes(pe(t))?"y":"x"}function ti(t){return Wi(Pn(t))}function zi(t,e,r){r===void 0&&(r=!1);let n=xe(t),i=ti(t),o=Qr(i),a=i==="x"?n===(r?"end":"start")?"right":"left":n==="start"?"bottom":"top";return e.reference[o]>e.floating[o]&&(a=mr(a)),[a,mr(a)]}function ta(t){let e=mr(t);return[vr(t),e,vr(e)]}function vr(t){return t.replace(/start|end/g,e=>Qo[e])}function ea(t,e,r){let n=["left","right"],i=["right","left"],o=["top","bottom"],a=["bottom","top"];switch(t){case"top":case"bottom":return r?e?i:n:e?n:i;case"left":case"right":return e?o:a;default:return[]}}function na(t,e,r,n){let i=xe(t),o=ea(pe(t),r==="start",n);return i&&(o=o.map(a=>a+"-"+i),e&&(o=o.concat(o.map(vr)))),o}function mr(t){return t.replace(/left|right|bottom|top/g,e=>Zo[e])}function ra(t){return{top:0,right:0,bottom:0,left:0,...t}}function ei(t){return typeof t!="number"?ra(t):{top:t,right:t,bottom:t,left:t}}function Cn(t){return{...t,top:t.y,left:t.x,right:t.x+t.width,bottom:t.y+t.height}}function Ii(t,e,r){let{reference:n,floating:i}=t,o=Pn(e),a=ti(e),d=Qr(a),f=pe(e),u=o==="y",w=n.x+n.width/2-i.width/2,m=n.y+n.height/2-i.height/2,E=n[d]/2-i[d]/2,O;switch(f){case"top":O={x:w,y:n.y-i.height};break;case"bottom":O={x:w,y:n.y+n.height};break;case"right":O={x:n.x+n.width,y:m};break;case"left":O={x:n.x-i.width,y:m};break;default:O={x:n.x,y:n.y}}switch(xe(e)){case"start":O[a]-=E*(r&&u?-1:1);break;case"end":O[a]+=E*(r&&u?-1:1);break}return O}var ia=async(t,e,r)=>{let{placement:n="bottom",strategy:i="absolute",middleware:o=[],platform:a}=r,d=o.filter(Boolean),f=await(a.isRTL==null?void 0:a.isRTL(e)),u=await a.getElementRects({reference:t,floating:e,strategy:i}),{x:w,y:m}=Ii(u,n,f),E=n,O={},S=0;for(let M=0;M({name:"arrow",options:t,async fn(e){let{x:r,y:n,placement:i,rects:o,platform:a,elements:d,middlewareData:f}=e,{element:u,padding:w=0}=je(t,e)||{};if(u==null)return{};let m=ei(w),E={x:r,y:n},O=ti(i),S=Qr(O),M=await a.getDimensions(u),I=O==="y",$=I?"top":"left",A=I?"bottom":"right",k=I?"clientHeight":"clientWidth",Y=o.reference[S]+o.reference[O]-E[O]-o.floating[S],nt=E[O]-o.reference[O],J=await(a.getOffsetParent==null?void 0:a.getOffsetParent(u)),U=J?J[k]:0;(!U||!await(a.isElement==null?void 0:a.isElement(J)))&&(U=d.floating[k]||o.floating[S]);let dt=Y/2-nt/2,X=U/2-M[S]/2-1,Z=Ee(m[$],X),mt=Ee(m[A],X),l=Z,h=U-M[S]-mt,v=U/2-M[S]/2+dt,p=Zr(l,v,h),j=!f.arrow&&xe(i)!=null&&v!==p&&o.reference[S]/2-(vxe(i)===t),...r.filter(i=>xe(i)!==t)]:r.filter(i=>pe(i)===i)).filter(i=>t?xe(i)===t||(e?vr(i)!==i:!1):!0)}var sa=function(t){return t===void 0&&(t={}),{name:"autoPlacement",options:t,async fn(e){var r,n,i;let{rects:o,middlewareData:a,placement:d,platform:f,elements:u}=e,{crossAxis:w=!1,alignment:m,allowedPlacements:E=Ri,autoAlignment:O=!0,...S}=je(t,e),M=m!==void 0||E===Ri?aa(m||null,O,E):E,I=await _n(e,S),$=((r=a.autoPlacement)==null?void 0:r.index)||0,A=M[$];if(A==null)return{};let k=zi(A,o,await(f.isRTL==null?void 0:f.isRTL(u.floating)));if(d!==A)return{reset:{placement:M[0]}};let Y=[I[pe(A)],I[k[0]],I[k[1]]],nt=[...((n=a.autoPlacement)==null?void 0:n.overflows)||[],{placement:A,overflows:Y}],J=M[$+1];if(J)return{data:{index:$+1,overflows:nt},reset:{placement:J}};let U=nt.map(Z=>{let mt=xe(Z.placement);return[Z.placement,mt&&w?Z.overflows.slice(0,2).reduce((l,h)=>l+h,0):Z.overflows[0],Z.overflows]}).sort((Z,mt)=>Z[1]-mt[1]),X=((i=U.filter(Z=>Z[2].slice(0,xe(Z[0])?2:3).every(mt=>mt<=0))[0])==null?void 0:i[0])||U[0][0];return X!==d?{data:{index:$+1,overflows:nt},reset:{placement:X}}:{}}}},la=function(t){return t===void 0&&(t={}),{name:"flip",options:t,async fn(e){var r,n;let{placement:i,middlewareData:o,rects:a,initialPlacement:d,platform:f,elements:u}=e,{mainAxis:w=!0,crossAxis:m=!0,fallbackPlacements:E,fallbackStrategy:O="bestFit",fallbackAxisSideDirection:S="none",flipAlignment:M=!0,...I}=je(t,e);if((r=o.arrow)!=null&&r.alignmentOffset)return{};let $=pe(i),A=pe(d)===d,k=await(f.isRTL==null?void 0:f.isRTL(u.floating)),Y=E||(A||!M?[mr(d)]:ta(d));!E&&S!=="none"&&Y.push(...na(d,M,S,k));let nt=[d,...Y],J=await _n(e,I),U=[],dt=((n=o.flip)==null?void 0:n.overflows)||[];if(w&&U.push(J[$]),m){let l=zi(i,a,k);U.push(J[l[0]],J[l[1]])}if(dt=[...dt,{placement:i,overflows:U}],!U.every(l=>l<=0)){var X,Z;let l=(((X=o.flip)==null?void 0:X.index)||0)+1,h=nt[l];if(h)return{data:{index:l,overflows:dt},reset:{placement:h}};let v=(Z=dt.filter(p=>p.overflows[0]<=0).sort((p,j)=>p.overflows[1]-j.overflows[1])[0])==null?void 0:Z.placement;if(!v)switch(O){case"bestFit":{var mt;let p=(mt=dt.map(j=>[j.placement,j.overflows.filter(P=>P>0).reduce((P,R)=>P+R,0)]).sort((j,P)=>j[1]-P[1])[0])==null?void 0:mt[0];p&&(v=p);break}case"initialPlacement":v=d;break}if(i!==v)return{reset:{placement:v}}}return{}}}};function Fi(t,e){return{top:t.top-e.height,right:t.right-e.width,bottom:t.bottom-e.height,left:t.left-e.width}}function Li(t){return $i.some(e=>t[e]>=0)}var ca=function(t){return t===void 0&&(t={}),{name:"hide",options:t,async fn(e){let{rects:r}=e,{strategy:n="referenceHidden",...i}=je(t,e);switch(n){case"referenceHidden":{let o=await _n(e,{...i,elementContext:"reference"}),a=Fi(o,r.reference);return{data:{referenceHiddenOffsets:a,referenceHidden:Li(a)}}}case"escaped":{let o=await _n(e,{...i,altBoundary:!0}),a=Fi(o,r.floating);return{data:{escapedOffsets:a,escaped:Li(a)}}}default:return{}}}}};function Ui(t){let e=Ee(...t.map(o=>o.left)),r=Ee(...t.map(o=>o.top)),n=ee(...t.map(o=>o.right)),i=ee(...t.map(o=>o.bottom));return{x:e,y:r,width:n-e,height:i-r}}function fa(t){let e=t.slice().sort((i,o)=>i.y-o.y),r=[],n=null;for(let i=0;in.height/2?r.push([o]):r[r.length-1].push(o),n=o}return r.map(i=>Cn(Ui(i)))}var ua=function(t){return t===void 0&&(t={}),{name:"inline",options:t,async fn(e){let{placement:r,elements:n,rects:i,platform:o,strategy:a}=e,{padding:d=2,x:f,y:u}=je(t,e),w=Array.from(await(o.getClientRects==null?void 0:o.getClientRects(n.reference))||[]),m=fa(w),E=Cn(Ui(w)),O=ei(d);function S(){if(m.length===2&&m[0].left>m[1].right&&f!=null&&u!=null)return m.find(I=>f>I.left-O.left&&fI.top-O.top&&u=2){if(Pn(r)==="y"){let Z=m[0],mt=m[m.length-1],l=pe(r)==="top",h=Z.top,v=mt.bottom,p=l?Z.left:mt.left,j=l?Z.right:mt.right,P=j-p,R=v-h;return{top:h,bottom:v,left:p,right:j,width:P,height:R,x:p,y:h}}let I=pe(r)==="left",$=ee(...m.map(Z=>Z.right)),A=Ee(...m.map(Z=>Z.left)),k=m.filter(Z=>I?Z.left===A:Z.right===$),Y=k[0].top,nt=k[k.length-1].bottom,J=A,U=$,dt=U-J,X=nt-Y;return{top:Y,bottom:nt,left:J,right:U,width:dt,height:X,x:J,y:Y}}return E}let M=await o.getElementRects({reference:{getBoundingClientRect:S},floating:n.floating,strategy:a});return i.reference.x!==M.reference.x||i.reference.y!==M.reference.y||i.reference.width!==M.reference.width||i.reference.height!==M.reference.height?{reset:{rects:M}}:{}}}};async function da(t,e){let{placement:r,platform:n,elements:i}=t,o=await(n.isRTL==null?void 0:n.isRTL(i.floating)),a=pe(r),d=xe(r),f=Pn(r)==="y",u=["left","top"].includes(a)?-1:1,w=o&&f?-1:1,m=je(e,t),{mainAxis:E,crossAxis:O,alignmentAxis:S}=typeof m=="number"?{mainAxis:m,crossAxis:0,alignmentAxis:null}:{mainAxis:0,crossAxis:0,alignmentAxis:null,...m};return d&&typeof S=="number"&&(O=d==="end"?S*-1:S),f?{x:O*w,y:E*u}:{x:E*u,y:O*w}}var Vi=function(t){return t===void 0&&(t=0),{name:"offset",options:t,async fn(e){var r,n;let{x:i,y:o,placement:a,middlewareData:d}=e,f=await da(e,t);return a===((r=d.offset)==null?void 0:r.placement)&&(n=d.arrow)!=null&&n.alignmentOffset?{}:{x:i+f.x,y:o+f.y,data:{...f,placement:a}}}}},pa=function(t){return t===void 0&&(t={}),{name:"shift",options:t,async fn(e){let{x:r,y:n,placement:i}=e,{mainAxis:o=!0,crossAxis:a=!1,limiter:d={fn:I=>{let{x:$,y:A}=I;return{x:$,y:A}}},...f}=je(t,e),u={x:r,y:n},w=await _n(e,f),m=Pn(pe(i)),E=Wi(m),O=u[E],S=u[m];if(o){let I=E==="y"?"top":"left",$=E==="y"?"bottom":"right",A=O+w[I],k=O-w[$];O=Zr(A,O,k)}if(a){let I=m==="y"?"top":"left",$=m==="y"?"bottom":"right",A=S+w[I],k=S-w[$];S=Zr(A,S,k)}let M=d.fn({...e,[E]:O,[m]:S});return{...M,data:{x:M.x-r,y:M.y-n}}}}},ha=function(t){return t===void 0&&(t={}),{name:"size",options:t,async fn(e){let{placement:r,rects:n,platform:i,elements:o}=e,{apply:a=()=>{},...d}=je(t,e),f=await _n(e,d),u=pe(r),w=xe(r),m=Pn(r)==="y",{width:E,height:O}=n.floating,S,M;u==="top"||u==="bottom"?(S=u,M=w===(await(i.isRTL==null?void 0:i.isRTL(o.floating))?"start":"end")?"left":"right"):(M=u,S=w==="end"?"top":"bottom");let I=O-f[S],$=E-f[M],A=!e.middlewareData.shift,k=I,Y=$;if(m){let J=E-f.left-f.right;Y=w||A?Ee($,J):J}else{let J=O-f.top-f.bottom;k=w||A?Ee(I,J):J}if(A&&!w){let J=ee(f.left,0),U=ee(f.right,0),dt=ee(f.top,0),X=ee(f.bottom,0);m?Y=E-2*(J!==0||U!==0?J+U:ee(f.left,f.right)):k=O-2*(dt!==0||X!==0?dt+X:ee(f.top,f.bottom))}await a({...e,availableWidth:Y,availableHeight:k});let nt=await i.getDimensions(o.floating);return E!==nt.width||O!==nt.height?{reset:{rects:!0}}:{}}}};function rn(t){return Yi(t)?(t.nodeName||"").toLowerCase():"#document"}function ce(t){var e;return(t==null||(e=t.ownerDocument)==null?void 0:e.defaultView)||window}function Be(t){var e;return(e=(Yi(t)?t.ownerDocument:t.document)||window.document)==null?void 0:e.documentElement}function Yi(t){return t instanceof Node||t instanceof ce(t).Node}function ke(t){return t instanceof Element||t instanceof ce(t).Element}function Te(t){return t instanceof HTMLElement||t instanceof ce(t).HTMLElement}function Ni(t){return typeof ShadowRoot>"u"?!1:t instanceof ShadowRoot||t instanceof ce(t).ShadowRoot}function Vn(t){let{overflow:e,overflowX:r,overflowY:n,display:i}=he(t);return/auto|scroll|overlay|hidden|clip/.test(e+n+r)&&!["inline","contents"].includes(i)}function va(t){return["table","td","th"].includes(rn(t))}function ni(t){let e=ri(),r=he(t);return r.transform!=="none"||r.perspective!=="none"||(r.containerType?r.containerType!=="normal":!1)||!e&&(r.backdropFilter?r.backdropFilter!=="none":!1)||!e&&(r.filter?r.filter!=="none":!1)||["transform","perspective","filter"].some(n=>(r.willChange||"").includes(n))||["paint","layout","strict","content"].some(n=>(r.contain||"").includes(n))}function ma(t){let e=Tn(t);for(;Te(e)&&!gr(e);){if(ni(e))return e;e=Tn(e)}return null}function ri(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}function gr(t){return["html","body","#document"].includes(rn(t))}function he(t){return ce(t).getComputedStyle(t)}function br(t){return ke(t)?{scrollLeft:t.scrollLeft,scrollTop:t.scrollTop}:{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function Tn(t){if(rn(t)==="html")return t;let e=t.assignedSlot||t.parentNode||Ni(t)&&t.host||Be(t);return Ni(e)?e.host:e}function Xi(t){let e=Tn(t);return gr(e)?t.ownerDocument?t.ownerDocument.body:t.body:Te(e)&&Vn(e)?e:Xi(e)}function Un(t,e,r){var n;e===void 0&&(e=[]),r===void 0&&(r=!0);let i=Xi(t),o=i===((n=t.ownerDocument)==null?void 0:n.body),a=ce(i);return o?e.concat(a,a.visualViewport||[],Vn(i)?i:[],a.frameElement&&r?Un(a.frameElement):[]):e.concat(i,Un(i,[],r))}function qi(t){let e=he(t),r=parseFloat(e.width)||0,n=parseFloat(e.height)||0,i=Te(t),o=i?t.offsetWidth:r,a=i?t.offsetHeight:n,d=hr(r)!==o||hr(n)!==a;return d&&(r=o,n=a),{width:r,height:n,$:d}}function ii(t){return ke(t)?t:t.contextElement}function Dn(t){let e=ii(t);if(!Te(e))return nn(1);let r=e.getBoundingClientRect(),{width:n,height:i,$:o}=qi(e),a=(o?hr(r.width):r.width)/n,d=(o?hr(r.height):r.height)/i;return(!a||!Number.isFinite(a))&&(a=1),(!d||!Number.isFinite(d))&&(d=1),{x:a,y:d}}var ga=nn(0);function Gi(t){let e=ce(t);return!ri()||!e.visualViewport?ga:{x:e.visualViewport.offsetLeft,y:e.visualViewport.offsetTop}}function ba(t,e,r){return e===void 0&&(e=!1),!r||e&&r!==ce(t)?!1:e}function vn(t,e,r,n){e===void 0&&(e=!1),r===void 0&&(r=!1);let i=t.getBoundingClientRect(),o=ii(t),a=nn(1);e&&(n?ke(n)&&(a=Dn(n)):a=Dn(t));let d=ba(o,r,n)?Gi(o):nn(0),f=(i.left+d.x)/a.x,u=(i.top+d.y)/a.y,w=i.width/a.x,m=i.height/a.y;if(o){let E=ce(o),O=n&&ke(n)?ce(n):n,S=E,M=S.frameElement;for(;M&&n&&O!==S;){let I=Dn(M),$=M.getBoundingClientRect(),A=he(M),k=$.left+(M.clientLeft+parseFloat(A.paddingLeft))*I.x,Y=$.top+(M.clientTop+parseFloat(A.paddingTop))*I.y;f*=I.x,u*=I.y,w*=I.x,m*=I.y,f+=k,u+=Y,S=ce(M),M=S.frameElement}}return Cn({width:w,height:m,x:f,y:u})}var ya=[":popover-open",":modal"];function Ki(t){return ya.some(e=>{try{return t.matches(e)}catch{return!1}})}function wa(t){let{elements:e,rect:r,offsetParent:n,strategy:i}=t,o=i==="fixed",a=Be(n),d=e?Ki(e.floating):!1;if(n===a||d&&o)return r;let f={scrollLeft:0,scrollTop:0},u=nn(1),w=nn(0),m=Te(n);if((m||!m&&!o)&&((rn(n)!=="body"||Vn(a))&&(f=br(n)),Te(n))){let E=vn(n);u=Dn(n),w.x=E.x+n.clientLeft,w.y=E.y+n.clientTop}return{width:r.width*u.x,height:r.height*u.y,x:r.x*u.x-f.scrollLeft*u.x+w.x,y:r.y*u.y-f.scrollTop*u.y+w.y}}function xa(t){return Array.from(t.getClientRects())}function Ji(t){return vn(Be(t)).left+br(t).scrollLeft}function Ea(t){let e=Be(t),r=br(t),n=t.ownerDocument.body,i=ee(e.scrollWidth,e.clientWidth,n.scrollWidth,n.clientWidth),o=ee(e.scrollHeight,e.clientHeight,n.scrollHeight,n.clientHeight),a=-r.scrollLeft+Ji(t),d=-r.scrollTop;return he(n).direction==="rtl"&&(a+=ee(e.clientWidth,n.clientWidth)-i),{width:i,height:o,x:a,y:d}}function Oa(t,e){let r=ce(t),n=Be(t),i=r.visualViewport,o=n.clientWidth,a=n.clientHeight,d=0,f=0;if(i){o=i.width,a=i.height;let u=ri();(!u||u&&e==="fixed")&&(d=i.offsetLeft,f=i.offsetTop)}return{width:o,height:a,x:d,y:f}}function Sa(t,e){let r=vn(t,!0,e==="fixed"),n=r.top+t.clientTop,i=r.left+t.clientLeft,o=Te(t)?Dn(t):nn(1),a=t.clientWidth*o.x,d=t.clientHeight*o.y,f=i*o.x,u=n*o.y;return{width:a,height:d,x:f,y:u}}function ki(t,e,r){let n;if(e==="viewport")n=Oa(t,r);else if(e==="document")n=Ea(Be(t));else if(ke(e))n=Sa(e,r);else{let i=Gi(t);n={...e,x:e.x-i.x,y:e.y-i.y}}return Cn(n)}function Zi(t,e){let r=Tn(t);return r===e||!ke(r)||gr(r)?!1:he(r).position==="fixed"||Zi(r,e)}function Aa(t,e){let r=e.get(t);if(r)return r;let n=Un(t,[],!1).filter(d=>ke(d)&&rn(d)!=="body"),i=null,o=he(t).position==="fixed",a=o?Tn(t):t;for(;ke(a)&&!gr(a);){let d=he(a),f=ni(a);!f&&d.position==="fixed"&&(i=null),(o?!f&&!i:!f&&d.position==="static"&&!!i&&["absolute","fixed"].includes(i.position)||Vn(a)&&!f&&Zi(t,a))?n=n.filter(w=>w!==a):i=d,a=Tn(a)}return e.set(t,n),n}function Da(t){let{element:e,boundary:r,rootBoundary:n,strategy:i}=t,a=[...r==="clippingAncestors"?Aa(e,this._c):[].concat(r),n],d=a[0],f=a.reduce((u,w)=>{let m=ki(e,w,i);return u.top=ee(m.top,u.top),u.right=Ee(m.right,u.right),u.bottom=Ee(m.bottom,u.bottom),u.left=ee(m.left,u.left),u},ki(e,d,i));return{width:f.right-f.left,height:f.bottom-f.top,x:f.left,y:f.top}}function Ca(t){let{width:e,height:r}=qi(t);return{width:e,height:r}}function _a(t,e,r){let n=Te(e),i=Be(e),o=r==="fixed",a=vn(t,!0,o,e),d={scrollLeft:0,scrollTop:0},f=nn(0);if(n||!n&&!o)if((rn(e)!=="body"||Vn(i))&&(d=br(e)),n){let m=vn(e,!0,o,e);f.x=m.x+e.clientLeft,f.y=m.y+e.clientTop}else i&&(f.x=Ji(i));let u=a.left+d.scrollLeft-f.x,w=a.top+d.scrollTop-f.y;return{x:u,y:w,width:a.width,height:a.height}}function ji(t,e){return!Te(t)||he(t).position==="fixed"?null:e?e(t):t.offsetParent}function Qi(t,e){let r=ce(t);if(!Te(t)||Ki(t))return r;let n=ji(t,e);for(;n&&va(n)&&he(n).position==="static";)n=ji(n,e);return n&&(rn(n)==="html"||rn(n)==="body"&&he(n).position==="static"&&!ni(n))?r:n||ma(t)||r}var Ta=async function(t){let e=this.getOffsetParent||Qi,r=this.getDimensions;return{reference:_a(t.reference,await e(t.floating),t.strategy),floating:{x:0,y:0,...await r(t.floating)}}};function Pa(t){return he(t).direction==="rtl"}var Ma={convertOffsetParentRelativeRectToViewportRelativeRect:wa,getDocumentElement:Be,getClippingRect:Da,getOffsetParent:Qi,getElementRects:Ta,getClientRects:xa,getDimensions:Ca,getScale:Dn,isElement:ke,isRTL:Pa};function Ra(t,e){let r=null,n,i=Be(t);function o(){var d;clearTimeout(n),(d=r)==null||d.disconnect(),r=null}function a(d,f){d===void 0&&(d=!1),f===void 0&&(f=1),o();let{left:u,top:w,width:m,height:E}=t.getBoundingClientRect();if(d||e(),!m||!E)return;let O=pr(w),S=pr(i.clientWidth-(u+m)),M=pr(i.clientHeight-(w+E)),I=pr(u),A={rootMargin:-O+"px "+-S+"px "+-M+"px "+-I+"px",threshold:ee(0,Ee(1,f))||1},k=!0;function Y(nt){let J=nt[0].intersectionRatio;if(J!==f){if(!k)return a();J?a(!1,J):n=setTimeout(()=>{a(!1,1e-7)},100)}k=!1}try{r=new IntersectionObserver(Y,{...A,root:i.ownerDocument})}catch{r=new IntersectionObserver(Y,A)}r.observe(t)}return a(!0),o}function Bi(t,e,r,n){n===void 0&&(n={});let{ancestorScroll:i=!0,ancestorResize:o=!0,elementResize:a=typeof ResizeObserver=="function",layoutShift:d=typeof IntersectionObserver=="function",animationFrame:f=!1}=n,u=ii(t),w=i||o?[...u?Un(u):[],...Un(e)]:[];w.forEach($=>{i&&$.addEventListener("scroll",r,{passive:!0}),o&&$.addEventListener("resize",r)});let m=u&&d?Ra(u,r):null,E=-1,O=null;a&&(O=new ResizeObserver($=>{let[A]=$;A&&A.target===u&&O&&(O.unobserve(e),cancelAnimationFrame(E),E=requestAnimationFrame(()=>{var k;(k=O)==null||k.observe(e)})),r()}),u&&!f&&O.observe(u),O.observe(e));let S,M=f?vn(t):null;f&&I();function I(){let $=vn(t);M&&($.x!==M.x||$.y!==M.y||$.width!==M.width||$.height!==M.height)&&r(),M=$,S=requestAnimationFrame(I)}return r(),()=>{var $;w.forEach(A=>{i&&A.removeEventListener("scroll",r),o&&A.removeEventListener("resize",r)}),m?.(),($=O)==null||$.disconnect(),O=null,f&&cancelAnimationFrame(S)}}var oi=sa,to=pa,eo=la,no=ha,ro=ca,io=oa,oo=ua,Hi=(t,e,r)=>{let n=new Map,i={platform:Ma,...r},o={...i.platform,_c:n};return ia(t,e,{...i,platform:o})},Ia=t=>{let e={placement:"bottom",strategy:"absolute",middleware:[]},r=Object.keys(t),n=i=>t[i];return r.includes("offset")&&e.middleware.push(Vi(n("offset"))),r.includes("teleport")&&(e.strategy="fixed"),r.includes("placement")&&(e.placement=n("placement")),r.includes("autoPlacement")&&!r.includes("flip")&&e.middleware.push(oi(n("autoPlacement"))),r.includes("flip")&&e.middleware.push(eo(n("flip"))),r.includes("shift")&&e.middleware.push(to(n("shift"))),r.includes("inline")&&e.middleware.push(oo(n("inline"))),r.includes("arrow")&&e.middleware.push(io(n("arrow"))),r.includes("hide")&&e.middleware.push(ro(n("hide"))),r.includes("size")&&e.middleware.push(no(n("size"))),e},Fa=(t,e)=>{let r={component:{trap:!1},float:{placement:"bottom",strategy:"absolute",middleware:[]}},n=i=>t[t.indexOf(i)+1];if(t.includes("trap")&&(r.component.trap=!0),t.includes("teleport")&&(r.float.strategy="fixed"),t.includes("offset")&&r.float.middleware.push(Vi(e.offset||10)),t.includes("placement")&&(r.float.placement=n("placement")),t.includes("autoPlacement")&&!t.includes("flip")&&r.float.middleware.push(oi(e.autoPlacement)),t.includes("flip")&&r.float.middleware.push(eo(e.flip)),t.includes("shift")&&r.float.middleware.push(to(e.shift)),t.includes("inline")&&r.float.middleware.push(oo(e.inline)),t.includes("arrow")&&r.float.middleware.push(io(e.arrow)),t.includes("hide")&&r.float.middleware.push(ro(e.hide)),t.includes("size")){let i=e.size?.availableWidth??null,o=e.size?.availableHeight??null;i&&delete e.size.availableWidth,o&&delete e.size.availableHeight,r.float.middleware.push(no({...e.size,apply({availableWidth:a,availableHeight:d,elements:f}){Object.assign(f.floating.style,{maxWidth:`${i??a}px`,maxHeight:`${o??d}px`})}}))}return r},La=t=>{var e="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split(""),r="";t||(t=Math.floor(Math.random()*e.length));for(var n=0;n{}){let r=!1;return function(){r?e.apply(this,arguments):(r=!0,t.apply(this,arguments))}}function ka(t){let e={dismissable:!0,trap:!1};function r(n,i=null){if(n){if(n.hasAttribute("aria-expanded")||n.setAttribute("aria-expanded",!1),i.hasAttribute("id"))n.setAttribute("aria-controls",i.getAttribute("id"));else{let o=`panel-${La(8)}`;n.setAttribute("aria-controls",o),i.setAttribute("id",o)}i.setAttribute("aria-modal",!0),i.setAttribute("role","dialog")}}t.magic("float",n=>(i={},o={})=>{let a={...e,...o},d=Object.keys(i).length>0?Ia(i):{middleware:[oi()]},f=n,u=n.parentElement.closest("[x-data]"),w=u.querySelector('[x-ref="panel"]');r(f,w);function m(){return w.style.display=="block"}function E(){w.style.display="none",f.setAttribute("aria-expanded","false"),a.trap&&w.setAttribute("x-trap","false"),Bi(n,w,M)}function O(){w.style.display="block",f.setAttribute("aria-expanded","true"),a.trap&&w.setAttribute("x-trap","true"),M()}function S(){m()?E():O()}async function M(){return await Hi(n,w,d).then(({middlewareData:I,placement:$,x:A,y:k})=>{if(I.arrow){let Y=I.arrow?.x,nt=I.arrow?.y,J=d.middleware.filter(dt=>dt.name=="arrow")[0].options.element,U={top:"bottom",right:"left",bottom:"top",left:"right"}[$.split("-")[0]];Object.assign(J.style,{left:Y!=null?`${Y}px`:"",top:nt!=null?`${nt}px`:"",right:"",bottom:"",[U]:"-4px"})}if(I.hide){let{referenceHidden:Y}=I.hide;Object.assign(w.style,{visibility:Y?"hidden":"visible"})}Object.assign(w.style,{left:`${A}px`,top:`${k}px`})})}a.dismissable&&(window.addEventListener("click",I=>{!u.contains(I.target)&&m()&&S()}),window.addEventListener("keydown",I=>{I.key==="Escape"&&m()&&S()},!0)),S()}),t.directive("float",(n,{modifiers:i,expression:o},{evaluate:a,effect:d})=>{let f=o?a(o):{},u=i.length>0?Fa(i,f):{},w=null;u.float.strategy=="fixed"&&(n.style.position="fixed");let m=U=>n.parentElement&&!n.parentElement.closest("[x-data]").contains(U.target)?n.close():null,E=U=>U.key==="Escape"?n.close():null,O=n.getAttribute("x-ref"),S=n.parentElement.closest("[x-data]"),M=S.querySelectorAll(`[\\@click^="$refs.${O}"]`),I=S.querySelectorAll(`[x-on\\:click^="$refs.${O}"]`);n.style.setProperty("display","none"),r([...M,...I][0],n),n._x_isShown=!1,n.trigger=null,n._x_doHide||(n._x_doHide=()=>{n.style.setProperty("display","none",i.includes("important")?"important":void 0)}),n._x_doShow||(n._x_doShow=()=>{n.style.setProperty("display","block",i.includes("important")?"important":void 0)});let $=()=>{n._x_doHide(),n._x_isShown=!1},A=()=>{n._x_doShow(),n._x_isShown=!0},k=()=>setTimeout(A),Y=Na(U=>U?A():$(),U=>{typeof n._x_toggleAndCascadeWithTransitions=="function"?n._x_toggleAndCascadeWithTransitions(n,U,A,$):U?k():$()}),nt,J=!0;d(()=>a(U=>{!J&&U===nt||(i.includes("immediate")&&(U?k():$()),Y(U),nt=U,J=!1)})),n.open=async function(U){n.trigger=U.currentTarget?U.currentTarget:U,Y(!0),n.trigger.setAttribute("aria-expanded","true"),u.component.trap&&n.setAttribute("x-trap","true"),w=Bi(n.trigger,n,()=>{Hi(n.trigger,n,u.float).then(({middlewareData:dt,placement:X,x:Z,y:mt})=>{if(dt.arrow){let l=dt.arrow?.x,h=dt.arrow?.y,v=u.float.middleware.filter(j=>j.name=="arrow")[0].options.element,p={top:"bottom",right:"left",bottom:"top",left:"right"}[X.split("-")[0]];Object.assign(v.style,{left:l!=null?`${l}px`:"",top:h!=null?`${h}px`:"",right:"",bottom:"",[p]:"-4px"})}if(dt.hide){let{referenceHidden:l}=dt.hide;Object.assign(n.style,{visibility:l?"hidden":"visible"})}Object.assign(n.style,{left:`${Z}px`,top:`${mt}px`})})}),window.addEventListener("click",m),window.addEventListener("keydown",E,!0)},n.close=function(){if(!n._x_isShown)return!1;Y(!1),n.trigger.setAttribute("aria-expanded","false"),u.component.trap&&n.setAttribute("x-trap","false"),w(),window.removeEventListener("click",m),window.removeEventListener("keydown",E,!1)},n.toggle=function(U){n._x_isShown?n.close():n.open(U)}})}var ao=ka;function ja(t){t.store("lazyLoadedAssets",{loaded:new Set,check(a){return Array.isArray(a)?a.every(d=>this.loaded.has(d)):this.loaded.has(a)},markLoaded(a){Array.isArray(a)?a.forEach(d=>this.loaded.add(d)):this.loaded.add(a)}});let e=a=>new CustomEvent(a,{bubbles:!0,composed:!0,cancelable:!0}),r=(a,d={},f,u)=>{let w=document.createElement(a);return Object.entries(d).forEach(([m,E])=>w[m]=E),f&&(u?f.insertBefore(w,u):f.appendChild(w)),w},n=(a,d,f={},u=null,w=null)=>{let m=a==="link"?`link[href="${d}"]`:`script[src="${d}"]`;if(document.querySelector(m)||t.store("lazyLoadedAssets").check(d))return Promise.resolve();let E=a==="link"?{...f,href:d}:{...f,src:d},O=r(a,E,u,w);return new Promise((S,M)=>{O.onload=()=>{t.store("lazyLoadedAssets").markLoaded(d),S()},O.onerror=()=>{M(new Error(`Failed to load ${a}: ${d}`))}})},i=async(a,d,f=null,u=null)=>{let w={type:"text/css",rel:"stylesheet"};d&&(w.media=d);let m=document.head,E=null;if(f&&u){let O=document.querySelector(`link[href*="${u}"]`);O?(m=O.parentElement,E=f==="before"?O:O.nextSibling):(console.warn(`Target (${u}) not found for ${a}. Appending to head.`),m=document.head,E=null)}await n("link",a,w,m,E)},o=async(a,d,f=null,u=null,w=null)=>{let m=document.head,E=null;if(f&&u){let S=document.querySelector(`script[src*="${u}"]`);S?(m=S.parentElement,E=f==="before"?S:S.nextSibling):(console.warn(`Target (${u}) not found for ${a}. Falling back to head or body.`),m=document.head,E=null)}else(d.has("body-start")||d.has("body-end"))&&(m=document.body,d.has("body-start")&&(E=document.body.firstChild));let O={};w&&(O.type="module"),await n("script",a,O,m,E)};t.directive("load-css",(a,{expression:d},{evaluate:f})=>{let u=f(d),w=a.media,m=a.getAttribute("data-dispatch"),E=a.getAttribute("data-css-before")?"before":a.getAttribute("data-css-after")?"after":null,O=a.getAttribute("data-css-before")||a.getAttribute("data-css-after")||null;Promise.all(u.map(S=>i(S,w,E,O))).then(()=>{m&&window.dispatchEvent(e(`${m}-css`))}).catch(console.error)}),t.directive("load-js",(a,{expression:d,modifiers:f},{evaluate:u})=>{let w=u(d),m=new Set(f),E=a.getAttribute("data-js-before")?"before":a.getAttribute("data-js-after")?"after":null,O=a.getAttribute("data-js-before")||a.getAttribute("data-js-after")||null,S=a.getAttribute("data-js-as-module")||a.getAttribute("data-as-module")||!1,M=a.getAttribute("data-dispatch");Promise.all(w.map(I=>o(I,m,E,O,S))).then(()=>{M&&window.dispatchEvent(e(`${M}-js`))}).catch(console.error)})}var so=ja;function Ba(){return!0}function Ha({component:t,argument:e}){return new Promise(r=>{if(e)window.addEventListener(e,()=>r(),{once:!0});else{let n=i=>{i.detail.id===t.id&&(window.removeEventListener("async-alpine:load",n),r())};window.addEventListener("async-alpine:load",n)}})}function $a(){return new Promise(t=>{"requestIdleCallback"in window?window.requestIdleCallback(t):setTimeout(t,200)})}function Wa({argument:t}){return new Promise(e=>{if(!t)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),e();let r=window.matchMedia(`(${t})`);r.matches?e():r.addEventListener("change",e,{once:!0})})}function za({component:t,argument:e}){return new Promise(r=>{let n=e||"0px 0px 0px 0px",i=new IntersectionObserver(o=>{o[0].isIntersecting&&(i.disconnect(),r())},{rootMargin:n});i.observe(t.el)})}var lo={eager:Ba,event:Ha,idle:$a,media:Wa,visible:za};async function Ua(t){let e=Va(t.strategy);await ai(t,e)}async function ai(t,e){if(e.type==="expression"){if(e.operator==="&&")return Promise.all(e.parameters.map(r=>ai(t,r)));if(e.operator==="||")return Promise.any(e.parameters.map(r=>ai(t,r)))}return lo[e.method]?lo[e.method]({component:t,argument:e.argument}):!1}function Va(t){let e=Ya(t),r=fo(e);return r.type==="method"?{type:"expression",operator:"&&",parameters:[r]}:r}function Ya(t){let e=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,r=[],n;for(;(n=e.exec(t))!==null;){let[i,o,a,d]=n;if(o!==void 0)r.push({type:"parenthesis",value:o});else if(a!==void 0)r.push({type:"operator",value:a==="|"?"&&":a});else{let f={type:"method",method:d.trim()};d.includes("(")&&(f.method=d.substring(0,d.indexOf("(")).trim(),f.argument=d.substring(d.indexOf("(")+1,d.indexOf(")"))),d.method==="immediate"&&(d.method="eager"),r.push(f)}}return r}function fo(t){let e=co(t);for(;t.length>0&&(t[0].value==="&&"||t[0].value==="|"||t[0].value==="||");){let r=t.shift().value,n=co(t);e.type==="expression"&&e.operator===r?e.parameters.push(n):e={type:"expression",operator:r,parameters:[e,n]}}return e}function co(t){if(t[0].value==="("){t.shift();let e=fo(t);return t[0].value===")"&&t.shift(),e}else return t.shift()}function uo(t){let e="load",r=t.prefixed("load-src"),n=t.prefixed("ignore"),i={defaultStrategy:"eager",keepRelativeURLs:!1},o=!1,a={},d=0;function f(){return d++}t.asyncOptions=A=>{i={...i,...A}},t.asyncData=(A,k=!1)=>{a[A]={loaded:!1,download:k}},t.asyncUrl=(A,k)=>{!A||!k||a[A]||(a[A]={loaded:!1,download:()=>import($(k))})},t.asyncAlias=A=>{o=A};let u=A=>{t.skipDuringClone(()=>{A._x_async||(A._x_async="init",A._x_ignore=!0,A.setAttribute(n,""))})()},w=async A=>{t.skipDuringClone(async()=>{if(A._x_async!=="init")return;A._x_async="await";let{name:k,strategy:Y}=m(A);await Ua({name:k,strategy:Y,el:A,id:A.id||f()}),A.isConnected&&(await E(k),A.isConnected&&(S(A),A._x_async="loaded"))})()};w.inline=u,t.directive(e,w).before("ignore");function m(A){let k=I(A.getAttribute(t.prefixed("data"))),Y=A.getAttribute(t.prefixed(e))||i.defaultStrategy,nt=A.getAttribute(r);return nt&&t.asyncUrl(k,nt),{name:k,strategy:Y}}async function E(A){if(A.startsWith("_x_async_")||(M(A),!a[A]||a[A].loaded))return;let k=await O(A);t.data(A,k),a[A].loaded=!0}async function O(A){if(!a[A])return;let k=await a[A].download(A);return typeof k=="function"?k:k[A]||k.default||Object.values(k)[0]||!1}function S(A){t.destroyTree(A),A._x_ignore=!1,A.removeAttribute(n),!A.closest(`[${n}]`)&&t.initTree(A)}function M(A){if(!(!o||a[A])){if(typeof o=="function"){t.asyncData(A,o);return}t.asyncUrl(A,o.replaceAll("[name]",A))}}function I(A){return(A||"").split(/[({]/g)[0]||`_x_async_${f()}`}function $(A){return i.keepRelativeURLs||new RegExp("^(?:[a-z+]+:)?//","i").test(A)?A:new URL(A,document.baseURI).href}}var Uo=Jo(vo(),1);function mo(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(i){return Object.getOwnPropertyDescriptor(t,i).enumerable})),r.push.apply(r,n)}return r}function Me(t){for(var e=1;e=0)&&(r[i]=t[i]);return r}function Ga(t,e){if(t==null)return{};var r=qa(t,e),n,i;if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);for(i=0;i=0)&&Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}var Ka="1.15.6";function He(t){if(typeof window<"u"&&window.navigator)return!!navigator.userAgent.match(t)}var We=He(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),tr=He(/Edge/i),go=He(/firefox/i),Gn=He(/safari/i)&&!He(/chrome/i)&&!He(/android/i),wi=He(/iP(ad|od|hone)/i),Ao=He(/chrome/i)&&He(/android/i),Do={capture:!1,passive:!1};function Ot(t,e,r){t.addEventListener(e,r,!We&&Do)}function Et(t,e,r){t.removeEventListener(e,r,!We&&Do)}function Tr(t,e){if(e){if(e[0]===">"&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch{return!1}return!1}}function Co(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function Se(t,e,r,n){if(t){r=r||document;do{if(e!=null&&(e[0]===">"?t.parentNode===r&&Tr(t,e):Tr(t,e))||n&&t===r)return t;if(t===r)break}while(t=Co(t))}return null}var bo=/\s+/g;function fe(t,e,r){if(t&&e)if(t.classList)t.classList[r?"add":"remove"](e);else{var n=(" "+t.className+" ").replace(bo," ").replace(" "+e+" "," ");t.className=(n+(r?" "+e:"")).replace(bo," ")}}function at(t,e,r){var n=t&&t.style;if(n){if(r===void 0)return document.defaultView&&document.defaultView.getComputedStyle?r=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(r=t.currentStyle),e===void 0?r:r[e];!(e in n)&&e.indexOf("webkit")===-1&&(e="-webkit-"+e),n[e]=r+(typeof r=="string"?"":"px")}}function Ln(t,e){var r="";if(typeof t=="string")r=t;else do{var n=at(t,"transform");n&&n!=="none"&&(r=n+" "+r)}while(!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(r)}function _o(t,e,r){if(t){var n=t.getElementsByTagName(e),i=0,o=n.length;if(r)for(;i=o:a=i<=o,!a)return n;if(n===Pe())break;n=sn(n,!1)}return!1}function Nn(t,e,r,n){for(var i=0,o=0,a=t.children;o2&&arguments[2]!==void 0?arguments[2]:{},i=n.evt,o=Ga(n,is);er.pluginEvent.bind(st)(e,r,Me({dragEl:N,parentEl:Ut,ghostEl:ut,rootEl:kt,nextEl:bn,lastDownEl:Ar,cloneEl:Wt,cloneHidden:an,dragStarted:Yn,putSortable:Zt,activeSortable:st.active,originalEvent:i,oldIndex:Fn,oldDraggableIndex:Jn,newIndex:ue,newDraggableIndex:on,hideGhostForTarget:No,unhideGhostForTarget:ko,cloneNowHidden:function(){an=!0},cloneNowShown:function(){an=!1},dispatchSortableEvent:function(d){ie({sortable:r,name:d,originalEvent:i})}},o))};function ie(t){rs(Me({putSortable:Zt,cloneEl:Wt,targetEl:N,rootEl:kt,oldIndex:Fn,oldDraggableIndex:Jn,newIndex:ue,newDraggableIndex:on},t))}var N,Ut,ut,kt,bn,Ar,Wt,an,Fn,ue,Jn,on,wr,Zt,In=!1,Pr=!1,Mr=[],mn,Oe,ci,fi,xo,Eo,Yn,Rn,Zn,Qn=!1,xr=!1,Dr,ne,ui=[],mi=!1,Rr=[],Fr=typeof document<"u",Er=wi,Oo=tr||We?"cssFloat":"float",os=Fr&&!Ao&&!wi&&"draggable"in document.createElement("div"),Io=function(){if(Fr){if(We)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto",t.style.pointerEvents==="auto"}}(),Fo=function(e,r){var n=at(e),i=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),o=Nn(e,0,r),a=Nn(e,1,r),d=o&&at(o),f=a&&at(a),u=d&&parseInt(d.marginLeft)+parseInt(d.marginRight)+qt(o).width,w=f&&parseInt(f.marginLeft)+parseInt(f.marginRight)+qt(a).width;if(n.display==="flex")return n.flexDirection==="column"||n.flexDirection==="column-reverse"?"vertical":"horizontal";if(n.display==="grid")return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&d.float&&d.float!=="none"){var m=d.float==="left"?"left":"right";return a&&(f.clear==="both"||f.clear===m)?"vertical":"horizontal"}return o&&(d.display==="block"||d.display==="flex"||d.display==="table"||d.display==="grid"||u>=i&&n[Oo]==="none"||a&&n[Oo]==="none"&&u+w>i)?"vertical":"horizontal"},as=function(e,r,n){var i=n?e.left:e.top,o=n?e.right:e.bottom,a=n?e.width:e.height,d=n?r.left:r.top,f=n?r.right:r.bottom,u=n?r.width:r.height;return i===d||o===f||i+a/2===d+u/2},ss=function(e,r){var n;return Mr.some(function(i){var o=i[se].options.emptyInsertThreshold;if(!(!o||xi(i))){var a=qt(i),d=e>=a.left-o&&e<=a.right+o,f=r>=a.top-o&&r<=a.bottom+o;if(d&&f)return n=i}}),n},Lo=function(e){function r(o,a){return function(d,f,u,w){var m=d.options.group.name&&f.options.group.name&&d.options.group.name===f.options.group.name;if(o==null&&(a||m))return!0;if(o==null||o===!1)return!1;if(a&&o==="clone")return o;if(typeof o=="function")return r(o(d,f,u,w),a)(d,f,u,w);var E=(a?d:f).options.group.name;return o===!0||typeof o=="string"&&o===E||o.join&&o.indexOf(E)>-1}}var n={},i=e.group;(!i||Sr(i)!="object")&&(i={name:i}),n.name=i.name,n.checkPull=r(i.pull,!0),n.checkPut=r(i.put),n.revertClone=i.revertClone,e.group=n},No=function(){!Io&&ut&&at(ut,"display","none")},ko=function(){!Io&&ut&&at(ut,"display","")};Fr&&!Ao&&document.addEventListener("click",function(t){if(Pr)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),Pr=!1,!1},!0);var gn=function(e){if(N){e=e.touches?e.touches[0]:e;var r=ss(e.clientX,e.clientY);if(r){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[i]=e[i]);n.target=n.rootEl=r,n.preventDefault=void 0,n.stopPropagation=void 0,r[se]._onDragOver(n)}}},ls=function(e){N&&N.parentNode[se]._isOutsideThisEl(e.target)};function st(t,e){if(!(t&&t.nodeType&&t.nodeType===1))throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=$e({},e),t[se]=this;var r={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Fo(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(a,d){a.setData("Text",d.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:st.supportPointer!==!1&&"PointerEvent"in window&&(!Gn||wi),emptyInsertThreshold:5};er.initializePlugins(this,t,r);for(var n in r)!(n in e)&&(e[n]=r[n]);Lo(e);for(var i in this)i.charAt(0)==="_"&&typeof this[i]=="function"&&(this[i]=this[i].bind(this));this.nativeDraggable=e.forceFallback?!1:os,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?Ot(t,"pointerdown",this._onTapStart):(Ot(t,"mousedown",this._onTapStart),Ot(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(Ot(t,"dragover",this),Ot(t,"dragenter",this)),Mr.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),$e(this,ts())}st.prototype={constructor:st,_isOutsideThisEl:function(e){!this.el.contains(e)&&e!==this.el&&(Rn=null)},_getDirection:function(e,r){return typeof this.options.direction=="function"?this.options.direction.call(this,e,r,N):this.options.direction},_onTapStart:function(e){if(e.cancelable){var r=this,n=this.el,i=this.options,o=i.preventOnFilter,a=e.type,d=e.touches&&e.touches[0]||e.pointerType&&e.pointerType==="touch"&&e,f=(d||e).target,u=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||f,w=i.filter;if(ms(n),!N&&!(/mousedown|pointerdown/.test(a)&&e.button!==0||i.disabled)&&!u.isContentEditable&&!(!this.nativeDraggable&&Gn&&f&&f.tagName.toUpperCase()==="SELECT")&&(f=Se(f,i.draggable,n,!1),!(f&&f.animated)&&Ar!==f)){if(Fn=ve(f),Jn=ve(f,i.draggable),typeof w=="function"){if(w.call(this,e,f,this)){ie({sortable:r,rootEl:u,name:"filter",targetEl:f,toEl:n,fromEl:n}),ae("filter",r,{evt:e}),o&&e.preventDefault();return}}else if(w&&(w=w.split(",").some(function(m){if(m=Se(u,m.trim(),n,!1),m)return ie({sortable:r,rootEl:m,name:"filter",targetEl:f,fromEl:n,toEl:n}),ae("filter",r,{evt:e}),!0}),w)){o&&e.preventDefault();return}i.handle&&!Se(u,i.handle,n,!1)||this._prepareDragStart(e,d,f)}}},_prepareDragStart:function(e,r,n){var i=this,o=i.el,a=i.options,d=o.ownerDocument,f;if(n&&!N&&n.parentNode===o){var u=qt(n);if(kt=o,N=n,Ut=N.parentNode,bn=N.nextSibling,Ar=n,wr=a.group,st.dragged=N,mn={target:N,clientX:(r||e).clientX,clientY:(r||e).clientY},xo=mn.clientX-u.left,Eo=mn.clientY-u.top,this._lastX=(r||e).clientX,this._lastY=(r||e).clientY,N.style["will-change"]="all",f=function(){if(ae("delayEnded",i,{evt:e}),st.eventCanceled){i._onDrop();return}i._disableDelayedDragEvents(),!go&&i.nativeDraggable&&(N.draggable=!0),i._triggerDragStart(e,r),ie({sortable:i,name:"choose",originalEvent:e}),fe(N,a.chosenClass,!0)},a.ignore.split(",").forEach(function(w){_o(N,w.trim(),di)}),Ot(d,"dragover",gn),Ot(d,"mousemove",gn),Ot(d,"touchmove",gn),a.supportPointer?(Ot(d,"pointerup",i._onDrop),!this.nativeDraggable&&Ot(d,"pointercancel",i._onDrop)):(Ot(d,"mouseup",i._onDrop),Ot(d,"touchend",i._onDrop),Ot(d,"touchcancel",i._onDrop)),go&&this.nativeDraggable&&(this.options.touchStartThreshold=4,N.draggable=!0),ae("delayStart",this,{evt:e}),a.delay&&(!a.delayOnTouchOnly||r)&&(!this.nativeDraggable||!(tr||We))){if(st.eventCanceled){this._onDrop();return}a.supportPointer?(Ot(d,"pointerup",i._disableDelayedDrag),Ot(d,"pointercancel",i._disableDelayedDrag)):(Ot(d,"mouseup",i._disableDelayedDrag),Ot(d,"touchend",i._disableDelayedDrag),Ot(d,"touchcancel",i._disableDelayedDrag)),Ot(d,"mousemove",i._delayedDragTouchMoveHandler),Ot(d,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&Ot(d,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(f,a.delay)}else f()}},_delayedDragTouchMoveHandler:function(e){var r=e.touches?e.touches[0]:e;Math.max(Math.abs(r.clientX-this._lastX),Math.abs(r.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){N&&di(N),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var e=this.el.ownerDocument;Et(e,"mouseup",this._disableDelayedDrag),Et(e,"touchend",this._disableDelayedDrag),Et(e,"touchcancel",this._disableDelayedDrag),Et(e,"pointerup",this._disableDelayedDrag),Et(e,"pointercancel",this._disableDelayedDrag),Et(e,"mousemove",this._delayedDragTouchMoveHandler),Et(e,"touchmove",this._delayedDragTouchMoveHandler),Et(e,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(e,r){r=r||e.pointerType=="touch"&&e,!this.nativeDraggable||r?this.options.supportPointer?Ot(document,"pointermove",this._onTouchMove):r?Ot(document,"touchmove",this._onTouchMove):Ot(document,"mousemove",this._onTouchMove):(Ot(N,"dragend",this),Ot(kt,"dragstart",this._onDragStart));try{document.selection?Cr(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch{}},_dragStarted:function(e,r){if(In=!1,kt&&N){ae("dragStarted",this,{evt:r}),this.nativeDraggable&&Ot(document,"dragover",ls);var n=this.options;!e&&fe(N,n.dragClass,!1),fe(N,n.ghostClass,!0),st.active=this,e&&this._appendGhost(),ie({sortable:this,name:"start",originalEvent:r})}else this._nulling()},_emulateDragOver:function(){if(Oe){this._lastX=Oe.clientX,this._lastY=Oe.clientY,No();for(var e=document.elementFromPoint(Oe.clientX,Oe.clientY),r=e;e&&e.shadowRoot&&(e=e.shadowRoot.elementFromPoint(Oe.clientX,Oe.clientY),e!==r);)r=e;if(N.parentNode[se]._isOutsideThisEl(e),r)do{if(r[se]){var n=void 0;if(n=r[se]._onDragOver({clientX:Oe.clientX,clientY:Oe.clientY,target:e,rootEl:r}),n&&!this.options.dragoverBubble)break}e=r}while(r=Co(r));ko()}},_onTouchMove:function(e){if(mn){var r=this.options,n=r.fallbackTolerance,i=r.fallbackOffset,o=e.touches?e.touches[0]:e,a=ut&&Ln(ut,!0),d=ut&&a&&a.a,f=ut&&a&&a.d,u=Er&&ne&&wo(ne),w=(o.clientX-mn.clientX+i.x)/(d||1)+(u?u[0]-ui[0]:0)/(d||1),m=(o.clientY-mn.clientY+i.y)/(f||1)+(u?u[1]-ui[1]:0)/(f||1);if(!st.active&&!In){if(n&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))=0&&(ie({rootEl:Ut,name:"add",toEl:Ut,fromEl:kt,originalEvent:e}),ie({sortable:this,name:"remove",toEl:Ut,originalEvent:e}),ie({rootEl:Ut,name:"sort",toEl:Ut,fromEl:kt,originalEvent:e}),ie({sortable:this,name:"sort",toEl:Ut,originalEvent:e})),Zt&&Zt.save()):ue!==Fn&&ue>=0&&(ie({sortable:this,name:"update",toEl:Ut,originalEvent:e}),ie({sortable:this,name:"sort",toEl:Ut,originalEvent:e})),st.active&&((ue==null||ue===-1)&&(ue=Fn,on=Jn),ie({sortable:this,name:"end",toEl:Ut,originalEvent:e}),this.save()))),this._nulling()},_nulling:function(){ae("nulling",this),kt=N=Ut=ut=bn=Wt=Ar=an=mn=Oe=Yn=ue=on=Fn=Jn=Rn=Zn=Zt=wr=st.dragged=st.ghost=st.clone=st.active=null,Rr.forEach(function(e){e.checked=!0}),Rr.length=ci=fi=0},handleEvent:function(e){switch(e.type){case"drop":case"dragend":this._onDrop(e);break;case"dragenter":case"dragover":N&&(this._onDragOver(e),cs(e));break;case"selectstart":e.preventDefault();break}},toArray:function(){for(var e=[],r,n=this.el.children,i=0,o=n.length,a=this.options;ii.right+o||t.clientY>n.bottom&&t.clientX>n.left:t.clientY>i.bottom+o||t.clientX>n.right&&t.clientY>n.top}function ps(t,e,r,n,i,o,a,d){var f=n?t.clientY:t.clientX,u=n?r.height:r.width,w=n?r.top:r.left,m=n?r.bottom:r.right,E=!1;if(!a){if(d&&Drw+u*o/2:fm-Dr)return-Zn}else if(f>w+u*(1-i)/2&&fm-u*o/2)?f>w+u/2?1:-1:0}function hs(t){return ve(N){t.directive("sortable",e=>{let r=parseInt(e.dataset?.sortableAnimationDuration);r!==0&&!r&&(r=300),e.sortable=Si.create(e,{group:e.getAttribute("x-sortable-group"),draggable:"[x-sortable-item]",handle:"[x-sortable-handle]",dataIdAttr:"x-sortable-item",animation:r,ghostClass:"fi-sortable-ghost"})})};var bs=Object.create,Ci=Object.defineProperty,ys=Object.getPrototypeOf,ws=Object.prototype.hasOwnProperty,xs=Object.getOwnPropertyNames,Es=Object.getOwnPropertyDescriptor,Os=t=>Ci(t,"__esModule",{value:!0}),Ho=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports),Ss=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of xs(e))!ws.call(t,n)&&n!=="default"&&Ci(t,n,{get:()=>e[n],enumerable:!(r=Es(e,n))||r.enumerable});return t},$o=t=>Ss(Os(Ci(t!=null?bs(ys(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t),As=Ho(t=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});function e(c){var s=c.getBoundingClientRect();return{width:s.width,height:s.height,top:s.top,right:s.right,bottom:s.bottom,left:s.left,x:s.left,y:s.top}}function r(c){if(c==null)return window;if(c.toString()!=="[object Window]"){var s=c.ownerDocument;return s&&s.defaultView||window}return c}function n(c){var s=r(c),b=s.pageXOffset,_=s.pageYOffset;return{scrollLeft:b,scrollTop:_}}function i(c){var s=r(c).Element;return c instanceof s||c instanceof Element}function o(c){var s=r(c).HTMLElement;return c instanceof s||c instanceof HTMLElement}function a(c){if(typeof ShadowRoot>"u")return!1;var s=r(c).ShadowRoot;return c instanceof s||c instanceof ShadowRoot}function d(c){return{scrollLeft:c.scrollLeft,scrollTop:c.scrollTop}}function f(c){return c===r(c)||!o(c)?n(c):d(c)}function u(c){return c?(c.nodeName||"").toLowerCase():null}function w(c){return((i(c)?c.ownerDocument:c.document)||window.document).documentElement}function m(c){return e(w(c)).left+n(c).scrollLeft}function E(c){return r(c).getComputedStyle(c)}function O(c){var s=E(c),b=s.overflow,_=s.overflowX,T=s.overflowY;return/auto|scroll|overlay|hidden/.test(b+T+_)}function S(c,s,b){b===void 0&&(b=!1);var _=w(s),T=e(c),L=o(s),z={scrollLeft:0,scrollTop:0},H={x:0,y:0};return(L||!L&&!b)&&((u(s)!=="body"||O(_))&&(z=f(s)),o(s)?(H=e(s),H.x+=s.clientLeft,H.y+=s.clientTop):_&&(H.x=m(_))),{x:T.left+z.scrollLeft-H.x,y:T.top+z.scrollTop-H.y,width:T.width,height:T.height}}function M(c){var s=e(c),b=c.offsetWidth,_=c.offsetHeight;return Math.abs(s.width-b)<=1&&(b=s.width),Math.abs(s.height-_)<=1&&(_=s.height),{x:c.offsetLeft,y:c.offsetTop,width:b,height:_}}function I(c){return u(c)==="html"?c:c.assignedSlot||c.parentNode||(a(c)?c.host:null)||w(c)}function $(c){return["html","body","#document"].indexOf(u(c))>=0?c.ownerDocument.body:o(c)&&O(c)?c:$(I(c))}function A(c,s){var b;s===void 0&&(s=[]);var _=$(c),T=_===((b=c.ownerDocument)==null?void 0:b.body),L=r(_),z=T?[L].concat(L.visualViewport||[],O(_)?_:[]):_,H=s.concat(z);return T?H:H.concat(A(I(z)))}function k(c){return["table","td","th"].indexOf(u(c))>=0}function Y(c){return!o(c)||E(c).position==="fixed"?null:c.offsetParent}function nt(c){var s=navigator.userAgent.toLowerCase().indexOf("firefox")!==-1,b=navigator.userAgent.indexOf("Trident")!==-1;if(b&&o(c)){var _=E(c);if(_.position==="fixed")return null}for(var T=I(c);o(T)&&["html","body"].indexOf(u(T))<0;){var L=E(T);if(L.transform!=="none"||L.perspective!=="none"||L.contain==="paint"||["transform","perspective"].indexOf(L.willChange)!==-1||s&&L.willChange==="filter"||s&&L.filter&&L.filter!=="none")return T;T=T.parentNode}return null}function J(c){for(var s=r(c),b=Y(c);b&&k(b)&&E(b).position==="static";)b=Y(b);return b&&(u(b)==="html"||u(b)==="body"&&E(b).position==="static")?s:b||nt(c)||s}var U="top",dt="bottom",X="right",Z="left",mt="auto",l=[U,dt,X,Z],h="start",v="end",p="clippingParents",j="viewport",P="popper",R="reference",Q=l.reduce(function(c,s){return c.concat([s+"-"+h,s+"-"+v])},[]),Vt=[].concat(l,[mt]).reduce(function(c,s){return c.concat([s,s+"-"+h,s+"-"+v])},[]),Re="beforeRead",ze="read",Nr="afterRead",kr="beforeMain",jr="main",Ue="afterMain",nr="beforeWrite",Br="write",rr="afterWrite",Ie=[Re,ze,Nr,kr,jr,Ue,nr,Br,rr];function Hr(c){var s=new Map,b=new Set,_=[];c.forEach(function(L){s.set(L.name,L)});function T(L){b.add(L.name);var z=[].concat(L.requires||[],L.requiresIfExists||[]);z.forEach(function(H){if(!b.has(H)){var G=s.get(H);G&&T(G)}}),_.push(L)}return c.forEach(function(L){b.has(L.name)||T(L)}),_}function me(c){var s=Hr(c);return Ie.reduce(function(b,_){return b.concat(s.filter(function(T){return T.phase===_}))},[])}function Ve(c){var s;return function(){return s||(s=new Promise(function(b){Promise.resolve().then(function(){s=void 0,b(c())})})),s}}function Ae(c){for(var s=arguments.length,b=new Array(s>1?s-1:0),_=1;_=0,_=b&&o(c)?J(c):c;return i(_)?s.filter(function(T){return i(T)&&kn(T,_)&&u(T)!=="body"}):[]}function wn(c,s,b){var _=s==="clippingParents"?yn(c):[].concat(s),T=[].concat(_,[b]),L=T[0],z=T.reduce(function(H,G){var ot=sr(c,G);return H.top=ge(ot.top,H.top),H.right=ln(ot.right,H.right),H.bottom=ln(ot.bottom,H.bottom),H.left=ge(ot.left,H.left),H},sr(c,L));return z.width=z.right-z.left,z.height=z.bottom-z.top,z.x=z.left,z.y=z.top,z}function cn(c){return c.split("-")[1]}function de(c){return["top","bottom"].indexOf(c)>=0?"x":"y"}function lr(c){var s=c.reference,b=c.element,_=c.placement,T=_?oe(_):null,L=_?cn(_):null,z=s.x+s.width/2-b.width/2,H=s.y+s.height/2-b.height/2,G;switch(T){case U:G={x:z,y:s.y-b.height};break;case dt:G={x:z,y:s.y+s.height};break;case X:G={x:s.x+s.width,y:H};break;case Z:G={x:s.x-b.width,y:H};break;default:G={x:s.x,y:s.y}}var ot=T?de(T):null;if(ot!=null){var V=ot==="y"?"height":"width";switch(L){case h:G[ot]=G[ot]-(s[V]/2-b[V]/2);break;case v:G[ot]=G[ot]+(s[V]/2-b[V]/2);break}}return G}function cr(){return{top:0,right:0,bottom:0,left:0}}function fr(c){return Object.assign({},cr(),c)}function ur(c,s){return s.reduce(function(b,_){return b[_]=c,b},{})}function qe(c,s){s===void 0&&(s={});var b=s,_=b.placement,T=_===void 0?c.placement:_,L=b.boundary,z=L===void 0?p:L,H=b.rootBoundary,G=H===void 0?j:H,ot=b.elementContext,V=ot===void 0?P:ot,Ct=b.altBoundary,Lt=Ct===void 0?!1:Ct,Dt=b.padding,xt=Dt===void 0?0:Dt,Mt=fr(typeof xt!="number"?xt:ur(xt,l)),St=V===P?R:P,Bt=c.elements.reference,Rt=c.rects.popper,Ht=c.elements[Lt?St:V],ct=wn(i(Ht)?Ht:Ht.contextElement||w(c.elements.popper),z,G),Pt=e(Bt),_t=lr({reference:Pt,element:Rt,strategy:"absolute",placement:T}),Nt=Xe(Object.assign({},Rt,_t)),Ft=V===P?Nt:Pt,Yt={top:ct.top-Ft.top+Mt.top,bottom:Ft.bottom-ct.bottom+Mt.bottom,left:ct.left-Ft.left+Mt.left,right:Ft.right-ct.right+Mt.right},$t=c.modifiersData.offset;if(V===P&&$t){var zt=$t[T];Object.keys(Yt).forEach(function(we){var te=[X,dt].indexOf(we)>=0?1:-1,Le=[U,dt].indexOf(we)>=0?"y":"x";Yt[we]+=zt[Le]*te})}return Yt}var dr="Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.",Vr="Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.",xn={placement:"bottom",modifiers:[],strategy:"absolute"};function fn(){for(var c=arguments.length,s=new Array(c),b=0;b100){console.error(Vr);break}if(V.reset===!0){V.reset=!1,Pt=-1;continue}var _t=V.orderedModifiers[Pt],Nt=_t.fn,Ft=_t.options,Yt=Ft===void 0?{}:Ft,$t=_t.name;typeof Nt=="function"&&(V=Nt({state:V,options:Yt,name:$t,instance:Dt})||V)}}},update:Ve(function(){return new Promise(function(St){Dt.forceUpdate(),St(V)})}),destroy:function(){Mt(),Lt=!0}};if(!fn(H,G))return console.error(dr),Dt;Dt.setOptions(ot).then(function(St){!Lt&&ot.onFirstUpdate&&ot.onFirstUpdate(St)});function xt(){V.orderedModifiers.forEach(function(St){var Bt=St.name,Rt=St.options,Ht=Rt===void 0?{}:Rt,ct=St.effect;if(typeof ct=="function"){var Pt=ct({state:V,name:Bt,instance:Dt,options:Ht}),_t=function(){};Ct.push(Pt||_t)}})}function Mt(){Ct.forEach(function(St){return St()}),Ct=[]}return Dt}}var On={passive:!0};function Yr(c){var s=c.state,b=c.instance,_=c.options,T=_.scroll,L=T===void 0?!0:T,z=_.resize,H=z===void 0?!0:z,G=r(s.elements.popper),ot=[].concat(s.scrollParents.reference,s.scrollParents.popper);return L&&ot.forEach(function(V){V.addEventListener("scroll",b.update,On)}),H&&G.addEventListener("resize",b.update,On),function(){L&&ot.forEach(function(V){V.removeEventListener("scroll",b.update,On)}),H&&G.removeEventListener("resize",b.update,On)}}var jn={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:Yr,data:{}};function Xr(c){var s=c.state,b=c.name;s.modifiersData[b]=lr({reference:s.rects.reference,element:s.rects.popper,strategy:"absolute",placement:s.placement})}var Bn={name:"popperOffsets",enabled:!0,phase:"read",fn:Xr,data:{}},qr={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Gr(c){var s=c.x,b=c.y,_=window,T=_.devicePixelRatio||1;return{x:Ye(Ye(s*T)/T)||0,y:Ye(Ye(b*T)/T)||0}}function Hn(c){var s,b=c.popper,_=c.popperRect,T=c.placement,L=c.offsets,z=c.position,H=c.gpuAcceleration,G=c.adaptive,ot=c.roundOffsets,V=ot===!0?Gr(L):typeof ot=="function"?ot(L):L,Ct=V.x,Lt=Ct===void 0?0:Ct,Dt=V.y,xt=Dt===void 0?0:Dt,Mt=L.hasOwnProperty("x"),St=L.hasOwnProperty("y"),Bt=Z,Rt=U,Ht=window;if(G){var ct=J(b),Pt="clientHeight",_t="clientWidth";ct===r(b)&&(ct=w(b),E(ct).position!=="static"&&(Pt="scrollHeight",_t="scrollWidth")),ct=ct,T===U&&(Rt=dt,xt-=ct[Pt]-_.height,xt*=H?1:-1),T===Z&&(Bt=X,Lt-=ct[_t]-_.width,Lt*=H?1:-1)}var Nt=Object.assign({position:z},G&&qr);if(H){var Ft;return Object.assign({},Nt,(Ft={},Ft[Rt]=St?"0":"",Ft[Bt]=Mt?"0":"",Ft.transform=(Ht.devicePixelRatio||1)<2?"translate("+Lt+"px, "+xt+"px)":"translate3d("+Lt+"px, "+xt+"px, 0)",Ft))}return Object.assign({},Nt,(s={},s[Rt]=St?xt+"px":"",s[Bt]=Mt?Lt+"px":"",s.transform="",s))}function g(c){var s=c.state,b=c.options,_=b.gpuAcceleration,T=_===void 0?!0:_,L=b.adaptive,z=L===void 0?!0:L,H=b.roundOffsets,G=H===void 0?!0:H,ot=E(s.elements.popper).transitionProperty||"";z&&["transform","top","right","bottom","left"].some(function(Ct){return ot.indexOf(Ct)>=0})&&console.warn(["Popper: Detected CSS transitions on at least one of the following",'CSS properties: "transform", "top", "right", "bottom", "left".',` - -`,'Disable the "computeStyles" modifier\'s `adaptive` option to allow',"for smooth transitions, or remove these properties from the CSS","transition declaration on the popper element if only transitioning","opacity or background-color for example.",` - -`,"We recommend using the popper element as a wrapper around an inner","element that can have any CSS property transitioned for animations."].join(" "));var V={placement:oe(s.placement),popper:s.elements.popper,popperRect:s.rects.popper,gpuAcceleration:T};s.modifiersData.popperOffsets!=null&&(s.styles.popper=Object.assign({},s.styles.popper,Hn(Object.assign({},V,{offsets:s.modifiersData.popperOffsets,position:s.options.strategy,adaptive:z,roundOffsets:G})))),s.modifiersData.arrow!=null&&(s.styles.arrow=Object.assign({},s.styles.arrow,Hn(Object.assign({},V,{offsets:s.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:G})))),s.attributes.popper=Object.assign({},s.attributes.popper,{"data-popper-placement":s.placement})}var y={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:g,data:{}};function D(c){var s=c.state;Object.keys(s.elements).forEach(function(b){var _=s.styles[b]||{},T=s.attributes[b]||{},L=s.elements[b];!o(L)||!u(L)||(Object.assign(L.style,_),Object.keys(T).forEach(function(z){var H=T[z];H===!1?L.removeAttribute(z):L.setAttribute(z,H===!0?"":H)}))})}function F(c){var s=c.state,b={popper:{position:s.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(s.elements.popper.style,b.popper),s.styles=b,s.elements.arrow&&Object.assign(s.elements.arrow.style,b.arrow),function(){Object.keys(s.elements).forEach(function(_){var T=s.elements[_],L=s.attributes[_]||{},z=Object.keys(s.styles.hasOwnProperty(_)?s.styles[_]:b[_]),H=z.reduce(function(G,ot){return G[ot]="",G},{});!o(T)||!u(T)||(Object.assign(T.style,H),Object.keys(L).forEach(function(G){T.removeAttribute(G)}))})}}var q={name:"applyStyles",enabled:!0,phase:"write",fn:D,effect:F,requires:["computeStyles"]};function W(c,s,b){var _=oe(c),T=[Z,U].indexOf(_)>=0?-1:1,L=typeof b=="function"?b(Object.assign({},s,{placement:c})):b,z=L[0],H=L[1];return z=z||0,H=(H||0)*T,[Z,X].indexOf(_)>=0?{x:H,y:z}:{x:z,y:H}}function B(c){var s=c.state,b=c.options,_=c.name,T=b.offset,L=T===void 0?[0,0]:T,z=Vt.reduce(function(V,Ct){return V[Ct]=W(Ct,s.rects,L),V},{}),H=z[s.placement],G=H.x,ot=H.y;s.modifiersData.popperOffsets!=null&&(s.modifiersData.popperOffsets.x+=G,s.modifiersData.popperOffsets.y+=ot),s.modifiersData[_]=z}var bt={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:B},lt={left:"right",right:"left",bottom:"top",top:"bottom"};function pt(c){return c.replace(/left|right|bottom|top/g,function(s){return lt[s]})}var yt={start:"end",end:"start"};function Tt(c){return c.replace(/start|end/g,function(s){return yt[s]})}function jt(c,s){s===void 0&&(s={});var b=s,_=b.placement,T=b.boundary,L=b.rootBoundary,z=b.padding,H=b.flipVariations,G=b.allowedAutoPlacements,ot=G===void 0?Vt:G,V=cn(_),Ct=V?H?Q:Q.filter(function(xt){return cn(xt)===V}):l,Lt=Ct.filter(function(xt){return ot.indexOf(xt)>=0});Lt.length===0&&(Lt=Ct,console.error(["Popper: The `allowedAutoPlacements` option did not allow any","placements. Ensure the `placement` option matches the variation","of the allowed placements.",'For example, "auto" cannot be used to allow "bottom-start".','Use "auto-start" instead.'].join(" ")));var Dt=Lt.reduce(function(xt,Mt){return xt[Mt]=qe(c,{placement:Mt,boundary:T,rootBoundary:L,padding:z})[oe(Mt)],xt},{});return Object.keys(Dt).sort(function(xt,Mt){return Dt[xt]-Dt[Mt]})}function At(c){if(oe(c)===mt)return[];var s=pt(c);return[Tt(c),s,Tt(s)]}function It(c){var s=c.state,b=c.options,_=c.name;if(!s.modifiersData[_]._skip){for(var T=b.mainAxis,L=T===void 0?!0:T,z=b.altAxis,H=z===void 0?!0:z,G=b.fallbackPlacements,ot=b.padding,V=b.boundary,Ct=b.rootBoundary,Lt=b.altBoundary,Dt=b.flipVariations,xt=Dt===void 0?!0:Dt,Mt=b.allowedAutoPlacements,St=s.options.placement,Bt=oe(St),Rt=Bt===St,Ht=G||(Rt||!xt?[pt(St)]:At(St)),ct=[St].concat(Ht).reduce(function(et,gt){return et.concat(oe(gt)===mt?jt(s,{placement:gt,boundary:V,rootBoundary:Ct,padding:ot,flipVariations:xt,allowedAutoPlacements:Mt}):gt)},[]),Pt=s.rects.reference,_t=s.rects.popper,Nt=new Map,Ft=!0,Yt=ct[0],$t=0;$t=0,dn=Le?"width":"height",Ze=qe(s,{placement:zt,boundary:V,rootBoundary:Ct,altBoundary:Lt,padding:ot}),Ne=Le?te?X:Z:te?dt:U;Pt[dn]>_t[dn]&&(Ne=pt(Ne));var $n=pt(Ne),Qe=[];if(L&&Qe.push(Ze[we]<=0),H&&Qe.push(Ze[Ne]<=0,Ze[$n]<=0),Qe.every(function(et){return et})){Yt=zt,Ft=!1;break}Nt.set(zt,Qe)}if(Ft)for(var Sn=xt?3:1,Wn=function(gt){var wt=ct.find(function(Kt){var Jt=Nt.get(Kt);if(Jt)return Jt.slice(0,gt).every(function(Ce){return Ce})});if(wt)return Yt=wt,"break"},C=Sn;C>0;C--){var K=Wn(C);if(K==="break")break}s.placement!==Yt&&(s.modifiersData[_]._skip=!0,s.placement=Yt,s.reset=!0)}}var rt={name:"flip",enabled:!0,phase:"main",fn:It,requiresIfExists:["offset"],data:{_skip:!1}};function ht(c){return c==="x"?"y":"x"}function vt(c,s,b){return ge(c,ln(s,b))}function tt(c){var s=c.state,b=c.options,_=c.name,T=b.mainAxis,L=T===void 0?!0:T,z=b.altAxis,H=z===void 0?!1:z,G=b.boundary,ot=b.rootBoundary,V=b.altBoundary,Ct=b.padding,Lt=b.tether,Dt=Lt===void 0?!0:Lt,xt=b.tetherOffset,Mt=xt===void 0?0:xt,St=qe(s,{boundary:G,rootBoundary:ot,padding:Ct,altBoundary:V}),Bt=oe(s.placement),Rt=cn(s.placement),Ht=!Rt,ct=de(Bt),Pt=ht(ct),_t=s.modifiersData.popperOffsets,Nt=s.rects.reference,Ft=s.rects.popper,Yt=typeof Mt=="function"?Mt(Object.assign({},s.rects,{placement:s.placement})):Mt,$t={x:0,y:0};if(_t){if(L||H){var zt=ct==="y"?U:Z,we=ct==="y"?dt:X,te=ct==="y"?"height":"width",Le=_t[ct],dn=_t[ct]+St[zt],Ze=_t[ct]-St[we],Ne=Dt?-Ft[te]/2:0,$n=Rt===h?Nt[te]:Ft[te],Qe=Rt===h?-Ft[te]:-Nt[te],Sn=s.elements.arrow,Wn=Dt&&Sn?M(Sn):{width:0,height:0},C=s.modifiersData["arrow#persistent"]?s.modifiersData["arrow#persistent"].padding:cr(),K=C[zt],et=C[we],gt=vt(0,Nt[te],Wn[te]),wt=Ht?Nt[te]/2-Ne-gt-K-Yt:$n-gt-K-Yt,Kt=Ht?-Nt[te]/2+Ne+gt+et+Yt:Qe+gt+et+Yt,Jt=s.elements.arrow&&J(s.elements.arrow),Ce=Jt?ct==="y"?Jt.clientTop||0:Jt.clientLeft||0:0,zn=s.modifiersData.offset?s.modifiersData.offset[s.placement][ct]:0,_e=_t[ct]+wt-zn-Ce,An=_t[ct]+Kt-zn;if(L){var pn=vt(Dt?ln(dn,_e):dn,Le,Dt?ge(Ze,An):Ze);_t[ct]=pn,$t[ct]=pn-Le}if(H){var tn=ct==="x"?U:Z,Kr=ct==="x"?dt:X,en=_t[Pt],hn=en+St[tn],_i=en-St[Kr],Ti=vt(Dt?ln(hn,_e):hn,en,Dt?ge(_i,An):_i);_t[Pt]=Ti,$t[Pt]=Ti-en}}s.modifiersData[_]=$t}}var it={name:"preventOverflow",enabled:!0,phase:"main",fn:tt,requiresIfExists:["offset"]},x=function(s,b){return s=typeof s=="function"?s(Object.assign({},b.rects,{placement:b.placement})):s,fr(typeof s!="number"?s:ur(s,l))};function Gt(c){var s,b=c.state,_=c.name,T=c.options,L=b.elements.arrow,z=b.modifiersData.popperOffsets,H=oe(b.placement),G=de(H),ot=[Z,X].indexOf(H)>=0,V=ot?"height":"width";if(!(!L||!z)){var Ct=x(T.padding,b),Lt=M(L),Dt=G==="y"?U:Z,xt=G==="y"?dt:X,Mt=b.rects.reference[V]+b.rects.reference[G]-z[G]-b.rects.popper[V],St=z[G]-b.rects.reference[G],Bt=J(L),Rt=Bt?G==="y"?Bt.clientHeight||0:Bt.clientWidth||0:0,Ht=Mt/2-St/2,ct=Ct[Dt],Pt=Rt-Lt[V]-Ct[xt],_t=Rt/2-Lt[V]/2+Ht,Nt=vt(ct,_t,Pt),Ft=G;b.modifiersData[_]=(s={},s[Ft]=Nt,s.centerOffset=Nt-_t,s)}}function ft(c){var s=c.state,b=c.options,_=b.element,T=_===void 0?"[data-popper-arrow]":_;if(T!=null&&!(typeof T=="string"&&(T=s.elements.popper.querySelector(T),!T))){if(o(T)||console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).',"To use an SVG arrow, wrap it in an HTMLElement that will be used as","the arrow."].join(" ")),!kn(s.elements.popper,T)){console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper',"element."].join(" "));return}s.elements.arrow=T}}var Fe={name:"arrow",enabled:!0,phase:"main",fn:Gt,effect:ft,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function be(c,s,b){return b===void 0&&(b={x:0,y:0}),{top:c.top-s.height-b.y,right:c.right-s.width+b.x,bottom:c.bottom-s.height+b.y,left:c.left-s.width-b.x}}function Ge(c){return[U,X,dt,Z].some(function(s){return c[s]>=0})}function Ke(c){var s=c.state,b=c.name,_=s.rects.reference,T=s.rects.popper,L=s.modifiersData.preventOverflow,z=qe(s,{elementContext:"reference"}),H=qe(s,{altBoundary:!0}),G=be(z,_),ot=be(H,T,L),V=Ge(G),Ct=Ge(ot);s.modifiersData[b]={referenceClippingOffsets:G,popperEscapeOffsets:ot,isReferenceHidden:V,hasPopperEscaped:Ct},s.attributes.popper=Object.assign({},s.attributes.popper,{"data-popper-reference-hidden":V,"data-popper-escaped":Ct})}var Je={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Ke},re=[jn,Bn,y,q],le=En({defaultModifiers:re}),ye=[jn,Bn,y,q,bt,rt,it,Fe,Je],un=En({defaultModifiers:ye});t.applyStyles=q,t.arrow=Fe,t.computeStyles=y,t.createPopper=un,t.createPopperLite=le,t.defaultModifiers=ye,t.detectOverflow=qe,t.eventListeners=jn,t.flip=rt,t.hide=Je,t.offset=bt,t.popperGenerator=En,t.popperOffsets=Bn,t.preventOverflow=it}),Wo=Ho(t=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var e=As(),r='',n="tippy-box",i="tippy-content",o="tippy-backdrop",a="tippy-arrow",d="tippy-svg-arrow",f={passive:!0,capture:!0};function u(g,y){return{}.hasOwnProperty.call(g,y)}function w(g,y,D){if(Array.isArray(g)){var F=g[y];return F??(Array.isArray(D)?D[y]:D)}return g}function m(g,y){var D={}.toString.call(g);return D.indexOf("[object")===0&&D.indexOf(y+"]")>-1}function E(g,y){return typeof g=="function"?g.apply(void 0,y):g}function O(g,y){if(y===0)return g;var D;return function(F){clearTimeout(D),D=setTimeout(function(){g(F)},y)}}function S(g,y){var D=Object.assign({},g);return y.forEach(function(F){delete D[F]}),D}function M(g){return g.split(/\s+/).filter(Boolean)}function I(g){return[].concat(g)}function $(g,y){g.indexOf(y)===-1&&g.push(y)}function A(g){return g.filter(function(y,D){return g.indexOf(y)===D})}function k(g){return g.split("-")[0]}function Y(g){return[].slice.call(g)}function nt(g){return Object.keys(g).reduce(function(y,D){return g[D]!==void 0&&(y[D]=g[D]),y},{})}function J(){return document.createElement("div")}function U(g){return["Element","Fragment"].some(function(y){return m(g,y)})}function dt(g){return m(g,"NodeList")}function X(g){return m(g,"MouseEvent")}function Z(g){return!!(g&&g._tippy&&g._tippy.reference===g)}function mt(g){return U(g)?[g]:dt(g)?Y(g):Array.isArray(g)?g:Y(document.querySelectorAll(g))}function l(g,y){g.forEach(function(D){D&&(D.style.transitionDuration=y+"ms")})}function h(g,y){g.forEach(function(D){D&&D.setAttribute("data-state",y)})}function v(g){var y,D=I(g),F=D[0];return!(F==null||(y=F.ownerDocument)==null)&&y.body?F.ownerDocument:document}function p(g,y){var D=y.clientX,F=y.clientY;return g.every(function(q){var W=q.popperRect,B=q.popperState,bt=q.props,lt=bt.interactiveBorder,pt=k(B.placement),yt=B.modifiersData.offset;if(!yt)return!0;var Tt=pt==="bottom"?yt.top.y:0,jt=pt==="top"?yt.bottom.y:0,At=pt==="right"?yt.left.x:0,It=pt==="left"?yt.right.x:0,rt=W.top-F+Tt>lt,ht=F-W.bottom-jt>lt,vt=W.left-D+At>lt,tt=D-W.right-It>lt;return rt||ht||vt||tt})}function j(g,y,D){var F=y+"EventListener";["transitionend","webkitTransitionEnd"].forEach(function(q){g[F](q,D)})}var P={isTouch:!1},R=0;function Q(){P.isTouch||(P.isTouch=!0,window.performance&&document.addEventListener("mousemove",Vt))}function Vt(){var g=performance.now();g-R<20&&(P.isTouch=!1,document.removeEventListener("mousemove",Vt)),R=g}function Re(){var g=document.activeElement;if(Z(g)){var y=g._tippy;g.blur&&!y.state.isVisible&&g.blur()}}function ze(){document.addEventListener("touchstart",Q,f),window.addEventListener("blur",Re)}var Nr=typeof window<"u"&&typeof document<"u",kr=Nr?navigator.userAgent:"",jr=/MSIE |Trident\//.test(kr);function Ue(g){var y=g==="destroy"?"n already-":" ";return[g+"() was called on a"+y+"destroyed instance. This is a no-op but","indicates a potential memory leak."].join(" ")}function nr(g){var y=/[ \t]{2,}/g,D=/^[ \t]*/gm;return g.replace(y," ").replace(D,"").trim()}function Br(g){return nr(` - %ctippy.js - - %c`+nr(g)+` - - %c\u{1F477}\u200D This is a development-only message. It will be removed in production. - `)}function rr(g){return[Br(g),"color: #00C584; font-size: 1.3em; font-weight: bold;","line-height: 1.5","color: #a6a095;"]}var Ie;Hr();function Hr(){Ie=new Set}function me(g,y){if(g&&!Ie.has(y)){var D;Ie.add(y),(D=console).warn.apply(D,rr(y))}}function Ve(g,y){if(g&&!Ie.has(y)){var D;Ie.add(y),(D=console).error.apply(D,rr(y))}}function Ae(g){var y=!g,D=Object.prototype.toString.call(g)==="[object Object]"&&!g.addEventListener;Ve(y,["tippy() was passed","`"+String(g)+"`","as its targets (first) argument. Valid types are: String, Element,","Element[], or NodeList."].join(" ")),Ve(D,["tippy() was passed a plain object which is not supported as an argument","for virtual positioning. Use props.getReferenceClientRect instead."].join(" "))}var De={animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},$r={allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999},Qt=Object.assign({appendTo:function(){return document.body},aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},De,{},$r),Wr=Object.keys(Qt),zr=function(y){ge(y,[]);var D=Object.keys(y);D.forEach(function(F){Qt[F]=y[F]})};function oe(g){var y=g.plugins||[],D=y.reduce(function(F,q){var W=q.name,B=q.defaultValue;return W&&(F[W]=g[W]!==void 0?g[W]:B),F},{});return Object.assign({},g,{},D)}function Ur(g,y){var D=y?Object.keys(oe(Object.assign({},Qt,{plugins:y}))):Wr,F=D.reduce(function(q,W){var B=(g.getAttribute("data-tippy-"+W)||"").trim();if(!B)return q;if(W==="content")q[W]=B;else try{q[W]=JSON.parse(B)}catch{q[W]=B}return q},{});return F}function ir(g,y){var D=Object.assign({},y,{content:E(y.content,[g])},y.ignoreAttributes?{}:Ur(g,y.plugins));return D.aria=Object.assign({},Qt.aria,{},D.aria),D.aria={expanded:D.aria.expanded==="auto"?y.interactive:D.aria.expanded,content:D.aria.content==="auto"?y.interactive?null:"describedby":D.aria.content},D}function ge(g,y){g===void 0&&(g={}),y===void 0&&(y=[]);var D=Object.keys(g);D.forEach(function(F){var q=S(Qt,Object.keys(De)),W=!u(q,F);W&&(W=y.filter(function(B){return B.name===F}).length===0),me(W,["`"+F+"`","is not a valid prop. You may have spelled it incorrectly, or if it's","a plugin, forgot to pass it in an array as props.plugins.",` - -`,`All props: https://atomiks.github.io/tippyjs/v6/all-props/ -`,"Plugins: https://atomiks.github.io/tippyjs/v6/plugins/"].join(" "))})}var ln=function(){return"innerHTML"};function Ye(g,y){g[ln()]=y}function or(g){var y=J();return g===!0?y.className=a:(y.className=d,U(g)?y.appendChild(g):Ye(y,g)),y}function kn(g,y){U(y.content)?(Ye(g,""),g.appendChild(y.content)):typeof y.content!="function"&&(y.allowHTML?Ye(g,y.content):g.textContent=y.content)}function Xe(g){var y=g.firstElementChild,D=Y(y.children);return{box:y,content:D.find(function(F){return F.classList.contains(i)}),arrow:D.find(function(F){return F.classList.contains(a)||F.classList.contains(d)}),backdrop:D.find(function(F){return F.classList.contains(o)})}}function ar(g){var y=J(),D=J();D.className=n,D.setAttribute("data-state","hidden"),D.setAttribute("tabindex","-1");var F=J();F.className=i,F.setAttribute("data-state","hidden"),kn(F,g.props),y.appendChild(D),D.appendChild(F),q(g.props,g.props);function q(W,B){var bt=Xe(y),lt=bt.box,pt=bt.content,yt=bt.arrow;B.theme?lt.setAttribute("data-theme",B.theme):lt.removeAttribute("data-theme"),typeof B.animation=="string"?lt.setAttribute("data-animation",B.animation):lt.removeAttribute("data-animation"),B.inertia?lt.setAttribute("data-inertia",""):lt.removeAttribute("data-inertia"),lt.style.maxWidth=typeof B.maxWidth=="number"?B.maxWidth+"px":B.maxWidth,B.role?lt.setAttribute("role",B.role):lt.removeAttribute("role"),(W.content!==B.content||W.allowHTML!==B.allowHTML)&&kn(pt,g.props),B.arrow?yt?W.arrow!==B.arrow&&(lt.removeChild(yt),lt.appendChild(or(B.arrow))):lt.appendChild(or(B.arrow)):yt&<.removeChild(yt)}return{popper:y,onUpdate:q}}ar.$$tippy=!0;var sr=1,yn=[],wn=[];function cn(g,y){var D=ir(g,Object.assign({},Qt,{},oe(nt(y)))),F,q,W,B=!1,bt=!1,lt=!1,pt=!1,yt,Tt,jt,At=[],It=O(Rt,D.interactiveDebounce),rt,ht=sr++,vt=null,tt=A(D.plugins),it={isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},x={id:ht,reference:g,popper:J(),popperInstance:vt,props:D,state:it,plugins:tt,clearDelayTimeouts:Le,setProps:dn,setContent:Ze,show:Ne,hide:$n,hideWithInteractivity:Qe,enable:we,disable:te,unmount:Sn,destroy:Wn};if(!D.render)return Ve(!0,"render() function has not been supplied."),x;var Gt=D.render(x),ft=Gt.popper,Fe=Gt.onUpdate;ft.setAttribute("data-tippy-root",""),ft.id="tippy-"+x.id,x.popper=ft,g._tippy=x,ft._tippy=x;var be=tt.map(function(C){return C.fn(x)}),Ge=g.hasAttribute("aria-expanded");return Mt(),T(),s(),b("onCreate",[x]),D.showOnCreate&&$t(),ft.addEventListener("mouseenter",function(){x.props.interactive&&x.state.isVisible&&x.clearDelayTimeouts()}),ft.addEventListener("mouseleave",function(C){x.props.interactive&&x.props.trigger.indexOf("mouseenter")>=0&&(ye().addEventListener("mousemove",It),It(C))}),x;function Ke(){var C=x.props.touch;return Array.isArray(C)?C:[C,0]}function Je(){return Ke()[0]==="hold"}function re(){var C;return!!((C=x.props.render)!=null&&C.$$tippy)}function le(){return rt||g}function ye(){var C=le().parentNode;return C?v(C):document}function un(){return Xe(ft)}function c(C){return x.state.isMounted&&!x.state.isVisible||P.isTouch||yt&&yt.type==="focus"?0:w(x.props.delay,C?0:1,Qt.delay)}function s(){ft.style.pointerEvents=x.props.interactive&&x.state.isVisible?"":"none",ft.style.zIndex=""+x.props.zIndex}function b(C,K,et){if(et===void 0&&(et=!0),be.forEach(function(wt){wt[C]&&wt[C].apply(void 0,K)}),et){var gt;(gt=x.props)[C].apply(gt,K)}}function _(){var C=x.props.aria;if(C.content){var K="aria-"+C.content,et=ft.id,gt=I(x.props.triggerTarget||g);gt.forEach(function(wt){var Kt=wt.getAttribute(K);if(x.state.isVisible)wt.setAttribute(K,Kt?Kt+" "+et:et);else{var Jt=Kt&&Kt.replace(et,"").trim();Jt?wt.setAttribute(K,Jt):wt.removeAttribute(K)}})}}function T(){if(!(Ge||!x.props.aria.expanded)){var C=I(x.props.triggerTarget||g);C.forEach(function(K){x.props.interactive?K.setAttribute("aria-expanded",x.state.isVisible&&K===le()?"true":"false"):K.removeAttribute("aria-expanded")})}}function L(){ye().removeEventListener("mousemove",It),yn=yn.filter(function(C){return C!==It})}function z(C){if(!(P.isTouch&&(lt||C.type==="mousedown"))&&!(x.props.interactive&&ft.contains(C.target))){if(le().contains(C.target)){if(P.isTouch||x.state.isVisible&&x.props.trigger.indexOf("click")>=0)return}else b("onClickOutside",[x,C]);x.props.hideOnClick===!0&&(x.clearDelayTimeouts(),x.hide(),bt=!0,setTimeout(function(){bt=!1}),x.state.isMounted||V())}}function H(){lt=!0}function G(){lt=!1}function ot(){var C=ye();C.addEventListener("mousedown",z,!0),C.addEventListener("touchend",z,f),C.addEventListener("touchstart",G,f),C.addEventListener("touchmove",H,f)}function V(){var C=ye();C.removeEventListener("mousedown",z,!0),C.removeEventListener("touchend",z,f),C.removeEventListener("touchstart",G,f),C.removeEventListener("touchmove",H,f)}function Ct(C,K){Dt(C,function(){!x.state.isVisible&&ft.parentNode&&ft.parentNode.contains(ft)&&K()})}function Lt(C,K){Dt(C,K)}function Dt(C,K){var et=un().box;function gt(wt){wt.target===et&&(j(et,"remove",gt),K())}if(C===0)return K();j(et,"remove",Tt),j(et,"add",gt),Tt=gt}function xt(C,K,et){et===void 0&&(et=!1);var gt=I(x.props.triggerTarget||g);gt.forEach(function(wt){wt.addEventListener(C,K,et),At.push({node:wt,eventType:C,handler:K,options:et})})}function Mt(){Je()&&(xt("touchstart",Bt,{passive:!0}),xt("touchend",Ht,{passive:!0})),M(x.props.trigger).forEach(function(C){if(C!=="manual")switch(xt(C,Bt),C){case"mouseenter":xt("mouseleave",Ht);break;case"focus":xt(jr?"focusout":"blur",ct);break;case"focusin":xt("focusout",ct);break}})}function St(){At.forEach(function(C){var K=C.node,et=C.eventType,gt=C.handler,wt=C.options;K.removeEventListener(et,gt,wt)}),At=[]}function Bt(C){var K,et=!1;if(!(!x.state.isEnabled||Pt(C)||bt)){var gt=((K=yt)==null?void 0:K.type)==="focus";yt=C,rt=C.currentTarget,T(),!x.state.isVisible&&X(C)&&yn.forEach(function(wt){return wt(C)}),C.type==="click"&&(x.props.trigger.indexOf("mouseenter")<0||B)&&x.props.hideOnClick!==!1&&x.state.isVisible?et=!0:$t(C),C.type==="click"&&(B=!et),et&&!gt&&zt(C)}}function Rt(C){var K=C.target,et=le().contains(K)||ft.contains(K);if(!(C.type==="mousemove"&&et)){var gt=Yt().concat(ft).map(function(wt){var Kt,Jt=wt._tippy,Ce=(Kt=Jt.popperInstance)==null?void 0:Kt.state;return Ce?{popperRect:wt.getBoundingClientRect(),popperState:Ce,props:D}:null}).filter(Boolean);p(gt,C)&&(L(),zt(C))}}function Ht(C){var K=Pt(C)||x.props.trigger.indexOf("click")>=0&&B;if(!K){if(x.props.interactive){x.hideWithInteractivity(C);return}zt(C)}}function ct(C){x.props.trigger.indexOf("focusin")<0&&C.target!==le()||x.props.interactive&&C.relatedTarget&&ft.contains(C.relatedTarget)||zt(C)}function Pt(C){return P.isTouch?Je()!==C.type.indexOf("touch")>=0:!1}function _t(){Nt();var C=x.props,K=C.popperOptions,et=C.placement,gt=C.offset,wt=C.getReferenceClientRect,Kt=C.moveTransition,Jt=re()?Xe(ft).arrow:null,Ce=wt?{getBoundingClientRect:wt,contextElement:wt.contextElement||le()}:g,zn={name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(pn){var tn=pn.state;if(re()){var Kr=un(),en=Kr.box;["placement","reference-hidden","escaped"].forEach(function(hn){hn==="placement"?en.setAttribute("data-placement",tn.placement):tn.attributes.popper["data-popper-"+hn]?en.setAttribute("data-"+hn,""):en.removeAttribute("data-"+hn)}),tn.attributes.popper={}}}},_e=[{name:"offset",options:{offset:gt}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!Kt}},zn];re()&&Jt&&_e.push({name:"arrow",options:{element:Jt,padding:3}}),_e.push.apply(_e,K?.modifiers||[]),x.popperInstance=e.createPopper(Ce,ft,Object.assign({},K,{placement:et,onFirstUpdate:jt,modifiers:_e}))}function Nt(){x.popperInstance&&(x.popperInstance.destroy(),x.popperInstance=null)}function Ft(){var C=x.props.appendTo,K,et=le();x.props.interactive&&C===Qt.appendTo||C==="parent"?K=et.parentNode:K=E(C,[et]),K.contains(ft)||K.appendChild(ft),_t(),me(x.props.interactive&&C===Qt.appendTo&&et.nextElementSibling!==ft,["Interactive tippy element may not be accessible via keyboard","navigation because it is not directly after the reference element","in the DOM source order.",` - -`,"Using a wrapper
or tag around the reference element","solves this by creating a new parentNode context.",` - -`,"Specifying `appendTo: document.body` silences this warning, but it","assumes you are using a focus management solution to handle","keyboard navigation.",` - -`,"See: https://atomiks.github.io/tippyjs/v6/accessibility/#interactivity"].join(" "))}function Yt(){return Y(ft.querySelectorAll("[data-tippy-root]"))}function $t(C){x.clearDelayTimeouts(),C&&b("onTrigger",[x,C]),ot();var K=c(!0),et=Ke(),gt=et[0],wt=et[1];P.isTouch&>==="hold"&&wt&&(K=wt),K?F=setTimeout(function(){x.show()},K):x.show()}function zt(C){if(x.clearDelayTimeouts(),b("onUntrigger",[x,C]),!x.state.isVisible){V();return}if(!(x.props.trigger.indexOf("mouseenter")>=0&&x.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(C.type)>=0&&B)){var K=c(!1);K?q=setTimeout(function(){x.state.isVisible&&x.hide()},K):W=requestAnimationFrame(function(){x.hide()})}}function we(){x.state.isEnabled=!0}function te(){x.hide(),x.state.isEnabled=!1}function Le(){clearTimeout(F),clearTimeout(q),cancelAnimationFrame(W)}function dn(C){if(me(x.state.isDestroyed,Ue("setProps")),!x.state.isDestroyed){b("onBeforeUpdate",[x,C]),St();var K=x.props,et=ir(g,Object.assign({},x.props,{},C,{ignoreAttributes:!0}));x.props=et,Mt(),K.interactiveDebounce!==et.interactiveDebounce&&(L(),It=O(Rt,et.interactiveDebounce)),K.triggerTarget&&!et.triggerTarget?I(K.triggerTarget).forEach(function(gt){gt.removeAttribute("aria-expanded")}):et.triggerTarget&&g.removeAttribute("aria-expanded"),T(),s(),Fe&&Fe(K,et),x.popperInstance&&(_t(),Yt().forEach(function(gt){requestAnimationFrame(gt._tippy.popperInstance.forceUpdate)})),b("onAfterUpdate",[x,C])}}function Ze(C){x.setProps({content:C})}function Ne(){me(x.state.isDestroyed,Ue("show"));var C=x.state.isVisible,K=x.state.isDestroyed,et=!x.state.isEnabled,gt=P.isTouch&&!x.props.touch,wt=w(x.props.duration,0,Qt.duration);if(!(C||K||et||gt)&&!le().hasAttribute("disabled")&&(b("onShow",[x],!1),x.props.onShow(x)!==!1)){if(x.state.isVisible=!0,re()&&(ft.style.visibility="visible"),s(),ot(),x.state.isMounted||(ft.style.transition="none"),re()){var Kt=un(),Jt=Kt.box,Ce=Kt.content;l([Jt,Ce],0)}jt=function(){var _e;if(!(!x.state.isVisible||pt)){if(pt=!0,ft.offsetHeight,ft.style.transition=x.props.moveTransition,re()&&x.props.animation){var An=un(),pn=An.box,tn=An.content;l([pn,tn],wt),h([pn,tn],"visible")}_(),T(),$(wn,x),(_e=x.popperInstance)==null||_e.forceUpdate(),x.state.isMounted=!0,b("onMount",[x]),x.props.animation&&re()&&Lt(wt,function(){x.state.isShown=!0,b("onShown",[x])})}},Ft()}}function $n(){me(x.state.isDestroyed,Ue("hide"));var C=!x.state.isVisible,K=x.state.isDestroyed,et=!x.state.isEnabled,gt=w(x.props.duration,1,Qt.duration);if(!(C||K||et)&&(b("onHide",[x],!1),x.props.onHide(x)!==!1)){if(x.state.isVisible=!1,x.state.isShown=!1,pt=!1,B=!1,re()&&(ft.style.visibility="hidden"),L(),V(),s(),re()){var wt=un(),Kt=wt.box,Jt=wt.content;x.props.animation&&(l([Kt,Jt],gt),h([Kt,Jt],"hidden"))}_(),T(),x.props.animation?re()&&Ct(gt,x.unmount):x.unmount()}}function Qe(C){me(x.state.isDestroyed,Ue("hideWithInteractivity")),ye().addEventListener("mousemove",It),$(yn,It),It(C)}function Sn(){me(x.state.isDestroyed,Ue("unmount")),x.state.isVisible&&x.hide(),x.state.isMounted&&(Nt(),Yt().forEach(function(C){C._tippy.unmount()}),ft.parentNode&&ft.parentNode.removeChild(ft),wn=wn.filter(function(C){return C!==x}),x.state.isMounted=!1,b("onHidden",[x]))}function Wn(){me(x.state.isDestroyed,Ue("destroy")),!x.state.isDestroyed&&(x.clearDelayTimeouts(),x.unmount(),St(),delete g._tippy,x.state.isDestroyed=!0,b("onDestroy",[x]))}}function de(g,y){y===void 0&&(y={});var D=Qt.plugins.concat(y.plugins||[]);Ae(g),ge(y,D),ze();var F=Object.assign({},y,{plugins:D}),q=mt(g),W=U(F.content),B=q.length>1;me(W&&B,["tippy() was passed an Element as the `content` prop, but more than","one tippy instance was created by this invocation. This means the","content element will only be appended to the last tippy instance.",` - -`,"Instead, pass the .innerHTML of the element, or use a function that","returns a cloned version of the element instead.",` - -`,`1) content: element.innerHTML -`,"2) content: () => element.cloneNode(true)"].join(" "));var bt=q.reduce(function(lt,pt){var yt=pt&&cn(pt,F);return yt&<.push(yt),lt},[]);return U(g)?bt[0]:bt}de.defaultProps=Qt,de.setDefaultProps=zr,de.currentInput=P;var lr=function(y){var D=y===void 0?{}:y,F=D.exclude,q=D.duration;wn.forEach(function(W){var B=!1;if(F&&(B=Z(F)?W.reference===F:W.popper===F.popper),!B){var bt=W.props.duration;W.setProps({duration:q}),W.hide(),W.state.isDestroyed||W.setProps({duration:bt})}})},cr=Object.assign({},e.applyStyles,{effect:function(y){var D=y.state,F={popper:{position:D.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(D.elements.popper.style,F.popper),D.styles=F,D.elements.arrow&&Object.assign(D.elements.arrow.style,F.arrow)}}),fr=function(y,D){var F;D===void 0&&(D={}),Ve(!Array.isArray(y),["The first argument passed to createSingleton() must be an array of","tippy instances. The passed value was",String(y)].join(" "));var q=y,W=[],B,bt=D.overrides,lt=[],pt=!1;function yt(){W=q.map(function(tt){return tt.reference})}function Tt(tt){q.forEach(function(it){tt?it.enable():it.disable()})}function jt(tt){return q.map(function(it){var x=it.setProps;return it.setProps=function(Gt){x(Gt),it.reference===B&&tt.setProps(Gt)},function(){it.setProps=x}})}function At(tt,it){var x=W.indexOf(it);if(it!==B){B=it;var Gt=(bt||[]).concat("content").reduce(function(ft,Fe){return ft[Fe]=q[x].props[Fe],ft},{});tt.setProps(Object.assign({},Gt,{getReferenceClientRect:typeof Gt.getReferenceClientRect=="function"?Gt.getReferenceClientRect:function(){return it.getBoundingClientRect()}}))}}Tt(!1),yt();var It={fn:function(){return{onDestroy:function(){Tt(!0)},onHidden:function(){B=null},onClickOutside:function(x){x.props.showOnCreate&&!pt&&(pt=!0,B=null)},onShow:function(x){x.props.showOnCreate&&!pt&&(pt=!0,At(x,W[0]))},onTrigger:function(x,Gt){At(x,Gt.currentTarget)}}}},rt=de(J(),Object.assign({},S(D,["overrides"]),{plugins:[It].concat(D.plugins||[]),triggerTarget:W,popperOptions:Object.assign({},D.popperOptions,{modifiers:[].concat(((F=D.popperOptions)==null?void 0:F.modifiers)||[],[cr])})})),ht=rt.show;rt.show=function(tt){if(ht(),!B&&tt==null)return At(rt,W[0]);if(!(B&&tt==null)){if(typeof tt=="number")return W[tt]&&At(rt,W[tt]);if(q.includes(tt)){var it=tt.reference;return At(rt,it)}if(W.includes(tt))return At(rt,tt)}},rt.showNext=function(){var tt=W[0];if(!B)return rt.show(0);var it=W.indexOf(B);rt.show(W[it+1]||tt)},rt.showPrevious=function(){var tt=W[W.length-1];if(!B)return rt.show(tt);var it=W.indexOf(B),x=W[it-1]||tt;rt.show(x)};var vt=rt.setProps;return rt.setProps=function(tt){bt=tt.overrides||bt,vt(tt)},rt.setInstances=function(tt){Tt(!0),lt.forEach(function(it){return it()}),q=tt,Tt(!1),yt(),jt(rt),rt.setProps({triggerTarget:W})},lt=jt(rt),rt},ur={mouseover:"mouseenter",focusin:"focus",click:"click"};function qe(g,y){Ve(!(y&&y.target),["You must specity a `target` prop indicating a CSS selector string matching","the target elements that should receive a tippy."].join(" "));var D=[],F=[],q=!1,W=y.target,B=S(y,["target"]),bt=Object.assign({},B,{trigger:"manual",touch:!1}),lt=Object.assign({},B,{showOnCreate:!0}),pt=de(g,bt),yt=I(pt);function Tt(ht){if(!(!ht.target||q)){var vt=ht.target.closest(W);if(vt){var tt=vt.getAttribute("data-tippy-trigger")||y.trigger||Qt.trigger;if(!vt._tippy&&!(ht.type==="touchstart"&&typeof lt.touch=="boolean")&&!(ht.type!=="touchstart"&&tt.indexOf(ur[ht.type])<0)){var it=de(vt,lt);it&&(F=F.concat(it))}}}}function jt(ht,vt,tt,it){it===void 0&&(it=!1),ht.addEventListener(vt,tt,it),D.push({node:ht,eventType:vt,handler:tt,options:it})}function At(ht){var vt=ht.reference;jt(vt,"touchstart",Tt,f),jt(vt,"mouseover",Tt),jt(vt,"focusin",Tt),jt(vt,"click",Tt)}function It(){D.forEach(function(ht){var vt=ht.node,tt=ht.eventType,it=ht.handler,x=ht.options;vt.removeEventListener(tt,it,x)}),D=[]}function rt(ht){var vt=ht.destroy,tt=ht.enable,it=ht.disable;ht.destroy=function(x){x===void 0&&(x=!0),x&&F.forEach(function(Gt){Gt.destroy()}),F=[],It(),vt()},ht.enable=function(){tt(),F.forEach(function(x){return x.enable()}),q=!1},ht.disable=function(){it(),F.forEach(function(x){return x.disable()}),q=!0},At(ht)}return yt.forEach(rt),pt}var dr={name:"animateFill",defaultValue:!1,fn:function(y){var D;if(!((D=y.props.render)!=null&&D.$$tippy))return Ve(y.props.animateFill,"The `animateFill` plugin requires the default render function."),{};var F=Xe(y.popper),q=F.box,W=F.content,B=y.props.animateFill?Vr():null;return{onCreate:function(){B&&(q.insertBefore(B,q.firstElementChild),q.setAttribute("data-animatefill",""),q.style.overflow="hidden",y.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(B){var lt=q.style.transitionDuration,pt=Number(lt.replace("ms",""));W.style.transitionDelay=Math.round(pt/10)+"ms",B.style.transitionDuration=lt,h([B],"visible")}},onShow:function(){B&&(B.style.transitionDuration="0ms")},onHide:function(){B&&h([B],"hidden")}}}};function Vr(){var g=J();return g.className=o,h([g],"hidden"),g}var xn={clientX:0,clientY:0},fn=[];function En(g){var y=g.clientX,D=g.clientY;xn={clientX:y,clientY:D}}function On(g){g.addEventListener("mousemove",En)}function Yr(g){g.removeEventListener("mousemove",En)}var jn={name:"followCursor",defaultValue:!1,fn:function(y){var D=y.reference,F=v(y.props.triggerTarget||D),q=!1,W=!1,B=!0,bt=y.props;function lt(){return y.props.followCursor==="initial"&&y.state.isVisible}function pt(){F.addEventListener("mousemove",jt)}function yt(){F.removeEventListener("mousemove",jt)}function Tt(){q=!0,y.setProps({getReferenceClientRect:null}),q=!1}function jt(rt){var ht=rt.target?D.contains(rt.target):!0,vt=y.props.followCursor,tt=rt.clientX,it=rt.clientY,x=D.getBoundingClientRect(),Gt=tt-x.left,ft=it-x.top;(ht||!y.props.interactive)&&y.setProps({getReferenceClientRect:function(){var be=D.getBoundingClientRect(),Ge=tt,Ke=it;vt==="initial"&&(Ge=be.left+Gt,Ke=be.top+ft);var Je=vt==="horizontal"?be.top:Ke,re=vt==="vertical"?be.right:Ge,le=vt==="horizontal"?be.bottom:Ke,ye=vt==="vertical"?be.left:Ge;return{width:re-ye,height:le-Je,top:Je,right:re,bottom:le,left:ye}}})}function At(){y.props.followCursor&&(fn.push({instance:y,doc:F}),On(F))}function It(){fn=fn.filter(function(rt){return rt.instance!==y}),fn.filter(function(rt){return rt.doc===F}).length===0&&Yr(F)}return{onCreate:At,onDestroy:It,onBeforeUpdate:function(){bt=y.props},onAfterUpdate:function(ht,vt){var tt=vt.followCursor;q||tt!==void 0&&bt.followCursor!==tt&&(It(),tt?(At(),y.state.isMounted&&!W&&!lt()&&pt()):(yt(),Tt()))},onMount:function(){y.props.followCursor&&!W&&(B&&(jt(xn),B=!1),lt()||pt())},onTrigger:function(ht,vt){X(vt)&&(xn={clientX:vt.clientX,clientY:vt.clientY}),W=vt.type==="focus"},onHidden:function(){y.props.followCursor&&(Tt(),yt(),B=!0)}}}};function Xr(g,y){var D;return{popperOptions:Object.assign({},g.popperOptions,{modifiers:[].concat((((D=g.popperOptions)==null?void 0:D.modifiers)||[]).filter(function(F){var q=F.name;return q!==y.name}),[y])})}}var Bn={name:"inlinePositioning",defaultValue:!1,fn:function(y){var D=y.reference;function F(){return!!y.props.inlinePositioning}var q,W=-1,B=!1,bt={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(jt){var At=jt.state;F()&&(q!==At.placement&&y.setProps({getReferenceClientRect:function(){return lt(At.placement)}}),q=At.placement)}};function lt(Tt){return qr(k(Tt),D.getBoundingClientRect(),Y(D.getClientRects()),W)}function pt(Tt){B=!0,y.setProps(Tt),B=!1}function yt(){B||pt(Xr(y.props,bt))}return{onCreate:yt,onAfterUpdate:yt,onTrigger:function(jt,At){if(X(At)){var It=Y(y.reference.getClientRects()),rt=It.find(function(ht){return ht.left-2<=At.clientX&&ht.right+2>=At.clientX&&ht.top-2<=At.clientY&&ht.bottom+2>=At.clientY});W=It.indexOf(rt)}},onUntrigger:function(){W=-1}}}};function qr(g,y,D,F){if(D.length<2||g===null)return y;if(D.length===2&&F>=0&&D[0].left>D[1].right)return D[F]||y;switch(g){case"top":case"bottom":{var q=D[0],W=D[D.length-1],B=g==="top",bt=q.top,lt=W.bottom,pt=B?q.left:W.left,yt=B?q.right:W.right,Tt=yt-pt,jt=lt-bt;return{top:bt,bottom:lt,left:pt,right:yt,width:Tt,height:jt}}case"left":case"right":{var At=Math.min.apply(Math,D.map(function(ft){return ft.left})),It=Math.max.apply(Math,D.map(function(ft){return ft.right})),rt=D.filter(function(ft){return g==="left"?ft.left===At:ft.right===It}),ht=rt[0].top,vt=rt[rt.length-1].bottom,tt=At,it=It,x=it-tt,Gt=vt-ht;return{top:ht,bottom:vt,left:tt,right:it,width:x,height:Gt}}default:return y}}var Gr={name:"sticky",defaultValue:!1,fn:function(y){var D=y.reference,F=y.popper;function q(){return y.popperInstance?y.popperInstance.state.elements.reference:D}function W(pt){return y.props.sticky===!0||y.props.sticky===pt}var B=null,bt=null;function lt(){var pt=W("reference")?q().getBoundingClientRect():null,yt=W("popper")?F.getBoundingClientRect():null;(pt&&Hn(B,pt)||yt&&Hn(bt,yt))&&y.popperInstance&&y.popperInstance.update(),B=pt,bt=yt,y.state.isMounted&&requestAnimationFrame(lt)}return{onMount:function(){y.props.sticky&<()}}}};function Hn(g,y){return g&&y?g.top!==y.top||g.right!==y.right||g.bottom!==y.bottom||g.left!==y.left:!0}de.setDefaultProps({render:ar}),t.animateFill=dr,t.createSingleton=fr,t.default=de,t.delegate=qe,t.followCursor=jn,t.hideAll=lr,t.inlinePositioning=Bn,t.roundArrow=r,t.sticky=Gr}),Ai=$o(Wo()),Ds=$o(Wo()),Cs=t=>{let e={plugins:[]},r=i=>t[t.indexOf(i)+1];if(t.includes("animation")&&(e.animation=r("animation")),t.includes("duration")&&(e.duration=parseInt(r("duration"))),t.includes("delay")){let i=r("delay");e.delay=i.includes("-")?i.split("-").map(o=>parseInt(o)):parseInt(i)}if(t.includes("cursor")){e.plugins.push(Ds.followCursor);let i=r("cursor");["x","initial"].includes(i)?e.followCursor=i==="x"?"horizontal":"initial":e.followCursor=!0}t.includes("on")&&(e.trigger=r("on")),t.includes("arrowless")&&(e.arrow=!1),t.includes("html")&&(e.allowHTML=!0),t.includes("interactive")&&(e.interactive=!0),t.includes("border")&&e.interactive&&(e.interactiveBorder=parseInt(r("border"))),t.includes("debounce")&&e.interactive&&(e.interactiveDebounce=parseInt(r("debounce"))),t.includes("max-width")&&(e.maxWidth=parseInt(r("max-width"))),t.includes("theme")&&(e.theme=r("theme")),t.includes("placement")&&(e.placement=r("placement"));let n={};return t.includes("no-flip")&&(n.modifiers||(n.modifiers=[]),n.modifiers.push({name:"flip",enabled:!1})),e.popperOptions=n,e};function Di(t){t.magic("tooltip",e=>(r,n={})=>{let i=n.timeout;delete n.timeout;let o=(0,Ai.default)(e,{content:r,trigger:"manual",...n});o.show(),setTimeout(()=>{o.hide(),setTimeout(()=>o.destroy(),n.duration||300)},i||2e3)}),t.directive("tooltip",(e,{modifiers:r,expression:n},{evaluateLater:i,effect:o,cleanup:a})=>{let d=r.length>0?Cs(r):{};e.__x_tippy||(e.__x_tippy=(0,Ai.default)(e,d)),a(()=>{e.__x_tippy&&(e.__x_tippy.destroy(),delete e.__x_tippy)});let f=()=>e.__x_tippy.enable(),u=()=>e.__x_tippy.disable(),w=m=>{m?(f(),e.__x_tippy.setContent(m)):u()};if(r.includes("raw"))w(n);else{let m=i(n);o(()=>{m(E=>{typeof E=="object"?(e.__x_tippy.setProps(E),f()):w(E)})})}})}Di.defaultProps=t=>(Ai.default.setDefaultProps(t),Di);var _s=Di,zo=_s;var Lr=()=>{document.querySelectorAll("[ax-load][x-ignore]").forEach(t=>{t.removeAttribute("x-ignore"),t.setAttribute("x-load",t.getAttribute("ax-load")),t.setAttribute("x-load-src",t.getAttribute("ax-load-src"))}),document.querySelectorAll("[ax-load]").forEach(t=>{t.setAttribute("x-load",t.getAttribute("ax-load")),t.setAttribute("x-load-src",t.getAttribute("ax-load-src"))})};document.body?(Lr(),new MutationObserver(Lr).observe(document.body,{childList:!0,subtree:!0})):document.addEventListener("DOMContentLoaded",()=>{Lr(),new MutationObserver(Lr).observe(document.body,{childList:!0,subtree:!0})});document.addEventListener("alpine:init",()=>{window.Alpine.plugin(ao),window.Alpine.plugin(so),window.Alpine.plugin(uo),window.Alpine.plugin(Bo),window.Alpine.plugin(zo)});var Ts=function(t,e,r){function n(w,m){for(let E of w){let O=i(E,m);if(O!==null)return O}}function i(w,m){let E=w.match(/^[\{\[]([^\[\]\{\}]*)[\}\]](.*)/s);if(E===null||E.length!==3)return null;let O=E[1],S=E[2];if(O.includes(",")){let[M,I]=O.split(",",2);if(I==="*"&&m>=M)return S;if(M==="*"&&m<=I)return S;if(m>=M&&m<=I)return S}return O==m?S:null}function o(w){return w.toString().charAt(0).toUpperCase()+w.toString().slice(1)}function a(w,m){if(m.length===0)return w;let E={};for(let[O,S]of Object.entries(m))E[":"+o(O??"")]=o(S??""),E[":"+O.toUpperCase()]=S.toString().toUpperCase(),E[":"+O]=S;return Object.entries(E).forEach(([O,S])=>{w=w.replaceAll(O,S)}),w}function d(w){return w.map(m=>m.replace(/^[\{\[]([^\[\]\{\}]*)[\}\]]/,""))}let f=t.split("|"),u=n(f,e);return u!=null?a(u.trim(),r):(f=d(f),a(f.length>1&&e>1?f[1]:f[0],r))};window.jsMd5=Uo.md5;window.pluralize=Ts;})(); -/*! Bundled license information: - -js-md5/src/md5.js: - (** - * [js-md5]{@link https://github.com/emn178/js-md5} - * - * @namespace md5 - * @version 0.8.3 - * @author Chen, Yi-Cyuan [emn178@gmail.com] - * @copyright Chen, Yi-Cyuan 2014-2023 - * @license MIT - *) - -sortablejs/modular/sortable.esm.js: - (**! - * Sortable 1.15.6 - * @author RubaXa - * @author owenm - * @license MIT - *) -*/ diff --git a/public/js/filament/tables/components/table.js b/public/js/filament/tables/components/table.js deleted file mode 100644 index 4e3ce3a..0000000 --- a/public/js/filament/tables/components/table.js +++ /dev/null @@ -1 +0,0 @@ -function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default}; diff --git a/public/js/filament/widgets/components/chart.js b/public/js/filament/widgets/components/chart.js deleted file mode 100644 index 00c4bd4..0000000 --- a/public/js/filament/widgets/components/chart.js +++ /dev/null @@ -1,30 +0,0 @@ -function Ft(){}var Io=function(){let i=0;return function(){return i++}}();function P(i){return i===null||typeof i>"u"}function B(i){if(Array.isArray&&Array.isArray(i))return!0;let t=Object.prototype.toString.call(i);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function F(i){return i!==null&&Object.prototype.toString.call(i)==="[object Object]"}var q=i=>(typeof i=="number"||i instanceof Number)&&isFinite(+i);function ft(i,t){return q(i)?i:t}function E(i,t){return typeof i>"u"?t:i}var Co=(i,t)=>typeof i=="string"&&i.endsWith("%")?parseFloat(i)/100:i/t,Tn=(i,t)=>typeof i=="string"&&i.endsWith("%")?parseFloat(i)/100*t:+i;function $(i,t,e){if(i&&typeof i.call=="function")return i.apply(e,t)}function V(i,t,e,s){let n,r,o;if(B(i))if(r=i.length,s)for(n=r-1;n>=0;n--)t.call(e,i[n],n);else for(n=0;ni,x:i=>i.x,y:i=>i.y};function Vt(i,t){return(po[t]||(po[t]=Ac(t)))(i)}function Ac(i){let t=Lc(i);return e=>{for(let s of t){if(s==="")break;e=e&&e[s]}return e}}function Lc(i){let t=i.split("."),e=[],s="";for(let n of t)s+=n,s.endsWith("\\")?s=s.slice(0,-1)+".":(e.push(s),s="");return e}function vs(i){return i.charAt(0).toUpperCase()+i.slice(1)}var dt=i=>typeof i<"u",zt=i=>typeof i=="function",vn=(i,t)=>{if(i.size!==t.size)return!1;for(let e of i)if(!t.has(e))return!1;return!0};function Ao(i){return i.type==="mouseup"||i.type==="click"||i.type==="contextmenu"}var j=Math.PI,H=2*j,Pc=H+j,ks=Number.POSITIVE_INFINITY,Nc=j/180,U=j/2,gi=j/4,yo=j*2/3,mt=Math.log10,Mt=Math.sign;function On(i){let t=Math.round(i);i=Pe(i,t,i/1e3)?t:i;let e=Math.pow(10,Math.floor(mt(i))),s=i/e;return(s<=1?1:s<=2?2:s<=5?5:10)*e}function Lo(i){let t=[],e=Math.sqrt(i),s;for(s=1;sn-r).pop(),t}function ge(i){return!isNaN(parseFloat(i))&&isFinite(i)}function Pe(i,t,e){return Math.abs(i-t)=i}function Dn(i,t,e){let s,n,r;for(s=0,n=i.length;sl&&c=Math.min(t,e)-s&&i<=Math.max(t,e)+s}function Ds(i,t,e){e=e||(o=>i[o]1;)r=n+s>>1,e(r)?n=r:s=r;return{lo:n,hi:s}}var Ct=(i,t,e,s)=>Ds(i,e,s?n=>i[n][t]<=e:n=>i[n][t]Ds(i,e,s=>i[s][t]>=e);function Wo(i,t,e){let s=0,n=i.length;for(;ss&&i[n-1]>e;)n--;return s>0||n{let s="_onData"+vs(e),n=i[e];Object.defineProperty(i,e,{configurable:!0,enumerable:!1,value(...r){let o=n.apply(this,r);return i._chartjs.listeners.forEach(a=>{typeof a[s]=="function"&&a[s](...r)}),o}})})}function Cn(i,t){let e=i._chartjs;if(!e)return;let s=e.listeners,n=s.indexOf(t);n!==-1&&s.splice(n,1),!(s.length>0)&&(zo.forEach(r=>{delete i[r]}),delete i._chartjs)}function Fn(i){let t=new Set,e,s;for(e=0,s=i.length;e"u"?function(i){return i()}:window.requestAnimationFrame}();function Ln(i,t,e){let s=e||(o=>Array.prototype.slice.call(o)),n=!1,r=[];return function(...o){r=s(o),n||(n=!0,An.call(window,()=>{n=!1,i.apply(t,r)}))}}function Ho(i,t){let e;return function(...s){return t?(clearTimeout(e),e=setTimeout(i,t,s)):i.apply(this,s),t}}var Es=i=>i==="start"?"left":i==="end"?"right":"center",nt=(i,t,e)=>i==="start"?t:i==="end"?e:(t+e)/2,Bo=(i,t,e,s)=>i===(s?"left":"right")?e:i==="center"?(t+e)/2:t;function Pn(i,t,e){let s=t.length,n=0,r=s;if(i._sorted){let{iScale:o,_parsed:a}=i,l=o.axis,{min:c,max:h,minDefined:u,maxDefined:d}=o.getUserBounds();u&&(n=tt(Math.min(Ct(a,o.axis,c).lo,e?s:Ct(t,l,o.getPixelForValue(c)).lo),0,s-1)),d?r=tt(Math.max(Ct(a,o.axis,h,!0).hi+1,e?0:Ct(t,l,o.getPixelForValue(h),!0).hi+1),n,s)-n:r=s-n}return{start:n,count:r}}function Nn(i){let{xScale:t,yScale:e,_scaleRanges:s}=i,n={xmin:t.min,xmax:t.max,ymin:e.min,ymax:e.max};if(!s)return i._scaleRanges=n,!0;let r=s.xmin!==t.min||s.xmax!==t.max||s.ymin!==e.min||s.ymax!==e.max;return Object.assign(s,n),r}var ys=i=>i===0||i===1,bo=(i,t,e)=>-(Math.pow(2,10*(i-=1))*Math.sin((i-t)*H/e)),xo=(i,t,e)=>Math.pow(2,-10*i)*Math.sin((i-t)*H/e)+1,Ie={linear:i=>i,easeInQuad:i=>i*i,easeOutQuad:i=>-i*(i-2),easeInOutQuad:i=>(i/=.5)<1?.5*i*i:-.5*(--i*(i-2)-1),easeInCubic:i=>i*i*i,easeOutCubic:i=>(i-=1)*i*i+1,easeInOutCubic:i=>(i/=.5)<1?.5*i*i*i:.5*((i-=2)*i*i+2),easeInQuart:i=>i*i*i*i,easeOutQuart:i=>-((i-=1)*i*i*i-1),easeInOutQuart:i=>(i/=.5)<1?.5*i*i*i*i:-.5*((i-=2)*i*i*i-2),easeInQuint:i=>i*i*i*i*i,easeOutQuint:i=>(i-=1)*i*i*i*i+1,easeInOutQuint:i=>(i/=.5)<1?.5*i*i*i*i*i:.5*((i-=2)*i*i*i*i+2),easeInSine:i=>-Math.cos(i*U)+1,easeOutSine:i=>Math.sin(i*U),easeInOutSine:i=>-.5*(Math.cos(j*i)-1),easeInExpo:i=>i===0?0:Math.pow(2,10*(i-1)),easeOutExpo:i=>i===1?1:-Math.pow(2,-10*i)+1,easeInOutExpo:i=>ys(i)?i:i<.5?.5*Math.pow(2,10*(i*2-1)):.5*(-Math.pow(2,-10*(i*2-1))+2),easeInCirc:i=>i>=1?i:-(Math.sqrt(1-i*i)-1),easeOutCirc:i=>Math.sqrt(1-(i-=1)*i),easeInOutCirc:i=>(i/=.5)<1?-.5*(Math.sqrt(1-i*i)-1):.5*(Math.sqrt(1-(i-=2)*i)+1),easeInElastic:i=>ys(i)?i:bo(i,.075,.3),easeOutElastic:i=>ys(i)?i:xo(i,.075,.3),easeInOutElastic(i){return ys(i)?i:i<.5?.5*bo(i*2,.1125,.45):.5+.5*xo(i*2-1,.1125,.45)},easeInBack(i){return i*i*((1.70158+1)*i-1.70158)},easeOutBack(i){return(i-=1)*i*((1.70158+1)*i+1.70158)+1},easeInOutBack(i){let t=1.70158;return(i/=.5)<1?.5*(i*i*(((t*=1.525)+1)*i-t)):.5*((i-=2)*i*(((t*=1.525)+1)*i+t)+2)},easeInBounce:i=>1-Ie.easeOutBounce(1-i),easeOutBounce(i){return i<1/2.75?7.5625*i*i:i<2/2.75?7.5625*(i-=1.5/2.75)*i+.75:i<2.5/2.75?7.5625*(i-=2.25/2.75)*i+.9375:7.5625*(i-=2.625/2.75)*i+.984375},easeInOutBounce:i=>i<.5?Ie.easeInBounce(i*2)*.5:Ie.easeOutBounce(i*2-1)*.5+.5};function _i(i){return i+.5|0}var Gt=(i,t,e)=>Math.max(Math.min(i,e),t);function pi(i){return Gt(_i(i*2.55),0,255)}function Xt(i){return Gt(_i(i*255),0,255)}function Wt(i){return Gt(_i(i/2.55)/100,0,1)}function _o(i){return Gt(_i(i*100),0,100)}var xt={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Sn=[..."0123456789ABCDEF"],Wc=i=>Sn[i&15],zc=i=>Sn[(i&240)>>4]+Sn[i&15],bs=i=>(i&240)>>4===(i&15),Vc=i=>bs(i.r)&&bs(i.g)&&bs(i.b)&&bs(i.a);function Hc(i){var t=i.length,e;return i[0]==="#"&&(t===4||t===5?e={r:255&xt[i[1]]*17,g:255&xt[i[2]]*17,b:255&xt[i[3]]*17,a:t===5?xt[i[4]]*17:255}:(t===7||t===9)&&(e={r:xt[i[1]]<<4|xt[i[2]],g:xt[i[3]]<<4|xt[i[4]],b:xt[i[5]]<<4|xt[i[6]],a:t===9?xt[i[7]]<<4|xt[i[8]]:255})),e}var Bc=(i,t)=>i<255?t(i):"";function $c(i){var t=Vc(i)?Wc:zc;return i?"#"+t(i.r)+t(i.g)+t(i.b)+Bc(i.a,t):void 0}var jc=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function $o(i,t,e){let s=t*Math.min(e,1-e),n=(r,o=(r+i/30)%12)=>e-s*Math.max(Math.min(o-3,9-o,1),-1);return[n(0),n(8),n(4)]}function Uc(i,t,e){let s=(n,r=(n+i/60)%6)=>e-e*t*Math.max(Math.min(r,4-r,1),0);return[s(5),s(3),s(1)]}function Yc(i,t,e){let s=$o(i,1,.5),n;for(t+e>1&&(n=1/(t+e),t*=n,e*=n),n=0;n<3;n++)s[n]*=1-t-e,s[n]+=t;return s}function Zc(i,t,e,s,n){return i===n?(t-e)/s+(t.5?h/(2-r-o):h/(r+o),l=Zc(e,s,n,h,r),l=l*60+.5),[l|0,c||0,a]}function Wn(i,t,e,s){return(Array.isArray(t)?i(t[0],t[1],t[2]):i(t,e,s)).map(Xt)}function zn(i,t,e){return Wn($o,i,t,e)}function qc(i,t,e){return Wn(Yc,i,t,e)}function Gc(i,t,e){return Wn(Uc,i,t,e)}function jo(i){return(i%360+360)%360}function Xc(i){let t=jc.exec(i),e=255,s;if(!t)return;t[5]!==s&&(e=t[6]?pi(+t[5]):Xt(+t[5]));let n=jo(+t[2]),r=+t[3]/100,o=+t[4]/100;return t[1]==="hwb"?s=qc(n,r,o):t[1]==="hsv"?s=Gc(n,r,o):s=zn(n,r,o),{r:s[0],g:s[1],b:s[2],a:e}}function Kc(i,t){var e=Rn(i);e[0]=jo(e[0]+t),e=zn(e),i.r=e[0],i.g=e[1],i.b=e[2]}function Jc(i){if(!i)return;let t=Rn(i),e=t[0],s=_o(t[1]),n=_o(t[2]);return i.a<255?`hsla(${e}, ${s}%, ${n}%, ${Wt(i.a)})`:`hsl(${e}, ${s}%, ${n}%)`}var wo={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},So={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function Qc(){let i={},t=Object.keys(So),e=Object.keys(wo),s,n,r,o,a;for(s=0;s>16&255,r>>8&255,r&255]}return i}var xs;function th(i){xs||(xs=Qc(),xs.transparent=[0,0,0,0]);let t=xs[i.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}var eh=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function ih(i){let t=eh.exec(i),e=255,s,n,r;if(t){if(t[7]!==s){let o=+t[7];e=t[8]?pi(o):Gt(o*255,0,255)}return s=+t[1],n=+t[3],r=+t[5],s=255&(t[2]?pi(s):Gt(s,0,255)),n=255&(t[4]?pi(n):Gt(n,0,255)),r=255&(t[6]?pi(r):Gt(r,0,255)),{r:s,g:n,b:r,a:e}}}function sh(i){return i&&(i.a<255?`rgba(${i.r}, ${i.g}, ${i.b}, ${Wt(i.a)})`:`rgb(${i.r}, ${i.g}, ${i.b})`)}var bn=i=>i<=.0031308?i*12.92:Math.pow(i,1/2.4)*1.055-.055,Ee=i=>i<=.04045?i/12.92:Math.pow((i+.055)/1.055,2.4);function nh(i,t,e){let s=Ee(Wt(i.r)),n=Ee(Wt(i.g)),r=Ee(Wt(i.b));return{r:Xt(bn(s+e*(Ee(Wt(t.r))-s))),g:Xt(bn(n+e*(Ee(Wt(t.g))-n))),b:Xt(bn(r+e*(Ee(Wt(t.b))-r))),a:i.a+e*(t.a-i.a)}}function _s(i,t,e){if(i){let s=Rn(i);s[t]=Math.max(0,Math.min(s[t]+s[t]*e,t===0?360:1)),s=zn(s),i.r=s[0],i.g=s[1],i.b=s[2]}}function Uo(i,t){return i&&Object.assign(t||{},i)}function ko(i){var t={r:0,g:0,b:0,a:255};return Array.isArray(i)?i.length>=3&&(t={r:i[0],g:i[1],b:i[2],a:255},i.length>3&&(t.a=Xt(i[3]))):(t=Uo(i,{r:0,g:0,b:0,a:1}),t.a=Xt(t.a)),t}function rh(i){return i.charAt(0)==="r"?ih(i):Xc(i)}var kn=class i{constructor(t){if(t instanceof i)return t;let e=typeof t,s;e==="object"?s=ko(t):e==="string"&&(s=Hc(t)||th(t)||rh(t)),this._rgb=s,this._valid=!!s}get valid(){return this._valid}get rgb(){var t=Uo(this._rgb);return t&&(t.a=Wt(t.a)),t}set rgb(t){this._rgb=ko(t)}rgbString(){return this._valid?sh(this._rgb):void 0}hexString(){return this._valid?$c(this._rgb):void 0}hslString(){return this._valid?Jc(this._rgb):void 0}mix(t,e){if(t){let s=this.rgb,n=t.rgb,r,o=e===r?.5:e,a=2*o-1,l=s.a-n.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;r=1-c,s.r=255&c*s.r+r*n.r+.5,s.g=255&c*s.g+r*n.g+.5,s.b=255&c*s.b+r*n.b+.5,s.a=o*s.a+(1-o)*n.a,this.rgb=s}return this}interpolate(t,e){return t&&(this._rgb=nh(this._rgb,t._rgb,e)),this}clone(){return new i(this.rgb)}alpha(t){return this._rgb.a=Xt(t),this}clearer(t){let e=this._rgb;return e.a*=1-t,this}greyscale(){let t=this._rgb,e=_i(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=e,this}opaquer(t){let e=this._rgb;return e.a*=1+t,this}negate(){let t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return _s(this._rgb,2,t),this}darken(t){return _s(this._rgb,2,-t),this}saturate(t){return _s(this._rgb,1,t),this}desaturate(t){return _s(this._rgb,1,-t),this}rotate(t){return Kc(this._rgb,t),this}};function Yo(i){return new kn(i)}function Zo(i){if(i&&typeof i=="object"){let t=i.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function Vn(i){return Zo(i)?i:Yo(i)}function xn(i){return Zo(i)?i:Yo(i).saturate(.5).darken(.1).hexString()}var Kt=Object.create(null),Is=Object.create(null);function yi(i,t){if(!t)return i;let e=t.split(".");for(let s=0,n=e.length;se.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(e,s)=>xn(s.backgroundColor),this.hoverBorderColor=(e,s)=>xn(s.borderColor),this.hoverColor=(e,s)=>xn(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t)}set(t,e){return _n(this,t,e)}get(t){return yi(this,t)}describe(t,e){return _n(Is,t,e)}override(t,e){return _n(Kt,t,e)}route(t,e,s,n){let r=yi(this,t),o=yi(this,s),a="_"+e;Object.defineProperties(r,{[a]:{value:r[e],writable:!0},[e]:{enumerable:!0,get(){let l=this[a],c=o[n];return F(l)?Object.assign({},c,l):E(l,c)},set(l){this[a]=l}}})}},A=new Mn({_scriptable:i=>!i.startsWith("on"),_indexable:i=>i!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});function oh(i){return!i||P(i.size)||P(i.family)?null:(i.style?i.style+" ":"")+(i.weight?i.weight+" ":"")+i.size+"px "+i.family}function bi(i,t,e,s,n){let r=t[n];return r||(r=t[n]=i.measureText(n).width,e.push(n)),r>s&&(s=r),s}function qo(i,t,e,s){s=s||{};let n=s.data=s.data||{},r=s.garbageCollect=s.garbageCollect||[];s.font!==t&&(n=s.data={},r=s.garbageCollect=[],s.font=t),i.save(),i.font=t;let o=0,a=e.length,l,c,h,u,d;for(l=0;le.length){for(l=0;l0&&i.stroke()}}function Fe(i,t,e){return e=e||.5,!t||i&&i.x>t.left-e&&i.xt.top-e&&i.y0&&r.strokeColor!=="",l,c;for(i.save(),i.font=n.string,ah(i,r),l=0;l+i||0;function Fs(i,t){let e={},s=F(t),n=s?Object.keys(t):t,r=F(i)?s?o=>E(i[o],i[t[o]]):o=>i[o]:()=>i;for(let o of n)e[o]=dh(r(o));return e}function $n(i){return Fs(i,{top:"y",right:"x",bottom:"y",left:"x"})}function te(i){return Fs(i,["topLeft","topRight","bottomLeft","bottomRight"])}function rt(i){let t=$n(i);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function Q(i,t){i=i||{},t=t||A.font;let e=E(i.size,t.size);typeof e=="string"&&(e=parseInt(e,10));let s=E(i.style,t.style);s&&!(""+s).match(hh)&&(console.warn('Invalid font style specified: "'+s+'"'),s="");let n={family:E(i.family,t.family),lineHeight:uh(E(i.lineHeight,t.lineHeight),e),size:e,style:s,weight:E(i.weight,t.weight),string:""};return n.string=oh(n),n}function We(i,t,e,s){let n=!0,r,o,a;for(r=0,o=i.length;re&&a===0?0:a+l;return{min:o(s,-Math.abs(r)),max:o(n,r)}}function Ht(i,t){return Object.assign(Object.create(i),t)}function As(i,t=[""],e=i,s,n=()=>i[0]){dt(s)||(s=ta("_fallback",i));let r={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:i,_rootScopes:e,_fallback:s,_getTarget:n,override:o=>As([o,...i],t,e,s)};return new Proxy(r,{deleteProperty(o,a){return delete o[a],delete o._keys,delete i[0][a],!0},get(o,a){return Jo(o,a,()=>_h(a,t,i,o))},getOwnPropertyDescriptor(o,a){return Reflect.getOwnPropertyDescriptor(o._scopes[0],a)},getPrototypeOf(){return Reflect.getPrototypeOf(i[0])},has(o,a){return To(o).includes(a)},ownKeys(o){return To(o)},set(o,a,l){let c=o._storage||(o._storage=n());return o[a]=c[a]=l,delete o._keys,!0}})}function me(i,t,e,s){let n={_cacheable:!1,_proxy:i,_context:t,_subProxy:e,_stack:new Set,_descriptors:jn(i,s),setContext:r=>me(i,r,e,s),override:r=>me(i.override(r),t,e,s)};return new Proxy(n,{deleteProperty(r,o){return delete r[o],delete i[o],!0},get(r,o,a){return Jo(r,o,()=>mh(r,o,a))},getOwnPropertyDescriptor(r,o){return r._descriptors.allKeys?Reflect.has(i,o)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(i,o)},getPrototypeOf(){return Reflect.getPrototypeOf(i)},has(r,o){return Reflect.has(i,o)},ownKeys(){return Reflect.ownKeys(i)},set(r,o,a){return i[o]=a,delete r[o],!0}})}function jn(i,t={scriptable:!0,indexable:!0}){let{_scriptable:e=t.scriptable,_indexable:s=t.indexable,_allKeys:n=t.allKeys}=i;return{allKeys:n,scriptable:e,indexable:s,isScriptable:zt(e)?e:()=>e,isIndexable:zt(s)?s:()=>s}}var fh=(i,t)=>i?i+vs(t):t,Un=(i,t)=>F(t)&&i!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function Jo(i,t,e){if(Object.prototype.hasOwnProperty.call(i,t))return i[t];let s=e();return i[t]=s,s}function mh(i,t,e){let{_proxy:s,_context:n,_subProxy:r,_descriptors:o}=i,a=s[t];return zt(a)&&o.isScriptable(t)&&(a=gh(t,a,i,e)),B(a)&&a.length&&(a=ph(t,a,i,o.isIndexable)),Un(t,a)&&(a=me(a,n,r&&r[t],o)),a}function gh(i,t,e,s){let{_proxy:n,_context:r,_subProxy:o,_stack:a}=e;if(a.has(i))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+i);return a.add(i),t=t(r,o||s),a.delete(i),Un(i,t)&&(t=Yn(n._scopes,n,i,t)),t}function ph(i,t,e,s){let{_proxy:n,_context:r,_subProxy:o,_descriptors:a}=e;if(dt(r.index)&&s(i))t=t[r.index%t.length];else if(F(t[0])){let l=t,c=n._scopes.filter(h=>h!==l);t=[];for(let h of l){let u=Yn(c,n,i,h);t.push(me(u,r,o&&o[i],a))}}return t}function Qo(i,t,e){return zt(i)?i(t,e):i}var yh=(i,t)=>i===!0?t:typeof i=="string"?Vt(t,i):void 0;function bh(i,t,e,s,n){for(let r of t){let o=yh(e,r);if(o){i.add(o);let a=Qo(o._fallback,e,n);if(dt(a)&&a!==e&&a!==s)return a}else if(o===!1&&dt(s)&&e!==s)return null}return!1}function Yn(i,t,e,s){let n=t._rootScopes,r=Qo(t._fallback,e,s),o=[...i,...n],a=new Set;a.add(s);let l=Mo(a,o,e,r||e,s);return l===null||dt(r)&&r!==e&&(l=Mo(a,o,r,l,s),l===null)?!1:As(Array.from(a),[""],n,r,()=>xh(t,e,s))}function Mo(i,t,e,s,n){for(;e;)e=bh(i,t,e,s,n);return e}function xh(i,t,e){let s=i._getTarget();t in s||(s[t]={});let n=s[t];return B(n)&&F(e)?e:n}function _h(i,t,e,s){let n;for(let r of t)if(n=ta(fh(r,i),e),dt(n))return Un(i,n)?Yn(e,s,i,n):n}function ta(i,t){for(let e of t){if(!e)continue;let s=e[i];if(dt(s))return s}}function To(i){let t=i._keys;return t||(t=i._keys=wh(i._scopes)),t}function wh(i){let t=new Set;for(let e of i)for(let s of Object.keys(e).filter(n=>!n.startsWith("_")))t.add(s);return Array.from(t)}function Zn(i,t,e,s){let{iScale:n}=i,{key:r="r"}=this._parsing,o=new Array(s),a,l,c,h;for(a=0,l=s;ati==="x"?"y":"x";function kh(i,t,e,s){let n=i.skip?t:i,r=t,o=e.skip?t:e,a=Ms(r,n),l=Ms(o,r),c=a/(a+l),h=l/(a+l);c=isNaN(c)?0:c,h=isNaN(h)?0:h;let u=s*c,d=s*h;return{previous:{x:r.x-u*(o.x-n.x),y:r.y-u*(o.y-n.y)},next:{x:r.x+d*(o.x-n.x),y:r.y+d*(o.y-n.y)}}}function Mh(i,t,e){let s=i.length,n,r,o,a,l,c=Ae(i,0);for(let h=0;h!c.skip)),t.cubicInterpolationMode==="monotone")vh(i,n);else{let c=s?i[i.length-1]:i[0];for(r=0,o=i.length;rwindow.getComputedStyle(i,null);function Dh(i,t){return Ps(i).getPropertyValue(t)}var Eh=["top","right","bottom","left"];function fe(i,t,e){let s={};e=e?"-"+e:"";for(let n=0;n<4;n++){let r=Eh[n];s[r]=parseFloat(i[t+"-"+r+e])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}var Ih=(i,t,e)=>(i>0||t>0)&&(!e||!e.shadowRoot);function Ch(i,t){let e=i.touches,s=e&&e.length?e[0]:i,{offsetX:n,offsetY:r}=s,o=!1,a,l;if(Ih(n,r,i.target))a=n,l=r;else{let c=t.getBoundingClientRect();a=s.clientX-c.left,l=s.clientY-c.top,o=!0}return{x:a,y:l,box:o}}function ee(i,t){if("native"in i)return i;let{canvas:e,currentDevicePixelRatio:s}=t,n=Ps(e),r=n.boxSizing==="border-box",o=fe(n,"padding"),a=fe(n,"border","width"),{x:l,y:c,box:h}=Ch(i,e),u=o.left+(h&&a.left),d=o.top+(h&&a.top),{width:f,height:m}=t;return r&&(f-=o.width+a.width,m-=o.height+a.height),{x:Math.round((l-u)/f*e.width/s),y:Math.round((c-d)/m*e.height/s)}}function Fh(i,t,e){let s,n;if(t===void 0||e===void 0){let r=Ls(i);if(!r)t=i.clientWidth,e=i.clientHeight;else{let o=r.getBoundingClientRect(),a=Ps(r),l=fe(a,"border","width"),c=fe(a,"padding");t=o.width-c.width-l.width,e=o.height-c.height-l.height,s=Ts(a.maxWidth,r,"clientWidth"),n=Ts(a.maxHeight,r,"clientHeight")}}return{width:t,height:e,maxWidth:s||ks,maxHeight:n||ks}}var wn=i=>Math.round(i*10)/10;function sa(i,t,e,s){let n=Ps(i),r=fe(n,"margin"),o=Ts(n.maxWidth,i,"clientWidth")||ks,a=Ts(n.maxHeight,i,"clientHeight")||ks,l=Fh(i,t,e),{width:c,height:h}=l;if(n.boxSizing==="content-box"){let u=fe(n,"border","width"),d=fe(n,"padding");c-=d.width+u.width,h-=d.height+u.height}return c=Math.max(0,c-r.width),h=Math.max(0,s?Math.floor(c/s):h-r.height),c=wn(Math.min(c,o,l.maxWidth)),h=wn(Math.min(h,a,l.maxHeight)),c&&!h&&(h=wn(c/2)),{width:c,height:h}}function Gn(i,t,e){let s=t||1,n=Math.floor(i.height*s),r=Math.floor(i.width*s);i.height=n/s,i.width=r/s;let o=i.canvas;return o.style&&(e||!o.style.height&&!o.style.width)&&(o.style.height=`${i.height}px`,o.style.width=`${i.width}px`),i.currentDevicePixelRatio!==s||o.height!==n||o.width!==r?(i.currentDevicePixelRatio=s,o.height=n,o.width=r,i.ctx.setTransform(s,0,0,s,0,0),!0):!1}var na=function(){let i=!1;try{let t={get passive(){return i=!0,!1}};window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch{}return i}();function Xn(i,t){let e=Dh(i,t),s=e&&e.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function qt(i,t,e,s){return{x:i.x+e*(t.x-i.x),y:i.y+e*(t.y-i.y)}}function ra(i,t,e,s){return{x:i.x+e*(t.x-i.x),y:s==="middle"?e<.5?i.y:t.y:s==="after"?e<1?i.y:t.y:e>0?t.y:i.y}}function oa(i,t,e,s){let n={x:i.cp2x,y:i.cp2y},r={x:t.cp1x,y:t.cp1y},o=qt(i,n,e),a=qt(n,r,e),l=qt(r,t,e),c=qt(o,a,e),h=qt(a,l,e);return qt(c,h,e)}var vo=new Map;function Ah(i,t){t=t||{};let e=i+JSON.stringify(t),s=vo.get(e);return s||(s=new Intl.NumberFormat(i,t),vo.set(e,s)),s}function ze(i,t,e){return Ah(t,e).format(i)}var Lh=function(i,t){return{x(e){return i+i+t-e},setWidth(e){t=e},textAlign(e){return e==="center"?e:e==="right"?"left":"right"},xPlus(e,s){return e-s},leftForLtr(e,s){return e-s}}},Ph=function(){return{x(i){return i},setWidth(i){},textAlign(i){return i},xPlus(i,t){return i+t},leftForLtr(i,t){return i}}};function pe(i,t,e){return i?Lh(t,e):Ph()}function Kn(i,t){let e,s;(t==="ltr"||t==="rtl")&&(e=i.canvas.style,s=[e.getPropertyValue("direction"),e.getPropertyPriority("direction")],e.setProperty("direction",t,"important"),i.prevTextDirection=s)}function Jn(i,t){t!==void 0&&(delete i.prevTextDirection,i.canvas.style.setProperty("direction",t[0],t[1]))}function aa(i){return i==="angle"?{between:Ne,compare:Rc,normalize:ct}:{between:At,compare:(t,e)=>t-e,normalize:t=>t}}function Oo({start:i,end:t,count:e,loop:s,style:n}){return{start:i%e,end:t%e,loop:s&&(t-i+1)%e===0,style:n}}function Nh(i,t,e){let{property:s,start:n,end:r}=e,{between:o,normalize:a}=aa(s),l=t.length,{start:c,end:h,loop:u}=i,d,f;if(u){for(c+=l,h+=l,d=0,f=l;dl(n,_,y)&&a(n,_)!==0,x=()=>a(r,y)===0||l(r,_,y),S=()=>g||w(),k=()=>!g||x();for(let v=h,T=h;v<=u;++v)b=t[v%o],!b.skip&&(y=c(b[s]),y!==_&&(g=l(y,n,r),p===null&&S()&&(p=a(y,n)===0?v:T),p!==null&&k()&&(m.push(Oo({start:p,end:v,loop:d,count:o,style:f})),p=null),T=v,_=y));return p!==null&&m.push(Oo({start:p,end:u,loop:d,count:o,style:f})),m}function tr(i,t){let e=[],s=i.segments;for(let n=0;nn&&i[r%t].skip;)r--;return r%=t,{start:n,end:r}}function Wh(i,t,e,s){let n=i.length,r=[],o=t,a=i[t],l;for(l=t+1;l<=e;++l){let c=i[l%n];c.skip||c.stop?a.skip||(s=!1,r.push({start:t%n,end:(l-1)%n,loop:s}),t=o=c.stop?l:null):(o=l,a.skip&&(t=l)),a=c}return o!==null&&r.push({start:t%n,end:o%n,loop:s}),r}function la(i,t){let e=i.points,s=i.options.spanGaps,n=e.length;if(!n)return[];let r=!!i._loop,{start:o,end:a}=Rh(e,n,r,s);if(s===!0)return Do(i,[{start:o,end:a,loop:r}],e,t);let l=aa({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(s-e.start,o)}))}_refresh(){this._request||(this._running=!0,this._request=An.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let e=0;this._charts.forEach((s,n)=>{if(!s.running||!s.items.length)return;let r=s.items,o=r.length-1,a=!1,l;for(;o>=0;--o)l=r[o],l._active?(l._total>s.duration&&(s.duration=l._total),l.tick(t),a=!0):(r[o]=r[r.length-1],r.pop());a&&(n.draw(),this._notify(n,s,t,"progress")),r.length||(s.running=!1,this._notify(n,s,t,"complete"),s.initial=!1),e+=r.length}),this._lastDate=t,e===0&&(this._running=!1)}_getAnims(t){let e=this._charts,s=e.get(t);return s||(s={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,s)),s}listen(t,e,s){this._getAnims(t).listeners[e].push(s)}add(t,e){!e||!e.length||this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){let e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce((s,n)=>Math.max(s,n._duration),0),this._refresh())}running(t){if(!this._running)return!1;let e=this._charts.get(t);return!(!e||!e.running||!e.items.length)}stop(t){let e=this._charts.get(t);if(!e||!e.items.length)return;let s=e.items,n=s.length-1;for(;n>=0;--n)s[n].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}},Bt=new hr,ca="transparent",Hh={boolean(i,t,e){return e>.5?t:i},color(i,t,e){let s=Vn(i||ca),n=s.valid&&Vn(t||ca);return n&&n.valid?n.mix(s,e).hexString():t},number(i,t,e){return i+(t-i)*e}},ur=class{constructor(t,e,s,n){let r=e[s];n=We([t.to,n,r,t.from]);let o=We([t.from,r,n]);this._active=!0,this._fn=t.fn||Hh[t.type||typeof o],this._easing=Ie[t.easing]||Ie.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=s,this._from=o,this._to=n,this._promises=void 0}active(){return this._active}update(t,e,s){if(this._active){this._notify(!1);let n=this._target[this._prop],r=s-this._start,o=this._duration-r;this._start=s,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=r,this._loop=!!t.loop,this._to=We([t.to,e,n,t.from]),this._from=We([t.from,n,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){let e=t-this._start,s=this._duration,n=this._prop,r=this._from,o=this._loop,a=this._to,l;if(this._active=r!==a&&(o||e1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[n]=this._fn(r,a,l)}wait(){let t=this._promises||(this._promises=[]);return new Promise((e,s)=>{t.push({res:e,rej:s})})}_notify(t){let e=t?"res":"rej",s=this._promises||[];for(let n=0;ni!=="onProgress"&&i!=="onComplete"&&i!=="fn"});A.set("animations",{colors:{type:"color",properties:$h},numbers:{type:"number",properties:Bh}});A.describe("animations",{_fallback:"animation"});A.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:i=>i|0}}}});var $s=class{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!F(t))return;let e=this._properties;Object.getOwnPropertyNames(t).forEach(s=>{let n=t[s];if(!F(n))return;let r={};for(let o of jh)r[o]=n[o];(B(n.properties)&&n.properties||[s]).forEach(o=>{(o===s||!e.has(o))&&e.set(o,r)})})}_animateOptions(t,e){let s=e.options,n=Yh(t,s);if(!n)return[];let r=this._createAnimations(n,s);return s.$shared&&Uh(t.options.$animations,s).then(()=>{t.options=s},()=>{}),r}_createAnimations(t,e){let s=this._properties,n=[],r=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now(),l;for(l=o.length-1;l>=0;--l){let c=o[l];if(c.charAt(0)==="$")continue;if(c==="options"){n.push(...this._animateOptions(t,e));continue}let h=e[c],u=r[c],d=s.get(c);if(u)if(d&&u.active()){u.update(d,h,a);continue}else u.cancel();if(!d||!d.duration){t[c]=h;continue}r[c]=u=new ur(d,t,c,h),n.push(u)}return n}update(t,e){if(this._properties.size===0){Object.assign(t,e);return}let s=this._createAnimations(t,e);if(s.length)return Bt.add(this._chart,s),!0}};function Uh(i,t){let e=[],s=Object.keys(t);for(let n=0;n0||!e&&r<0)return n.index}return null}function ma(i,t){let{chart:e,_cachedMeta:s}=i,n=e._stacks||(e._stacks={}),{iScale:r,vScale:o,index:a}=s,l=r.axis,c=o.axis,h=Xh(r,o,s),u=t.length,d;for(let f=0;fe[s].axis===t).shift()}function Qh(i,t){return Ht(i,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function tu(i,t,e){return Ht(i,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:e,index:t,mode:"default",type:"data"})}function ki(i,t){let e=i.controller.index,s=i.vScale&&i.vScale.axis;if(s){t=t||i._parsed;for(let n of t){let r=n._stacks;if(!r||r[s]===void 0||r[s][e]===void 0)return;delete r[s][e]}}}var ir=i=>i==="reset"||i==="none",ga=(i,t)=>t?i:Object.assign({},i),eu=(i,t,e)=>i&&!t.hidden&&t._stacked&&{keys:tl(e,!0),values:null},gt=class{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.initialize()}initialize(){let t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=da(t.vScale,t),this.addElements()}updateIndex(t){this.index!==t&&ki(this._cachedMeta),this.index=t}linkScales(){let t=this.chart,e=this._cachedMeta,s=this.getDataset(),n=(u,d,f,m)=>u==="x"?d:u==="r"?m:f,r=e.xAxisID=E(s.xAxisID,er(t,"x")),o=e.yAxisID=E(s.yAxisID,er(t,"y")),a=e.rAxisID=E(s.rAxisID,er(t,"r")),l=e.indexAxis,c=e.iAxisID=n(l,r,o,a),h=e.vAxisID=n(l,o,r,a);e.xScale=this.getScaleForId(r),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(c),e.vScale=this.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){let e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){let t=this._cachedMeta;this._data&&Cn(this._data,this),t._stacked&&ki(t)}_dataCheck(){let t=this.getDataset(),e=t.data||(t.data=[]),s=this._data;if(F(e))this._data=Gh(e);else if(s!==e){if(s){Cn(s,this);let n=this._cachedMeta;ki(n),n._parsed=[]}e&&Object.isExtensible(e)&&Vo(e,this),this._syncList=[],this._data=e}}addElements(){let t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){let e=this._cachedMeta,s=this.getDataset(),n=!1;this._dataCheck();let r=e._stacked;e._stacked=da(e.vScale,e),e.stack!==s.stack&&(n=!0,ki(e),e.stack=s.stack),this._resyncElements(t),(n||r!==e._stacked)&&ma(this,e._parsed)}configure(){let t=this.chart.config,e=t.datasetScopeKeys(this._type),s=t.getOptionScopes(this.getDataset(),e,!0);this.options=t.createResolver(s,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,e){let{_cachedMeta:s,_data:n}=this,{iScale:r,_stacked:o}=s,a=r.axis,l=t===0&&e===n.length?!0:s._sorted,c=t>0&&s._parsed[t-1],h,u,d;if(this._parsing===!1)s._parsed=n,s._sorted=!0,d=n;else{B(n[t])?d=this.parseArrayData(s,n,t,e):F(n[t])?d=this.parseObjectData(s,n,t,e):d=this.parsePrimitiveData(s,n,t,e);let f=()=>u[a]===null||c&&u[a]g||u=0;--d)if(!m()){this.updateRangeFromParsed(c,t,f,l);break}}return c}getAllParsedValues(t){let e=this._cachedMeta._parsed,s=[],n,r,o;for(n=0,r=e.length;n=0&&tthis.getContext(s,n),g=c.resolveNamedOptions(d,f,m,u);return g.$shared&&(g.$shared=l,r[o]=Object.freeze(ga(g,l))),g}_resolveAnimations(t,e,s){let n=this.chart,r=this._cachedDataOpts,o=`animation-${e}`,a=r[o];if(a)return a;let l;if(n.options.animation!==!1){let h=this.chart.config,u=h.datasetAnimationScopeKeys(this._type,e),d=h.getOptionScopes(this.getDataset(),u);l=h.createResolver(d,this.getContext(t,s,e))}let c=new $s(n,l&&l.animations);return l&&l._cacheable&&(r[o]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||ir(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){let s=this.resolveDataElementOptions(t,e),n=this._sharedOptions,r=this.getSharedOptions(s),o=this.includeOptions(e,r)||r!==n;return this.updateSharedOptions(r,e,s),{sharedOptions:r,includeOptions:o}}updateElement(t,e,s,n){ir(n)?Object.assign(t,s):this._resolveAnimations(e,n).update(t,s)}updateSharedOptions(t,e,s){t&&!ir(e)&&this._resolveAnimations(void 0,e).update(t,s)}_setStyle(t,e,s,n){t.active=n;let r=this.getStyle(e,n);this._resolveAnimations(e,s,n).update(t,{options:!n&&this.getSharedOptions(r)||r})}removeHoverStyle(t,e,s){this._setStyle(t,s,"active",!1)}setHoverStyle(t,e,s){this._setStyle(t,s,"active",!0)}_removeDatasetHoverStyle(){let t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){let t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){let e=this._data,s=this._cachedMeta.data;for(let[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];let n=s.length,r=e.length,o=Math.min(r,n);o&&this.parse(0,o),r>n?this._insertElements(n,r-n,t):r{for(c.length+=e,a=c.length-1;a>=o;a--)c[a]=c[a-e]};for(l(r),a=t;an-r))}return i._cache.$bar}function su(i){let t=i.iScale,e=iu(t,i.type),s=t._length,n,r,o,a,l=()=>{o===32767||o===-32768||(dt(a)&&(s=Math.min(s,Math.abs(o-a)||s)),a=o)};for(n=0,r=e.length;n0?n[i-1]:null,a=iMath.abs(a)&&(l=a,c=o),t[e.axis]=c,t._custom={barStart:l,barEnd:c,start:n,end:r,min:o,max:a}}function el(i,t,e,s){return B(i)?ou(i,t,e,s):t[e.axis]=e.parse(i,s),t}function pa(i,t,e,s){let n=i.iScale,r=i.vScale,o=n.getLabels(),a=n===r,l=[],c,h,u,d;for(c=e,h=e+s;c=e?1:-1)}function lu(i){let t,e,s,n,r;return i.horizontal?(t=i.base>i.x,e="left",s="right"):(t=i.basel.controller.options.grouped),r=s.options.stacked,o=[],a=l=>{let c=l.controller.getParsed(e),h=c&&c[l.vScale.axis];if(P(h)||isNaN(h))return!0};for(let l of n)if(!(e!==void 0&&a(l))&&((r===!1||o.indexOf(l.stack)===-1||r===void 0&&l.stack===void 0)&&o.push(l.stack),l.index===t))break;return o.length||o.push(void 0),o}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,s){let n=this._getStacks(t,s),r=e!==void 0?n.indexOf(e):-1;return r===-1?n.length-1:r}_getRuler(){let t=this.options,e=this._cachedMeta,s=e.iScale,n=[],r,o;for(r=0,o=e.data.length;r=0;--s)e=Math.max(e,t[s].size(this.resolveDataElementOptions(s))/2);return e>0&&e}getLabelAndValue(t){let e=this._cachedMeta,{xScale:s,yScale:n}=e,r=this.getParsed(t),o=s.getLabelForValue(r.x),a=n.getLabelForValue(r.y),l=r._custom;return{label:e.label,value:"("+o+", "+a+(l?", "+l:"")+")"}}update(t){let e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,s,n){let r=n==="reset",{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:l,includeOptions:c}=this._getSharedOptions(e,n),h=o.axis,u=a.axis;for(let d=e;dNe(_,a,l,!0)?1:Math.max(w,w*e,x,x*e),m=(_,w,x)=>Ne(_,a,l,!0)?-1:Math.min(w,w*e,x,x*e),g=f(0,c,u),p=f(U,h,d),y=m(j,c,u),b=m(j+U,h,d);s=(g-y)/2,n=(p-b)/2,r=-(g+y)/2,o=-(p+b)/2}return{ratioX:s,ratioY:n,offsetX:r,offsetY:o}}var ne=class extends gt{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){let s=this.getDataset().data,n=this._cachedMeta;if(this._parsing===!1)n._parsed=s;else{let r=l=>+s[l];if(F(s[t])){let{key:l="value"}=this._parsing;r=c=>+Vt(s[c],l)}let o,a;for(o=t,a=t+e;o0&&!isNaN(t)?H*(Math.abs(t)/e):0}getLabelAndValue(t){let e=this._cachedMeta,s=this.chart,n=s.data.labels||[],r=ze(e._parsed[t],s.options.locale);return{label:n[t]||"",value:r}}getMaxBorderWidth(t){let e=0,s=this.chart,n,r,o,a,l;if(!t){for(n=0,r=s.data.datasets.length;ni!=="spacing",_indexable:i=>i!=="spacing"};ne.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){let t=i.data;if(t.labels.length&&t.datasets.length){let{labels:{pointStyle:e}}=i.legend.options;return t.labels.map((s,n)=>{let o=i.getDatasetMeta(0).controller.getStyle(n);return{text:s,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,lineWidth:o.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(n),index:n}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}},tooltip:{callbacks:{title(){return""},label(i){let t=i.label,e=": "+i.formattedValue;return B(t)?(t=t.slice(),t[0]+=e):t+=e,t}}}}};var je=class extends gt{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(t){let e=this._cachedMeta,{dataset:s,data:n=[],_dataset:r}=e,o=this.chart._animationsDisabled,{start:a,count:l}=Pn(e,n,o);this._drawStart=a,this._drawCount=l,Nn(e)&&(a=0,l=n.length),s._chart=this.chart,s._datasetIndex=this.index,s._decimated=!!r._decimated,s.points=n;let c=this.resolveDatasetElementOptions(t);this.options.showLine||(c.borderWidth=0),c.segment=this.options.segment,this.updateElement(s,void 0,{animated:!o,options:c},t),this.updateElements(n,a,l,t)}updateElements(t,e,s,n){let r=n==="reset",{iScale:o,vScale:a,_stacked:l,_dataset:c}=this._cachedMeta,{sharedOptions:h,includeOptions:u}=this._getSharedOptions(e,n),d=o.axis,f=a.axis,{spanGaps:m,segment:g}=this.options,p=ge(m)?m:Number.POSITIVE_INFINITY,y=this.chart._animationsDisabled||r||n==="none",b=e>0&&this.getParsed(e-1);for(let _=e;_0&&Math.abs(x[d]-b[d])>p,g&&(S.parsed=x,S.raw=c.data[_]),u&&(S.options=h||this.resolveDataElementOptions(_,w.active?"active":n)),y||this.updateElement(w,_,S,n),b=x}}getMaxOverflow(){let t=this._cachedMeta,e=t.dataset,s=e.options&&e.options.borderWidth||0,n=t.data||[];if(!n.length)return s;let r=n[0].size(this.resolveDataElementOptions(0)),o=n[n.length-1].size(this.resolveDataElementOptions(n.length-1));return Math.max(s,r,o)/2}draw(){let t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}};je.id="line";je.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1};je.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};var Ue=class extends gt{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){let e=this._cachedMeta,s=this.chart,n=s.data.labels||[],r=ze(e._parsed[t].r,s.options.locale);return{label:n[t]||"",value:r}}parseObjectData(t,e,s,n){return Zn.bind(this)(t,e,s,n)}update(t){let e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){let t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach((s,n)=>{let r=this.getParsed(n).r;!isNaN(r)&&this.chart.getDataVisibility(n)&&(re.max&&(e.max=r))}),e}_updateRadius(){let t=this.chart,e=t.chartArea,s=t.options,n=Math.min(e.right-e.left,e.bottom-e.top),r=Math.max(n/2,0),o=Math.max(s.cutoutPercentage?r/100*s.cutoutPercentage:1,0),a=(r-o)/t.getVisibleDatasetCount();this.outerRadius=r-a*this.index,this.innerRadius=this.outerRadius-a}updateElements(t,e,s,n){let r=n==="reset",o=this.chart,l=o.options.animation,c=this._cachedMeta.rScale,h=c.xCenter,u=c.yCenter,d=c.getIndexAngle(0)-.5*j,f=d,m,g=360/this.countVisibleElements();for(m=0;m{!isNaN(this.getParsed(n).r)&&this.chart.getDataVisibility(n)&&e++}),e}_computeAngle(t,e,s){return this.chart.getDataVisibility(t)?_t(this.resolveDataElementOptions(t,e).angle||s):0}};Ue.id="polarArea";Ue.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0};Ue.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){let t=i.data;if(t.labels.length&&t.datasets.length){let{labels:{pointStyle:e}}=i.legend.options;return t.labels.map((s,n)=>{let o=i.getDatasetMeta(0).controller.getStyle(n);return{text:s,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,lineWidth:o.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(n),index:n}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}},tooltip:{callbacks:{title(){return""},label(i){return i.chart.data.labels[i.dataIndex]+": "+i.formattedValue}}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};var Ii=class extends ne{};Ii.id="pie";Ii.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};var Ye=class extends gt{getLabelAndValue(t){let e=this._cachedMeta.vScale,s=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(s[e.axis])}}parseObjectData(t,e,s,n){return Zn.bind(this)(t,e,s,n)}update(t){let e=this._cachedMeta,s=e.dataset,n=e.data||[],r=e.iScale.getLabels();if(s.points=n,t!=="resize"){let o=this.resolveDatasetElementOptions(t);this.options.showLine||(o.borderWidth=0);let a={_loop:!0,_fullLoop:r.length===n.length,options:o};this.updateElement(s,void 0,a,t)}this.updateElements(n,0,n.length,t)}updateElements(t,e,s,n){let r=this._cachedMeta.rScale,o=n==="reset";for(let a=e;a{n[r]=s[r]&&s[r].active()?s[r]._to:this[r]}),n}};pt.defaults={};pt.defaultRoutes=void 0;var il={values(i){return B(i)?i:""+i},numeric(i,t,e){if(i===0)return"0";let s=this.chart.options.locale,n,r=i;if(e.length>1){let c=Math.max(Math.abs(e[0].value),Math.abs(e[e.length-1].value));(c<1e-4||c>1e15)&&(n="scientific"),r=fu(i,e)}let o=mt(Math.abs(r)),a=Math.max(Math.min(-1*Math.floor(o),20),0),l={notation:n,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),ze(i,s,l)},logarithmic(i,t,e){if(i===0)return"0";let s=i/Math.pow(10,Math.floor(mt(i)));return s===1||s===2||s===5?il.numeric.call(this,i,t,e):""}};function fu(i,t){let e=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(e)>=1&&i!==Math.floor(i)&&(e=i-Math.floor(i)),e}var Gs={formatters:il};A.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(i,t)=>t.lineWidth,tickColor:(i,t)=>t.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Gs.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}});A.route("scale.ticks","color","","color");A.route("scale.grid","color","","borderColor");A.route("scale.grid","borderColor","","borderColor");A.route("scale.title","color","","color");A.describe("scale",{_fallback:!1,_scriptable:i=>!i.startsWith("before")&&!i.startsWith("after")&&i!=="callback"&&i!=="parser",_indexable:i=>i!=="borderDash"&&i!=="tickBorderDash"});A.describe("scales",{_fallback:"scale"});A.describe("scale.ticks",{_scriptable:i=>i!=="backdropPadding"&&i!=="callback",_indexable:i=>i!=="backdropPadding"});function mu(i,t){let e=i.options.ticks,s=e.maxTicksLimit||gu(i),n=e.major.enabled?yu(t):[],r=n.length,o=n[0],a=n[r-1],l=[];if(r>s)return bu(t,l,n,r/s),l;let c=pu(n,t,s);if(r>0){let h,u,d=r>1?Math.round((a-o)/(r-1)):null;for(Ns(t,l,c,P(d)?0:o-d,o),h=0,u=r-1;hn)return l}return Math.max(n,1)}function yu(i){let t=[],e,s;for(e=0,s=i.length;ei==="left"?"right":i==="right"?"left":i,xa=(i,t,e)=>t==="top"||t==="left"?i[t]+e:i[t]-e;function _a(i,t){let e=[],s=i.length/t,n=i.length,r=0;for(;ro+a)))return l}function Su(i,t){V(i,e=>{let s=e.gc,n=s.length/2,r;if(n>t){for(r=0;rs?s:e,s=n&&e>s?e:s,{min:ft(e,ft(s,e)),max:ft(s,ft(e,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){let t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){$(this.options.beforeUpdate,[this])}update(t,e,s){let{beginAtZero:n,grace:r,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=s=Object.assign({left:0,right:0,top:0,bottom:0},s),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+s.left+s.right:this.height+s.top+s.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Ko(this,r,n),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();let l=a=r||s<=1||!this.isHorizontal()){this.labelRotation=n;return}let h=this._getLabelSizes(),u=h.widest.width,d=h.highest.height,f=tt(this.chart.width-u,0,this.maxWidth);a=t.offset?this.maxWidth/s:f/(s-1),u+6>a&&(a=f/(s-(t.offset?.5:1)),l=this.maxHeight-Mi(t.grid)-e.padding-wa(t.title,this.chart.options.font),c=Math.sqrt(u*u+d*d),o=Os(Math.min(Math.asin(tt((h.highest.height+6)/a,-1,1)),Math.asin(tt(l/c,-1,1))-Math.asin(tt(d/c,-1,1)))),o=Math.max(n,Math.min(r,o))),this.labelRotation=o}afterCalculateLabelRotation(){$(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){$(this.options.beforeFit,[this])}fit(){let t={width:0,height:0},{chart:e,options:{ticks:s,title:n,grid:r}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){let l=wa(n,e.options.font);if(a?(t.width=this.maxWidth,t.height=Mi(r)+l):(t.height=this.maxHeight,t.width=Mi(r)+l),s.display&&this.ticks.length){let{first:c,last:h,widest:u,highest:d}=this._getLabelSizes(),f=s.padding*2,m=_t(this.labelRotation),g=Math.cos(m),p=Math.sin(m);if(a){let y=s.mirror?0:p*u.width+g*d.height;t.height=Math.min(this.maxHeight,t.height+y+f)}else{let y=s.mirror?0:g*u.width+p*d.height;t.width=Math.min(this.maxWidth,t.width+y+f)}this._calculatePadding(c,h,p,g)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,s,n){let{ticks:{align:r,padding:o},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){let h=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1),d=0,f=0;l?c?(d=n*t.width,f=s*e.height):(d=s*t.height,f=n*e.width):r==="start"?f=e.width:r==="end"?d=t.width:r!=="inner"&&(d=t.width/2,f=e.width/2),this.paddingLeft=Math.max((d-h+o)*this.width/(this.width-h),0),this.paddingRight=Math.max((f-u+o)*this.width/(this.width-u),0)}else{let h=e.height/2,u=t.height/2;r==="start"?(h=0,u=t.height):r==="end"&&(h=e.height,u=0),this.paddingTop=h+o,this.paddingBottom=u+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){$(this.options.afterFit,[this])}isHorizontal(){let{axis:t,position:e}=this.options;return e==="top"||e==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let e,s;for(e=0,s=t.length;e({width:r[k]||0,height:o[k]||0});return{first:S(0),last:S(e-1),widest:S(w),highest:S(x),widths:r,heights:o}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){let e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);let e=this._startPixel+t*this._length;return No(this._alignToPixels?Jt(this.chart,e,0):e)}getDecimalForPixel(t){let e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){let{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){let e=this.ticks||[];if(t>=0&&ta*n?a/s:l/n:l*n0}_computeGridLineItems(t){let e=this.axis,s=this.chart,n=this.options,{grid:r,position:o}=n,a=r.offset,l=this.isHorizontal(),h=this.ticks.length+(a?1:0),u=Mi(r),d=[],f=r.setContext(this.getContext()),m=f.drawBorder?f.borderWidth:0,g=m/2,p=function(D){return Jt(s,D,m)},y,b,_,w,x,S,k,v,T,C,N,L;if(o==="top")y=p(this.bottom),S=this.bottom-u,v=y-g,C=p(t.top)+g,L=t.bottom;else if(o==="bottom")y=p(this.top),C=t.top,L=p(t.bottom)-g,S=y+g,v=this.top+u;else if(o==="left")y=p(this.right),x=this.right-u,k=y-g,T=p(t.left)+g,N=t.right;else if(o==="right")y=p(this.left),T=t.left,N=p(t.right)-g,x=y+g,k=this.left+u;else if(e==="x"){if(o==="center")y=p((t.top+t.bottom)/2+.5);else if(F(o)){let D=Object.keys(o)[0],J=o[D];y=p(this.chart.scales[D].getPixelForValue(J))}C=t.top,L=t.bottom,S=y+g,v=S+u}else if(e==="y"){if(o==="center")y=p((t.left+t.right)/2);else if(F(o)){let D=Object.keys(o)[0],J=o[D];y=p(this.chart.scales[D].getPixelForValue(J))}x=y-g,k=x-u,T=t.left,N=t.right}let K=E(n.ticks.maxTicksLimit,h),lt=Math.max(1,Math.ceil(h/K));for(b=0;br.value===t);return n>=0?e.setContext(this.getContext(n)).lineWidth:0}drawGrid(t){let e=this.options.grid,s=this.ctx,n=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t)),r,o,a=(l,c,h)=>{!h.width||!h.color||(s.save(),s.lineWidth=h.width,s.strokeStyle=h.color,s.setLineDash(h.borderDash||[]),s.lineDashOffset=h.borderDashOffset,s.beginPath(),s.moveTo(l.x,l.y),s.lineTo(c.x,c.y),s.stroke(),s.restore())};if(e.display)for(r=0,o=n.length;r{this.draw(n)}}]:[{z:s,draw:n=>{this.drawBackground(),this.drawGrid(n),this.drawTitle()}},{z:s+1,draw:()=>{this.drawBorder()}},{z:e,draw:n=>{this.drawLabels(n)}}]}getMatchingVisibleMetas(t){let e=this.chart.getSortedVisibleDatasetMetas(),s=this.axis+"AxisID",n=[],r,o;for(r=0,o=e.length;r{let s=e.split("."),n=s.pop(),r=[i].concat(s).join("."),o=t[e].split("."),a=o.pop(),l=o.join(".");A.route(r,n,l,a)})}function Eu(i){return"id"in i&&"defaults"in i}var dr=class{constructor(){this.controllers=new He(gt,"datasets",!0),this.elements=new He(pt,"elements"),this.plugins=new He(Object,"plugins"),this.scales=new He(be,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,s){[...e].forEach(n=>{let r=s||this._getRegistryForType(n);s||r.isForType(n)||r===this.plugins&&n.id?this._exec(t,r,n):V(n,o=>{let a=s||this._getRegistryForType(o);this._exec(t,a,o)})})}_exec(t,e,s){let n=vs(t);$(s["before"+n],[],s),e[t](s),$(s["after"+n],[],s)}_getRegistryForType(t){for(let e=0;e0&&this.getParsed(e-1);for(let w=e;w0&&Math.abs(S[f]-_[f])>y,p&&(k.parsed=S,k.raw=c.data[w]),d&&(k.options=u||this.resolveDataElementOptions(w,x.active?"active":n)),b||this.updateElement(x,w,k,n),_=S}this.updateSharedOptions(u,n,h)}getMaxOverflow(){let t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let a=0;for(let l=e.length-1;l>=0;--l)a=Math.max(a,e[l].size(this.resolveDataElementOptions(l))/2);return a>0&&a}let s=t.dataset,n=s.options&&s.options.borderWidth||0;if(!e.length)return n;let r=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(n,r,o)/2}};Ze.id="scatter";Ze.defaults={datasetElementType:!1,dataElementType:"point",showLine:!1,fill:!1};Ze.overrides={interaction:{mode:"point"},plugins:{tooltip:{callbacks:{title(){return""},label(i){return"("+i.label+", "+i.formattedValue+")"}}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var Iu=Object.freeze({__proto__:null,BarController:Be,BubbleController:$e,DoughnutController:ne,LineController:je,PolarAreaController:Ue,PieController:Ii,RadarController:Ye,ScatterController:Ze});function ye(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}var Ci=class{constructor(t){this.options=t||{}}init(t){}formats(){return ye()}parse(t,e){return ye()}format(t,e){return ye()}add(t,e,s){return ye()}diff(t,e,s){return ye()}startOf(t,e,s){return ye()}endOf(t,e){return ye()}};Ci.override=function(i){Object.assign(Ci.prototype,i)};var kr={_date:Ci};function Cu(i,t,e,s){let{controller:n,data:r,_sorted:o}=i,a=n._cachedMeta.iScale;if(a&&t===a.axis&&t!=="r"&&o&&r.length){let l=a._reversePixels?Ro:Ct;if(s){if(n._sharedOptions){let c=r[0],h=typeof c.getRange=="function"&&c.getRange(t);if(h){let u=l(r,t,e-h),d=l(r,t,e+h);return{lo:u.lo,hi:d.hi}}}}else return l(r,t,e)}return{lo:0,hi:r.length-1}}function Wi(i,t,e,s,n){let r=i.getSortedVisibleDatasetMetas(),o=e[t];for(let a=0,l=r.length;a{l[o](t[e],n)&&(r.push({element:l,datasetIndex:c,index:h}),a=a||l.inRange(t.x,t.y,n))}),s&&!a?[]:r}var Pu={evaluateInteractionItems:Wi,modes:{index(i,t,e,s){let n=ee(t,i),r=e.axis||"x",o=e.includeInvisible||!1,a=e.intersect?nr(i,n,r,s,o):rr(i,n,r,!1,s,o),l=[];return a.length?(i.getSortedVisibleDatasetMetas().forEach(c=>{let h=a[0].index,u=c.data[h];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:h})}),l):[]},dataset(i,t,e,s){let n=ee(t,i),r=e.axis||"xy",o=e.includeInvisible||!1,a=e.intersect?nr(i,n,r,s,o):rr(i,n,r,!1,s,o);if(a.length>0){let l=a[0].datasetIndex,c=i.getDatasetMeta(l).data;a=[];for(let h=0;he.pos===t)}function ka(i,t){return i.filter(e=>sl.indexOf(e.pos)===-1&&e.box.axis===t)}function vi(i,t){return i.sort((e,s)=>{let n=t?s:e,r=t?e:s;return n.weight===r.weight?n.index-r.index:n.weight-r.weight})}function Nu(i){let t=[],e,s,n,r,o,a;for(e=0,s=(i||[]).length;ec.box.fullSize),!0),s=vi(Ti(t,"left"),!0),n=vi(Ti(t,"right")),r=vi(Ti(t,"top"),!0),o=vi(Ti(t,"bottom")),a=ka(t,"x"),l=ka(t,"y");return{fullSize:e,leftAndTop:s.concat(r),rightAndBottom:n.concat(l).concat(o).concat(a),chartArea:Ti(t,"chartArea"),vertical:s.concat(n).concat(l),horizontal:r.concat(o).concat(a)}}function Ma(i,t,e,s){return Math.max(i[e],t[e])+Math.max(i[s],t[s])}function nl(i,t){i.top=Math.max(i.top,t.top),i.left=Math.max(i.left,t.left),i.bottom=Math.max(i.bottom,t.bottom),i.right=Math.max(i.right,t.right)}function Vu(i,t,e,s){let{pos:n,box:r}=e,o=i.maxPadding;if(!F(n)){e.size&&(i[n]-=e.size);let u=s[e.stack]||{size:0,count:1};u.size=Math.max(u.size,e.horizontal?r.height:r.width),e.size=u.size/u.count,i[n]+=e.size}r.getPadding&&nl(o,r.getPadding());let a=Math.max(0,t.outerWidth-Ma(o,i,"left","right")),l=Math.max(0,t.outerHeight-Ma(o,i,"top","bottom")),c=a!==i.w,h=l!==i.h;return i.w=a,i.h=l,e.horizontal?{same:c,other:h}:{same:h,other:c}}function Hu(i){let t=i.maxPadding;function e(s){let n=Math.max(t[s]-i[s],0);return i[s]+=n,n}i.y+=e("top"),i.x+=e("left"),e("right"),e("bottom")}function Bu(i,t){let e=t.maxPadding;function s(n){let r={left:0,top:0,right:0,bottom:0};return n.forEach(o=>{r[o]=Math.max(t[o],e[o])}),r}return s(i?["left","right"]:["top","bottom"])}function Di(i,t,e,s){let n=[],r,o,a,l,c,h;for(r=0,o=i.length,c=0;r{typeof g.beforeLayout=="function"&&g.beforeLayout()});let h=l.reduce((g,p)=>p.box.options&&p.box.options.display===!1?g:g+1,0)||1,u=Object.freeze({outerWidth:t,outerHeight:e,padding:n,availableWidth:r,availableHeight:o,vBoxMaxWidth:r/2/h,hBoxMaxHeight:o/2}),d=Object.assign({},n);nl(d,rt(s));let f=Object.assign({maxPadding:d,w:r,h:o,x:n.left,y:n.top},n),m=Wu(l.concat(c),u);Di(a.fullSize,f,u,m),Di(l,f,u,m),Di(c,f,u,m)&&Di(l,f,u,m),Hu(f),Ta(a.leftAndTop,f,u,m),f.x+=f.w,f.y+=f.h,Ta(a.rightAndBottom,f,u,m),i.chartArea={left:f.left,top:f.top,right:f.left+f.w,bottom:f.top+f.h,height:f.h,width:f.w},V(a.chartArea,g=>{let p=g.box;Object.assign(p,i.chartArea),p.update(f.w,f.h,{left:0,top:0,right:0,bottom:0})})}},js=class{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,s){}removeEventListener(t,e,s){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,s,n){return e=Math.max(0,e||t.width),s=s||t.height,{width:e,height:Math.max(0,n?Math.floor(e/n):s)}}isAttached(t){return!0}updateConfig(t){}},fr=class extends js{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}},Bs="$chartjs",$u={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},va=i=>i===null||i==="";function ju(i,t){let e=i.style,s=i.getAttribute("height"),n=i.getAttribute("width");if(i[Bs]={initial:{height:s,width:n,style:{display:e.display,height:e.height,width:e.width}}},e.display=e.display||"block",e.boxSizing=e.boxSizing||"border-box",va(n)){let r=Xn(i,"width");r!==void 0&&(i.width=r)}if(va(s))if(i.style.height==="")i.height=i.width/(t||2);else{let r=Xn(i,"height");r!==void 0&&(i.height=r)}return i}var rl=na?{passive:!0}:!1;function Uu(i,t,e){i.addEventListener(t,e,rl)}function Yu(i,t,e){i.canvas.removeEventListener(t,e,rl)}function Zu(i,t){let e=$u[i.type]||i.type,{x:s,y:n}=ee(i,t);return{type:e,chart:t,native:i,x:s!==void 0?s:null,y:n!==void 0?n:null}}function Us(i,t){for(let e of i)if(e===t||e.contains(t))return!0}function qu(i,t,e){let s=i.canvas,n=new MutationObserver(r=>{let o=!1;for(let a of r)o=o||Us(a.addedNodes,s),o=o&&!Us(a.removedNodes,s);o&&e()});return n.observe(document,{childList:!0,subtree:!0}),n}function Gu(i,t,e){let s=i.canvas,n=new MutationObserver(r=>{let o=!1;for(let a of r)o=o||Us(a.removedNodes,s),o=o&&!Us(a.addedNodes,s);o&&e()});return n.observe(document,{childList:!0,subtree:!0}),n}var Fi=new Map,Oa=0;function ol(){let i=window.devicePixelRatio;i!==Oa&&(Oa=i,Fi.forEach((t,e)=>{e.currentDevicePixelRatio!==i&&t()}))}function Xu(i,t){Fi.size||window.addEventListener("resize",ol),Fi.set(i,t)}function Ku(i){Fi.delete(i),Fi.size||window.removeEventListener("resize",ol)}function Ju(i,t,e){let s=i.canvas,n=s&&Ls(s);if(!n)return;let r=Ln((a,l)=>{let c=n.clientWidth;e(a,l),c{let l=a[0],c=l.contentRect.width,h=l.contentRect.height;c===0&&h===0||r(c,h)});return o.observe(n),Xu(i,r),o}function or(i,t,e){e&&e.disconnect(),t==="resize"&&Ku(i)}function Qu(i,t,e){let s=i.canvas,n=Ln(r=>{i.ctx!==null&&e(Zu(r,i))},i,r=>{let o=r[0];return[o,o.offsetX,o.offsetY]});return Uu(s,t,n),n}var mr=class extends js{acquireContext(t,e){let s=t&&t.getContext&&t.getContext("2d");return s&&s.canvas===t?(ju(t,e),s):null}releaseContext(t){let e=t.canvas;if(!e[Bs])return!1;let s=e[Bs].initial;["height","width"].forEach(r=>{let o=s[r];P(o)?e.removeAttribute(r):e.setAttribute(r,o)});let n=s.style||{};return Object.keys(n).forEach(r=>{e.style[r]=n[r]}),e.width=e.width,delete e[Bs],!0}addEventListener(t,e,s){this.removeEventListener(t,e);let n=t.$proxies||(t.$proxies={}),o={attach:qu,detach:Gu,resize:Ju}[e]||Qu;n[e]=o(t,e,s)}removeEventListener(t,e){let s=t.$proxies||(t.$proxies={}),n=s[e];if(!n)return;({attach:or,detach:or,resize:or}[e]||Yu)(t,e,n),s[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,s,n){return sa(t,e,s,n)}isAttached(t){let e=Ls(t);return!!(e&&e.isConnected)}};function td(i){return!qn()||typeof OffscreenCanvas<"u"&&i instanceof OffscreenCanvas?fr:mr}var gr=class{constructor(){this._init=[]}notify(t,e,s,n){e==="beforeInit"&&(this._init=this._createDescriptors(t,!0),this._notify(this._init,t,"install"));let r=n?this._descriptors(t).filter(n):this._descriptors(t),o=this._notify(r,t,e,s);return e==="afterDestroy"&&(this._notify(r,t,"stop"),this._notify(this._init,t,"uninstall")),o}_notify(t,e,s,n){n=n||{};for(let r of t){let o=r.plugin,a=o[s],l=[e,n,r.options];if($(a,l,o)===!1&&n.cancelable)return!1}return!0}invalidate(){P(this._cache)||(this._oldCache=this._cache,this._cache=void 0)}_descriptors(t){if(this._cache)return this._cache;let e=this._cache=this._createDescriptors(t);return this._notifyStateChanges(t),e}_createDescriptors(t,e){let s=t&&t.config,n=E(s.options&&s.options.plugins,{}),r=ed(s);return n===!1&&!e?[]:sd(t,r,n,e)}_notifyStateChanges(t){let e=this._oldCache||[],s=this._cache,n=(r,o)=>r.filter(a=>!o.some(l=>a.plugin.id===l.plugin.id));this._notify(n(e,s),t,"stop"),this._notify(n(s,e),t,"start")}};function ed(i){let t={},e=[],s=Object.keys(Pt.plugins.items);for(let r=0;r{let l=s[a];if(!F(l))return console.error(`Invalid scale configuration for scale: ${a}`);if(l._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${a}`);let c=yr(a,l),h=od(c,n),u=e.scales||{};r[c]=r[c]||a,o[a]=Le(Object.create(null),[{axis:c},l,u[c],u[h]])}),i.data.datasets.forEach(a=>{let l=a.type||i.type,c=a.indexAxis||pr(l,t),u=(Kt[l]||{}).scales||{};Object.keys(u).forEach(d=>{let f=rd(d,c),m=a[f+"AxisID"]||r[f]||f;o[m]=o[m]||Object.create(null),Le(o[m],[{axis:f},s[m],u[d]])})}),Object.keys(o).forEach(a=>{let l=o[a];Le(l,[A.scales[l.type],A.scale])}),o}function al(i){let t=i.options||(i.options={});t.plugins=E(t.plugins,{}),t.scales=ld(i,t)}function ll(i){return i=i||{},i.datasets=i.datasets||[],i.labels=i.labels||[],i}function cd(i){return i=i||{},i.data=ll(i.data),al(i),i}var Da=new Map,cl=new Set;function Ws(i,t){let e=Da.get(i);return e||(e=t(),Da.set(i,e),cl.add(e)),e}var Oi=(i,t,e)=>{let s=Vt(t,e);s!==void 0&&i.add(s)},br=class{constructor(t){this._config=cd(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=ll(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){let t=this._config;this.clearCache(),al(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Ws(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,e){return Ws(`${t}.transition.${e}`,()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,e){return Ws(`${t}-${e}`,()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]])}pluginScopeKeys(t){let e=t.id,s=this.type;return Ws(`${s}-plugin-${e}`,()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,e){let s=this._scopeCache,n=s.get(t);return(!n||e)&&(n=new Map,s.set(t,n)),n}getOptionScopes(t,e,s){let{options:n,type:r}=this,o=this._cachedScopes(t,s),a=o.get(e);if(a)return a;let l=new Set;e.forEach(h=>{t&&(l.add(t),h.forEach(u=>Oi(l,t,u))),h.forEach(u=>Oi(l,n,u)),h.forEach(u=>Oi(l,Kt[r]||{},u)),h.forEach(u=>Oi(l,A,u)),h.forEach(u=>Oi(l,Is,u))});let c=Array.from(l);return c.length===0&&c.push(Object.create(null)),cl.has(e)&&o.set(e,c),c}chartOptionScopes(){let{options:t,type:e}=this;return[t,Kt[e]||{},A.datasets[e]||{},{type:e},A,Is]}resolveNamedOptions(t,e,s,n=[""]){let r={$shared:!0},{resolver:o,subPrefixes:a}=Ea(this._resolverCache,t,n),l=o;if(ud(o,e)){r.$shared=!1,s=zt(s)?s():s;let c=this.createResolver(t,s,a);l=me(o,s,c)}for(let c of e)r[c]=l[c];return r}createResolver(t,e,s=[""],n){let{resolver:r}=Ea(this._resolverCache,t,s);return F(e)?me(r,e,void 0,n):r}};function Ea(i,t,e){let s=i.get(t);s||(s=new Map,i.set(t,s));let n=e.join(),r=s.get(n);return r||(r={resolver:As(t,e),subPrefixes:e.filter(a=>!a.toLowerCase().includes("hover"))},s.set(n,r)),r}var hd=i=>F(i)&&Object.getOwnPropertyNames(i).reduce((t,e)=>t||zt(i[e]),!1);function ud(i,t){let{isScriptable:e,isIndexable:s}=jn(i);for(let n of t){let r=e(n),o=s(n),a=(o||r)&&i[n];if(r&&(zt(a)||hd(a))||o&&B(a))return!0}return!1}var dd="3.9.1",fd=["top","bottom","left","right","chartArea"];function Ia(i,t){return i==="top"||i==="bottom"||fd.indexOf(i)===-1&&t==="x"}function Ca(i,t){return function(e,s){return e[i]===s[i]?e[t]-s[t]:e[i]-s[i]}}function Fa(i){let t=i.chart,e=t.options.animation;t.notifyPlugins("afterRender"),$(e&&e.onComplete,[i],t)}function md(i){let t=i.chart,e=t.options.animation;$(e&&e.onProgress,[i],t)}function hl(i){return qn()&&typeof i=="string"?i=document.getElementById(i):i&&i.length&&(i=i[0]),i&&i.canvas&&(i=i.canvas),i}var Ys={},ul=i=>{let t=hl(i);return Object.values(Ys).filter(e=>e.canvas===t).pop()};function gd(i,t,e){let s=Object.keys(i);for(let n of s){let r=+n;if(r>=t){let o=i[n];delete i[n],(e>0||r>t)&&(i[r+e]=o)}}}function pd(i,t,e,s){return!e||i.type==="mouseout"?null:s?t:i}var xe=class{constructor(t,e){let s=this.config=new br(e),n=hl(t),r=ul(n);if(r)throw new Error("Canvas is already in use. Chart with ID '"+r.id+"' must be destroyed before the canvas with ID '"+r.canvas.id+"' can be reused.");let o=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||td(n)),this.platform.updateConfig(s);let a=this.platform.acquireContext(n,o.aspectRatio),l=a&&a.canvas,c=l&&l.height,h=l&&l.width;if(this.id=Io(),this.ctx=a,this.canvas=l,this.width=h,this.height=c,this._options=o,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new gr,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=Ho(u=>this.update(u),o.resizeDelay||0),this._dataChanges=[],Ys[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}Bt.listen(this,"complete",Fa),Bt.listen(this,"progress",md),this._initialize(),this.attached&&this.update()}get aspectRatio(){let{options:{aspectRatio:t,maintainAspectRatio:e},width:s,height:n,_aspectRatio:r}=this;return P(t)?e&&r?r:n?s/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():Gn(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Hn(this.canvas,this.ctx),this}stop(){return Bt.stop(this),this}resize(t,e){Bt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){let s=this.options,n=this.canvas,r=s.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(n,t,e,r),a=s.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,Gn(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),$(s.onResize,[this,o],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){let e=this.options.scales||{};V(e,(s,n)=>{s.id=n})}buildOrUpdateScales(){let t=this.options,e=t.scales,s=this.scales,n=Object.keys(s).reduce((o,a)=>(o[a]=!1,o),{}),r=[];e&&(r=r.concat(Object.keys(e).map(o=>{let a=e[o],l=yr(o,a),c=l==="r",h=l==="x";return{options:a,dposition:c?"chartArea":h?"bottom":"left",dtype:c?"radialLinear":h?"category":"linear"}}))),V(r,o=>{let a=o.options,l=a.id,c=yr(l,a),h=E(a.type,o.dtype);(a.position===void 0||Ia(a.position,c)!==Ia(o.dposition))&&(a.position=o.dposition),n[l]=!0;let u=null;if(l in s&&s[l].type===h)u=s[l];else{let d=Pt.getScale(h);u=new d({id:l,type:h,ctx:this.ctx,chart:this}),s[u.id]=u}u.init(a,t)}),V(n,(o,a)=>{o||delete s[a]}),V(s,o=>{ot.configure(this,o,o.options),ot.addBox(this,o)})}_updateMetasets(){let t=this._metasets,e=this.data.datasets.length,s=t.length;if(t.sort((n,r)=>n.index-r.index),s>e){for(let n=e;ne.length&&delete this._stacks,t.forEach((s,n)=>{e.filter(r=>r===s._dataset).length===0&&this._destroyDatasetMeta(n)})}buildOrUpdateControllers(){let t=[],e=this.data.datasets,s,n;for(this._removeUnreferencedMetasets(),s=0,n=e.length;s{this.getDatasetMeta(e).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){let e=this.config;e.update();let s=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),n=this._animationsDisabled=!s.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;let r=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let c=0,h=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Ca("z","_idx"));let{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){V(this.scales,t=>{ot.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){let t=this.options,e=new Set(Object.keys(this._listeners)),s=new Set(t.events);(!vn(e,s)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){let{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(let{method:s,start:n,count:r}of e){let o=s==="_removeElements"?-r:r;gd(t,n,o)}}_getUniformDataChanges(){let t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];let e=this.data.datasets.length,s=r=>new Set(t.filter(o=>o[0]===r).map((o,a)=>a+","+o.splice(1).join(","))),n=s(0);for(let r=1;rr.split(",")).map(r=>({method:r[1],start:+r[2],count:+r[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;ot.update(this,this.width,this.height,t);let e=this.chartArea,s=e.width<=0||e.height<=0;this._layers=[],V(this.boxes,n=>{s&&n.position==="chartArea"||(n.configure&&n.configure(),this._layers.push(...n._layers()))},this),this._layers.forEach((n,r)=>{n._idx=r}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let e=0,s=this.data.datasets.length;e=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){let e=this.ctx,s=t._clip,n=!s.disabled,r=this.chartArea,o={meta:t,index:t.index,cancelable:!0};this.notifyPlugins("beforeDatasetDraw",o)!==!1&&(n&&wi(e,{left:s.left===!1?0:r.left-s.left,right:s.right===!1?this.width:r.right+s.right,top:s.top===!1?0:r.top-s.top,bottom:s.bottom===!1?this.height:r.bottom+s.bottom}),t.controller.draw(),n&&Si(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Fe(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,s,n){let r=Pu.modes[e];return typeof r=="function"?r(this,t,s,n):[]}getDatasetMeta(t){let e=this.data.datasets[t],s=this._metasets,n=s.filter(r=>r&&r._dataset===e).pop();return n||(n={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},s.push(n)),n}getContext(){return this.$context||(this.$context=Ht(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){let e=this.data.datasets[t];if(!e)return!1;let s=this.getDatasetMeta(t);return typeof s.hidden=="boolean"?!s.hidden:!e.hidden}setDatasetVisibility(t,e){let s=this.getDatasetMeta(t);s.hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,s){let n=s?"show":"hide",r=this.getDatasetMeta(t),o=r.controller._resolveAnimations(void 0,n);dt(e)?(r.data[e].hidden=!s,this.update()):(this.setDatasetVisibility(t,s),o.update(r,{visible:s}),this.update(a=>a.datasetIndex===t?n:void 0))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){let e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),Bt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,r,o),t[r]=o},n=(r,o,a)=>{r.offsetX=o,r.offsetY=a,this._eventHandler(r)};V(this.options.events,r=>s(r,n))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});let t=this._responsiveListeners,e=this.platform,s=(l,c)=>{e.addEventListener(this,l,c),t[l]=c},n=(l,c)=>{t[l]&&(e.removeEventListener(this,l,c),delete t[l])},r=(l,c)=>{this.canvas&&this.resize(l,c)},o,a=()=>{n("attach",a),this.attached=!0,this.resize(),s("resize",r),s("detach",o)};o=()=>{this.attached=!1,n("resize",r),this._stop(),this._resize(0,0),s("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){V(this._listeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._listeners={},V(this._responsiveListeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,e,s){let n=s?"set":"remove",r,o,a,l;for(e==="dataset"&&(r=this.getDatasetMeta(t[0].datasetIndex),r.controller["_"+n+"DatasetHoverStyle"]()),a=0,l=t.length;a{let a=this.getDatasetMeta(r);if(!a)throw new Error("No dataset found at index "+r);return{datasetIndex:r,element:a.data[o],index:o}});!xi(s,e)&&(this._active=s,this._lastEvent=null,this._updateHoverStyles(s,e))}notifyPlugins(t,e,s){return this._plugins.notify(this,t,e,s)}_updateHoverStyles(t,e,s){let n=this.options.hover,r=(l,c)=>l.filter(h=>!c.some(u=>h.datasetIndex===u.datasetIndex&&h.index===u.index)),o=r(e,t),a=s?t:r(t,e);o.length&&this.updateHoverStyle(o,n.mode,!1),a.length&&n.mode&&this.updateHoverStyle(a,n.mode,!0)}_eventHandler(t,e){let s={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},n=o=>(o.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",s,n)===!1)return;let r=this._handleEvent(t,e,s.inChartArea);return s.cancelable=!1,this.notifyPlugins("afterEvent",s,n),(r||s.changed)&&this.render(),this}_handleEvent(t,e,s){let{_active:n=[],options:r}=this,o=e,a=this._getActiveElements(t,n,s,o),l=Ao(t),c=pd(t,this._lastEvent,s,l);s&&(this._lastEvent=null,$(r.onHover,[t,a,this],this),l&&$(r.onClick,[t,a,this],this));let h=!xi(a,n);return(h||e)&&(this._active=a,this._updateHoverStyles(a,n,e)),this._lastEvent=c,h}_getActiveElements(t,e,s,n){if(t.type==="mouseout")return[];if(!s)return e;let r=this.options.hover;return this.getElementsAtEventForMode(t,r.mode,r,n)}},Aa=()=>V(xe.instances,i=>i._plugins.invalidate()),ie=!0;Object.defineProperties(xe,{defaults:{enumerable:ie,value:A},instances:{enumerable:ie,value:Ys},overrides:{enumerable:ie,value:Kt},registry:{enumerable:ie,value:Pt},version:{enumerable:ie,value:dd},getChart:{enumerable:ie,value:ul},register:{enumerable:ie,value:(...i)=>{Pt.add(...i),Aa()}},unregister:{enumerable:ie,value:(...i)=>{Pt.remove(...i),Aa()}}});function dl(i,t,e){let{startAngle:s,pixelMargin:n,x:r,y:o,outerRadius:a,innerRadius:l}=t,c=n/a;i.beginPath(),i.arc(r,o,a,s-c,e+c),l>n?(c=n/l,i.arc(r,o,l,e+c,s-c,!0)):i.arc(r,o,n,e+U,s-U),i.closePath(),i.clip()}function yd(i){return Fs(i,["outerStart","outerEnd","innerStart","innerEnd"])}function bd(i,t,e,s){let n=yd(i.options.borderRadius),r=(e-t)/2,o=Math.min(r,s*t/2),a=l=>{let c=(e-Math.min(r,l))*s/2;return tt(l,0,Math.min(r,c))};return{outerStart:a(n.outerStart),outerEnd:a(n.outerEnd),innerStart:tt(n.innerStart,0,o),innerEnd:tt(n.innerEnd,0,o)}}function Ve(i,t,e,s){return{x:e+i*Math.cos(t),y:s+i*Math.sin(t)}}function xr(i,t,e,s,n,r){let{x:o,y:a,startAngle:l,pixelMargin:c,innerRadius:h}=t,u=Math.max(t.outerRadius+s+e-c,0),d=h>0?h+s+e+c:0,f=0,m=n-l;if(s){let D=h>0?h-s:0,J=u>0?u-s:0,X=(D+J)/2,de=X!==0?m*X/(X+s):m;f=(m-de)/2}let g=Math.max(.001,m*u-e/j)/u,p=(m-g)/2,y=l+p+f,b=n-p-f,{outerStart:_,outerEnd:w,innerStart:x,innerEnd:S}=bd(t,d,u,b-y),k=u-_,v=u-w,T=y+_/k,C=b-w/v,N=d+x,L=d+S,K=y+x/N,lt=b-S/L;if(i.beginPath(),r){if(i.arc(o,a,u,T,C),w>0){let X=Ve(v,C,o,a);i.arc(X.x,X.y,w,C,b+U)}let D=Ve(L,b,o,a);if(i.lineTo(D.x,D.y),S>0){let X=Ve(L,lt,o,a);i.arc(X.x,X.y,S,b+U,lt+Math.PI)}if(i.arc(o,a,d,b-S/d,y+x/d,!0),x>0){let X=Ve(N,K,o,a);i.arc(X.x,X.y,x,K+Math.PI,y-U)}let J=Ve(k,y,o,a);if(i.lineTo(J.x,J.y),_>0){let X=Ve(k,T,o,a);i.arc(X.x,X.y,_,y-U,T)}}else{i.moveTo(o,a);let D=Math.cos(T)*u+o,J=Math.sin(T)*u+a;i.lineTo(D,J);let X=Math.cos(C)*u+o,de=Math.sin(C)*u+a;i.lineTo(X,de)}i.closePath()}function xd(i,t,e,s,n){let{fullCircles:r,startAngle:o,circumference:a}=t,l=t.endAngle;if(r){xr(i,t,e,s,o+H,n);for(let c=0;c=H||Ne(r,a,l),g=At(o,c+d,h+d);return m&&g}getCenterPoint(t){let{x:e,y:s,startAngle:n,endAngle:r,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius","circumference"],t),{offset:l,spacing:c}=this.options,h=(n+r)/2,u=(o+a+c+l)/2;return{x:e+Math.cos(h)*u,y:s+Math.sin(h)*u}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){let{options:e,circumference:s}=this,n=(e.offset||0)/2,r=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin=e.borderAlign==="inner"?.33:0,this.fullCircles=s>H?Math.floor(s/H):0,s===0||this.innerRadius<0||this.outerRadius<0)return;t.save();let a=0;if(n){a=n/2;let c=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(c)*a,Math.sin(c)*a),this.circumference>=j&&(a=n)}t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor;let l=xd(t,this,a,r,o);wd(t,this,a,r,l,o),t.restore()}};qe.id="arc";qe.defaults={borderAlign:"center",borderColor:"#fff",borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0};qe.defaultRoutes={backgroundColor:"backgroundColor"};function fl(i,t,e=t){i.lineCap=E(e.borderCapStyle,t.borderCapStyle),i.setLineDash(E(e.borderDash,t.borderDash)),i.lineDashOffset=E(e.borderDashOffset,t.borderDashOffset),i.lineJoin=E(e.borderJoinStyle,t.borderJoinStyle),i.lineWidth=E(e.borderWidth,t.borderWidth),i.strokeStyle=E(e.borderColor,t.borderColor)}function Sd(i,t,e){i.lineTo(e.x,e.y)}function kd(i){return i.stepped?Go:i.tension||i.cubicInterpolationMode==="monotone"?Xo:Sd}function ml(i,t,e={}){let s=i.length,{start:n=0,end:r=s-1}=e,{start:o,end:a}=t,l=Math.max(n,o),c=Math.min(r,a),h=na&&r>a;return{count:s,start:l,loop:t.loop,ilen:c(o+(c?a-w:w))%r,_=()=>{g!==p&&(i.lineTo(h,p),i.lineTo(h,g),i.lineTo(h,y))};for(l&&(f=n[b(0)],i.moveTo(f.x,f.y)),d=0;d<=a;++d){if(f=n[b(d)],f.skip)continue;let w=f.x,x=f.y,S=w|0;S===m?(xp&&(p=x),h=(u*h+w)/++u):(_(),i.lineTo(w,x),m=S,u=0,g=p=x),y=x}_()}function _r(i){let t=i.options,e=t.borderDash&&t.borderDash.length;return!i._decimated&&!i._loop&&!t.tension&&t.cubicInterpolationMode!=="monotone"&&!t.stepped&&!e?Td:Md}function vd(i){return i.stepped?ra:i.tension||i.cubicInterpolationMode==="monotone"?oa:qt}function Od(i,t,e,s){let n=t._path;n||(n=t._path=new Path2D,t.path(n,e,s)&&n.closePath()),fl(i,t.options),i.stroke(n)}function Dd(i,t,e,s){let{segments:n,options:r}=t,o=_r(t);for(let a of n)fl(i,r,a.style),i.beginPath(),o(i,t,a,{start:e,end:e+s-1})&&i.closePath(),i.stroke()}var Ed=typeof Path2D=="function";function Id(i,t,e,s){Ed&&!t.options.segment?Od(i,t,e,s):Dd(i,t,e,s)}var Nt=class extends pt{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){let s=this.options;if((s.tension||s.cubicInterpolationMode==="monotone")&&!s.stepped&&!this._pointsUpdated){let n=s.spanGaps?this._loop:this._fullLoop;ia(this._points,s,t,n,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=la(this,this.options.segment))}first(){let t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){let t=this.segments,e=this.points,s=t.length;return s&&e[t[s-1].end]}interpolate(t,e){let s=this.options,n=t[e],r=this.points,o=tr(this,{property:e,start:n,end:n});if(!o.length)return;let a=[],l=vd(s),c,h;for(c=0,h=o.length;ci!=="borderDash"&&i!=="fill"};function La(i,t,e,s){let n=i.options,{[e]:r}=i.getProps([e],s);return Math.abs(t-r)=e)return i.slice(t,t+e);let o=[],a=(e-2)/(r-2),l=0,c=t+e-1,h=t,u,d,f,m,g;for(o[l++]=i[h],u=0;uf&&(f=m,d=i[b],g=b);o[l++]=d,h=g}return o[l++]=i[c],o}function Wd(i,t,e,s){let n=0,r=0,o,a,l,c,h,u,d,f,m,g,p=[],y=t+e-1,b=i[t].x,w=i[y].x-b;for(o=t;og&&(g=c,d=o),n=(r*n+a.x)/++r;else{let S=o-1;if(!P(u)&&!P(d)){let k=Math.min(u,d),v=Math.max(u,d);k!==f&&k!==S&&p.push({...i[k],x:n}),v!==f&&v!==S&&p.push({...i[v],x:n})}o>0&&S!==f&&p.push(i[S]),p.push(a),h=x,r=0,m=g=c,u=d=f=o}}return p}function pl(i){if(i._decimated){let t=i._data;delete i._decimated,delete i._data,Object.defineProperty(i,"data",{value:t})}}function Pa(i){i.data.datasets.forEach(t=>{pl(t)})}function zd(i,t){let e=t.length,s=0,n,{iScale:r}=i,{min:o,max:a,minDefined:l,maxDefined:c}=r.getUserBounds();return l&&(s=tt(Ct(t,r.axis,o).lo,0,e-1)),c?n=tt(Ct(t,r.axis,a).hi+1,s,e)-s:n=e-s,{start:s,count:n}}var Vd={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(i,t,e)=>{if(!e.enabled){Pa(i);return}let s=i.width;i.data.datasets.forEach((n,r)=>{let{_data:o,indexAxis:a}=n,l=i.getDatasetMeta(r),c=o||n.data;if(We([a,i.options.indexAxis])==="y"||!l.controller.supportsDecimation)return;let h=i.scales[l.xAxisID];if(h.type!=="linear"&&h.type!=="time"||i.options.parsing)return;let{start:u,count:d}=zd(l,c),f=e.threshold||4*s;if(d<=f){pl(n);return}P(o)&&(n._data=c,delete n.data,Object.defineProperty(n,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(g){this._data=g}}));let m;switch(e.algorithm){case"lttb":m=Rd(c,u,d,s,e);break;case"min-max":m=Wd(c,u,d,s);break;default:throw new Error(`Unsupported decimation algorithm '${e.algorithm}'`)}n._decimated=m})},destroy(i){Pa(i)}};function Hd(i,t,e){let s=i.segments,n=i.points,r=t.points,o=[];for(let a of s){let{start:l,end:c}=a;c=Mr(l,c,n);let h=wr(e,n[l],n[c],a.loop);if(!t.segments){o.push({source:a,target:h,start:n[l],end:n[c]});continue}let u=tr(t,h);for(let d of u){let f=wr(e,r[d.start],r[d.end],d.loop),m=Qn(a,n,f);for(let g of m)o.push({source:g,target:d,start:{[e]:Na(h,f,"start",Math.max)},end:{[e]:Na(h,f,"end",Math.min)}})}}return o}function wr(i,t,e,s){if(s)return;let n=t[i],r=e[i];return i==="angle"&&(n=ct(n),r=ct(r)),{property:i,start:n,end:r}}function Bd(i,t){let{x:e=null,y:s=null}=i||{},n=t.points,r=[];return t.segments.forEach(({start:o,end:a})=>{a=Mr(o,a,n);let l=n[o],c=n[a];s!==null?(r.push({x:l.x,y:s}),r.push({x:c.x,y:s})):e!==null&&(r.push({x:e,y:l.y}),r.push({x:e,y:c.y}))}),r}function Mr(i,t,e){for(;t>i;t--){let s=e[t];if(!isNaN(s.x)&&!isNaN(s.y))break}return t}function Na(i,t,e,s){return i&&t?s(i[e],t[e]):i?i[e]:t?t[e]:0}function yl(i,t){let e=[],s=!1;return B(i)?(s=!0,e=i):e=Bd(i,t),e.length?new Nt({points:e,options:{tension:0},_loop:s,_fullLoop:s}):null}function Ra(i){return i&&i.fill!==!1}function $d(i,t,e){let n=i[t].fill,r=[t],o;if(!e)return n;for(;n!==!1&&r.indexOf(n)===-1;){if(!q(n))return n;if(o=i[n],!o)return!1;if(o.visible)return n;r.push(n),n=o.fill}return!1}function jd(i,t,e){let s=qd(i);if(F(s))return isNaN(s.value)?!1:s;let n=parseFloat(s);return q(n)&&Math.floor(n)===n?Ud(s[0],t,n,e):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function Ud(i,t,e,s){return(i==="-"||i==="+")&&(e=t+e),e===t||e<0||e>=s?!1:e}function Yd(i,t){let e=null;return i==="start"?e=t.bottom:i==="end"?e=t.top:F(i)?e=t.getPixelForValue(i.value):t.getBasePixel&&(e=t.getBasePixel()),e}function Zd(i,t,e){let s;return i==="start"?s=e:i==="end"?s=t.options.reverse?t.min:t.max:F(i)?s=i.value:s=t.getBaseValue(),s}function qd(i){let t=i.options,e=t.fill,s=E(e&&e.target,e);return s===void 0&&(s=!!t.backgroundColor),s===!1||s===null?!1:s===!0?"origin":s}function Gd(i){let{scale:t,index:e,line:s}=i,n=[],r=s.segments,o=s.points,a=Xd(t,e);a.push(yl({x:null,y:t.bottom},s));for(let l=0;l=0;--o){let a=n[o].$filler;a&&(a.line.updateControlPoints(r,a.axis),s&&a.fill&&cr(i.ctx,a,r))}},beforeDatasetsDraw(i,t,e){if(e.drawTime!=="beforeDatasetsDraw")return;let s=i.getSortedVisibleDatasetMetas();for(let n=s.length-1;n>=0;--n){let r=s[n].$filler;Ra(r)&&cr(i.ctx,r,i.chartArea)}},beforeDatasetDraw(i,t,e){let s=t.meta.$filler;!Ra(s)||e.drawTime!=="beforeDatasetDraw"||cr(i.ctx,s,i.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}},Ha=(i,t)=>{let{boxHeight:e=t,boxWidth:s=t}=i;return i.usePointStyle&&(e=Math.min(e,t),s=i.pointStyleWidth||Math.min(s,t)),{boxWidth:s,boxHeight:e,itemHeight:Math.max(t,e)}},lf=(i,t)=>i!==null&&t!==null&&i.datasetIndex===t.datasetIndex&&i.index===t.index,qs=class extends pt{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,s){this.maxWidth=t,this.maxHeight=e,this._margins=s,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){let t=this.options.labels||{},e=$(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter(s=>t.filter(s,this.chart.data))),t.sort&&(e=e.sort((s,n)=>t.sort(s,n,this.chart.data))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){let{options:t,ctx:e}=this;if(!t.display){this.width=this.height=0;return}let s=t.labels,n=Q(s.font),r=n.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=Ha(s,r),c,h;e.font=n.string,this.isHorizontal()?(c=this.maxWidth,h=this._fitRows(o,r,a,l)+10):(h=this.maxHeight,c=this._fitCols(o,r,a,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,s,n){let{ctx:r,maxWidth:o,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],h=n+a,u=t;r.textAlign="left",r.textBaseline="middle";let d=-1,f=-h;return this.legendItems.forEach((m,g)=>{let p=s+e/2+r.measureText(m.text).width;(g===0||c[c.length-1]+p+2*a>o)&&(u+=h,c[c.length-(g>0?0:1)]=0,f+=h,d++),l[g]={left:0,top:f,row:d,width:p,height:n},c[c.length-1]+=p+a}),u}_fitCols(t,e,s,n){let{ctx:r,maxHeight:o,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],h=o-t,u=a,d=0,f=0,m=0,g=0;return this.legendItems.forEach((p,y)=>{let b=s+e/2+r.measureText(p.text).width;y>0&&f+n+2*a>h&&(u+=d+a,c.push({width:d,height:f}),m+=d+a,g++,d=f=0),l[y]={left:m,top:f,col:g,width:b,height:n},d=Math.max(d,b),f+=n+a}),u+=d,c.push({width:d,height:f}),u}adjustHitBoxes(){if(!this.options.display)return;let t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:s,labels:{padding:n},rtl:r}}=this,o=pe(r,this.left,this.width);if(this.isHorizontal()){let a=0,l=nt(s,this.left+n,this.right-this.lineWidths[a]);for(let c of e)a!==c.row&&(a=c.row,l=nt(s,this.left+n,this.right-this.lineWidths[a])),c.top+=this.top+t+n,c.left=o.leftForLtr(o.x(l),c.width),l+=c.width+n}else{let a=0,l=nt(s,this.top+t+n,this.bottom-this.columnSizes[a].height);for(let c of e)c.col!==a&&(a=c.col,l=nt(s,this.top+t+n,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+n,c.left=o.leftForLtr(o.x(c.left),c.width),l+=c.height+n}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){let t=this.ctx;wi(t,this),this._draw(),Si(t)}}_draw(){let{options:t,columnSizes:e,lineWidths:s,ctx:n}=this,{align:r,labels:o}=t,a=A.color,l=pe(t.rtl,this.left,this.width),c=Q(o.font),{color:h,padding:u}=o,d=c.size,f=d/2,m;this.drawTitle(),n.textAlign=l.textAlign("left"),n.textBaseline="middle",n.lineWidth=.5,n.font=c.string;let{boxWidth:g,boxHeight:p,itemHeight:y}=Ha(o,d),b=function(k,v,T){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;n.save();let C=E(T.lineWidth,1);if(n.fillStyle=E(T.fillStyle,a),n.lineCap=E(T.lineCap,"butt"),n.lineDashOffset=E(T.lineDashOffset,0),n.lineJoin=E(T.lineJoin,"miter"),n.lineWidth=C,n.strokeStyle=E(T.strokeStyle,a),n.setLineDash(E(T.lineDash,[])),o.usePointStyle){let N={radius:p*Math.SQRT2/2,pointStyle:T.pointStyle,rotation:T.rotation,borderWidth:C},L=l.xPlus(k,g/2),K=v+f;Bn(n,N,L,K,o.pointStyleWidth&&g)}else{let N=v+Math.max((d-p)/2,0),L=l.leftForLtr(k,g),K=te(T.borderRadius);n.beginPath(),Object.values(K).some(lt=>lt!==0)?Re(n,{x:L,y:N,w:g,h:p,radius:K}):n.rect(L,N,g,p),n.fill(),C!==0&&n.stroke()}n.restore()},_=function(k,v,T){Qt(n,T.text,k,v+y/2,c,{strikethrough:T.hidden,textAlign:l.textAlign(T.textAlign)})},w=this.isHorizontal(),x=this._computeTitleHeight();w?m={x:nt(r,this.left+u,this.right-s[0]),y:this.top+u+x,line:0}:m={x:this.left+u,y:nt(r,this.top+x+u,this.bottom-e[0].height),line:0},Kn(this.ctx,t.textDirection);let S=y+u;this.legendItems.forEach((k,v)=>{n.strokeStyle=k.fontColor||h,n.fillStyle=k.fontColor||h;let T=n.measureText(k.text).width,C=l.textAlign(k.textAlign||(k.textAlign=o.textAlign)),N=g+f+T,L=m.x,K=m.y;l.setWidth(this.width),w?v>0&&L+N+u>this.right&&(K=m.y+=S,m.line++,L=m.x=nt(r,this.left+u,this.right-s[m.line])):v>0&&K+S>this.bottom&&(L=m.x=L+e[m.line].width+u,m.line++,K=m.y=nt(r,this.top+x+u,this.bottom-e[m.line].height));let lt=l.x(L);b(lt,K,k),L=Bo(C,L+g+f,w?L+N:this.right,t.rtl),_(l.x(L),K,k),w?m.x+=N+u:m.y+=S}),Jn(this.ctx,t.textDirection)}drawTitle(){let t=this.options,e=t.title,s=Q(e.font),n=rt(e.padding);if(!e.display)return;let r=pe(t.rtl,this.left,this.width),o=this.ctx,a=e.position,l=s.size/2,c=n.top+l,h,u=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+c,u=nt(t.align,u,this.right-d);else{let m=this.columnSizes.reduce((g,p)=>Math.max(g,p.height),0);h=c+nt(t.align,this.top,this.bottom-m-t.labels.padding-this._computeTitleHeight())}let f=nt(a,u,u+d);o.textAlign=r.textAlign(Es(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=s.string,Qt(o,e.text,f,h,s)}_computeTitleHeight(){let t=this.options.title,e=Q(t.font),s=rt(t.padding);return t.display?e.lineHeight+s.height:0}_getLegendItemAt(t,e){let s,n,r;if(At(t,this.left,this.right)&&At(e,this.top,this.bottom)){for(r=this.legendHitBoxes,s=0;si.chart.options.color,boxWidth:40,padding:10,generateLabels(i){let t=i.data.datasets,{labels:{usePointStyle:e,pointStyle:s,textAlign:n,color:r}}=i.legend.options;return i._getSortedDatasetMetas().map(o=>{let a=o.controller.getStyle(e?0:void 0),l=rt(a.borderWidth);return{text:t[o.index].label,fillStyle:a.backgroundColor,fontColor:r,hidden:!o.visible,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:(l.width+l.height)/4,strokeStyle:a.borderColor,pointStyle:s||a.pointStyle,rotation:a.rotation,textAlign:n||a.textAlign,borderRadius:0,datasetIndex:o.index}},this)}},title:{color:i=>i.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:i=>!i.startsWith("on"),labels:{_scriptable:i=>!["generateLabels","filter","sort"].includes(i)}}},Ai=class extends pt{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){let s=this.options;if(this.left=0,this.top=0,!s.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=e;let n=B(s.text)?s.text.length:1;this._padding=rt(s.padding);let r=n*Q(s.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=r:this.width=r}isHorizontal(){let t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){let{top:e,left:s,bottom:n,right:r,options:o}=this,a=o.align,l=0,c,h,u;return this.isHorizontal()?(h=nt(a,s,r),u=e+t,c=r-s):(o.position==="left"?(h=s+t,u=nt(a,n,e),l=j*-.5):(h=r-t,u=nt(a,e,n),l=j*.5),c=n-e),{titleX:h,titleY:u,maxWidth:c,rotation:l}}draw(){let t=this.ctx,e=this.options;if(!e.display)return;let s=Q(e.font),r=s.lineHeight/2+this._padding.top,{titleX:o,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(r);Qt(t,e.text,0,0,s,{color:e.color,maxWidth:l,rotation:c,textAlign:Es(e.align),textBaseline:"middle",translation:[o,a]})}};function uf(i,t){let e=new Ai({ctx:i.ctx,options:t,chart:i});ot.configure(i,e,t),ot.addBox(i,e),i.titleBlock=e}var df={id:"title",_element:Ai,start(i,t,e){uf(i,e)},stop(i){let t=i.titleBlock;ot.removeBox(i,t),delete i.titleBlock},beforeUpdate(i,t,e){let s=i.titleBlock;ot.configure(i,s,e),s.options=e},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}},zs=new WeakMap,ff={id:"subtitle",start(i,t,e){let s=new Ai({ctx:i.ctx,options:e,chart:i});ot.configure(i,s,e),ot.addBox(i,s),zs.set(i,s)},stop(i){ot.removeBox(i,zs.get(i)),zs.delete(i)},beforeUpdate(i,t,e){let s=zs.get(i);ot.configure(i,s,e),s.options=e},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}},Ei={average(i){if(!i.length)return!1;let t,e,s=0,n=0,r=0;for(t=0,e=i.length;t-1?i.split(` -`):i}function mf(i,t){let{element:e,datasetIndex:s,index:n}=t,r=i.getDatasetMeta(s).controller,{label:o,value:a}=r.getLabelAndValue(n);return{chart:i,label:o,parsed:r.getParsed(n),raw:i.data.datasets[s].data[n],formattedValue:a,dataset:r.getDataset(),dataIndex:n,datasetIndex:s,element:e}}function Ba(i,t){let e=i.chart.ctx,{body:s,footer:n,title:r}=i,{boxWidth:o,boxHeight:a}=t,l=Q(t.bodyFont),c=Q(t.titleFont),h=Q(t.footerFont),u=r.length,d=n.length,f=s.length,m=rt(t.padding),g=m.height,p=0,y=s.reduce((w,x)=>w+x.before.length+x.lines.length+x.after.length,0);if(y+=i.beforeBody.length+i.afterBody.length,u&&(g+=u*c.lineHeight+(u-1)*t.titleSpacing+t.titleMarginBottom),y){let w=t.displayColors?Math.max(a,l.lineHeight):l.lineHeight;g+=f*w+(y-f)*l.lineHeight+(y-1)*t.bodySpacing}d&&(g+=t.footerMarginTop+d*h.lineHeight+(d-1)*t.footerSpacing);let b=0,_=function(w){p=Math.max(p,e.measureText(w).width+b)};return e.save(),e.font=c.string,V(i.title,_),e.font=l.string,V(i.beforeBody.concat(i.afterBody),_),b=t.displayColors?o+2+t.boxPadding:0,V(s,w=>{V(w.before,_),V(w.lines,_),V(w.after,_)}),b=0,e.font=h.string,V(i.footer,_),e.restore(),p+=m.width,{width:p,height:g}}function gf(i,t){let{y:e,height:s}=t;return ei.height-s/2?"bottom":"center"}function pf(i,t,e,s){let{x:n,width:r}=s,o=e.caretSize+e.caretPadding;if(i==="left"&&n+r+o>t.width||i==="right"&&n-r-o<0)return!0}function yf(i,t,e,s){let{x:n,width:r}=e,{width:o,chartArea:{left:a,right:l}}=i,c="center";return s==="center"?c=n<=(a+l)/2?"left":"right":n<=r/2?c="left":n>=o-r/2&&(c="right"),pf(c,i,t,e)&&(c="center"),c}function $a(i,t,e){let s=e.yAlign||t.yAlign||gf(i,e);return{xAlign:e.xAlign||t.xAlign||yf(i,t,e,s),yAlign:s}}function bf(i,t){let{x:e,width:s}=i;return t==="right"?e-=s:t==="center"&&(e-=s/2),e}function xf(i,t,e){let{y:s,height:n}=i;return t==="top"?s+=e:t==="bottom"?s-=n+e:s-=n/2,s}function ja(i,t,e,s){let{caretSize:n,caretPadding:r,cornerRadius:o}=i,{xAlign:a,yAlign:l}=e,c=n+r,{topLeft:h,topRight:u,bottomLeft:d,bottomRight:f}=te(o),m=bf(t,a),g=xf(t,l,c);return l==="center"?a==="left"?m+=c:a==="right"&&(m-=c):a==="left"?m-=Math.max(h,d)+n:a==="right"&&(m+=Math.max(u,f)+n),{x:tt(m,0,s.width-t.width),y:tt(g,0,s.height-t.height)}}function Vs(i,t,e){let s=rt(e.padding);return t==="center"?i.x+i.width/2:t==="right"?i.x+i.width-s.right:i.x+s.left}function Ua(i){return Lt([],$t(i))}function _f(i,t,e){return Ht(i,{tooltip:t,tooltipItems:e,type:"tooltip"})}function Ya(i,t){let e=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return e?i.override(e):i}var Li=class extends pt{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart||t._chart,this._chart=this.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){let t=this._cachedAnimations;if(t)return t;let e=this.chart,s=this.options.setContext(this.getContext()),n=s.enabled&&e.options.animation&&s.animations,r=new $s(this.chart,n);return n._cacheable&&(this._cachedAnimations=Object.freeze(r)),r}getContext(){return this.$context||(this.$context=_f(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,e){let{callbacks:s}=e,n=s.beforeTitle.apply(this,[t]),r=s.title.apply(this,[t]),o=s.afterTitle.apply(this,[t]),a=[];return a=Lt(a,$t(n)),a=Lt(a,$t(r)),a=Lt(a,$t(o)),a}getBeforeBody(t,e){return Ua(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){let{callbacks:s}=e,n=[];return V(t,r=>{let o={before:[],lines:[],after:[]},a=Ya(s,r);Lt(o.before,$t(a.beforeLabel.call(this,r))),Lt(o.lines,a.label.call(this,r)),Lt(o.after,$t(a.afterLabel.call(this,r))),n.push(o)}),n}getAfterBody(t,e){return Ua(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){let{callbacks:s}=e,n=s.beforeFooter.apply(this,[t]),r=s.footer.apply(this,[t]),o=s.afterFooter.apply(this,[t]),a=[];return a=Lt(a,$t(n)),a=Lt(a,$t(r)),a=Lt(a,$t(o)),a}_createItems(t){let e=this._active,s=this.chart.data,n=[],r=[],o=[],a=[],l,c;for(l=0,c=e.length;lt.filter(h,u,d,s))),t.itemSort&&(a=a.sort((h,u)=>t.itemSort(h,u,s))),V(a,h=>{let u=Ya(t.callbacks,h);n.push(u.labelColor.call(this,h)),r.push(u.labelPointStyle.call(this,h)),o.push(u.labelTextColor.call(this,h))}),this.labelColors=n,this.labelPointStyles=r,this.labelTextColors=o,this.dataPoints=a,a}update(t,e){let s=this.options.setContext(this.getContext()),n=this._active,r,o=[];if(!n.length)this.opacity!==0&&(r={opacity:0});else{let a=Ei[s.position].call(this,n,this._eventPosition);o=this._createItems(s),this.title=this.getTitle(o,s),this.beforeBody=this.getBeforeBody(o,s),this.body=this.getBody(o,s),this.afterBody=this.getAfterBody(o,s),this.footer=this.getFooter(o,s);let l=this._size=Ba(this,s),c=Object.assign({},a,l),h=$a(this.chart,s,c),u=ja(s,c,h,this.chart);this.xAlign=h.xAlign,this.yAlign=h.yAlign,r={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=o,this.$context=void 0,r&&this._resolveAnimations().update(this,r),t&&s.external&&s.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,s,n){let r=this.getCaretPosition(t,s,n);e.lineTo(r.x1,r.y1),e.lineTo(r.x2,r.y2),e.lineTo(r.x3,r.y3)}getCaretPosition(t,e,s){let{xAlign:n,yAlign:r}=this,{caretSize:o,cornerRadius:a}=s,{topLeft:l,topRight:c,bottomLeft:h,bottomRight:u}=te(a),{x:d,y:f}=t,{width:m,height:g}=e,p,y,b,_,w,x;return r==="center"?(w=f+g/2,n==="left"?(p=d,y=p-o,_=w+o,x=w-o):(p=d+m,y=p+o,_=w-o,x=w+o),b=p):(n==="left"?y=d+Math.max(l,h)+o:n==="right"?y=d+m-Math.max(c,u)-o:y=this.caretX,r==="top"?(_=f,w=_-o,p=y-o,b=y+o):(_=f+g,w=_+o,p=y+o,b=y-o),x=_),{x1:p,x2:y,x3:b,y1:_,y2:w,y3:x}}drawTitle(t,e,s){let n=this.title,r=n.length,o,a,l;if(r){let c=pe(s.rtl,this.x,this.width);for(t.x=Vs(this,s.titleAlign,s),e.textAlign=c.textAlign(s.titleAlign),e.textBaseline="middle",o=Q(s.titleFont),a=s.titleSpacing,e.fillStyle=s.titleColor,e.font=o.string,l=0;l_!==0)?(t.beginPath(),t.fillStyle=r.multiKeyBackground,Re(t,{x:p,y:g,w:c,h:l,radius:b}),t.fill(),t.stroke(),t.fillStyle=o.backgroundColor,t.beginPath(),Re(t,{x:y,y:g+1,w:c-2,h:l-2,radius:b}),t.fill()):(t.fillStyle=r.multiKeyBackground,t.fillRect(p,g,c,l),t.strokeRect(p,g,c,l),t.fillStyle=o.backgroundColor,t.fillRect(y,g+1,c-2,l-2))}t.fillStyle=this.labelTextColors[s]}drawBody(t,e,s){let{body:n}=this,{bodySpacing:r,bodyAlign:o,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:h}=s,u=Q(s.bodyFont),d=u.lineHeight,f=0,m=pe(s.rtl,this.x,this.width),g=function(v){e.fillText(v,m.x(t.x+f),t.y+d/2),t.y+=d+r},p=m.textAlign(o),y,b,_,w,x,S,k;for(e.textAlign=o,e.textBaseline="middle",e.font=u.string,t.x=Vs(this,p,s),e.fillStyle=s.bodyColor,V(this.beforeBody,g),f=a&&p!=="right"?o==="center"?c/2+h:c+2+h:0,w=0,S=n.length;w0&&e.stroke()}_updateAnimationTarget(t){let e=this.chart,s=this.$animations,n=s&&s.x,r=s&&s.y;if(n||r){let o=Ei[t.position].call(this,this._active,this._eventPosition);if(!o)return;let a=this._size=Ba(this,t),l=Object.assign({},o,this._size),c=$a(e,t,l),h=ja(t,l,c,e);(n._to!==h.x||r._to!==h.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=o.x,this.caretY=o.y,this._resolveAnimations().update(this,h))}}_willRender(){return!!this.opacity}draw(t){let e=this.options.setContext(this.getContext()),s=this.opacity;if(!s)return;this._updateAnimationTarget(e);let n={width:this.width,height:this.height},r={x:this.x,y:this.y};s=Math.abs(s)<.001?0:s;let o=rt(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=s,this.drawBackground(r,t,n,e),Kn(t,e.textDirection),r.y+=o.top,this.drawTitle(r,t,e),this.drawBody(r,t,e),this.drawFooter(r,t,e),Jn(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){let s=this._active,n=t.map(({datasetIndex:a,index:l})=>{let c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),r=!xi(s,n),o=this._positionChanged(n,e);(r||o)&&(this._active=n,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,s=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;let n=this.options,r=this._active||[],o=this._getActiveElements(t,r,e,s),a=this._positionChanged(o,t),l=e||!xi(o,r)||a;return l&&(this._active=o,(n.enabled||n.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),l}_getActiveElements(t,e,s,n){let r=this.options;if(t.type==="mouseout")return[];if(!n)return e;let o=this.chart.getElementsAtEventForMode(t,r.mode,r,s);return r.reverse&&o.reverse(),o}_positionChanged(t,e){let{caretX:s,caretY:n,options:r}=this,o=Ei[r.position].call(this,t,e);return o!==!1&&(s!==o.x||n!==o.y)}};Li.positioners=Ei;var wf={id:"tooltip",_element:Li,positioners:Ei,afterInit(i,t,e){e&&(i.tooltip=new Li({chart:i,options:e}))},beforeUpdate(i,t,e){i.tooltip&&i.tooltip.initialize(e)},reset(i,t,e){i.tooltip&&i.tooltip.initialize(e)},afterDraw(i){let t=i.tooltip;if(t&&t._willRender()){let e={tooltip:t};if(i.notifyPlugins("beforeTooltipDraw",e)===!1)return;t.draw(i.ctx),i.notifyPlugins("afterTooltipDraw",e)}},afterEvent(i,t){if(i.tooltip){let e=t.replay;i.tooltip.handleEvent(t.event,e,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(i,t)=>t.bodyFont.size,boxWidth:(i,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:Ft,title(i){if(i.length>0){let t=i[0],e=t.chart.data.labels,s=e?e.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(s>0&&t.dataIndexi!=="filter"&&i!=="itemSort"&&i!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},Sf=Object.freeze({__proto__:null,Decimation:Vd,Filler:af,Legend:hf,SubTitle:ff,Title:df,Tooltip:wf}),kf=(i,t,e,s)=>(typeof t=="string"?(e=i.push(t)-1,s.unshift({index:e,label:t})):isNaN(t)&&(e=null),e);function Mf(i,t,e,s){let n=i.indexOf(t);if(n===-1)return kf(i,t,e,s);let r=i.lastIndexOf(t);return n!==r?e:n}var Tf=(i,t)=>i===null?null:tt(Math.round(i),0,t),Ke=class extends be{constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){let e=this._addedLabels;if(e.length){let s=this.getLabels();for(let{index:n,label:r}of e)s[n]===r&&s.splice(n,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(P(t))return null;let s=this.getLabels();return e=isFinite(e)&&s[e]===t?e:Mf(s,t,E(e,t),this._addedLabels),Tf(e,s.length-1)}determineDataLimits(){let{minDefined:t,maxDefined:e}=this.getUserBounds(),{min:s,max:n}=this.getMinMax(!0);this.options.bounds==="ticks"&&(t||(s=0),e||(n=this.getLabels().length-1)),this.min=s,this.max=n}buildTicks(){let t=this.min,e=this.max,s=this.options.offset,n=[],r=this.getLabels();r=t===0&&e===r.length-1?r:r.slice(t,e+1),this._valueRange=Math.max(r.length-(s?0:1),1),this._startValue=this.min-(s?.5:0);for(let o=t;o<=e;o++)n.push({value:o});return n}getLabelForValue(t){let e=this.getLabels();return t>=0&&te.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}};Ke.id="category";Ke.defaults={ticks:{callback:Ke.prototype.getLabelForValue}};function vf(i,t){let e=[],{bounds:n,step:r,min:o,max:a,precision:l,count:c,maxTicks:h,maxDigits:u,includeBounds:d}=i,f=r||1,m=h-1,{min:g,max:p}=t,y=!P(o),b=!P(a),_=!P(c),w=(p-g)/(u+1),x=On((p-g)/m/f)*f,S,k,v,T;if(x<1e-14&&!y&&!b)return[{value:g},{value:p}];T=Math.ceil(p/x)-Math.floor(g/x),T>m&&(x=On(T*x/m/f)*f),P(l)||(S=Math.pow(10,l),x=Math.ceil(x*S)/S),n==="ticks"?(k=Math.floor(g/x)*x,v=Math.ceil(p/x)*x):(k=g,v=p),y&&b&&r&&Po((a-o)/r,x/1e3)?(T=Math.round(Math.min((a-o)/x,h)),x=(a-o)/T,k=o,v=a):_?(k=y?o:k,v=b?a:v,T=c-1,x=(v-k)/T):(T=(v-k)/x,Pe(T,Math.round(T),x/1e3)?T=Math.round(T):T=Math.ceil(T));let C=Math.max(En(x),En(k));S=Math.pow(10,P(l)?C:l),k=Math.round(k*S)/S,v=Math.round(v*S)/S;let N=0;for(y&&(d&&k!==o?(e.push({value:o}),kn=e?n:l,a=l=>r=s?r:l;if(t){let l=Mt(n),c=Mt(r);l<0&&c<0?a(0):l>0&&c>0&&o(0)}if(n===r){let l=1;(r>=Number.MAX_SAFE_INTEGER||n<=Number.MIN_SAFE_INTEGER)&&(l=Math.abs(r*.05)),a(r+l),t||o(n-l)}this.min=n,this.max=r}getTickLimit(){let t=this.options.ticks,{maxTicksLimit:e,stepSize:s}=t,n;return s?(n=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,n>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${n} ticks. Limiting to 1000.`),n=1e3)):(n=this.computeTickLimit(),e=e||11),e&&(n=Math.min(e,n)),n}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){let t=this.options,e=t.ticks,s=this.getTickLimit();s=Math.max(2,s);let n={maxTicks:s,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:e.includeBounds!==!1},r=this._range||this,o=vf(n,r);return t.bounds==="ticks"&&Dn(o,this,"value"),t.reverse?(o.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),o}configure(){let t=this.ticks,e=this.min,s=this.max;if(super.configure(),this.options.offset&&t.length){let n=(s-e)/Math.max(t.length-1,1)/2;e-=n,s+=n}this._startValue=e,this._endValue=s,this._valueRange=s-e}getLabelForValue(t){return ze(t,this.chart.options.locale,this.options.ticks.format)}},Pi=class extends Je{determineDataLimits(){let{min:t,max:e}=this.getMinMax(!0);this.min=q(t)?t:0,this.max=q(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){let t=this.isHorizontal(),e=t?this.width:this.height,s=_t(this.options.ticks.minRotation),n=(t?Math.sin(s):Math.cos(s))||.001,r=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,r.lineHeight/n))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}};Pi.id="linear";Pi.defaults={ticks:{callback:Gs.formatters.numeric}};function qa(i){return i/Math.pow(10,Math.floor(mt(i)))===1}function Of(i,t){let e=Math.floor(mt(t.max)),s=Math.ceil(t.max/Math.pow(10,e)),n=[],r=ft(i.min,Math.pow(10,Math.floor(mt(t.min)))),o=Math.floor(mt(r)),a=Math.floor(r/Math.pow(10,o)),l=o<0?Math.pow(10,Math.abs(o)):1;do n.push({value:r,major:qa(r)}),++a,a===10&&(a=1,++o,l=o>=0?1:l),r=Math.round(a*Math.pow(10,o)*l)/l;while(o0?s:null}determineDataLimits(){let{min:t,max:e}=this.getMinMax(!0);this.min=q(t)?Math.max(0,t):null,this.max=q(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this.handleTickRangeOptions()}handleTickRangeOptions(){let{minDefined:t,maxDefined:e}=this.getUserBounds(),s=this.min,n=this.max,r=l=>s=t?s:l,o=l=>n=e?n:l,a=(l,c)=>Math.pow(10,Math.floor(mt(l))+c);s===n&&(s<=0?(r(1),o(10)):(r(a(s,-1)),o(a(n,1)))),s<=0&&r(a(n,-1)),n<=0&&o(a(s,1)),this._zero&&this.min!==this._suggestedMin&&s===a(this.min,0)&&r(a(s,-1)),this.min=s,this.max=n}buildTicks(){let t=this.options,e={min:this._userMin,max:this._userMax},s=Of(e,this);return t.bounds==="ticks"&&Dn(s,this,"value"),t.reverse?(s.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),s}getLabelForValue(t){return t===void 0?"0":ze(t,this.chart.options.locale,this.options.ticks.format)}configure(){let t=this.min;super.configure(),this._startValue=mt(t),this._valueRange=mt(this.max)-mt(t)}getPixelForValue(t){return(t===void 0||t===0)&&(t=this.min),t===null||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(mt(t)-this._startValue)/this._valueRange)}getValueForPixel(t){let e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}};Ni.id="logarithmic";Ni.defaults={ticks:{callback:Gs.formatters.logarithmic,major:{enabled:!0}}};function Sr(i){let t=i.ticks;if(t.display&&i.display){let e=rt(t.backdropPadding);return E(t.font&&t.font.size,A.font.size)+e.height}return 0}function Df(i,t,e){return e=B(e)?e:[e],{w:qo(i,t.string,e),h:e.length*t.lineHeight}}function Ga(i,t,e,s,n){return i===s||i===n?{start:t-e/2,end:t+e/2}:in?{start:t-e,end:t}:{start:t,end:t+e}}function Ef(i){let t={l:i.left+i._padding.left,r:i.right-i._padding.right,t:i.top+i._padding.top,b:i.bottom-i._padding.bottom},e=Object.assign({},t),s=[],n=[],r=i._pointLabels.length,o=i.options.pointLabels,a=o.centerPointLabels?j/r:0;for(let l=0;lt.r&&(a=(s.end-t.r)/r,i.r=Math.max(i.r,t.r+a)),n.startt.b&&(l=(n.end-t.b)/o,i.b=Math.max(i.b,t.b+l))}function Cf(i,t,e){let s=[],n=i._pointLabels.length,r=i.options,o=Sr(r)/2,a=i.drawingArea,l=r.pointLabels.centerPointLabels?j/n:0;for(let c=0;c270||e<90)&&(i-=t),i}function Pf(i,t){let{ctx:e,options:{pointLabels:s}}=i;for(let n=t-1;n>=0;n--){let r=s.setContext(i.getPointLabelContext(n)),o=Q(r.font),{x:a,y:l,textAlign:c,left:h,top:u,right:d,bottom:f}=i._pointLabelItems[n],{backdropColor:m}=r;if(!P(m)){let g=te(r.borderRadius),p=rt(r.backdropPadding);e.fillStyle=m;let y=h-p.left,b=u-p.top,_=d-h+p.width,w=f-u+p.height;Object.values(g).some(x=>x!==0)?(e.beginPath(),Re(e,{x:y,y:b,w:_,h:w,radius:g}),e.fill()):e.fillRect(y,b,_,w)}Qt(e,i._pointLabels[n],a,l+o.lineHeight/2,o,{color:r.color,textAlign:c,textBaseline:"middle"})}}function bl(i,t,e,s){let{ctx:n}=i;if(e)n.arc(i.xCenter,i.yCenter,t,0,H);else{let r=i.getPointPosition(0,t);n.moveTo(r.x,r.y);for(let o=1;o{let n=$(this.options.pointLabels.callback,[e,s],this);return n||n===0?n:""}).filter((e,s)=>this.chart.getDataVisibility(s))}fit(){let t=this.options;t.display&&t.pointLabels.display?Ef(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,s,n){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((s-n)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,s,n))}getIndexAngle(t){let e=H/(this._pointLabels.length||1),s=this.options.startAngle||0;return ct(t*e+_t(s))}getDistanceFromCenterForValue(t){if(P(t))return NaN;let e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(P(t))return NaN;let e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){let e=this._pointLabels||[];if(t>=0&&t{if(h!==0){a=this.getDistanceFromCenterForValue(c.value);let u=n.setContext(this.getContext(h-1));Nf(this,u,a,r)}}),s.display){for(t.save(),o=r-1;o>=0;o--){let c=s.setContext(this.getPointLabelContext(o)),{color:h,lineWidth:u}=c;!u||!h||(t.lineWidth=u,t.strokeStyle=h,t.setLineDash(c.borderDash),t.lineDashOffset=c.borderDashOffset,a=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),l=this.getPointPosition(o,a),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){let t=this.ctx,e=this.options,s=e.ticks;if(!s.display)return;let n=this.getIndexAngle(0),r,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(n),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach((a,l)=>{if(l===0&&!e.reverse)return;let c=s.setContext(this.getContext(l)),h=Q(c.font);if(r=this.getDistanceFromCenterForValue(this.ticks[l].value),c.showLabelBackdrop){t.font=h.string,o=t.measureText(a.label).width,t.fillStyle=c.backdropColor;let u=rt(c.backdropPadding);t.fillRect(-o/2-u.left,-r-h.size/2-u.top,o+u.width,h.size+u.height)}Qt(t,a.label,0,-r,h,{color:c.color})}),t.restore()}drawTitle(){}};_e.id="radialLinear";_e.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Gs.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(i){return i},padding:5,centerPointLabels:!1}};_e.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};_e.descriptors={angleLines:{_fallback:"grid"}};var Xs={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ht=Object.keys(Xs);function Wf(i,t){return i-t}function Xa(i,t){if(P(t))return null;let e=i._adapter,{parser:s,round:n,isoWeekday:r}=i._parseOpts,o=t;return typeof s=="function"&&(o=s(o)),q(o)||(o=typeof s=="string"?e.parse(o,s):e.parse(o)),o===null?null:(n&&(o=n==="week"&&(ge(r)||r===!0)?e.startOf(o,"isoWeek",r):e.startOf(o,n)),+o)}function Ka(i,t,e,s){let n=ht.length;for(let r=ht.indexOf(i);r=ht.indexOf(e);r--){let o=ht[r];if(Xs[o].common&&i._adapter.diff(n,s,o)>=t-1)return o}return ht[e?ht.indexOf(e):0]}function Vf(i){for(let t=ht.indexOf(i)+1,e=ht.length;t=t?e[s]:e[n];i[r]=!0}}function Hf(i,t,e,s){let n=i._adapter,r=+n.startOf(t[0].value,s),o=t[t.length-1].value,a,l;for(a=r;a<=o;a=+n.add(a,1,s))l=e[a],l>=0&&(t[l].major=!0);return t}function Qa(i,t,e){let s=[],n={},r=t.length,o,a;for(o=0;o+t.value))}initOffsets(t){let e=0,s=0,n,r;this.options.offset&&t.length&&(n=this.getDecimalForValue(t[0]),t.length===1?e=1-n:e=(this.getDecimalForValue(t[1])-n)/2,r=this.getDecimalForValue(t[t.length-1]),t.length===1?s=r:s=(r-this.getDecimalForValue(t[t.length-2]))/2);let o=t.length<3?.5:.25;e=tt(e,0,o),s=tt(s,0,o),this._offsets={start:e,end:s,factor:1/(e+1+s)}}_generate(){let t=this._adapter,e=this.min,s=this.max,n=this.options,r=n.time,o=r.unit||Ka(r.minUnit,e,s,this._getLabelCapacity(e)),a=E(r.stepSize,1),l=o==="week"?r.isoWeekday:!1,c=ge(l)||l===!0,h={},u=e,d,f;if(c&&(u=+t.startOf(u,"isoWeek",l)),u=+t.startOf(u,c?"day":o),t.diff(s,e,o)>1e5*a)throw new Error(e+" and "+s+" are too far apart with stepSize of "+a+" "+o);let m=n.ticks.source==="data"&&this.getDataTimestamps();for(d=u,f=0;dg-p).map(g=>+g)}getLabelForValue(t){let e=this._adapter,s=this.options.time;return s.tooltipFormat?e.format(t,s.tooltipFormat):e.format(t,s.displayFormats.datetime)}_tickFormatFunction(t,e,s,n){let r=this.options,o=r.time.displayFormats,a=this._unit,l=this._majorUnit,c=a&&o[a],h=l&&o[l],u=s[e],d=l&&h&&u&&u.major,f=this._adapter.format(t,n||(d?h:c)),m=r.ticks.callback;return m?$(m,[f,e,s],this):f}generateTickLabels(t){let e,s,n;for(e=0,s=t.length;e0?a:1}getDataTimestamps(){let t=this._cache.data||[],e,s;if(t.length)return t;let n=this.getMatchingVisibleMetas();if(this._normalized&&n.length)return this._cache.data=n[0].controller.getAllParsedValues(this);for(e=0,s=n.length;e=i[s].pos&&t<=i[n].pos&&({lo:s,hi:n}=Ct(i,"pos",t)),{pos:r,time:a}=i[s],{pos:o,time:l}=i[n]):(t>=i[s].time&&t<=i[n].time&&({lo:s,hi:n}=Ct(i,"time",t)),{time:r,pos:a}=i[s],{time:o,pos:l}=i[n]);let c=o-r;return c?a+(l-a)*(t-r)/c:a}var Ri=class extends we{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){let t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Hs(e,this.min),this._tableRange=Hs(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){let{min:e,max:s}=this,n=[],r=[],o,a,l,c,h;for(o=0,a=t.length;o=e&&c<=s&&n.push(c);if(n.length<2)return[{time:e,pos:0},{time:s,pos:1}];for(o=0,a=n.length;o=0?m:1e3+m,(d-f)/(60*1e3)}equals(t){return t.type==="iana"&&t.name===this.name}get isValid(){return this.valid}};var _l={};function Zf(i,t={}){let e=JSON.stringify([i,t]),s=_l[e];return s||(s=new Intl.ListFormat(i,t),_l[e]=s),s}var Ir=new Map;function Cr(i,t={}){let e=JSON.stringify([i,t]),s=Ir.get(e);return s===void 0&&(s=new Intl.DateTimeFormat(i,t),Ir.set(e,s)),s}var Fr=new Map;function qf(i,t={}){let e=JSON.stringify([i,t]),s=Fr.get(e);return s===void 0&&(s=new Intl.NumberFormat(i,t),Fr.set(e,s)),s}var Ar=new Map;function Gf(i,t={}){let{base:e,...s}=t,n=JSON.stringify([i,s]),r=Ar.get(n);return r===void 0&&(r=new Intl.RelativeTimeFormat(i,t),Ar.set(n,r)),r}var ns=null;function Xf(){return ns||(ns=new Intl.DateTimeFormat().resolvedOptions().locale,ns)}var Lr=new Map;function wl(i){let t=Lr.get(i);return t===void 0&&(t=new Intl.DateTimeFormat(i).resolvedOptions(),Lr.set(i,t)),t}var Pr=new Map;function Kf(i){let t=Pr.get(i);if(!t){let e=new Intl.Locale(i);t="getWeekInfo"in e?e.getWeekInfo():e.weekInfo,"minimalDays"in t||(t={...Sl,...t}),Pr.set(i,t)}return t}function Jf(i){let t=i.indexOf("-x-");t!==-1&&(i=i.substring(0,t));let e=i.indexOf("-u-");if(e===-1)return[i];{let s,n;try{s=Cr(i).resolvedOptions(),n=i}catch{let l=i.substring(0,e);s=Cr(l).resolvedOptions(),n=l}let{numberingSystem:r,calendar:o}=s;return[n,r,o]}}function Qf(i,t,e){return(e||t)&&(i.includes("-u-")||(i+="-u"),e&&(i+=`-ca-${e}`),t&&(i+=`-nu-${t}`)),i}function tm(i){let t=[];for(let e=1;e<=12;e++){let s=I.utc(2009,e,1);t.push(i(s))}return t}function em(i){let t=[];for(let e=1;e<=7;e++){let s=I.utc(2016,11,13+e);t.push(i(s))}return t}function sn(i,t,e,s){let n=i.listingMode();return n==="error"?null:n==="en"?e(t):s(t)}function im(i){return i.numberingSystem&&i.numberingSystem!=="latn"?!1:i.numberingSystem==="latn"||!i.locale||i.locale.startsWith("en")||wl(i.locale).numberingSystem==="latn"}var Nr=class{constructor(t,e,s){this.padTo=s.padTo||0,this.floor=s.floor||!1;let{padTo:n,floor:r,...o}=s;if(!e||Object.keys(o).length>0){let a={useGrouping:!1,...s};s.padTo>0&&(a.minimumIntegerDigits=s.padTo),this.inf=qf(t,a)}}format(t){if(this.inf){let e=this.floor?Math.floor(t):t;return this.inf.format(e)}else{let e=this.floor?Math.floor(t):ei(t,3);return Y(e,this.padTo)}}},Rr=class{constructor(t,e,s){this.opts=s,this.originalZone=void 0;let n;if(this.opts.timeZone)this.dt=t;else if(t.zone.type==="fixed"){let o=-1*(t.offset/60),a=o>=0?`Etc/GMT+${o}`:`Etc/GMT${o}`;t.offset!==0&&at.create(a).valid?(n=a,this.dt=t):(n="UTC",this.dt=t.offset===0?t:t.setZone("UTC").plus({minutes:t.offset}),this.originalZone=t.zone)}else t.zone.type==="system"?this.dt=t:t.zone.type==="iana"?(this.dt=t,n=t.zone.name):(n="UTC",this.dt=t.setZone("UTC").plus({minutes:t.offset}),this.originalZone=t.zone);let r={...this.opts};r.timeZone=r.timeZone||n,this.dtf=Cr(e,r)}format(){return this.originalZone?this.formatToParts().map(({value:t})=>t).join(""):this.dtf.format(this.dt.toJSDate())}formatToParts(){let t=this.dtf.formatToParts(this.dt.toJSDate());return this.originalZone?t.map(e=>{if(e.type==="timeZoneName"){let s=this.originalZone.offsetName(this.dt.ts,{locale:this.dt.locale,format:this.opts.timeZoneName});return{...e,value:s}}else return e}):t}resolvedOptions(){return this.dtf.resolvedOptions()}},Wr=class{constructor(t,e,s){this.opts={style:"long",...s},!e&&nn()&&(this.rtf=Gf(t,s))}format(t,e){return this.rtf?this.rtf.format(t,e):kl(e,t,this.opts.numeric,this.opts.style!=="long")}formatToParts(t,e){return this.rtf?this.rtf.formatToParts(t,e):[]}},Sl={firstDay:1,minimalDays:4,weekend:[6,7]},W=class i{static fromOpts(t){return i.create(t.locale,t.numberingSystem,t.outputCalendar,t.weekSettings,t.defaultToEN)}static create(t,e,s,n,r=!1){let o=t||R.defaultLocale,a=o||(r?"en-US":Xf()),l=e||R.defaultNumberingSystem,c=s||R.defaultOutputCalendar,h=rs(n)||R.defaultWeekSettings;return new i(a,l,c,h,o)}static resetCache(){ns=null,Ir.clear(),Fr.clear(),Ar.clear(),Lr.clear(),Pr.clear()}static fromObject({locale:t,numberingSystem:e,outputCalendar:s,weekSettings:n}={}){return i.create(t,e,s,n)}constructor(t,e,s,n,r){let[o,a,l]=Jf(t);this.locale=o,this.numberingSystem=e||a||null,this.outputCalendar=s||l||null,this.weekSettings=n,this.intl=Qf(this.locale,this.numberingSystem,this.outputCalendar),this.weekdaysCache={format:{},standalone:{}},this.monthsCache={format:{},standalone:{}},this.meridiemCache=null,this.eraCache={},this.specifiedLocale=r,this.fastNumbersCached=null}get fastNumbers(){return this.fastNumbersCached==null&&(this.fastNumbersCached=im(this)),this.fastNumbersCached}listingMode(){let t=this.isEnglish(),e=(this.numberingSystem===null||this.numberingSystem==="latn")&&(this.outputCalendar===null||this.outputCalendar==="gregory");return t&&e?"en":"intl"}clone(t){return!t||Object.getOwnPropertyNames(t).length===0?this:i.create(t.locale||this.specifiedLocale,t.numberingSystem||this.numberingSystem,t.outputCalendar||this.outputCalendar,rs(t.weekSettings)||this.weekSettings,t.defaultToEN||!1)}redefaultToEN(t={}){return this.clone({...t,defaultToEN:!0})}redefaultToSystem(t={}){return this.clone({...t,defaultToEN:!1})}months(t,e=!1){return sn(this,t,zr,()=>{let s=e?{month:t,day:"numeric"}:{month:t},n=e?"format":"standalone";return this.monthsCache[n][t]||(this.monthsCache[n][t]=tm(r=>this.extract(r,s,"month"))),this.monthsCache[n][t]})}weekdays(t,e=!1){return sn(this,t,Vr,()=>{let s=e?{weekday:t,year:"numeric",month:"long",day:"numeric"}:{weekday:t},n=e?"format":"standalone";return this.weekdaysCache[n][t]||(this.weekdaysCache[n][t]=em(r=>this.extract(r,s,"weekday"))),this.weekdaysCache[n][t]})}meridiems(){return sn(this,void 0,()=>Hr,()=>{if(!this.meridiemCache){let t={hour:"numeric",hourCycle:"h12"};this.meridiemCache=[I.utc(2016,11,13,9),I.utc(2016,11,13,19)].map(e=>this.extract(e,t,"dayperiod"))}return this.meridiemCache})}eras(t){return sn(this,t,Br,()=>{let e={era:t};return this.eraCache[t]||(this.eraCache[t]=[I.utc(-40,1,1),I.utc(2017,1,1)].map(s=>this.extract(s,e,"era"))),this.eraCache[t]})}extract(t,e,s){let n=this.dtFormatter(t,e),r=n.formatToParts(),o=r.find(a=>a.type.toLowerCase()===s);return o?o.value:null}numberFormatter(t={}){return new Nr(this.intl,t.forceSimple||this.fastNumbers,t)}dtFormatter(t,e={}){return new Rr(t,this.intl,e)}relFormatter(t={}){return new Wr(this.intl,this.isEnglish(),t)}listFormatter(t={}){return Zf(this.intl,t)}isEnglish(){return this.locale==="en"||this.locale.toLowerCase()==="en-us"||wl(this.intl).locale.startsWith("en-us")}getWeekSettings(){return this.weekSettings?this.weekSettings:rn()?Kf(this.locale):Sl}getStartOfWeek(){return this.getWeekSettings().firstDay}getMinDaysInFirstWeek(){return this.getWeekSettings().minimalDays}getWeekendDays(){return this.getWeekSettings().weekend}equals(t){return this.locale===t.locale&&this.numberingSystem===t.numberingSystem&&this.outputCalendar===t.outputCalendar}toString(){return`Locale(${this.locale}, ${this.numberingSystem}, ${this.outputCalendar})`}};var jr=null,et=class i extends ut{static get utcInstance(){return jr===null&&(jr=new i(0)),jr}static instance(t){return t===0?i.utcInstance:new i(t)}static parseSpecifier(t){if(t){let e=t.match(/^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$/i);if(e)return new i(Se(e[1],e[2]))}return null}constructor(t){super(),this.fixed=t}get type(){return"fixed"}get name(){return this.fixed===0?"UTC":`UTC${ae(this.fixed,"narrow")}`}get ianaName(){return this.fixed===0?"Etc/UTC":`Etc/GMT${ae(-this.fixed,"narrow")}`}offsetName(){return this.name}formatOffset(t,e){return ae(this.fixed,e)}get isUniversal(){return!0}offset(){return this.fixed}equals(t){return t.type==="fixed"&&t.fixed===this.fixed}get isValid(){return!0}};var ii=class extends ut{constructor(t){super(),this.zoneName=t}get type(){return"invalid"}get name(){return this.zoneName}get isUniversal(){return!1}offsetName(){return null}formatOffset(){return""}offset(){return NaN}equals(){return!1}get isValid(){return!1}};function Dt(i,t){let e;if(O(i)||i===null)return t;if(i instanceof ut)return i;if(Ml(i)){let s=i.toLowerCase();return s==="default"?t:s==="local"||s==="system"?oe.instance:s==="utc"||s==="gmt"?et.utcInstance:et.parseSpecifier(s)||at.create(i)}else return Et(i)?et.instance(i):typeof i=="object"&&"offset"in i&&typeof i.offset=="function"?i:new ii(i)}var Yr={arab:"[\u0660-\u0669]",arabext:"[\u06F0-\u06F9]",bali:"[\u1B50-\u1B59]",beng:"[\u09E6-\u09EF]",deva:"[\u0966-\u096F]",fullwide:"[\uFF10-\uFF19]",gujr:"[\u0AE6-\u0AEF]",hanidec:"[\u3007|\u4E00|\u4E8C|\u4E09|\u56DB|\u4E94|\u516D|\u4E03|\u516B|\u4E5D]",khmr:"[\u17E0-\u17E9]",knda:"[\u0CE6-\u0CEF]",laoo:"[\u0ED0-\u0ED9]",limb:"[\u1946-\u194F]",mlym:"[\u0D66-\u0D6F]",mong:"[\u1810-\u1819]",mymr:"[\u1040-\u1049]",orya:"[\u0B66-\u0B6F]",tamldec:"[\u0BE6-\u0BEF]",telu:"[\u0C66-\u0C6F]",thai:"[\u0E50-\u0E59]",tibt:"[\u0F20-\u0F29]",latn:"\\d"},Tl={arab:[1632,1641],arabext:[1776,1785],bali:[6992,7001],beng:[2534,2543],deva:[2406,2415],fullwide:[65296,65303],gujr:[2790,2799],khmr:[6112,6121],knda:[3302,3311],laoo:[3792,3801],limb:[6470,6479],mlym:[3430,3439],mong:[6160,6169],mymr:[4160,4169],orya:[2918,2927],tamldec:[3046,3055],telu:[3174,3183],thai:[3664,3673],tibt:[3872,3881]},sm=Yr.hanidec.replace(/[\[|\]]/g,"").split("");function vl(i){let t=parseInt(i,10);if(isNaN(t)){t="";for(let e=0;e=r&&s<=o&&(t+=s-r)}}return parseInt(t,10)}else return t}var Ur=new Map;function Ol(){Ur.clear()}function wt({numberingSystem:i},t=""){let e=i||"latn",s=Ur.get(e);s===void 0&&(s=new Map,Ur.set(e,s));let n=s.get(t);return n===void 0&&(n=new RegExp(`${Yr[e]}${t}`),s.set(t,n)),n}var Dl=()=>Date.now(),El="system",Il=null,Cl=null,Fl=null,Al=60,Ll,Pl=null,R=class{static get now(){return Dl}static set now(t){Dl=t}static set defaultZone(t){El=t}static get defaultZone(){return Dt(El,oe.instance)}static get defaultLocale(){return Il}static set defaultLocale(t){Il=t}static get defaultNumberingSystem(){return Cl}static set defaultNumberingSystem(t){Cl=t}static get defaultOutputCalendar(){return Fl}static set defaultOutputCalendar(t){Fl=t}static get defaultWeekSettings(){return Pl}static set defaultWeekSettings(t){Pl=rs(t)}static get twoDigitCutoffYear(){return Al}static set twoDigitCutoffYear(t){Al=t%100}static get throwOnInvalid(){return Ll}static set throwOnInvalid(t){Ll=t}static resetCaches(){W.resetCache(),at.resetCache(),I.resetCache(),Ol()}};var it=class{constructor(t,e){this.reason=t,this.explanation=e}toMessage(){return this.explanation?`${this.reason}: ${this.explanation}`:this.reason}};var Nl=[0,31,59,90,120,151,181,212,243,273,304,334],Rl=[0,31,60,91,121,152,182,213,244,274,305,335];function St(i,t){return new it("unit out of range",`you specified ${t} (of type ${typeof t}) as a ${i}, which is invalid`)}function on(i,t,e){let s=new Date(Date.UTC(i,t-1,e));i<100&&i>=0&&s.setUTCFullYear(s.getUTCFullYear()-1900);let n=s.getUTCDay();return n===0?7:n}function Wl(i,t,e){return e+(Me(i)?Rl:Nl)[t-1]}function zl(i,t){let e=Me(i)?Rl:Nl,s=e.findIndex(r=>rke(s,t,e)?(c=s+1,l=1):c=s,{weekYear:c,weekNumber:l,weekday:a,...ls(i)}}function Zr(i,t=4,e=1){let{weekYear:s,weekNumber:n,weekday:r}=i,o=an(on(s,1,t),e),a=le(s),l=n*7+r-o-7+t,c;l<1?(c=s-1,l+=le(c)):l>a?(c=s+1,l-=le(s)):c=s;let{month:h,day:u}=zl(c,l);return{year:c,month:h,day:u,...ls(i)}}function ln(i){let{year:t,month:e,day:s}=i,n=Wl(t,e,s);return{year:t,ordinal:n,...ls(i)}}function qr(i){let{year:t,ordinal:e}=i,{month:s,day:n}=zl(t,e);return{year:t,month:s,day:n,...ls(i)}}function Gr(i,t){if(!O(i.localWeekday)||!O(i.localWeekNumber)||!O(i.localWeekYear)){if(!O(i.weekday)||!O(i.weekNumber)||!O(i.weekYear))throw new Tt("Cannot mix locale-based week fields with ISO-based week fields");return O(i.localWeekday)||(i.weekday=i.localWeekday),O(i.localWeekNumber)||(i.weekNumber=i.localWeekNumber),O(i.localWeekYear)||(i.weekYear=i.localWeekYear),delete i.localWeekday,delete i.localWeekNumber,delete i.localWeekYear,{minDaysInFirstWeek:t.getMinDaysInFirstWeek(),startOfWeek:t.getStartOfWeek()}}else return{minDaysInFirstWeek:4,startOfWeek:1}}function Vl(i,t=4,e=1){let s=as(i.weekYear),n=bt(i.weekNumber,1,ke(i.weekYear,t,e)),r=bt(i.weekday,1,7);return s?n?r?!1:St("weekday",i.weekday):St("week",i.weekNumber):St("weekYear",i.weekYear)}function Hl(i){let t=as(i.year),e=bt(i.ordinal,1,le(i.year));return t?e?!1:St("ordinal",i.ordinal):St("year",i.year)}function Xr(i){let t=as(i.year),e=bt(i.month,1,12),s=bt(i.day,1,si(i.year,i.month));return t?e?s?!1:St("day",i.day):St("month",i.month):St("year",i.year)}function Kr(i){let{hour:t,minute:e,second:s,millisecond:n}=i,r=bt(t,0,23)||t===24&&e===0&&s===0&&n===0,o=bt(e,0,59),a=bt(s,0,59),l=bt(n,0,999);return r?o?a?l?!1:St("millisecond",n):St("second",s):St("minute",e):St("hour",t)}function O(i){return typeof i>"u"}function Et(i){return typeof i=="number"}function as(i){return typeof i=="number"&&i%1===0}function Ml(i){return typeof i=="string"}function $l(i){return Object.prototype.toString.call(i)==="[object Date]"}function nn(){try{return typeof Intl<"u"&&!!Intl.RelativeTimeFormat}catch{return!1}}function rn(){try{return typeof Intl<"u"&&!!Intl.Locale&&("weekInfo"in Intl.Locale.prototype||"getWeekInfo"in Intl.Locale.prototype)}catch{return!1}}function jl(i){return Array.isArray(i)?i:[i]}function Jr(i,t,e){if(i.length!==0)return i.reduce((s,n)=>{let r=[t(n),n];return s&&e(s[0],r[0])===s[0]?s:r},null)[1]}function Ul(i,t){return t.reduce((e,s)=>(e[s]=i[s],e),{})}function ce(i,t){return Object.prototype.hasOwnProperty.call(i,t)}function rs(i){if(i==null)return null;if(typeof i!="object")throw new G("Week settings must be an object");if(!bt(i.firstDay,1,7)||!bt(i.minimalDays,1,7)||!Array.isArray(i.weekend)||i.weekend.some(t=>!bt(t,1,7)))throw new G("Invalid week settings");return{firstDay:i.firstDay,minimalDays:i.minimalDays,weekend:Array.from(i.weekend)}}function bt(i,t,e){return as(i)&&i>=t&&i<=e}function nm(i,t){return i-t*Math.floor(i/t)}function Y(i,t=2){let e=i<0,s;return e?s="-"+(""+-i).padStart(t,"0"):s=(""+i).padStart(t,"0"),s}function Ut(i){if(!(O(i)||i===null||i===""))return parseInt(i,10)}function he(i){if(!(O(i)||i===null||i===""))return parseFloat(i)}function cs(i){if(!(O(i)||i===null||i==="")){let t=parseFloat("0."+i)*1e3;return Math.floor(t)}}function ei(i,t,e=!1){let s=10**t;return(e?Math.trunc:Math.round)(i*s)/s}function Me(i){return i%4===0&&(i%100!==0||i%400===0)}function le(i){return Me(i)?366:365}function si(i,t){let e=nm(t-1,12)+1,s=i+(t-e)/12;return e===2?Me(s)?29:28:[31,null,31,30,31,30,31,31,30,31,30,31][e-1]}function ti(i){let t=Date.UTC(i.year,i.month-1,i.day,i.hour,i.minute,i.second,i.millisecond);return i.year<100&&i.year>=0&&(t=new Date(t),t.setUTCFullYear(i.year,i.month-1,i.day)),+t}function Bl(i,t,e){return-an(on(i,1,t),e)+t-1}function ke(i,t=4,e=1){let s=Bl(i,t,e),n=Bl(i+1,t,e);return(le(i)-s+n)/7}function hs(i){return i>99?i:i>R.twoDigitCutoffYear?1900+i:2e3+i}function en(i,t,e,s=null){let n=new Date(i),r={hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"};s&&(r.timeZone=s);let o={timeZoneName:t,...r},a=new Intl.DateTimeFormat(e,o).formatToParts(n).find(l=>l.type.toLowerCase()==="timezonename");return a?a.value:null}function Se(i,t){let e=parseInt(i,10);Number.isNaN(e)&&(e=0);let s=parseInt(t,10)||0,n=e<0||Object.is(e,-0)?-s:s;return e*60+n}function Qr(i){let t=Number(i);if(typeof i=="boolean"||i===""||Number.isNaN(t))throw new G(`Invalid unit value ${i}`);return t}function ni(i,t){let e={};for(let s in i)if(ce(i,s)){let n=i[s];if(n==null)continue;e[t(s)]=Qr(n)}return e}function ae(i,t){let e=Math.trunc(Math.abs(i/60)),s=Math.trunc(Math.abs(i%60)),n=i>=0?"+":"-";switch(t){case"short":return`${n}${Y(e,2)}:${Y(s,2)}`;case"narrow":return`${n}${e}${s>0?`:${s}`:""}`;case"techie":return`${n}${Y(e,2)}${Y(s,2)}`;default:throw new RangeError(`Value format ${t} is out of range for property format`)}}function ls(i){return Ul(i,["hour","minute","second","millisecond"])}var rm=["January","February","March","April","May","June","July","August","September","October","November","December"],to=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],om=["J","F","M","A","M","J","J","A","S","O","N","D"];function zr(i){switch(i){case"narrow":return[...om];case"short":return[...to];case"long":return[...rm];case"numeric":return["1","2","3","4","5","6","7","8","9","10","11","12"];case"2-digit":return["01","02","03","04","05","06","07","08","09","10","11","12"];default:return null}}var eo=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],io=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],am=["M","T","W","T","F","S","S"];function Vr(i){switch(i){case"narrow":return[...am];case"short":return[...io];case"long":return[...eo];case"numeric":return["1","2","3","4","5","6","7"];default:return null}}var Hr=["AM","PM"],lm=["Before Christ","Anno Domini"],cm=["BC","AD"],hm=["B","A"];function Br(i){switch(i){case"narrow":return[...hm];case"short":return[...cm];case"long":return[...lm];default:return null}}function Yl(i){return Hr[i.hour<12?0:1]}function Zl(i,t){return Vr(t)[i.weekday-1]}function ql(i,t){return zr(t)[i.month-1]}function Gl(i,t){return Br(t)[i.year<0?0:1]}function kl(i,t,e="always",s=!1){let n={years:["year","yr."],quarters:["quarter","qtr."],months:["month","mo."],weeks:["week","wk."],days:["day","day","days"],hours:["hour","hr."],minutes:["minute","min."],seconds:["second","sec."]},r=["hours","minutes","seconds"].indexOf(i)===-1;if(e==="auto"&&r){let u=i==="days";switch(t){case 1:return u?"tomorrow":`next ${n[i][0]}`;case-1:return u?"yesterday":`last ${n[i][0]}`;case 0:return u?"today":`this ${n[i][0]}`;default:}}let o=Object.is(t,-0)||t<0,a=Math.abs(t),l=a===1,c=n[i],h=s?l?c[1]:c[2]||c[1]:l?n[i][0]:i;return o?`${a} ${h} ago`:`in ${a} ${h}`}function Xl(i,t){let e="";for(let s of i)s.literal?e+=s.val:e+=t(s.val);return e}var um={D:re,DD:zi,DDD:Vi,DDDD:Hi,t:Bi,tt:$i,ttt:ji,tttt:Ui,T:Yi,TT:Zi,TTT:qi,TTTT:Gi,f:Xi,ff:Ji,fff:ts,ffff:is,F:Ki,FF:Qi,FFF:es,FFFF:ss},st=class i{static create(t,e={}){return new i(t,e)}static parseFormat(t){let e=null,s="",n=!1,r=[];for(let o=0;o0&&r.push({literal:n||/^\s+$/.test(s),val:s}),e=null,s="",n=!n):n||a===e?s+=a:(s.length>0&&r.push({literal:/^\s+$/.test(s),val:s}),s=a,e=a)}return s.length>0&&r.push({literal:n||/^\s+$/.test(s),val:s}),r}static macroTokenToFormatOpts(t){return um[t]}constructor(t,e){this.opts=e,this.loc=t,this.systemLoc=null}formatWithSystemDefault(t,e){return this.systemLoc===null&&(this.systemLoc=this.loc.redefaultToSystem()),this.systemLoc.dtFormatter(t,{...this.opts,...e}).format()}dtFormatter(t,e={}){return this.loc.dtFormatter(t,{...this.opts,...e})}formatDateTime(t,e){return this.dtFormatter(t,e).format()}formatDateTimeParts(t,e){return this.dtFormatter(t,e).formatToParts()}formatInterval(t,e){return this.dtFormatter(t.start,e).dtf.formatRange(t.start.toJSDate(),t.end.toJSDate())}resolvedOptions(t,e){return this.dtFormatter(t,e).resolvedOptions()}num(t,e=0){if(this.opts.forceSimple)return Y(t,e);let s={...this.opts};return e>0&&(s.padTo=e),this.loc.numberFormatter(s).format(t)}formatDateTimeFromString(t,e){let s=this.loc.listingMode()==="en",n=this.loc.outputCalendar&&this.loc.outputCalendar!=="gregory",r=(f,m)=>this.loc.extract(t,f,m),o=f=>t.isOffsetFixed&&t.offset===0&&f.allowZ?"Z":t.isValid?t.zone.formatOffset(t.ts,f.format):"",a=()=>s?Yl(t):r({hour:"numeric",hourCycle:"h12"},"dayperiod"),l=(f,m)=>s?ql(t,f):r(m?{month:f}:{month:f,day:"numeric"},"month"),c=(f,m)=>s?Zl(t,f):r(m?{weekday:f}:{weekday:f,month:"long",day:"numeric"},"weekday"),h=f=>{let m=i.macroTokenToFormatOpts(f);return m?this.formatWithSystemDefault(t,m):f},u=f=>s?Gl(t,f):r({era:f},"era"),d=f=>{switch(f){case"S":return this.num(t.millisecond);case"u":case"SSS":return this.num(t.millisecond,3);case"s":return this.num(t.second);case"ss":return this.num(t.second,2);case"uu":return this.num(Math.floor(t.millisecond/10),2);case"uuu":return this.num(Math.floor(t.millisecond/100));case"m":return this.num(t.minute);case"mm":return this.num(t.minute,2);case"h":return this.num(t.hour%12===0?12:t.hour%12);case"hh":return this.num(t.hour%12===0?12:t.hour%12,2);case"H":return this.num(t.hour);case"HH":return this.num(t.hour,2);case"Z":return o({format:"narrow",allowZ:this.opts.allowZ});case"ZZ":return o({format:"short",allowZ:this.opts.allowZ});case"ZZZ":return o({format:"techie",allowZ:this.opts.allowZ});case"ZZZZ":return t.zone.offsetName(t.ts,{format:"short",locale:this.loc.locale});case"ZZZZZ":return t.zone.offsetName(t.ts,{format:"long",locale:this.loc.locale});case"z":return t.zoneName;case"a":return a();case"d":return n?r({day:"numeric"},"day"):this.num(t.day);case"dd":return n?r({day:"2-digit"},"day"):this.num(t.day,2);case"c":return this.num(t.weekday);case"ccc":return c("short",!0);case"cccc":return c("long",!0);case"ccccc":return c("narrow",!0);case"E":return this.num(t.weekday);case"EEE":return c("short",!1);case"EEEE":return c("long",!1);case"EEEEE":return c("narrow",!1);case"L":return n?r({month:"numeric",day:"numeric"},"month"):this.num(t.month);case"LL":return n?r({month:"2-digit",day:"numeric"},"month"):this.num(t.month,2);case"LLL":return l("short",!0);case"LLLL":return l("long",!0);case"LLLLL":return l("narrow",!0);case"M":return n?r({month:"numeric"},"month"):this.num(t.month);case"MM":return n?r({month:"2-digit"},"month"):this.num(t.month,2);case"MMM":return l("short",!1);case"MMMM":return l("long",!1);case"MMMMM":return l("narrow",!1);case"y":return n?r({year:"numeric"},"year"):this.num(t.year);case"yy":return n?r({year:"2-digit"},"year"):this.num(t.year.toString().slice(-2),2);case"yyyy":return n?r({year:"numeric"},"year"):this.num(t.year,4);case"yyyyyy":return n?r({year:"numeric"},"year"):this.num(t.year,6);case"G":return u("short");case"GG":return u("long");case"GGGGG":return u("narrow");case"kk":return this.num(t.weekYear.toString().slice(-2),2);case"kkkk":return this.num(t.weekYear,4);case"W":return this.num(t.weekNumber);case"WW":return this.num(t.weekNumber,2);case"n":return this.num(t.localWeekNumber);case"nn":return this.num(t.localWeekNumber,2);case"ii":return this.num(t.localWeekYear.toString().slice(-2),2);case"iiii":return this.num(t.localWeekYear,4);case"o":return this.num(t.ordinal);case"ooo":return this.num(t.ordinal,3);case"q":return this.num(t.quarter);case"qq":return this.num(t.quarter,2);case"X":return this.num(Math.floor(t.ts/1e3));case"x":return this.num(t.ts);default:return h(f)}};return Xl(i.parseFormat(e),d)}formatDurationFromString(t,e){let s=l=>{switch(l[0]){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":return"hour";case"d":return"day";case"w":return"week";case"M":return"month";case"y":return"year";default:return null}},n=l=>c=>{let h=s(c);return h?this.num(l.get(h),c.length):c},r=i.parseFormat(e),o=r.reduce((l,{literal:c,val:h})=>c?l:l.concat(h),[]),a=t.shiftTo(...o.map(s).filter(l=>l));return Xl(r,n(a))}};var Jl=/[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;function oi(...i){let t=i.reduce((e,s)=>e+s.source,"");return RegExp(`^${t}$`)}function ai(...i){return t=>i.reduce(([e,s,n],r)=>{let[o,a,l]=r(t,n);return[{...e,...o},a||s,l]},[{},null,1]).slice(0,2)}function li(i,...t){if(i==null)return[null,null];for(let[e,s]of t){let n=e.exec(i);if(n)return s(n)}return[null,null]}function Ql(...i){return(t,e)=>{let s={},n;for(n=0;nf!==void 0&&(m||f&&h)?-f:f;return[{years:d(he(e)),months:d(he(s)),weeks:d(he(n)),days:d(he(r)),hours:d(he(o)),minutes:d(he(a)),seconds:d(he(l),l==="-0"),milliseconds:d(cs(c),u)}]}var Mm={GMT:0,EDT:-4*60,EST:-5*60,CDT:-5*60,CST:-6*60,MDT:-6*60,MST:-7*60,PDT:-7*60,PST:-8*60};function ro(i,t,e,s,n,r,o){let a={year:t.length===2?hs(Ut(t)):Ut(t),month:to.indexOf(e)+1,day:Ut(s),hour:Ut(n),minute:Ut(r)};return o&&(a.second=Ut(o)),i&&(a.weekday=i.length>3?eo.indexOf(i)+1:io.indexOf(i)+1),a}var Tm=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/;function vm(i){let[,t,e,s,n,r,o,a,l,c,h,u]=i,d=ro(t,n,s,e,r,o,a),f;return l?f=Mm[l]:c?f=0:f=Se(h,u),[d,new et(f)]}function Om(i){return i.replace(/\([^()]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").trim()}var Dm=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d) GMT$/,Em=/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/,Im=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \d|\d\d) (\d\d):(\d\d):(\d\d) (\d{4})$/;function Kl(i){let[,t,e,s,n,r,o,a]=i;return[ro(t,n,s,e,r,o,a),et.utcInstance]}function Cm(i){let[,t,e,s,n,r,o,a]=i;return[ro(t,a,e,s,n,r,o),et.utcInstance]}var Fm=oi(fm,no),Am=oi(mm,no),Lm=oi(gm,no),Pm=oi(ec),sc=ai(_m,ci,us,ds),Nm=ai(pm,ci,us,ds),Rm=ai(ym,ci,us,ds),Wm=ai(ci,us,ds);function nc(i){return li(i,[Fm,sc],[Am,Nm],[Lm,Rm],[Pm,Wm])}function rc(i){return li(Om(i),[Tm,vm])}function oc(i){return li(i,[Dm,Kl],[Em,Kl],[Im,Cm])}function ac(i){return li(i,[Sm,km])}var zm=ai(ci);function lc(i){return li(i,[wm,zm])}var Vm=oi(bm,xm),Hm=oi(ic),Bm=ai(ci,us,ds);function cc(i){return li(i,[Vm,sc],[Hm,Bm])}var hc="Invalid Duration",dc={weeks:{days:7,hours:7*24,minutes:7*24*60,seconds:7*24*60*60,milliseconds:7*24*60*60*1e3},days:{hours:24,minutes:24*60,seconds:24*60*60,milliseconds:24*60*60*1e3},hours:{minutes:60,seconds:60*60,milliseconds:60*60*1e3},minutes:{seconds:60,milliseconds:60*1e3},seconds:{milliseconds:1e3}},$m={years:{quarters:4,months:12,weeks:52,days:365,hours:365*24,minutes:365*24*60,seconds:365*24*60*60,milliseconds:365*24*60*60*1e3},quarters:{months:3,weeks:13,days:91,hours:91*24,minutes:91*24*60,seconds:91*24*60*60,milliseconds:91*24*60*60*1e3},months:{weeks:4,days:30,hours:30*24,minutes:30*24*60,seconds:30*24*60*60,milliseconds:30*24*60*60*1e3},...dc},kt=146097/400,hi=146097/4800,jm={years:{quarters:4,months:12,weeks:kt/7,days:kt,hours:kt*24,minutes:kt*24*60,seconds:kt*24*60*60,milliseconds:kt*24*60*60*1e3},quarters:{months:3,weeks:kt/28,days:kt/4,hours:kt*24/4,minutes:kt*24*60/4,seconds:kt*24*60*60/4,milliseconds:kt*24*60*60*1e3/4},months:{weeks:hi/7,days:hi,hours:hi*24,minutes:hi*24*60,seconds:hi*24*60*60,milliseconds:hi*24*60*60*1e3},...dc},Te=["years","quarters","months","weeks","days","hours","minutes","seconds","milliseconds"],Um=Te.slice(0).reverse();function ue(i,t,e=!1){let s={values:e?t.values:{...i.values,...t.values||{}},loc:i.loc.clone(t.loc),conversionAccuracy:t.conversionAccuracy||i.conversionAccuracy,matrix:t.matrix||i.matrix};return new Z(s)}function fc(i,t){let e=t.milliseconds??0;for(let s of Um.slice(1))t[s]&&(e+=t[s]*i[s].milliseconds);return e}function uc(i,t){let e=fc(i,t)<0?-1:1;Te.reduceRight((s,n)=>{if(O(t[n]))return s;if(s){let r=t[s]*e,o=i[n][s],a=Math.floor(r/o);t[n]+=a*e,t[s]-=a*o*e}return n},null),Te.reduce((s,n)=>{if(O(t[n]))return s;if(s){let r=t[s]%1;t[s]-=r,t[n]+=r*i[s][n]}return n},null)}function Ym(i){let t={};for(let[e,s]of Object.entries(i))s!==0&&(t[e]=s);return t}var Z=class i{constructor(t){let e=t.conversionAccuracy==="longterm"||!1,s=e?jm:$m;t.matrix&&(s=t.matrix),this.values=t.values,this.loc=t.loc||W.create(),this.conversionAccuracy=e?"longterm":"casual",this.invalid=t.invalid||null,this.matrix=s,this.isLuxonDuration=!0}static fromMillis(t,e){return i.fromObject({milliseconds:t},e)}static fromObject(t,e={}){if(t==null||typeof t!="object")throw new G(`Duration.fromObject: argument expected to be an object, got ${t===null?"null":typeof t}`);return new i({values:ni(t,i.normalizeUnit),loc:W.fromObject(e),conversionAccuracy:e.conversionAccuracy,matrix:e.matrix})}static fromDurationLike(t){if(Et(t))return i.fromMillis(t);if(i.isDuration(t))return t;if(typeof t=="object")return i.fromObject(t);throw new G(`Unknown duration argument ${t} of type ${typeof t}`)}static fromISO(t,e){let[s]=ac(t);return s?i.fromObject(s,e):i.invalid("unparsable",`the input "${t}" can't be parsed as ISO 8601`)}static fromISOTime(t,e){let[s]=lc(t);return s?i.fromObject(s,e):i.invalid("unparsable",`the input "${t}" can't be parsed as ISO 8601`)}static invalid(t,e=null){if(!t)throw new G("need to specify a reason the Duration is invalid");let s=t instanceof it?t:new it(t,e);if(R.throwOnInvalid)throw new Qs(s);return new i({invalid:s})}static normalizeUnit(t){let e={year:"years",years:"years",quarter:"quarters",quarters:"quarters",month:"months",months:"months",week:"weeks",weeks:"weeks",day:"days",days:"days",hour:"hours",hours:"hours",minute:"minutes",minutes:"minutes",second:"seconds",seconds:"seconds",millisecond:"milliseconds",milliseconds:"milliseconds"}[t&&t.toLowerCase()];if(!e)throw new Qe(t);return e}static isDuration(t){return t&&t.isLuxonDuration||!1}get locale(){return this.isValid?this.loc.locale:null}get numberingSystem(){return this.isValid?this.loc.numberingSystem:null}toFormat(t,e={}){let s={...e,floor:e.round!==!1&&e.floor!==!1};return this.isValid?st.create(this.loc,s).formatDurationFromString(this,t):hc}toHuman(t={}){if(!this.isValid)return hc;let e=Te.map(s=>{let n=this.values[s];return O(n)?null:this.loc.numberFormatter({style:"unit",unitDisplay:"long",...t,unit:s.slice(0,-1)}).format(n)}).filter(s=>s);return this.loc.listFormatter({type:"conjunction",style:t.listStyle||"narrow",...t}).format(e)}toObject(){return this.isValid?{...this.values}:{}}toISO(){if(!this.isValid)return null;let t="P";return this.years!==0&&(t+=this.years+"Y"),(this.months!==0||this.quarters!==0)&&(t+=this.months+this.quarters*3+"M"),this.weeks!==0&&(t+=this.weeks+"W"),this.days!==0&&(t+=this.days+"D"),(this.hours!==0||this.minutes!==0||this.seconds!==0||this.milliseconds!==0)&&(t+="T"),this.hours!==0&&(t+=this.hours+"H"),this.minutes!==0&&(t+=this.minutes+"M"),(this.seconds!==0||this.milliseconds!==0)&&(t+=ei(this.seconds+this.milliseconds/1e3,3)+"S"),t==="P"&&(t+="T0S"),t}toISOTime(t={}){if(!this.isValid)return null;let e=this.toMillis();return e<0||e>=864e5?null:(t={suppressMilliseconds:!1,suppressSeconds:!1,includePrefix:!1,format:"extended",...t,includeOffset:!1},I.fromMillis(e,{zone:"UTC"}).toISOTime(t))}toJSON(){return this.toISO()}toString(){return this.toISO()}[Symbol.for("nodejs.util.inspect.custom")](){return this.isValid?`Duration { values: ${JSON.stringify(this.values)} }`:`Duration { Invalid, reason: ${this.invalidReason} }`}toMillis(){return this.isValid?fc(this.matrix,this.values):NaN}valueOf(){return this.toMillis()}plus(t){if(!this.isValid)return this;let e=i.fromDurationLike(t),s={};for(let n of Te)(ce(e.values,n)||ce(this.values,n))&&(s[n]=e.get(n)+this.get(n));return ue(this,{values:s},!0)}minus(t){if(!this.isValid)return this;let e=i.fromDurationLike(t);return this.plus(e.negate())}mapUnits(t){if(!this.isValid)return this;let e={};for(let s of Object.keys(this.values))e[s]=Qr(t(this.values[s],s));return ue(this,{values:e},!0)}get(t){return this[i.normalizeUnit(t)]}set(t){if(!this.isValid)return this;let e={...this.values,...ni(t,i.normalizeUnit)};return ue(this,{values:e})}reconfigure({locale:t,numberingSystem:e,conversionAccuracy:s,matrix:n}={}){let o={loc:this.loc.clone({locale:t,numberingSystem:e}),matrix:n,conversionAccuracy:s};return ue(this,o)}as(t){return this.isValid?this.shiftTo(t).get(t):NaN}normalize(){if(!this.isValid)return this;let t=this.toObject();return uc(this.matrix,t),ue(this,{values:t},!0)}rescale(){if(!this.isValid)return this;let t=Ym(this.normalize().shiftToAll().toObject());return ue(this,{values:t},!0)}shiftTo(...t){if(!this.isValid)return this;if(t.length===0)return this;t=t.map(o=>i.normalizeUnit(o));let e={},s={},n=this.toObject(),r;for(let o of Te)if(t.indexOf(o)>=0){r=o;let a=0;for(let c in s)a+=this.matrix[c][o]*s[c],s[c]=0;Et(n[o])&&(a+=n[o]);let l=Math.trunc(a);e[o]=l,s[o]=(a*1e3-l*1e3)/1e3}else Et(n[o])&&(s[o]=n[o]);for(let o in s)s[o]!==0&&(e[r]+=o===r?s[o]:s[o]/this.matrix[r][o]);return uc(this.matrix,e),ue(this,{values:e},!0)}shiftToAll(){return this.isValid?this.shiftTo("years","months","weeks","days","hours","minutes","seconds","milliseconds"):this}negate(){if(!this.isValid)return this;let t={};for(let e of Object.keys(this.values))t[e]=this.values[e]===0?0:-this.values[e];return ue(this,{values:t},!0)}get years(){return this.isValid?this.values.years||0:NaN}get quarters(){return this.isValid?this.values.quarters||0:NaN}get months(){return this.isValid?this.values.months||0:NaN}get weeks(){return this.isValid?this.values.weeks||0:NaN}get days(){return this.isValid?this.values.days||0:NaN}get hours(){return this.isValid?this.values.hours||0:NaN}get minutes(){return this.isValid?this.values.minutes||0:NaN}get seconds(){return this.isValid?this.values.seconds||0:NaN}get milliseconds(){return this.isValid?this.values.milliseconds||0:NaN}get isValid(){return this.invalid===null}get invalidReason(){return this.invalid?this.invalid.reason:null}get invalidExplanation(){return this.invalid?this.invalid.explanation:null}equals(t){if(!this.isValid||!t.isValid||!this.loc.equals(t.loc))return!1;function e(s,n){return s===void 0||s===0?n===void 0||n===0:s===n}for(let s of Te)if(!e(this.values[s],t.values[s]))return!1;return!0}};var ui="Invalid Interval";function Zm(i,t){return!i||!i.isValid?Yt.invalid("missing or invalid start"):!t||!t.isValid?Yt.invalid("missing or invalid end"):tt:!1}isBefore(t){return this.isValid?this.e<=t:!1}contains(t){return this.isValid?this.s<=t&&this.e>t:!1}set({start:t,end:e}={}){return this.isValid?i.fromDateTimes(t||this.s,e||this.e):this}splitAt(...t){if(!this.isValid)return[];let e=t.map(di).filter(o=>this.contains(o)).sort((o,a)=>o.toMillis()-a.toMillis()),s=[],{s:n}=this,r=0;for(;n+this.e?this.e:o;s.push(i.fromDateTimes(n,a)),n=a,r+=1}return s}splitBy(t){let e=Z.fromDurationLike(t);if(!this.isValid||!e.isValid||e.as("milliseconds")===0)return[];let{s}=this,n=1,r,o=[];for(;sl*n));r=+a>+this.e?this.e:a,o.push(i.fromDateTimes(s,r)),s=r,n+=1}return o}divideEqually(t){return this.isValid?this.splitBy(this.length()/t).slice(0,t):[]}overlaps(t){return this.e>t.s&&this.s=t.e:!1}equals(t){return!this.isValid||!t.isValid?!1:this.s.equals(t.s)&&this.e.equals(t.e)}intersection(t){if(!this.isValid)return this;let e=this.s>t.s?this.s:t.s,s=this.e=s?null:i.fromDateTimes(e,s)}union(t){if(!this.isValid)return this;let e=this.st.e?this.e:t.e;return i.fromDateTimes(e,s)}static merge(t){let[e,s]=t.sort((n,r)=>n.s-r.s).reduce(([n,r],o)=>r?r.overlaps(o)||r.abutsStart(o)?[n,r.union(o)]:[n.concat([r]),o]:[n,o],[[],null]);return s&&e.push(s),e}static xor(t){let e=null,s=0,n=[],r=t.map(l=>[{time:l.s,type:"s"},{time:l.e,type:"e"}]),o=Array.prototype.concat(...r),a=o.sort((l,c)=>l.time-c.time);for(let l of a)s+=l.type==="s"?1:-1,s===1?e=l.time:(e&&+e!=+l.time&&n.push(i.fromDateTimes(e,l.time)),e=null);return i.merge(n)}difference(...t){return i.xor([this].concat(t)).map(e=>this.intersection(e)).filter(e=>e&&!e.isEmpty())}toString(){return this.isValid?`[${this.s.toISO()} \u2013 ${this.e.toISO()})`:ui}[Symbol.for("nodejs.util.inspect.custom")](){return this.isValid?`Interval { start: ${this.s.toISO()}, end: ${this.e.toISO()} }`:`Interval { Invalid, reason: ${this.invalidReason} }`}toLocaleString(t=re,e={}){return this.isValid?st.create(this.s.loc.clone(e),t).formatInterval(this):ui}toISO(t){return this.isValid?`${this.s.toISO(t)}/${this.e.toISO(t)}`:ui}toISODate(){return this.isValid?`${this.s.toISODate()}/${this.e.toISODate()}`:ui}toISOTime(t){return this.isValid?`${this.s.toISOTime(t)}/${this.e.toISOTime(t)}`:ui}toFormat(t,{separator:e=" \u2013 "}={}){return this.isValid?`${this.s.toFormat(t)}${e}${this.e.toFormat(t)}`:ui}toDuration(t,e){return this.isValid?this.e.diff(this.s,t,e):Z.invalid(this.invalidReason)}mapEndpoints(t){return i.fromDateTimes(t(this.s),t(this.e))}};var Zt=class{static hasDST(t=R.defaultZone){let e=I.now().setZone(t).set({month:12});return!t.isUniversal&&e.offset!==e.set({month:6}).offset}static isValidIANAZone(t){return at.isValidZone(t)}static normalizeZone(t){return Dt(t,R.defaultZone)}static getStartOfWeek({locale:t=null,locObj:e=null}={}){return(e||W.create(t)).getStartOfWeek()}static getMinimumDaysInFirstWeek({locale:t=null,locObj:e=null}={}){return(e||W.create(t)).getMinDaysInFirstWeek()}static getWeekendWeekdays({locale:t=null,locObj:e=null}={}){return(e||W.create(t)).getWeekendDays().slice()}static months(t="long",{locale:e=null,numberingSystem:s=null,locObj:n=null,outputCalendar:r="gregory"}={}){return(n||W.create(e,s,r)).months(t)}static monthsFormat(t="long",{locale:e=null,numberingSystem:s=null,locObj:n=null,outputCalendar:r="gregory"}={}){return(n||W.create(e,s,r)).months(t,!0)}static weekdays(t="long",{locale:e=null,numberingSystem:s=null,locObj:n=null}={}){return(n||W.create(e,s,null)).weekdays(t)}static weekdaysFormat(t="long",{locale:e=null,numberingSystem:s=null,locObj:n=null}={}){return(n||W.create(e,s,null)).weekdays(t,!0)}static meridiems({locale:t=null}={}){return W.create(t).meridiems()}static eras(t="short",{locale:e=null}={}){return W.create(e,null,"gregory").eras(t)}static features(){return{relative:nn(),localeWeek:rn()}}};function mc(i,t){let e=n=>n.toUTC(0,{keepLocalTime:!0}).startOf("day").valueOf(),s=e(t)-e(i);return Math.floor(Z.fromMillis(s).as("days"))}function qm(i,t,e){let s=[["years",(l,c)=>c.year-l.year],["quarters",(l,c)=>c.quarter-l.quarter+(c.year-l.year)*4],["months",(l,c)=>c.month-l.month+(c.year-l.year)*12],["weeks",(l,c)=>{let h=mc(l,c);return(h-h%7)/7}],["days",mc]],n={},r=i,o,a;for(let[l,c]of s)e.indexOf(l)>=0&&(o=l,n[l]=c(i,t),a=r.plus(n),a>t?(n[l]--,i=r.plus(n),i>t&&(a=i,n[l]--,i=r.plus(n))):i=a);return[i,n,a,o]}function gc(i,t,e,s){let[n,r,o,a]=qm(i,t,e),l=t-n,c=e.filter(u=>["hours","minutes","seconds","milliseconds"].indexOf(u)>=0);c.length===0&&(o0?Z.fromMillis(l,s).shiftTo(...c).plus(h):h}var Gm="missing Intl.DateTimeFormat.formatToParts support";function z(i,t=e=>e){return{regex:i,deser:([e])=>t(vl(e))}}var Xm="\xA0",bc=`[ ${Xm}]`,xc=new RegExp(bc,"g");function Km(i){return i.replace(/\./g,"\\.?").replace(xc,bc)}function pc(i){return i.replace(/\./g,"").replace(xc," ").toLowerCase()}function It(i,t){return i===null?null:{regex:RegExp(i.map(Km).join("|")),deser:([e])=>i.findIndex(s=>pc(e)===pc(s))+t}}function yc(i,t){return{regex:i,deser:([,e,s])=>Se(e,s),groups:t}}function cn(i){return{regex:i,deser:([t])=>t}}function Jm(i){return i.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function Qm(i,t){let e=wt(t),s=wt(t,"{2}"),n=wt(t,"{3}"),r=wt(t,"{4}"),o=wt(t,"{6}"),a=wt(t,"{1,2}"),l=wt(t,"{1,3}"),c=wt(t,"{1,6}"),h=wt(t,"{1,9}"),u=wt(t,"{2,4}"),d=wt(t,"{4,6}"),f=p=>({regex:RegExp(Jm(p.val)),deser:([y])=>y,literal:!0}),g=(p=>{if(i.literal)return f(p);switch(p.val){case"G":return It(t.eras("short"),0);case"GG":return It(t.eras("long"),0);case"y":return z(c);case"yy":return z(u,hs);case"yyyy":return z(r);case"yyyyy":return z(d);case"yyyyyy":return z(o);case"M":return z(a);case"MM":return z(s);case"MMM":return It(t.months("short",!0),1);case"MMMM":return It(t.months("long",!0),1);case"L":return z(a);case"LL":return z(s);case"LLL":return It(t.months("short",!1),1);case"LLLL":return It(t.months("long",!1),1);case"d":return z(a);case"dd":return z(s);case"o":return z(l);case"ooo":return z(n);case"HH":return z(s);case"H":return z(a);case"hh":return z(s);case"h":return z(a);case"mm":return z(s);case"m":return z(a);case"q":return z(a);case"qq":return z(s);case"s":return z(a);case"ss":return z(s);case"S":return z(l);case"SSS":return z(n);case"u":return cn(h);case"uu":return cn(a);case"uuu":return z(e);case"a":return It(t.meridiems(),0);case"kkkk":return z(r);case"kk":return z(u,hs);case"W":return z(a);case"WW":return z(s);case"E":case"c":return z(e);case"EEE":return It(t.weekdays("short",!1),1);case"EEEE":return It(t.weekdays("long",!1),1);case"ccc":return It(t.weekdays("short",!0),1);case"cccc":return It(t.weekdays("long",!0),1);case"Z":case"ZZ":return yc(new RegExp(`([+-]${a.source})(?::(${s.source}))?`),2);case"ZZZ":return yc(new RegExp(`([+-]${a.source})(${s.source})?`),2);case"z":return cn(/[a-z_+-/]{1,256}?/i);case" ":return cn(/[^\S\n\r]/);default:return f(p)}})(i)||{invalidReason:Gm};return g.token=i,g}var tg={year:{"2-digit":"yy",numeric:"yyyyy"},month:{numeric:"M","2-digit":"MM",short:"MMM",long:"MMMM"},day:{numeric:"d","2-digit":"dd"},weekday:{short:"EEE",long:"EEEE"},dayperiod:"a",dayPeriod:"a",hour12:{numeric:"h","2-digit":"hh"},hour24:{numeric:"H","2-digit":"HH"},minute:{numeric:"m","2-digit":"mm"},second:{numeric:"s","2-digit":"ss"},timeZoneName:{long:"ZZZZZ",short:"ZZZ"}};function eg(i,t,e){let{type:s,value:n}=i;if(s==="literal"){let l=/^\s+$/.test(n);return{literal:!l,val:l?" ":n}}let r=t[s],o=s;s==="hour"&&(t.hour12!=null?o=t.hour12?"hour12":"hour24":t.hourCycle!=null?t.hourCycle==="h11"||t.hourCycle==="h12"?o="hour12":o="hour24":o=e.hour12?"hour12":"hour24");let a=tg[o];if(typeof a=="object"&&(a=a[r]),a)return{literal:!1,val:a}}function ig(i){return[`^${i.map(e=>e.regex).reduce((e,s)=>`${e}(${s.source})`,"")}$`,i]}function sg(i,t,e){let s=i.match(t);if(s){let n={},r=1;for(let o in e)if(ce(e,o)){let a=e[o],l=a.groups?a.groups+1:1;!a.literal&&a.token&&(n[a.token.val[0]]=a.deser(s.slice(r,r+l))),r+=l}return[s,n]}else return[s,{}]}function ng(i){let t=r=>{switch(r){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":case"H":return"hour";case"d":return"day";case"o":return"ordinal";case"L":case"M":return"month";case"y":return"year";case"E":case"c":return"weekday";case"W":return"weekNumber";case"k":return"weekYear";case"q":return"quarter";default:return null}},e=null,s;return O(i.z)||(e=at.create(i.z)),O(i.Z)||(e||(e=new et(i.Z)),s=i.Z),O(i.q)||(i.M=(i.q-1)*3+1),O(i.h)||(i.h<12&&i.a===1?i.h+=12:i.h===12&&i.a===0&&(i.h=0)),i.G===0&&i.y&&(i.y=-i.y),O(i.u)||(i.S=cs(i.u)),[Object.keys(i).reduce((r,o)=>{let a=t(o);return a&&(r[a]=i[o]),r},{}),e,s]}var oo=null;function rg(){return oo||(oo=I.fromMillis(1555555555555)),oo}function og(i,t){if(i.literal)return i;let e=st.macroTokenToFormatOpts(i.val),s=co(e,t);return s==null||s.includes(void 0)?i:s}function ao(i,t){return Array.prototype.concat(...i.map(e=>og(e,t)))}var fs=class{constructor(t,e){if(this.locale=t,this.format=e,this.tokens=ao(st.parseFormat(e),t),this.units=this.tokens.map(s=>Qm(s,t)),this.disqualifyingUnit=this.units.find(s=>s.invalidReason),!this.disqualifyingUnit){let[s,n]=ig(this.units);this.regex=RegExp(s,"i"),this.handlers=n}}explainFromTokens(t){if(this.isValid){let[e,s]=sg(t,this.regex,this.handlers),[n,r,o]=s?ng(s):[null,null,void 0];if(ce(s,"a")&&ce(s,"H"))throw new Tt("Can't include meridiem when specifying 24-hour format");return{input:t,tokens:this.tokens,regex:this.regex,rawMatches:e,matches:s,result:n,zone:r,specificOffset:o}}else return{input:t,tokens:this.tokens,invalidReason:this.invalidReason}}get isValid(){return!this.disqualifyingUnit}get invalidReason(){return this.disqualifyingUnit?this.disqualifyingUnit.invalidReason:null}};function lo(i,t,e){return new fs(i,e).explainFromTokens(t)}function _c(i,t,e){let{result:s,zone:n,specificOffset:r,invalidReason:o}=lo(i,t,e);return[s,n,r,o]}function co(i,t){if(!i)return null;let s=st.create(t,i).dtFormatter(rg()),n=s.formatToParts(),r=s.resolvedOptions();return n.map(o=>eg(o,i,r))}var ho="Invalid DateTime",wc=864e13;function ms(i){return new it("unsupported zone",`the zone "${i.name}" is not supported`)}function uo(i){return i.weekData===null&&(i.weekData=os(i.c)),i.weekData}function fo(i){return i.localWeekData===null&&(i.localWeekData=os(i.c,i.loc.getMinDaysInFirstWeek(),i.loc.getStartOfWeek())),i.localWeekData}function ve(i,t){let e={ts:i.ts,zone:i.zone,c:i.c,o:i.o,loc:i.loc,invalid:i.invalid};return new I({...e,...t,old:e})}function Dc(i,t,e){let s=i-t*60*1e3,n=e.offset(s);if(t===n)return[s,t];s-=(n-t)*60*1e3;let r=e.offset(s);return n===r?[s,n]:[i-Math.min(n,r)*60*1e3,Math.max(n,r)]}function hn(i,t){i+=t*60*1e3;let e=new Date(i);return{year:e.getUTCFullYear(),month:e.getUTCMonth()+1,day:e.getUTCDate(),hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds(),millisecond:e.getUTCMilliseconds()}}function dn(i,t,e){return Dc(ti(i),t,e)}function Sc(i,t){let e=i.o,s=i.c.year+Math.trunc(t.years),n=i.c.month+Math.trunc(t.months)+Math.trunc(t.quarters)*3,r={...i.c,year:s,month:n,day:Math.min(i.c.day,si(s,n))+Math.trunc(t.days)+Math.trunc(t.weeks)*7},o=Z.fromObject({years:t.years-Math.trunc(t.years),quarters:t.quarters-Math.trunc(t.quarters),months:t.months-Math.trunc(t.months),weeks:t.weeks-Math.trunc(t.weeks),days:t.days-Math.trunc(t.days),hours:t.hours,minutes:t.minutes,seconds:t.seconds,milliseconds:t.milliseconds}).as("milliseconds"),a=ti(r),[l,c]=Dc(a,e,i.zone);return o!==0&&(l+=o,c=i.zone.offset(l)),{ts:l,o:c}}function fi(i,t,e,s,n,r){let{setZone:o,zone:a}=e;if(i&&Object.keys(i).length!==0||t){let l=t||a,c=I.fromObject(i,{...e,zone:l,specificOffset:r});return o?c:c.setZone(a)}else return I.invalid(new it("unparsable",`the input "${n}" can't be parsed as ${s}`))}function un(i,t,e=!0){return i.isValid?st.create(W.create("en-US"),{allowZ:e,forceSimple:!0}).formatDateTimeFromString(i,t):null}function mo(i,t){let e=i.c.year>9999||i.c.year<0,s="";return e&&i.c.year>=0&&(s+="+"),s+=Y(i.c.year,e?6:4),t?(s+="-",s+=Y(i.c.month),s+="-",s+=Y(i.c.day)):(s+=Y(i.c.month),s+=Y(i.c.day)),s}function kc(i,t,e,s,n,r){let o=Y(i.c.hour);return t?(o+=":",o+=Y(i.c.minute),(i.c.millisecond!==0||i.c.second!==0||!e)&&(o+=":")):o+=Y(i.c.minute),(i.c.millisecond!==0||i.c.second!==0||!e)&&(o+=Y(i.c.second),(i.c.millisecond!==0||!s)&&(o+=".",o+=Y(i.c.millisecond,3))),n&&(i.isOffsetFixed&&i.offset===0&&!r?o+="Z":i.o<0?(o+="-",o+=Y(Math.trunc(-i.o/60)),o+=":",o+=Y(Math.trunc(-i.o%60))):(o+="+",o+=Y(Math.trunc(i.o/60)),o+=":",o+=Y(Math.trunc(i.o%60)))),r&&(o+="["+i.zone.ianaName+"]"),o}var Ec={month:1,day:1,hour:0,minute:0,second:0,millisecond:0},ag={weekNumber:1,weekday:1,hour:0,minute:0,second:0,millisecond:0},lg={ordinal:1,hour:0,minute:0,second:0,millisecond:0},Ic=["year","month","day","hour","minute","second","millisecond"],cg=["weekYear","weekNumber","weekday","hour","minute","second","millisecond"],hg=["year","ordinal","hour","minute","second","millisecond"];function ug(i){let t={year:"year",years:"year",month:"month",months:"month",day:"day",days:"day",hour:"hour",hours:"hour",minute:"minute",minutes:"minute",quarter:"quarter",quarters:"quarter",second:"second",seconds:"second",millisecond:"millisecond",milliseconds:"millisecond",weekday:"weekday",weekdays:"weekday",weeknumber:"weekNumber",weeksnumber:"weekNumber",weeknumbers:"weekNumber",weekyear:"weekYear",weekyears:"weekYear",ordinal:"ordinal"}[i.toLowerCase()];if(!t)throw new Qe(i);return t}function Mc(i){switch(i.toLowerCase()){case"localweekday":case"localweekdays":return"localWeekday";case"localweeknumber":case"localweeknumbers":return"localWeekNumber";case"localweekyear":case"localweekyears":return"localWeekYear";default:return ug(i)}}function dg(i){if(gs===void 0&&(gs=R.now()),i.type!=="iana")return i.offset(gs);let t=i.name,e=go.get(t);return e===void 0&&(e=i.offset(gs),go.set(t,e)),e}function Tc(i,t){let e=Dt(t.zone,R.defaultZone);if(!e.isValid)return I.invalid(ms(e));let s=W.fromObject(t),n,r;if(O(i.year))n=R.now();else{for(let l of Ic)O(i[l])&&(i[l]=Ec[l]);let o=Xr(i)||Kr(i);if(o)return I.invalid(o);let a=dg(e);[n,r]=dn(i,a,e)}return new I({ts:n,zone:e,loc:s,o:r})}function vc(i,t,e){let s=O(e.round)?!0:e.round,n=(o,a)=>(o=ei(o,s||e.calendary?0:2,!0),t.loc.clone(e).relFormatter(e).format(o,a)),r=o=>e.calendary?t.hasSame(i,o)?0:t.startOf(o).diff(i.startOf(o),o).get(o):t.diff(i,o).get(o);if(e.unit)return n(r(e.unit),e.unit);for(let o of e.units){let a=r(o);if(Math.abs(a)>=1)return n(a,o)}return n(i>t?-0:0,e.units[e.units.length-1])}function Oc(i){let t={},e;return i.length>0&&typeof i[i.length-1]=="object"?(t=i[i.length-1],e=Array.from(i).slice(0,i.length-1)):e=Array.from(i),[t,e]}var gs,go=new Map,I=class i{constructor(t){let e=t.zone||R.defaultZone,s=t.invalid||(Number.isNaN(t.ts)?new it("invalid input"):null)||(e.isValid?null:ms(e));this.ts=O(t.ts)?R.now():t.ts;let n=null,r=null;if(!s)if(t.old&&t.old.ts===this.ts&&t.old.zone.equals(e))[n,r]=[t.old.c,t.old.o];else{let a=Et(t.o)&&!t.old?t.o:e.offset(this.ts);n=hn(this.ts,a),s=Number.isNaN(n.year)?new it("invalid input"):null,n=s?null:n,r=s?null:a}this._zone=e,this.loc=t.loc||W.create(),this.invalid=s,this.weekData=null,this.localWeekData=null,this.c=n,this.o=r,this.isLuxonDateTime=!0}static now(){return new i({})}static local(){let[t,e]=Oc(arguments),[s,n,r,o,a,l,c]=e;return Tc({year:s,month:n,day:r,hour:o,minute:a,second:l,millisecond:c},t)}static utc(){let[t,e]=Oc(arguments),[s,n,r,o,a,l,c]=e;return t.zone=et.utcInstance,Tc({year:s,month:n,day:r,hour:o,minute:a,second:l,millisecond:c},t)}static fromJSDate(t,e={}){let s=$l(t)?t.valueOf():NaN;if(Number.isNaN(s))return i.invalid("invalid input");let n=Dt(e.zone,R.defaultZone);return n.isValid?new i({ts:s,zone:n,loc:W.fromObject(e)}):i.invalid(ms(n))}static fromMillis(t,e={}){if(Et(t))return t<-wc||t>wc?i.invalid("Timestamp out of range"):new i({ts:t,zone:Dt(e.zone,R.defaultZone),loc:W.fromObject(e)});throw new G(`fromMillis requires a numerical input, but received a ${typeof t} with value ${t}`)}static fromSeconds(t,e={}){if(Et(t))return new i({ts:t*1e3,zone:Dt(e.zone,R.defaultZone),loc:W.fromObject(e)});throw new G("fromSeconds requires a numerical input")}static fromObject(t,e={}){t=t||{};let s=Dt(e.zone,R.defaultZone);if(!s.isValid)return i.invalid(ms(s));let n=W.fromObject(e),r=ni(t,Mc),{minDaysInFirstWeek:o,startOfWeek:a}=Gr(r,n),l=R.now(),c=O(e.specificOffset)?s.offset(l):e.specificOffset,h=!O(r.ordinal),u=!O(r.year),d=!O(r.month)||!O(r.day),f=u||d,m=r.weekYear||r.weekNumber;if((f||h)&&m)throw new Tt("Can't mix weekYear/weekNumber units with year/month/day or ordinals");if(d&&h)throw new Tt("Can't mix ordinal dates with month/day");let g=m||r.weekday&&!f,p,y,b=hn(l,c);g?(p=cg,y=ag,b=os(b,o,a)):h?(p=hg,y=lg,b=ln(b)):(p=Ic,y=Ec);let _=!1;for(let C of p){let N=r[C];O(N)?_?r[C]=y[C]:r[C]=b[C]:_=!0}let w=g?Vl(r,o,a):h?Hl(r):Xr(r),x=w||Kr(r);if(x)return i.invalid(x);let S=g?Zr(r,o,a):h?qr(r):r,[k,v]=dn(S,c,s),T=new i({ts:k,zone:s,o:v,loc:n});return r.weekday&&f&&t.weekday!==T.weekday?i.invalid("mismatched weekday",`you can't specify both a weekday of ${r.weekday} and a date of ${T.toISO()}`):T.isValid?T:i.invalid(T.invalid)}static fromISO(t,e={}){let[s,n]=nc(t);return fi(s,n,e,"ISO 8601",t)}static fromRFC2822(t,e={}){let[s,n]=rc(t);return fi(s,n,e,"RFC 2822",t)}static fromHTTP(t,e={}){let[s,n]=oc(t);return fi(s,n,e,"HTTP",e)}static fromFormat(t,e,s={}){if(O(t)||O(e))throw new G("fromFormat requires an input string and a format");let{locale:n=null,numberingSystem:r=null}=s,o=W.fromOpts({locale:n,numberingSystem:r,defaultToEN:!0}),[a,l,c,h]=_c(o,t,e);return h?i.invalid(h):fi(a,l,s,`format ${e}`,t,c)}static fromString(t,e,s={}){return i.fromFormat(t,e,s)}static fromSQL(t,e={}){let[s,n]=cc(t);return fi(s,n,e,"SQL",t)}static invalid(t,e=null){if(!t)throw new G("need to specify a reason the DateTime is invalid");let s=t instanceof it?t:new it(t,e);if(R.throwOnInvalid)throw new Ks(s);return new i({invalid:s})}static isDateTime(t){return t&&t.isLuxonDateTime||!1}static parseFormatForOpts(t,e={}){let s=co(t,W.fromObject(e));return s?s.map(n=>n?n.val:null).join(""):null}static expandFormat(t,e={}){return ao(st.parseFormat(t),W.fromObject(e)).map(n=>n.val).join("")}static resetCache(){gs=void 0,go.clear()}get(t){return this[t]}get isValid(){return this.invalid===null}get invalidReason(){return this.invalid?this.invalid.reason:null}get invalidExplanation(){return this.invalid?this.invalid.explanation:null}get locale(){return this.isValid?this.loc.locale:null}get numberingSystem(){return this.isValid?this.loc.numberingSystem:null}get outputCalendar(){return this.isValid?this.loc.outputCalendar:null}get zone(){return this._zone}get zoneName(){return this.isValid?this.zone.name:null}get year(){return this.isValid?this.c.year:NaN}get quarter(){return this.isValid?Math.ceil(this.c.month/3):NaN}get month(){return this.isValid?this.c.month:NaN}get day(){return this.isValid?this.c.day:NaN}get hour(){return this.isValid?this.c.hour:NaN}get minute(){return this.isValid?this.c.minute:NaN}get second(){return this.isValid?this.c.second:NaN}get millisecond(){return this.isValid?this.c.millisecond:NaN}get weekYear(){return this.isValid?uo(this).weekYear:NaN}get weekNumber(){return this.isValid?uo(this).weekNumber:NaN}get weekday(){return this.isValid?uo(this).weekday:NaN}get isWeekend(){return this.isValid&&this.loc.getWeekendDays().includes(this.weekday)}get localWeekday(){return this.isValid?fo(this).weekday:NaN}get localWeekNumber(){return this.isValid?fo(this).weekNumber:NaN}get localWeekYear(){return this.isValid?fo(this).weekYear:NaN}get ordinal(){return this.isValid?ln(this.c).ordinal:NaN}get monthShort(){return this.isValid?Zt.months("short",{locObj:this.loc})[this.month-1]:null}get monthLong(){return this.isValid?Zt.months("long",{locObj:this.loc})[this.month-1]:null}get weekdayShort(){return this.isValid?Zt.weekdays("short",{locObj:this.loc})[this.weekday-1]:null}get weekdayLong(){return this.isValid?Zt.weekdays("long",{locObj:this.loc})[this.weekday-1]:null}get offset(){return this.isValid?+this.o:NaN}get offsetNameShort(){return this.isValid?this.zone.offsetName(this.ts,{format:"short",locale:this.locale}):null}get offsetNameLong(){return this.isValid?this.zone.offsetName(this.ts,{format:"long",locale:this.locale}):null}get isOffsetFixed(){return this.isValid?this.zone.isUniversal:null}get isInDST(){return this.isOffsetFixed?!1:this.offset>this.set({month:1,day:1}).offset||this.offset>this.set({month:5}).offset}getPossibleOffsets(){if(!this.isValid||this.isOffsetFixed)return[this];let t=864e5,e=6e4,s=ti(this.c),n=this.zone.offset(s-t),r=this.zone.offset(s+t),o=this.zone.offset(s-n*e),a=this.zone.offset(s-r*e);if(o===a)return[this];let l=s-o*e,c=s-a*e,h=hn(l,o),u=hn(c,a);return h.hour===u.hour&&h.minute===u.minute&&h.second===u.second&&h.millisecond===u.millisecond?[ve(this,{ts:l}),ve(this,{ts:c})]:[this]}get isInLeapYear(){return Me(this.year)}get daysInMonth(){return si(this.year,this.month)}get daysInYear(){return this.isValid?le(this.year):NaN}get weeksInWeekYear(){return this.isValid?ke(this.weekYear):NaN}get weeksInLocalWeekYear(){return this.isValid?ke(this.localWeekYear,this.loc.getMinDaysInFirstWeek(),this.loc.getStartOfWeek()):NaN}resolvedLocaleOptions(t={}){let{locale:e,numberingSystem:s,calendar:n}=st.create(this.loc.clone(t),t).resolvedOptions(this);return{locale:e,numberingSystem:s,outputCalendar:n}}toUTC(t=0,e={}){return this.setZone(et.instance(t),e)}toLocal(){return this.setZone(R.defaultZone)}setZone(t,{keepLocalTime:e=!1,keepCalendarTime:s=!1}={}){if(t=Dt(t,R.defaultZone),t.equals(this.zone))return this;if(t.isValid){let n=this.ts;if(e||s){let r=t.offset(this.ts),o=this.toObject();[n]=dn(o,r,t)}return ve(this,{ts:n,zone:t})}else return i.invalid(ms(t))}reconfigure({locale:t,numberingSystem:e,outputCalendar:s}={}){let n=this.loc.clone({locale:t,numberingSystem:e,outputCalendar:s});return ve(this,{loc:n})}setLocale(t){return this.reconfigure({locale:t})}set(t){if(!this.isValid)return this;let e=ni(t,Mc),{minDaysInFirstWeek:s,startOfWeek:n}=Gr(e,this.loc),r=!O(e.weekYear)||!O(e.weekNumber)||!O(e.weekday),o=!O(e.ordinal),a=!O(e.year),l=!O(e.month)||!O(e.day),c=a||l,h=e.weekYear||e.weekNumber;if((c||o)&&h)throw new Tt("Can't mix weekYear/weekNumber units with year/month/day or ordinals");if(l&&o)throw new Tt("Can't mix ordinal dates with month/day");let u;r?u=Zr({...os(this.c,s,n),...e},s,n):O(e.ordinal)?(u={...this.toObject(),...e},O(e.day)&&(u.day=Math.min(si(u.year,u.month),u.day))):u=qr({...ln(this.c),...e});let[d,f]=dn(u,this.o,this.zone);return ve(this,{ts:d,o:f})}plus(t){if(!this.isValid)return this;let e=Z.fromDurationLike(t);return ve(this,Sc(this,e))}minus(t){if(!this.isValid)return this;let e=Z.fromDurationLike(t).negate();return ve(this,Sc(this,e))}startOf(t,{useLocaleWeeks:e=!1}={}){if(!this.isValid)return this;let s={},n=Z.normalizeUnit(t);switch(n){case"years":s.month=1;case"quarters":case"months":s.day=1;case"weeks":case"days":s.hour=0;case"hours":s.minute=0;case"minutes":s.second=0;case"seconds":s.millisecond=0;break;case"milliseconds":break}if(n==="weeks")if(e){let r=this.loc.getStartOfWeek(),{weekday:o}=this;othis.valueOf(),a=o?this:t,l=o?t:this,c=gc(a,l,r,n);return o?c.negate():c}diffNow(t="milliseconds",e={}){return this.diff(i.now(),t,e)}until(t){return this.isValid?Yt.fromDateTimes(this,t):this}hasSame(t,e,s){if(!this.isValid)return!1;let n=t.valueOf(),r=this.setZone(t.zone,{keepLocalTime:!0});return r.startOf(e,s)<=n&&n<=r.endOf(e,s)}equals(t){return this.isValid&&t.isValid&&this.valueOf()===t.valueOf()&&this.zone.equals(t.zone)&&this.loc.equals(t.loc)}toRelative(t={}){if(!this.isValid)return null;let e=t.base||i.fromObject({},{zone:this.zone}),s=t.padding?thise.valueOf(),Math.min)}static max(...t){if(!t.every(i.isDateTime))throw new G("max requires all arguments be DateTimes");return Jr(t,e=>e.valueOf(),Math.max)}static fromFormatExplain(t,e,s={}){let{locale:n=null,numberingSystem:r=null}=s,o=W.fromOpts({locale:n,numberingSystem:r,defaultToEN:!0});return lo(o,t,e)}static fromStringExplain(t,e,s={}){return i.fromFormatExplain(t,e,s)}static buildFormatParser(t,e={}){let{locale:s=null,numberingSystem:n=null}=e,r=W.fromOpts({locale:s,numberingSystem:n,defaultToEN:!0});return new fs(r,t)}static fromFormatParser(t,e,s={}){if(O(t)||O(e))throw new G("fromFormatParser requires an input string and a format parser");let{locale:n=null,numberingSystem:r=null}=s,o=W.fromOpts({locale:n,numberingSystem:r,defaultToEN:!0});if(!o.equals(e.locale))throw new G(`fromFormatParser called with a locale of ${o}, but the format parser was created for ${e.locale}`);let{result:a,zone:l,specificOffset:c,invalidReason:h}=e.explainFromTokens(t);return h?i.invalid(h):fi(a,l,s,`format ${e.format}`,t,c)}static get DATE_SHORT(){return re}static get DATE_MED(){return zi}static get DATE_MED_WITH_WEEKDAY(){return Tr}static get DATE_FULL(){return Vi}static get DATE_HUGE(){return Hi}static get TIME_SIMPLE(){return Bi}static get TIME_WITH_SECONDS(){return $i}static get TIME_WITH_SHORT_OFFSET(){return ji}static get TIME_WITH_LONG_OFFSET(){return Ui}static get TIME_24_SIMPLE(){return Yi}static get TIME_24_WITH_SECONDS(){return Zi}static get TIME_24_WITH_SHORT_OFFSET(){return qi}static get TIME_24_WITH_LONG_OFFSET(){return Gi}static get DATETIME_SHORT(){return Xi}static get DATETIME_SHORT_WITH_SECONDS(){return Ki}static get DATETIME_MED(){return Ji}static get DATETIME_MED_WITH_SECONDS(){return Qi}static get DATETIME_MED_WITH_WEEKDAY(){return vr}static get DATETIME_FULL(){return ts}static get DATETIME_FULL_WITH_SECONDS(){return es}static get DATETIME_HUGE(){return is}static get DATETIME_HUGE_WITH_SECONDS(){return ss}};function di(i){if(I.isDateTime(i))return i;if(i&&i.valueOf&&Et(i.valueOf()))return I.fromJSDate(i);if(i&&typeof i=="object")return I.fromObject(i);throw new G(`Unknown datetime argument: ${i}, of type ${typeof i}`)}var fg={datetime:I.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:I.TIME_WITH_SECONDS,minute:I.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};kr._date.override({_id:"luxon",_create:function(i){return I.fromMillis(i,this.options)},init(i){this.options.locale||(this.options.locale=i.locale)},formats:function(){return fg},parse:function(i,t){let e=this.options,s=typeof i;return i===null||s==="undefined"?null:(s==="number"?i=this._create(i):s==="string"?typeof t=="string"?i=I.fromFormat(i,t,e):i=I.fromISO(i,e):i instanceof Date?i=I.fromJSDate(i,e):s==="object"&&!(i instanceof I)&&(i=I.fromObject(i,e)),i.isValid?i.valueOf():null)},format:function(i,t){let e=this._create(i);return typeof t=="string"?e.toFormat(t):e.toLocaleString(t)},add:function(i,t,e){let s={};return s[e]=t,this._create(i).plus(s).valueOf()},diff:function(i,t,e){return this._create(i).diff(this._create(t)).as(e).valueOf()},startOf:function(i,t,e){if(t==="isoWeek"){e=Math.trunc(Math.min(Math.max(0,e),6));let s=this._create(i);return s.minus({days:(s.weekday-e+7)%7}).startOf("day").valueOf()}return t?this._create(i).startOf(t).valueOf():i},endOf:function(i,t){return this._create(i).endOf(t).valueOf()}});function fn({cachedData:i,options:t,type:e}){return{init:function(){this.initChart(),this.$wire.$on("updateChartData",({data:s})=>{fn=this.getChart(),fn.data=s,fn.update("resize")}),Alpine.effect(()=>{Alpine.store("theme"),this.$nextTick(()=>{this.getChart()&&(this.getChart().destroy(),this.initChart())})}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Alpine.store("theme")==="system"&&this.$nextTick(()=>{this.getChart().destroy(),this.initChart()})})},initChart:function(s=null){var o,a,l,c,h,u,d,f,m;if(!this.$refs.canvas||!this.$refs.backgroundColorElement||!this.$refs.borderColorElement||!this.$refs.textColorElement||!this.$refs.gridColorElement)return;Rt.defaults.animation.duration=0,Rt.defaults.backgroundColor=getComputedStyle(this.$refs.backgroundColorElement).color;let n=getComputedStyle(this.$refs.borderColorElement).color;Rt.defaults.borderColor=n,Rt.defaults.color=getComputedStyle(this.$refs.textColorElement).color,Rt.defaults.font.family=getComputedStyle(this.$el).fontFamily,Rt.defaults.plugins.legend.labels.boxWidth=12,Rt.defaults.plugins.legend.position="bottom";let r=getComputedStyle(this.$refs.gridColorElement).color;return t??(t={}),t.borderWidth??(t.borderWidth=2),t.pointBackgroundColor??(t.pointBackgroundColor=n),t.pointHitRadius??(t.pointHitRadius=4),t.pointRadius??(t.pointRadius=2),t.scales??(t.scales={}),(o=t.scales).x??(o.x={}),(a=t.scales.x).grid??(a.grid={}),(l=t.scales.x.grid).color??(l.color=r),(c=t.scales.x.grid).display??(c.display=!1),(h=t.scales.x.grid).drawBorder??(h.drawBorder=!1),(u=t.scales).y??(u.y={}),(d=t.scales.y).grid??(d.grid={}),(f=t.scales.y.grid).color??(f.color=r),(m=t.scales.y.grid).drawBorder??(m.drawBorder=!1),new Rt(this.$refs.canvas,{type:e,data:s??i,options:t,plugins:window.filamentChartJsPlugins??[]})},getChart:function(){return this.$refs.canvas?Rt.getChart(this.$refs.canvas):null}}}export{fn as default}; -/*! Bundled license information: - -chart.js/dist/chunks/helpers.segment.mjs: -chart.js/dist/chart.mjs: - (*! - * Chart.js v3.9.1 - * https://www.chartjs.org - * (c) 2022 Chart.js Contributors - * Released under the MIT License - *) - -chart.js/dist/chunks/helpers.segment.mjs: - (*! - * @kurkle/color v0.2.1 - * https://github.com/kurkle/color#readme - * (c) 2022 Jukka Kurkela - * Released under the MIT License - *) - -chartjs-adapter-luxon/dist/chartjs-adapter-luxon.esm.js: - (*! - * chartjs-adapter-luxon v1.3.1 - * https://www.chartjs.org - * (c) 2023 chartjs-adapter-luxon Contributors - * Released under the MIT license - *) -*/ diff --git a/public/js/filament/widgets/components/stats-overview/stat/chart.js b/public/js/filament/widgets/components/stats-overview/stat/chart.js deleted file mode 100644 index 8abc23b..0000000 --- a/public/js/filament/widgets/components/stats-overview/stat/chart.js +++ /dev/null @@ -1,22 +0,0 @@ -function rt(){}var Hs=function(){let i=0;return function(){return i++}}();function T(i){return i===null||typeof i>"u"}function I(i){if(Array.isArray&&Array.isArray(i))return!0;let t=Object.prototype.toString.call(i);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function D(i){return i!==null&&Object.prototype.toString.call(i)==="[object Object]"}var W=i=>(typeof i=="number"||i instanceof Number)&&isFinite(+i);function Q(i,t){return W(i)?i:t}function C(i,t){return typeof i>"u"?t:i}var js=(i,t)=>typeof i=="string"&&i.endsWith("%")?parseFloat(i)/100:i/t,Di=(i,t)=>typeof i=="string"&&i.endsWith("%")?parseFloat(i)/100*t:+i;function z(i,t,e){if(i&&typeof i.call=="function")return i.apply(e,t)}function E(i,t,e,s){let n,o,a;if(I(i))if(o=i.length,s)for(n=o-1;n>=0;n--)t.call(e,i[n],n);else for(n=0;ni,x:i=>i.x,y:i=>i.y};function gt(i,t){return(Ds[t]||(Ds[t]=Io(t)))(i)}function Io(i){let t=zo(i);return e=>{for(let s of t){if(s==="")break;e=e&&e[s]}return e}}function zo(i){let t=i.split("."),e=[],s="";for(let n of t)s+=n,s.endsWith("\\")?s=s.slice(0,-1)+".":(e.push(s),s="");return e}function Xe(i){return i.charAt(0).toUpperCase()+i.slice(1)}var J=i=>typeof i<"u",ft=i=>typeof i=="function",Oi=(i,t)=>{if(i.size!==t.size)return!1;for(let e of i)if(!t.has(e))return!1;return!0};function Ys(i){return i.type==="mouseup"||i.type==="click"||i.type==="contextmenu"}var B=Math.PI,F=2*B,Bo=F+B,je=Number.POSITIVE_INFINITY,Vo=B/180,V=B/2,ue=B/4,Os=B*2/3,tt=Math.log10,ot=Math.sign;function Ai(i){let t=Math.round(i);i=Ut(i,t,i/1e3)?t:i;let e=Math.pow(10,Math.floor(tt(i))),s=i/e;return(s<=1?1:s<=2?2:s<=5?5:10)*e}function Xs(i){let t=[],e=Math.sqrt(i),s;for(s=1;sn-o).pop(),t}function Lt(i){return!isNaN(parseFloat(i))&&isFinite(i)}function Ut(i,t,e){return Math.abs(i-t)=i}function Ti(i,t,e){let s,n,o;for(s=0,n=i.length;sl&&c=Math.min(t,e)-s&&i<=Math.max(t,e)+s}function Ke(i,t,e){e=e||(a=>i[a]1;)o=n+s>>1,e(o)?n=o:s=o;return{lo:n,hi:s}}var at=(i,t,e,s)=>Ke(i,e,s?n=>i[n][t]<=e:n=>i[n][t]Ke(i,e,s=>i[s][t]>=e);function Gs(i,t,e){let s=0,n=i.length;for(;ss&&i[n-1]>e;)n--;return s>0||n{let s="_onData"+Xe(e),n=i[e];Object.defineProperty(i,e,{configurable:!0,enumerable:!1,value(...o){let a=n.apply(this,o);return i._chartjs.listeners.forEach(r=>{typeof r[s]=="function"&&r[s](...o)}),a}})})}function Ei(i,t){let e=i._chartjs;if(!e)return;let s=e.listeners,n=s.indexOf(t);n!==-1&&s.splice(n,1),!(s.length>0)&&(Zs.forEach(o=>{delete i[o]}),delete i._chartjs)}function Fi(i){let t=new Set,e,s;for(e=0,s=i.length;e"u"?function(i){return i()}:window.requestAnimationFrame}();function zi(i,t,e){let s=e||(a=>Array.prototype.slice.call(a)),n=!1,o=[];return function(...a){o=s(a),n||(n=!0,Ii.call(window,()=>{n=!1,i.apply(t,o)}))}}function Qs(i,t){let e;return function(...s){return t?(clearTimeout(e),e=setTimeout(i,t,s)):i.apply(this,s),t}}var qe=i=>i==="start"?"left":i==="end"?"right":"center",X=(i,t,e)=>i==="start"?t:i==="end"?e:(t+e)/2,tn=(i,t,e,s)=>i===(s?"left":"right")?e:i==="center"?(t+e)/2:t;function Bi(i,t,e){let s=t.length,n=0,o=s;if(i._sorted){let{iScale:a,_parsed:r}=i,l=a.axis,{min:c,max:h,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=Y(Math.min(at(r,a.axis,c).lo,e?s:at(t,l,a.getPixelForValue(c)).lo),0,s-1)),u?o=Y(Math.max(at(r,a.axis,h,!0).hi+1,e?0:at(t,l,a.getPixelForValue(h),!0).hi+1),n,s)-n:o=s-n}return{start:n,count:o}}function Vi(i){let{xScale:t,yScale:e,_scaleRanges:s}=i,n={xmin:t.min,xmax:t.max,ymin:e.min,ymax:e.max};if(!s)return i._scaleRanges=n,!0;let o=s.xmin!==t.min||s.xmax!==t.max||s.ymin!==e.min||s.ymax!==e.max;return Object.assign(s,n),o}var ze=i=>i===0||i===1,As=(i,t,e)=>-(Math.pow(2,10*(i-=1))*Math.sin((i-t)*F/e)),Ts=(i,t,e)=>Math.pow(2,-10*i)*Math.sin((i-t)*F/e)+1,Ht={linear:i=>i,easeInQuad:i=>i*i,easeOutQuad:i=>-i*(i-2),easeInOutQuad:i=>(i/=.5)<1?.5*i*i:-.5*(--i*(i-2)-1),easeInCubic:i=>i*i*i,easeOutCubic:i=>(i-=1)*i*i+1,easeInOutCubic:i=>(i/=.5)<1?.5*i*i*i:.5*((i-=2)*i*i+2),easeInQuart:i=>i*i*i*i,easeOutQuart:i=>-((i-=1)*i*i*i-1),easeInOutQuart:i=>(i/=.5)<1?.5*i*i*i*i:-.5*((i-=2)*i*i*i-2),easeInQuint:i=>i*i*i*i*i,easeOutQuint:i=>(i-=1)*i*i*i*i+1,easeInOutQuint:i=>(i/=.5)<1?.5*i*i*i*i*i:.5*((i-=2)*i*i*i*i+2),easeInSine:i=>-Math.cos(i*V)+1,easeOutSine:i=>Math.sin(i*V),easeInOutSine:i=>-.5*(Math.cos(B*i)-1),easeInExpo:i=>i===0?0:Math.pow(2,10*(i-1)),easeOutExpo:i=>i===1?1:-Math.pow(2,-10*i)+1,easeInOutExpo:i=>ze(i)?i:i<.5?.5*Math.pow(2,10*(i*2-1)):.5*(-Math.pow(2,-10*(i*2-1))+2),easeInCirc:i=>i>=1?i:-(Math.sqrt(1-i*i)-1),easeOutCirc:i=>Math.sqrt(1-(i-=1)*i),easeInOutCirc:i=>(i/=.5)<1?-.5*(Math.sqrt(1-i*i)-1):.5*(Math.sqrt(1-(i-=2)*i)+1),easeInElastic:i=>ze(i)?i:As(i,.075,.3),easeOutElastic:i=>ze(i)?i:Ts(i,.075,.3),easeInOutElastic(i){return ze(i)?i:i<.5?.5*As(i*2,.1125,.45):.5+.5*Ts(i*2-1,.1125,.45)},easeInBack(i){return i*i*((1.70158+1)*i-1.70158)},easeOutBack(i){return(i-=1)*i*((1.70158+1)*i+1.70158)+1},easeInOutBack(i){let t=1.70158;return(i/=.5)<1?.5*(i*i*(((t*=1.525)+1)*i-t)):.5*((i-=2)*i*(((t*=1.525)+1)*i+t)+2)},easeInBounce:i=>1-Ht.easeOutBounce(1-i),easeOutBounce(i){return i<1/2.75?7.5625*i*i:i<2/2.75?7.5625*(i-=1.5/2.75)*i+.75:i<2.5/2.75?7.5625*(i-=2.25/2.75)*i+.9375:7.5625*(i-=2.625/2.75)*i+.984375},easeInOutBounce:i=>i<.5?Ht.easeInBounce(i*2)*.5:Ht.easeOutBounce(i*2-1)*.5+.5};function be(i){return i+.5|0}var xt=(i,t,e)=>Math.max(Math.min(i,e),t);function fe(i){return xt(be(i*2.55),0,255)}function yt(i){return xt(be(i*255),0,255)}function ut(i){return xt(be(i/2.55)/100,0,1)}function Ls(i){return xt(be(i*100),0,100)}var st={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Si=[..."0123456789ABCDEF"],No=i=>Si[i&15],Ho=i=>Si[(i&240)>>4]+Si[i&15],Be=i=>(i&240)>>4===(i&15),jo=i=>Be(i.r)&&Be(i.g)&&Be(i.b)&&Be(i.a);function $o(i){var t=i.length,e;return i[0]==="#"&&(t===4||t===5?e={r:255&st[i[1]]*17,g:255&st[i[2]]*17,b:255&st[i[3]]*17,a:t===5?st[i[4]]*17:255}:(t===7||t===9)&&(e={r:st[i[1]]<<4|st[i[2]],g:st[i[3]]<<4|st[i[4]],b:st[i[5]]<<4|st[i[6]],a:t===9?st[i[7]]<<4|st[i[8]]:255})),e}var Yo=(i,t)=>i<255?t(i):"";function Xo(i){var t=jo(i)?No:Ho;return i?"#"+t(i.r)+t(i.g)+t(i.b)+Yo(i.a,t):void 0}var Uo=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function en(i,t,e){let s=t*Math.min(e,1-e),n=(o,a=(o+i/30)%12)=>e-s*Math.max(Math.min(a-3,9-a,1),-1);return[n(0),n(8),n(4)]}function Ko(i,t,e){let s=(n,o=(n+i/60)%6)=>e-e*t*Math.max(Math.min(o,4-o,1),0);return[s(5),s(3),s(1)]}function qo(i,t,e){let s=en(i,1,.5),n;for(t+e>1&&(n=1/(t+e),t*=n,e*=n),n=0;n<3;n++)s[n]*=1-t-e,s[n]+=t;return s}function Go(i,t,e,s,n){return i===n?(t-e)/s+(t.5?h/(2-o-a):h/(o+a),l=Go(e,s,n,h,o),l=l*60+.5),[l|0,c||0,r]}function Ni(i,t,e,s){return(Array.isArray(t)?i(t[0],t[1],t[2]):i(t,e,s)).map(yt)}function Hi(i,t,e){return Ni(en,i,t,e)}function Zo(i,t,e){return Ni(qo,i,t,e)}function Jo(i,t,e){return Ni(Ko,i,t,e)}function sn(i){return(i%360+360)%360}function Qo(i){let t=Uo.exec(i),e=255,s;if(!t)return;t[5]!==s&&(e=t[6]?fe(+t[5]):yt(+t[5]));let n=sn(+t[2]),o=+t[3]/100,a=+t[4]/100;return t[1]==="hwb"?s=Zo(n,o,a):t[1]==="hsv"?s=Jo(n,o,a):s=Hi(n,o,a),{r:s[0],g:s[1],b:s[2],a:e}}function ta(i,t){var e=Wi(i);e[0]=sn(e[0]+t),e=Hi(e),i.r=e[0],i.g=e[1],i.b=e[2]}function ea(i){if(!i)return;let t=Wi(i),e=t[0],s=Ls(t[1]),n=Ls(t[2]);return i.a<255?`hsla(${e}, ${s}%, ${n}%, ${ut(i.a)})`:`hsl(${e}, ${s}%, ${n}%)`}var Rs={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Es={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function ia(){let i={},t=Object.keys(Es),e=Object.keys(Rs),s,n,o,a,r;for(s=0;s>16&255,o>>8&255,o&255]}return i}var Ve;function sa(i){Ve||(Ve=ia(),Ve.transparent=[0,0,0,0]);let t=Ve[i.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}var na=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function oa(i){let t=na.exec(i),e=255,s,n,o;if(t){if(t[7]!==s){let a=+t[7];e=t[8]?fe(a):xt(a*255,0,255)}return s=+t[1],n=+t[3],o=+t[5],s=255&(t[2]?fe(s):xt(s,0,255)),n=255&(t[4]?fe(n):xt(n,0,255)),o=255&(t[6]?fe(o):xt(o,0,255)),{r:s,g:n,b:o,a:e}}}function aa(i){return i&&(i.a<255?`rgba(${i.r}, ${i.g}, ${i.b}, ${ut(i.a)})`:`rgb(${i.r}, ${i.g}, ${i.b})`)}var vi=i=>i<=.0031308?i*12.92:Math.pow(i,1/2.4)*1.055-.055,Nt=i=>i<=.04045?i/12.92:Math.pow((i+.055)/1.055,2.4);function ra(i,t,e){let s=Nt(ut(i.r)),n=Nt(ut(i.g)),o=Nt(ut(i.b));return{r:yt(vi(s+e*(Nt(ut(t.r))-s))),g:yt(vi(n+e*(Nt(ut(t.g))-n))),b:yt(vi(o+e*(Nt(ut(t.b))-o))),a:i.a+e*(t.a-i.a)}}function We(i,t,e){if(i){let s=Wi(i);s[t]=Math.max(0,Math.min(s[t]+s[t]*e,t===0?360:1)),s=Hi(s),i.r=s[0],i.g=s[1],i.b=s[2]}}function nn(i,t){return i&&Object.assign(t||{},i)}function Fs(i){var t={r:0,g:0,b:0,a:255};return Array.isArray(i)?i.length>=3&&(t={r:i[0],g:i[1],b:i[2],a:255},i.length>3&&(t.a=yt(i[3]))):(t=nn(i,{r:0,g:0,b:0,a:1}),t.a=yt(t.a)),t}function la(i){return i.charAt(0)==="r"?oa(i):Qo(i)}var Pi=class i{constructor(t){if(t instanceof i)return t;let e=typeof t,s;e==="object"?s=Fs(t):e==="string"&&(s=$o(t)||sa(t)||la(t)),this._rgb=s,this._valid=!!s}get valid(){return this._valid}get rgb(){var t=nn(this._rgb);return t&&(t.a=ut(t.a)),t}set rgb(t){this._rgb=Fs(t)}rgbString(){return this._valid?aa(this._rgb):void 0}hexString(){return this._valid?Xo(this._rgb):void 0}hslString(){return this._valid?ea(this._rgb):void 0}mix(t,e){if(t){let s=this.rgb,n=t.rgb,o,a=e===o?.5:e,r=2*a-1,l=s.a-n.a,c=((r*l===-1?r:(r+l)/(1+r*l))+1)/2;o=1-c,s.r=255&c*s.r+o*n.r+.5,s.g=255&c*s.g+o*n.g+.5,s.b=255&c*s.b+o*n.b+.5,s.a=a*s.a+(1-a)*n.a,this.rgb=s}return this}interpolate(t,e){return t&&(this._rgb=ra(this._rgb,t._rgb,e)),this}clone(){return new i(this.rgb)}alpha(t){return this._rgb.a=yt(t),this}clearer(t){let e=this._rgb;return e.a*=1-t,this}greyscale(){let t=this._rgb,e=be(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=e,this}opaquer(t){let e=this._rgb;return e.a*=1+t,this}negate(){let t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return We(this._rgb,2,t),this}darken(t){return We(this._rgb,2,-t),this}saturate(t){return We(this._rgb,1,t),this}desaturate(t){return We(this._rgb,1,-t),this}rotate(t){return ta(this._rgb,t),this}};function on(i){return new Pi(i)}function an(i){if(i&&typeof i=="object"){let t=i.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function ji(i){return an(i)?i:on(i)}function Mi(i){return an(i)?i:on(i).saturate(.5).darken(.1).hexString()}var vt=Object.create(null),Ge=Object.create(null);function ge(i,t){if(!t)return i;let e=t.split(".");for(let s=0,n=e.length;se.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(e,s)=>Mi(s.backgroundColor),this.hoverBorderColor=(e,s)=>Mi(s.borderColor),this.hoverColor=(e,s)=>Mi(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t)}set(t,e){return wi(this,t,e)}get(t){return ge(this,t)}describe(t,e){return wi(Ge,t,e)}override(t,e){return wi(vt,t,e)}route(t,e,s,n){let o=ge(this,t),a=ge(this,s),r="_"+e;Object.defineProperties(o,{[r]:{value:o[e],writable:!0},[e]:{enumerable:!0,get(){let l=this[r],c=a[n];return D(l)?Object.assign({},c,l):C(l,c)},set(l){this[r]=l}}})}},O=new Ci({_scriptable:i=>!i.startsWith("on"),_indexable:i=>i!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});function ca(i){return!i||T(i.size)||T(i.family)?null:(i.style?i.style+" ":"")+(i.weight?i.weight+" ":"")+i.size+"px "+i.family}function pe(i,t,e,s,n){let o=t[n];return o||(o=t[n]=i.measureText(n).width,e.push(n)),o>s&&(s=o),s}function rn(i,t,e,s){s=s||{};let n=s.data=s.data||{},o=s.garbageCollect=s.garbageCollect||[];s.font!==t&&(n=s.data={},o=s.garbageCollect=[],s.font=t),i.save(),i.font=t;let a=0,r=e.length,l,c,h,d,u;for(l=0;le.length){for(l=0;l0&&i.stroke()}}function $t(i,t,e){return e=e||.5,!t||i&&i.x>t.left-e&&i.xt.top-e&&i.y0&&o.strokeColor!=="",l,c;for(i.save(),i.font=n.string,ha(i,o),l=0;l+i||0;function Je(i,t){let e={},s=D(t),n=s?Object.keys(t):t,o=D(i)?s?a=>C(i[a],i[t[a]]):a=>i[a]:()=>i;for(let a of n)e[a]=pa(o(a));return e}function Xi(i){return Je(i,{top:"y",right:"x",bottom:"y",left:"x"})}function kt(i){return Je(i,["topLeft","topRight","bottomLeft","bottomRight"])}function U(i){let t=Xi(i);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function $(i,t){i=i||{},t=t||O.font;let e=C(i.size,t.size);typeof e=="string"&&(e=parseInt(e,10));let s=C(i.style,t.style);s&&!(""+s).match(fa)&&(console.warn('Invalid font style specified: "'+s+'"'),s="");let n={family:C(i.family,t.family),lineHeight:ga(C(i.lineHeight,t.lineHeight),e),size:e,style:s,weight:C(i.weight,t.weight),string:""};return n.string=ca(n),n}function Gt(i,t,e,s){let n=!0,o,a,r;for(o=0,a=i.length;oe&&r===0?0:r+l;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function pt(i,t){return Object.assign(Object.create(i),t)}function Qe(i,t=[""],e=i,s,n=()=>i[0]){J(s)||(s=fn("_fallback",i));let o={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:i,_rootScopes:e,_fallback:s,_getTarget:n,override:a=>Qe([a,...i],t,e,s)};return new Proxy(o,{deleteProperty(a,r){return delete a[r],delete a._keys,delete i[0][r],!0},get(a,r){return dn(a,r,()=>wa(r,t,i,a))},getOwnPropertyDescriptor(a,r){return Reflect.getOwnPropertyDescriptor(a._scopes[0],r)},getPrototypeOf(){return Reflect.getPrototypeOf(i[0])},has(a,r){return zs(a).includes(r)},ownKeys(a){return zs(a)},set(a,r,l){let c=a._storage||(a._storage=n());return a[r]=c[r]=l,delete a._keys,!0}})}function Tt(i,t,e,s){let n={_cacheable:!1,_proxy:i,_context:t,_subProxy:e,_stack:new Set,_descriptors:Ui(i,s),setContext:o=>Tt(i,o,e,s),override:o=>Tt(i.override(o),t,e,s)};return new Proxy(n,{deleteProperty(o,a){return delete o[a],delete i[a],!0},get(o,a,r){return dn(o,a,()=>ba(o,a,r))},getOwnPropertyDescriptor(o,a){return o._descriptors.allKeys?Reflect.has(i,a)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(i,a)},getPrototypeOf(){return Reflect.getPrototypeOf(i)},has(o,a){return Reflect.has(i,a)},ownKeys(){return Reflect.ownKeys(i)},set(o,a,r){return i[a]=r,delete o[a],!0}})}function Ui(i,t={scriptable:!0,indexable:!0}){let{_scriptable:e=t.scriptable,_indexable:s=t.indexable,_allKeys:n=t.allKeys}=i;return{allKeys:n,scriptable:e,indexable:s,isScriptable:ft(e)?e:()=>e,isIndexable:ft(s)?s:()=>s}}var ma=(i,t)=>i?i+Xe(t):t,Ki=(i,t)=>D(t)&&i!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function dn(i,t,e){if(Object.prototype.hasOwnProperty.call(i,t))return i[t];let s=e();return i[t]=s,s}function ba(i,t,e){let{_proxy:s,_context:n,_subProxy:o,_descriptors:a}=i,r=s[t];return ft(r)&&a.isScriptable(t)&&(r=_a(t,r,i,e)),I(r)&&r.length&&(r=xa(t,r,i,a.isIndexable)),Ki(t,r)&&(r=Tt(r,n,o&&o[t],a)),r}function _a(i,t,e,s){let{_proxy:n,_context:o,_subProxy:a,_stack:r}=e;if(r.has(i))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+i);return r.add(i),t=t(o,a||s),r.delete(i),Ki(i,t)&&(t=qi(n._scopes,n,i,t)),t}function xa(i,t,e,s){let{_proxy:n,_context:o,_subProxy:a,_descriptors:r}=e;if(J(o.index)&&s(i))t=t[o.index%t.length];else if(D(t[0])){let l=t,c=n._scopes.filter(h=>h!==l);t=[];for(let h of l){let d=qi(c,n,i,h);t.push(Tt(d,o,a&&a[i],r))}}return t}function un(i,t,e){return ft(i)?i(t,e):i}var ya=(i,t)=>i===!0?t:typeof i=="string"?gt(t,i):void 0;function va(i,t,e,s,n){for(let o of t){let a=ya(e,o);if(a){i.add(a);let r=un(a._fallback,e,n);if(J(r)&&r!==e&&r!==s)return r}else if(a===!1&&J(s)&&e!==s)return null}return!1}function qi(i,t,e,s){let n=t._rootScopes,o=un(t._fallback,e,s),a=[...i,...n],r=new Set;r.add(s);let l=Is(r,a,e,o||e,s);return l===null||J(o)&&o!==e&&(l=Is(r,a,o,l,s),l===null)?!1:Qe(Array.from(r),[""],n,o,()=>Ma(t,e,s))}function Is(i,t,e,s,n){for(;e;)e=va(i,t,e,s,n);return e}function Ma(i,t,e){let s=i._getTarget();t in s||(s[t]={});let n=s[t];return I(n)&&D(e)?e:n}function wa(i,t,e,s){let n;for(let o of t)if(n=fn(ma(o,i),e),J(n))return Ki(i,n)?qi(e,s,i,n):n}function fn(i,t){for(let e of t){if(!e)continue;let s=e[i];if(J(s))return s}}function zs(i){let t=i._keys;return t||(t=i._keys=ka(i._scopes)),t}function ka(i){let t=new Set;for(let e of i)for(let s of Object.keys(e).filter(n=>!n.startsWith("_")))t.add(s);return Array.from(t)}function Gi(i,t,e,s){let{iScale:n}=i,{key:o="r"}=this._parsing,a=new Array(s),r,l,c,h;for(r=0,l=s;rti==="x"?"y":"x";function Pa(i,t,e,s){let n=i.skip?t:i,o=t,a=e.skip?t:e,r=$e(o,n),l=$e(a,o),c=r/(r+l),h=l/(r+l);c=isNaN(c)?0:c,h=isNaN(h)?0:h;let d=s*c,u=s*h;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function Ca(i,t,e){let s=i.length,n,o,a,r,l,c=Yt(i,0);for(let h=0;h!c.skip)),t.cubicInterpolationMode==="monotone")Oa(i,n);else{let c=s?i[i.length-1]:i[0];for(o=0,a=i.length;owindow.getComputedStyle(i,null);function Ta(i,t){return ei(i).getPropertyValue(t)}var La=["top","right","bottom","left"];function At(i,t,e){let s={};e=e?"-"+e:"";for(let n=0;n<4;n++){let o=La[n];s[o]=parseFloat(i[t+"-"+o+e])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}var Ra=(i,t,e)=>(i>0||t>0)&&(!e||!e.shadowRoot);function Ea(i,t){let e=i.touches,s=e&&e.length?e[0]:i,{offsetX:n,offsetY:o}=s,a=!1,r,l;if(Ra(n,o,i.target))r=n,l=o;else{let c=t.getBoundingClientRect();r=s.clientX-c.left,l=s.clientY-c.top,a=!0}return{x:r,y:l,box:a}}function St(i,t){if("native"in i)return i;let{canvas:e,currentDevicePixelRatio:s}=t,n=ei(e),o=n.boxSizing==="border-box",a=At(n,"padding"),r=At(n,"border","width"),{x:l,y:c,box:h}=Ea(i,e),d=a.left+(h&&r.left),u=a.top+(h&&r.top),{width:f,height:g}=t;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*e.width/s),y:Math.round((c-u)/g*e.height/s)}}function Fa(i,t,e){let s,n;if(t===void 0||e===void 0){let o=ti(i);if(!o)t=i.clientWidth,e=i.clientHeight;else{let a=o.getBoundingClientRect(),r=ei(o),l=At(r,"border","width"),c=At(r,"padding");t=a.width-c.width-l.width,e=a.height-c.height-l.height,s=Ye(r.maxWidth,o,"clientWidth"),n=Ye(r.maxHeight,o,"clientHeight")}}return{width:t,height:e,maxWidth:s||je,maxHeight:n||je}}var ki=i=>Math.round(i*10)/10;function mn(i,t,e,s){let n=ei(i),o=At(n,"margin"),a=Ye(n.maxWidth,i,"clientWidth")||je,r=Ye(n.maxHeight,i,"clientHeight")||je,l=Fa(i,t,e),{width:c,height:h}=l;if(n.boxSizing==="content-box"){let d=At(n,"border","width"),u=At(n,"padding");c-=u.width+d.width,h-=u.height+d.height}return c=Math.max(0,c-o.width),h=Math.max(0,s?Math.floor(c/s):h-o.height),c=ki(Math.min(c,a,l.maxWidth)),h=ki(Math.min(h,r,l.maxHeight)),c&&!h&&(h=ki(c/2)),{width:c,height:h}}function Ji(i,t,e){let s=t||1,n=Math.floor(i.height*s),o=Math.floor(i.width*s);i.height=n/s,i.width=o/s;let a=i.canvas;return a.style&&(e||!a.style.height&&!a.style.width)&&(a.style.height=`${i.height}px`,a.style.width=`${i.width}px`),i.currentDevicePixelRatio!==s||a.height!==n||a.width!==o?(i.currentDevicePixelRatio=s,a.height=n,a.width=o,i.ctx.setTransform(s,0,0,s,0,0),!0):!1}var bn=function(){let i=!1;try{let t={get passive(){return i=!0,!1}};window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch{}return i}();function Qi(i,t){let e=Ta(i,t),s=e&&e.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function _t(i,t,e,s){return{x:i.x+e*(t.x-i.x),y:i.y+e*(t.y-i.y)}}function _n(i,t,e,s){return{x:i.x+e*(t.x-i.x),y:s==="middle"?e<.5?i.y:t.y:s==="after"?e<1?i.y:t.y:e>0?t.y:i.y}}function xn(i,t,e,s){let n={x:i.cp2x,y:i.cp2y},o={x:t.cp1x,y:t.cp1y},a=_t(i,n,e),r=_t(n,o,e),l=_t(o,t,e),c=_t(a,r,e),h=_t(r,l,e);return _t(c,h,e)}var Bs=new Map;function Ia(i,t){t=t||{};let e=i+JSON.stringify(t),s=Bs.get(e);return s||(s=new Intl.NumberFormat(i,t),Bs.set(e,s)),s}function Zt(i,t,e){return Ia(t,e).format(i)}var za=function(i,t){return{x(e){return i+i+t-e},setWidth(e){t=e},textAlign(e){return e==="center"?e:e==="right"?"left":"right"},xPlus(e,s){return e-s},leftForLtr(e,s){return e-s}}},Ba=function(){return{x(i){return i},setWidth(i){},textAlign(i){return i},xPlus(i,t){return i+t},leftForLtr(i,t){return i}}};function Rt(i,t,e){return i?za(t,e):Ba()}function ts(i,t){let e,s;(t==="ltr"||t==="rtl")&&(e=i.canvas.style,s=[e.getPropertyValue("direction"),e.getPropertyPriority("direction")],e.setProperty("direction",t,"important"),i.prevTextDirection=s)}function es(i,t){t!==void 0&&(delete i.prevTextDirection,i.canvas.style.setProperty("direction",t[0],t[1]))}function yn(i){return i==="angle"?{between:Kt,compare:Wo,normalize:G}:{between:lt,compare:(t,e)=>t-e,normalize:t=>t}}function Vs({start:i,end:t,count:e,loop:s,style:n}){return{start:i%e,end:t%e,loop:s&&(t-i+1)%e===0,style:n}}function Va(i,t,e){let{property:s,start:n,end:o}=e,{between:a,normalize:r}=yn(s),l=t.length,{start:c,end:h,loop:d}=i,u,f;if(d){for(c+=l,h+=l,u=0,f=l;ul(n,v,b)&&r(n,v)!==0,x=()=>r(o,b)===0||l(o,v,b),M=()=>p||y(),w=()=>!p||x();for(let S=h,k=h;S<=d;++S)_=t[S%a],!_.skip&&(b=c(_[s]),b!==v&&(p=l(b,n,o),m===null&&M()&&(m=r(b,n)===0?S:k),m!==null&&w()&&(g.push(Vs({start:m,end:S,loop:u,count:a,style:f})),m=null),k=S,v=b));return m!==null&&g.push(Vs({start:m,end:d,loop:u,count:a,style:f})),g}function ss(i,t){let e=[],s=i.segments;for(let n=0;nn&&i[o%t].skip;)o--;return o%=t,{start:n,end:o}}function Na(i,t,e,s){let n=i.length,o=[],a=t,r=i[t],l;for(l=t+1;l<=e;++l){let c=i[l%n];c.skip||c.stop?r.skip||(s=!1,o.push({start:t%n,end:(l-1)%n,loop:s}),t=a=c.stop?l:null):(a=l,r.skip&&(t=l)),r=c}return a!==null&&o.push({start:t%n,end:a%n,loop:s}),o}function vn(i,t){let e=i.points,s=i.options.spanGaps,n=e.length;if(!n)return[];let o=!!i._loop,{start:a,end:r}=Wa(e,n,o,s);if(s===!0)return Ws(i,[{start:a,end:r,loop:o}],e,t);let l=rr({chart:t,initial:e.initial,numSteps:a,currentStep:Math.min(s-e.start,a)}))}_refresh(){this._request||(this._running=!0,this._request=Ii.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let e=0;this._charts.forEach((s,n)=>{if(!s.running||!s.items.length)return;let o=s.items,a=o.length-1,r=!1,l;for(;a>=0;--a)l=o[a],l._active?(l._total>s.duration&&(s.duration=l._total),l.tick(t),r=!0):(o[a]=o[o.length-1],o.pop());r&&(n.draw(),this._notify(n,s,t,"progress")),o.length||(s.running=!1,this._notify(n,s,t,"complete"),s.initial=!1),e+=o.length}),this._lastDate=t,e===0&&(this._running=!1)}_getAnims(t){let e=this._charts,s=e.get(t);return s||(s={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,s)),s}listen(t,e,s){this._getAnims(t).listeners[e].push(s)}add(t,e){!e||!e.length||this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){let e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce((s,n)=>Math.max(s,n._duration),0),this._refresh())}running(t){if(!this._running)return!1;let e=this._charts.get(t);return!(!e||!e.running||!e.items.length)}stop(t){let e=this._charts.get(t);if(!e||!e.items.length)return;let s=e.items,n=s.length-1;for(;n>=0;--n)s[n].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}},mt=new fs,Mn="transparent",$a={boolean(i,t,e){return e>.5?t:i},color(i,t,e){let s=ji(i||Mn),n=s.valid&&ji(t||Mn);return n&&n.valid?n.mix(s,e).hexString():t},number(i,t,e){return i+(t-i)*e}},gs=class{constructor(t,e,s,n){let o=e[s];n=Gt([t.to,n,o,t.from]);let a=Gt([t.from,o,n]);this._active=!0,this._fn=t.fn||$a[t.type||typeof a],this._easing=Ht[t.easing]||Ht.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=s,this._from=a,this._to=n,this._promises=void 0}active(){return this._active}update(t,e,s){if(this._active){this._notify(!1);let n=this._target[this._prop],o=s-this._start,a=this._duration-o;this._start=s,this._duration=Math.floor(Math.max(a,t.duration)),this._total+=o,this._loop=!!t.loop,this._to=Gt([t.to,e,n,t.from]),this._from=Gt([t.from,n,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){let e=t-this._start,s=this._duration,n=this._prop,o=this._from,a=this._loop,r=this._to,l;if(this._active=o!==r&&(a||e1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[n]=this._fn(o,r,l)}wait(){let t=this._promises||(this._promises=[]);return new Promise((e,s)=>{t.push({res:e,rej:s})})}_notify(t){let e=t?"res":"rej",s=this._promises||[];for(let n=0;ni!=="onProgress"&&i!=="onComplete"&&i!=="fn"});O.set("animations",{colors:{type:"color",properties:Xa},numbers:{type:"number",properties:Ya}});O.describe("animations",{_fallback:"animation"});O.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:i=>i|0}}}});var ci=class{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!D(t))return;let e=this._properties;Object.getOwnPropertyNames(t).forEach(s=>{let n=t[s];if(!D(n))return;let o={};for(let a of Ua)o[a]=n[a];(I(n.properties)&&n.properties||[s]).forEach(a=>{(a===s||!e.has(a))&&e.set(a,o)})})}_animateOptions(t,e){let s=e.options,n=qa(t,s);if(!n)return[];let o=this._createAnimations(n,s);return s.$shared&&Ka(t.options.$animations,s).then(()=>{t.options=s},()=>{}),o}_createAnimations(t,e){let s=this._properties,n=[],o=t.$animations||(t.$animations={}),a=Object.keys(e),r=Date.now(),l;for(l=a.length-1;l>=0;--l){let c=a[l];if(c.charAt(0)==="$")continue;if(c==="options"){n.push(...this._animateOptions(t,e));continue}let h=e[c],d=o[c],u=s.get(c);if(d)if(u&&d.active()){d.update(u,h,r);continue}else d.cancel();if(!u||!u.duration){t[c]=h;continue}o[c]=d=new gs(u,t,c,h),n.push(d)}return n}update(t,e){if(this._properties.size===0){Object.assign(t,e);return}let s=this._createAnimations(t,e);if(s.length)return mt.add(this._chart,s),!0}};function Ka(i,t){let e=[],s=Object.keys(t);for(let n=0;n0||!e&&o<0)return n.index}return null}function Cn(i,t){let{chart:e,_cachedMeta:s}=i,n=e._stacks||(e._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,c=a.axis,h=Qa(o,a,s),d=t.length,u;for(let f=0;fe[s].axis===t).shift()}function ir(i,t){return pt(i,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function sr(i,t,e){return pt(i,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:e,index:t,mode:"default",type:"data"})}function ye(i,t){let e=i.controller.index,s=i.vScale&&i.vScale.axis;if(s){t=t||i._parsed;for(let n of t){let o=n._stacks;if(!o||o[s]===void 0||o[s][e]===void 0)return;delete o[s][e]}}}var os=i=>i==="reset"||i==="none",Dn=(i,t)=>t?i:Object.assign({},i),nr=(i,t,e)=>i&&!t.hidden&&t._stacked&&{keys:go(e,!0),values:null},et=class{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.initialize()}initialize(){let t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Sn(t.vScale,t),this.addElements()}updateIndex(t){this.index!==t&&ye(this._cachedMeta),this.index=t}linkScales(){let t=this.chart,e=this._cachedMeta,s=this.getDataset(),n=(d,u,f,g)=>d==="x"?u:d==="r"?g:f,o=e.xAxisID=C(s.xAxisID,ns(t,"x")),a=e.yAxisID=C(s.yAxisID,ns(t,"y")),r=e.rAxisID=C(s.rAxisID,ns(t,"r")),l=e.indexAxis,c=e.iAxisID=n(l,o,a,r),h=e.vAxisID=n(l,a,o,r);e.xScale=this.getScaleForId(o),e.yScale=this.getScaleForId(a),e.rScale=this.getScaleForId(r),e.iScale=this.getScaleForId(c),e.vScale=this.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){let e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){let t=this._cachedMeta;this._data&&Ei(this._data,this),t._stacked&&ye(t)}_dataCheck(){let t=this.getDataset(),e=t.data||(t.data=[]),s=this._data;if(D(e))this._data=Ja(e);else if(s!==e){if(s){Ei(s,this);let n=this._cachedMeta;ye(n),n._parsed=[]}e&&Object.isExtensible(e)&&Js(e,this),this._syncList=[],this._data=e}}addElements(){let t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){let e=this._cachedMeta,s=this.getDataset(),n=!1;this._dataCheck();let o=e._stacked;e._stacked=Sn(e.vScale,e),e.stack!==s.stack&&(n=!0,ye(e),e.stack=s.stack),this._resyncElements(t),(n||o!==e._stacked)&&Cn(this,e._parsed)}configure(){let t=this.chart.config,e=t.datasetScopeKeys(this._type),s=t.getOptionScopes(this.getDataset(),e,!0);this.options=t.createResolver(s,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,e){let{_cachedMeta:s,_data:n}=this,{iScale:o,_stacked:a}=s,r=o.axis,l=t===0&&e===n.length?!0:s._sorted,c=t>0&&s._parsed[t-1],h,d,u;if(this._parsing===!1)s._parsed=n,s._sorted=!0,u=n;else{I(n[t])?u=this.parseArrayData(s,n,t,e):D(n[t])?u=this.parseObjectData(s,n,t,e):u=this.parsePrimitiveData(s,n,t,e);let f=()=>d[r]===null||c&&d[r]p||d=0;--u)if(!g()){this.updateRangeFromParsed(c,t,f,l);break}}return c}getAllParsedValues(t){let e=this._cachedMeta._parsed,s=[],n,o,a;for(n=0,o=e.length;n=0&&tthis.getContext(s,n),p=c.resolveNamedOptions(u,f,g,d);return p.$shared&&(p.$shared=l,o[a]=Object.freeze(Dn(p,l))),p}_resolveAnimations(t,e,s){let n=this.chart,o=this._cachedDataOpts,a=`animation-${e}`,r=o[a];if(r)return r;let l;if(n.options.animation!==!1){let h=this.chart.config,d=h.datasetAnimationScopeKeys(this._type,e),u=h.getOptionScopes(this.getDataset(),d);l=h.createResolver(u,this.getContext(t,s,e))}let c=new ci(n,l&&l.animations);return l&&l._cacheable&&(o[a]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||os(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){let s=this.resolveDataElementOptions(t,e),n=this._sharedOptions,o=this.getSharedOptions(s),a=this.includeOptions(e,o)||o!==n;return this.updateSharedOptions(o,e,s),{sharedOptions:o,includeOptions:a}}updateElement(t,e,s,n){os(n)?Object.assign(t,s):this._resolveAnimations(e,n).update(t,s)}updateSharedOptions(t,e,s){t&&!os(e)&&this._resolveAnimations(void 0,e).update(t,s)}_setStyle(t,e,s,n){t.active=n;let o=this.getStyle(e,n);this._resolveAnimations(e,s,n).update(t,{options:!n&&this.getSharedOptions(o)||o})}removeHoverStyle(t,e,s){this._setStyle(t,s,"active",!1)}setHoverStyle(t,e,s){this._setStyle(t,s,"active",!0)}_removeDatasetHoverStyle(){let t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){let t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){let e=this._data,s=this._cachedMeta.data;for(let[r,l,c]of this._syncList)this[r](l,c);this._syncList=[];let n=s.length,o=e.length,a=Math.min(o,n);a&&this.parse(0,a),o>n?this._insertElements(n,o-n,t):o{for(c.length+=e,r=c.length-1;r>=a;r--)c[r]=c[r-e]};for(l(o),r=t;rn-o))}return i._cache.$bar}function ar(i){let t=i.iScale,e=or(t,i.type),s=t._length,n,o,a,r,l=()=>{a===32767||a===-32768||(J(r)&&(s=Math.min(s,Math.abs(a-r)||s)),r=a)};for(n=0,o=e.length;n0?n[i-1]:null,r=iMath.abs(r)&&(l=r,c=a),t[e.axis]=c,t._custom={barStart:l,barEnd:c,start:n,end:o,min:a,max:r}}function po(i,t,e,s){return I(i)?cr(i,t,e,s):t[e.axis]=e.parse(i,s),t}function On(i,t,e,s){let n=i.iScale,o=i.vScale,a=n.getLabels(),r=n===o,l=[],c,h,d,u;for(c=e,h=e+s;c=e?1:-1)}function dr(i){let t,e,s,n,o;return i.horizontal?(t=i.base>i.x,e="left",s="right"):(t=i.basel.controller.options.grouped),o=s.options.stacked,a=[],r=l=>{let c=l.controller.getParsed(e),h=c&&c[l.vScale.axis];if(T(h)||isNaN(h))return!0};for(let l of n)if(!(e!==void 0&&r(l))&&((o===!1||a.indexOf(l.stack)===-1||o===void 0&&l.stack===void 0)&&a.push(l.stack),l.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,s){let n=this._getStacks(t,s),o=e!==void 0?n.indexOf(e):-1;return o===-1?n.length-1:o}_getRuler(){let t=this.options,e=this._cachedMeta,s=e.iScale,n=[],o,a;for(o=0,a=e.data.length;o=0;--s)e=Math.max(e,t[s].size(this.resolveDataElementOptions(s))/2);return e>0&&e}getLabelAndValue(t){let e=this._cachedMeta,{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:e.label,value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){let e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,s,n){let o=n==="reset",{iScale:a,vScale:r}=this._cachedMeta,{sharedOptions:l,includeOptions:c}=this._getSharedOptions(e,n),h=a.axis,d=r.axis;for(let u=e;uKt(v,r,l,!0)?1:Math.max(y,y*e,x,x*e),g=(v,y,x)=>Kt(v,r,l,!0)?-1:Math.min(y,y*e,x,x*e),p=f(0,c,d),m=f(V,h,u),b=g(B,c,d),_=g(B+V,h,u);s=(p-b)/2,n=(m-_)/2,o=-(p+b)/2,a=-(m+_)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}var Dt=class extends et{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){let s=this.getDataset().data,n=this._cachedMeta;if(this._parsing===!1)n._parsed=s;else{let o=l=>+s[l];if(D(s[t])){let{key:l="value"}=this._parsing;o=c=>+gt(s[c],l)}let a,r;for(a=t,r=t+e;a0&&!isNaN(t)?F*(Math.abs(t)/e):0}getLabelAndValue(t){let e=this._cachedMeta,s=this.chart,n=s.data.labels||[],o=Zt(e._parsed[t],s.options.locale);return{label:n[t]||"",value:o}}getMaxBorderWidth(t){let e=0,s=this.chart,n,o,a,r,l;if(!t){for(n=0,o=s.data.datasets.length;ni!=="spacing",_indexable:i=>i!=="spacing"};Dt.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){let t=i.data;if(t.labels.length&&t.datasets.length){let{labels:{pointStyle:e}}=i.legend.options;return t.labels.map((s,n)=>{let a=i.getDatasetMeta(0).controller.getStyle(n);return{text:s,fillStyle:a.backgroundColor,strokeStyle:a.borderColor,lineWidth:a.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(n),index:n}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}},tooltip:{callbacks:{title(){return""},label(i){let t=i.label,e=": "+i.formattedValue;return I(t)?(t=t.slice(),t[0]+=e):t+=e,t}}}}};var ie=class extends et{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(t){let e=this._cachedMeta,{dataset:s,data:n=[],_dataset:o}=e,a=this.chart._animationsDisabled,{start:r,count:l}=Bi(e,n,a);this._drawStart=r,this._drawCount=l,Vi(e)&&(r=0,l=n.length),s._chart=this.chart,s._datasetIndex=this.index,s._decimated=!!o._decimated,s.points=n;let c=this.resolveDatasetElementOptions(t);this.options.showLine||(c.borderWidth=0),c.segment=this.options.segment,this.updateElement(s,void 0,{animated:!a,options:c},t),this.updateElements(n,r,l,t)}updateElements(t,e,s,n){let o=n==="reset",{iScale:a,vScale:r,_stacked:l,_dataset:c}=this._cachedMeta,{sharedOptions:h,includeOptions:d}=this._getSharedOptions(e,n),u=a.axis,f=r.axis,{spanGaps:g,segment:p}=this.options,m=Lt(g)?g:Number.POSITIVE_INFINITY,b=this.chart._animationsDisabled||o||n==="none",_=e>0&&this.getParsed(e-1);for(let v=e;v0&&Math.abs(x[u]-_[u])>m,p&&(M.parsed=x,M.raw=c.data[v]),d&&(M.options=h||this.resolveDataElementOptions(v,y.active?"active":n)),b||this.updateElement(y,v,M,n),_=x}}getMaxOverflow(){let t=this._cachedMeta,e=t.dataset,s=e.options&&e.options.borderWidth||0,n=t.data||[];if(!n.length)return s;let o=n[0].size(this.resolveDataElementOptions(0)),a=n[n.length-1].size(this.resolveDataElementOptions(n.length-1));return Math.max(s,o,a)/2}draw(){let t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}};ie.id="line";ie.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1};ie.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};var se=class extends et{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){let e=this._cachedMeta,s=this.chart,n=s.data.labels||[],o=Zt(e._parsed[t].r,s.options.locale);return{label:n[t]||"",value:o}}parseObjectData(t,e,s,n){return Gi.bind(this)(t,e,s,n)}update(t){let e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){let t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach((s,n)=>{let o=this.getParsed(n).r;!isNaN(o)&&this.chart.getDataVisibility(n)&&(oe.max&&(e.max=o))}),e}_updateRadius(){let t=this.chart,e=t.chartArea,s=t.options,n=Math.min(e.right-e.left,e.bottom-e.top),o=Math.max(n/2,0),a=Math.max(s.cutoutPercentage?o/100*s.cutoutPercentage:1,0),r=(o-a)/t.getVisibleDatasetCount();this.outerRadius=o-r*this.index,this.innerRadius=this.outerRadius-r}updateElements(t,e,s,n){let o=n==="reset",a=this.chart,l=a.options.animation,c=this._cachedMeta.rScale,h=c.xCenter,d=c.yCenter,u=c.getIndexAngle(0)-.5*B,f=u,g,p=360/this.countVisibleElements();for(g=0;g{!isNaN(this.getParsed(n).r)&&this.chart.getDataVisibility(n)&&e++}),e}_computeAngle(t,e,s){return this.chart.getDataVisibility(t)?nt(this.resolveDataElementOptions(t,e).angle||s):0}};se.id="polarArea";se.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0};se.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){let t=i.data;if(t.labels.length&&t.datasets.length){let{labels:{pointStyle:e}}=i.legend.options;return t.labels.map((s,n)=>{let a=i.getDatasetMeta(0).controller.getStyle(n);return{text:s,fillStyle:a.backgroundColor,strokeStyle:a.borderColor,lineWidth:a.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(n),index:n}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}},tooltip:{callbacks:{title(){return""},label(i){return i.chart.data.labels[i.dataIndex]+": "+i.formattedValue}}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};var Ce=class extends Dt{};Ce.id="pie";Ce.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};var ne=class extends et{getLabelAndValue(t){let e=this._cachedMeta.vScale,s=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(s[e.axis])}}parseObjectData(t,e,s,n){return Gi.bind(this)(t,e,s,n)}update(t){let e=this._cachedMeta,s=e.dataset,n=e.data||[],o=e.iScale.getLabels();if(s.points=n,t!=="resize"){let a=this.resolveDatasetElementOptions(t);this.options.showLine||(a.borderWidth=0);let r={_loop:!0,_fullLoop:o.length===n.length,options:a};this.updateElement(s,void 0,r,t)}this.updateElements(n,0,n.length,t)}updateElements(t,e,s,n){let o=this._cachedMeta.rScale,a=n==="reset";for(let r=e;r{n[o]=s[o]&&s[o].active()?s[o]._to:this[o]}),n}};it.defaults={};it.defaultRoutes=void 0;var mo={values(i){return I(i)?i:""+i},numeric(i,t,e){if(i===0)return"0";let s=this.chart.options.locale,n,o=i;if(e.length>1){let c=Math.max(Math.abs(e[0].value),Math.abs(e[e.length-1].value));(c<1e-4||c>1e15)&&(n="scientific"),o=mr(i,e)}let a=tt(Math.abs(o)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),Zt(i,s,l)},logarithmic(i,t,e){if(i===0)return"0";let s=i/Math.pow(10,Math.floor(tt(i)));return s===1||s===2||s===5?mo.numeric.call(this,i,t,e):""}};function mr(i,t){let e=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(e)>=1&&i!==Math.floor(i)&&(e=i-Math.floor(i)),e}var pi={formatters:mo};O.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(i,t)=>t.lineWidth,tickColor:(i,t)=>t.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:pi.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}});O.route("scale.ticks","color","","color");O.route("scale.grid","color","","borderColor");O.route("scale.grid","borderColor","","borderColor");O.route("scale.title","color","","color");O.describe("scale",{_fallback:!1,_scriptable:i=>!i.startsWith("before")&&!i.startsWith("after")&&i!=="callback"&&i!=="parser",_indexable:i=>i!=="borderDash"&&i!=="tickBorderDash"});O.describe("scales",{_fallback:"scale"});O.describe("scale.ticks",{_scriptable:i=>i!=="backdropPadding"&&i!=="callback",_indexable:i=>i!=="backdropPadding"});function br(i,t){let e=i.options.ticks,s=e.maxTicksLimit||_r(i),n=e.major.enabled?yr(t):[],o=n.length,a=n[0],r=n[o-1],l=[];if(o>s)return vr(t,l,n,o/s),l;let c=xr(n,t,s);if(o>0){let h,d,u=o>1?Math.round((r-a)/(o-1)):null;for(ii(t,l,c,T(u)?0:a-u,a),h=0,d=o-1;hn)return l}return Math.max(n,1)}function yr(i){let t=[],e,s;for(e=0,s=i.length;ei==="left"?"right":i==="right"?"left":i,Ln=(i,t,e)=>t==="top"||t==="left"?i[t]+e:i[t]-e;function Rn(i,t){let e=[],s=i.length/t,n=i.length,o=0;for(;oa+r)))return l}function Sr(i,t){E(i,e=>{let s=e.gc,n=s.length/2,o;if(n>t){for(o=0;os?s:e,s=n&&e>s?e:s,{min:Q(e,Q(s,e)),max:Q(s,Q(e,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){let t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){z(this.options.beforeUpdate,[this])}update(t,e,s){let{beginAtZero:n,grace:o,ticks:a}=this.options,r=a.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=s=Object.assign({left:0,right:0,top:0,bottom:0},s),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+s.left+s.right:this.height+s.top+s.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=hn(this,o,n),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();let l=r=o||s<=1||!this.isHorizontal()){this.labelRotation=n;return}let h=this._getLabelSizes(),d=h.widest.width,u=h.highest.height,f=Y(this.chart.width-d,0,this.maxWidth);r=t.offset?this.maxWidth/s:f/(s-1),d+6>r&&(r=f/(s-(t.offset?.5:1)),l=this.maxHeight-ve(t.grid)-e.padding-En(t.title,this.chart.options.font),c=Math.sqrt(d*d+u*u),a=Ue(Math.min(Math.asin(Y((h.highest.height+6)/r,-1,1)),Math.asin(Y(l/c,-1,1))-Math.asin(Y(u/c,-1,1)))),a=Math.max(n,Math.min(o,a))),this.labelRotation=a}afterCalculateLabelRotation(){z(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){z(this.options.beforeFit,[this])}fit(){let t={width:0,height:0},{chart:e,options:{ticks:s,title:n,grid:o}}=this,a=this._isVisible(),r=this.isHorizontal();if(a){let l=En(n,e.options.font);if(r?(t.width=this.maxWidth,t.height=ve(o)+l):(t.height=this.maxHeight,t.width=ve(o)+l),s.display&&this.ticks.length){let{first:c,last:h,widest:d,highest:u}=this._getLabelSizes(),f=s.padding*2,g=nt(this.labelRotation),p=Math.cos(g),m=Math.sin(g);if(r){let b=s.mirror?0:m*d.width+p*u.height;t.height=Math.min(this.maxHeight,t.height+b+f)}else{let b=s.mirror?0:p*d.width+m*u.height;t.width=Math.min(this.maxWidth,t.width+b+f)}this._calculatePadding(c,h,m,p)}}this._handleMargins(),r?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,s,n){let{ticks:{align:o,padding:a},position:r}=this.options,l=this.labelRotation!==0,c=r!=="top"&&this.axis==="x";if(this.isHorizontal()){let h=this.getPixelForTick(0)-this.left,d=this.right-this.getPixelForTick(this.ticks.length-1),u=0,f=0;l?c?(u=n*t.width,f=s*e.height):(u=s*t.height,f=n*e.width):o==="start"?f=e.width:o==="end"?u=t.width:o!=="inner"&&(u=t.width/2,f=e.width/2),this.paddingLeft=Math.max((u-h+a)*this.width/(this.width-h),0),this.paddingRight=Math.max((f-d+a)*this.width/(this.width-d),0)}else{let h=e.height/2,d=t.height/2;o==="start"?(h=0,d=t.height):o==="end"&&(h=e.height,d=0),this.paddingTop=h+a,this.paddingBottom=d+a}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){z(this.options.afterFit,[this])}isHorizontal(){let{axis:t,position:e}=this.options;return e==="top"||e==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let e,s;for(e=0,s=t.length;e({width:o[w]||0,height:a[w]||0});return{first:M(0),last:M(e-1),widest:M(y),highest:M(x),widths:o,heights:a}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){let e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);let e=this._startPixel+t*this._length;return Ks(this._alignToPixels?Mt(this.chart,e,0):e)}getDecimalForPixel(t){let e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){let{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){let e=this.ticks||[];if(t>=0&&tr*n?r/s:l/n:l*n0}_computeGridLineItems(t){let e=this.axis,s=this.chart,n=this.options,{grid:o,position:a}=n,r=o.offset,l=this.isHorizontal(),h=this.ticks.length+(r?1:0),d=ve(o),u=[],f=o.setContext(this.getContext()),g=f.drawBorder?f.borderWidth:0,p=g/2,m=function(P){return Mt(s,P,g)},b,_,v,y,x,M,w,S,k,L,R,A;if(a==="top")b=m(this.bottom),M=this.bottom-d,S=b-p,L=m(t.top)+p,A=t.bottom;else if(a==="bottom")b=m(this.top),L=t.top,A=m(t.bottom)-p,M=b+p,S=this.top+d;else if(a==="left")b=m(this.right),x=this.right-d,w=b-p,k=m(t.left)+p,R=t.right;else if(a==="right")b=m(this.left),k=t.left,R=m(t.right)-p,x=b+p,w=this.left+d;else if(e==="x"){if(a==="center")b=m((t.top+t.bottom)/2+.5);else if(D(a)){let P=Object.keys(a)[0],j=a[P];b=m(this.chart.scales[P].getPixelForValue(j))}L=t.top,A=t.bottom,M=b+p,S=M+d}else if(e==="y"){if(a==="center")b=m((t.left+t.right)/2);else if(D(a)){let P=Object.keys(a)[0],j=a[P];b=m(this.chart.scales[P].getPixelForValue(j))}x=b-p,w=x-d,k=t.left,R=t.right}let H=C(n.ticks.maxTicksLimit,h),q=Math.max(1,Math.ceil(h/H));for(_=0;_o.value===t);return n>=0?e.setContext(this.getContext(n)).lineWidth:0}drawGrid(t){let e=this.options.grid,s=this.ctx,n=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t)),o,a,r=(l,c,h)=>{!h.width||!h.color||(s.save(),s.lineWidth=h.width,s.strokeStyle=h.color,s.setLineDash(h.borderDash||[]),s.lineDashOffset=h.borderDashOffset,s.beginPath(),s.moveTo(l.x,l.y),s.lineTo(c.x,c.y),s.stroke(),s.restore())};if(e.display)for(o=0,a=n.length;o{this.draw(n)}}]:[{z:s,draw:n=>{this.drawBackground(),this.drawGrid(n),this.drawTitle()}},{z:s+1,draw:()=>{this.drawBorder()}},{z:e,draw:n=>{this.drawLabels(n)}}]}getMatchingVisibleMetas(t){let e=this.chart.getSortedVisibleDatasetMetas(),s=this.axis+"AxisID",n=[],o,a;for(o=0,a=e.length;o{let s=e.split("."),n=s.pop(),o=[i].concat(s).join("."),a=t[e].split("."),r=a.pop(),l=a.join(".");O.route(o,n,l,r)})}function Lr(i){return"id"in i&&"defaults"in i}var ps=class{constructor(){this.controllers=new Qt(et,"datasets",!0),this.elements=new Qt(it,"elements"),this.plugins=new Qt(Object,"plugins"),this.scales=new Qt(Ft,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,s){[...e].forEach(n=>{let o=s||this._getRegistryForType(n);s||o.isForType(n)||o===this.plugins&&n.id?this._exec(t,o,n):E(n,a=>{let r=s||this._getRegistryForType(a);this._exec(t,r,a)})})}_exec(t,e,s){let n=Xe(t);z(s["before"+n],[],s),e[t](s),z(s["after"+n],[],s)}_getRegistryForType(t){for(let e=0;e0&&this.getParsed(e-1);for(let y=e;y0&&Math.abs(M[f]-v[f])>b,m&&(w.parsed=M,w.raw=c.data[y]),u&&(w.options=d||this.resolveDataElementOptions(y,x.active?"active":n)),_||this.updateElement(x,y,w,n),v=M}this.updateSharedOptions(d,n,h)}getMaxOverflow(){let t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let r=0;for(let l=e.length-1;l>=0;--l)r=Math.max(r,e[l].size(this.resolveDataElementOptions(l))/2);return r>0&&r}let s=t.dataset,n=s.options&&s.options.borderWidth||0;if(!e.length)return n;let o=e[0].size(this.resolveDataElementOptions(0)),a=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(n,o,a)/2}};oe.id="scatter";oe.defaults={datasetElementType:!1,dataElementType:"point",showLine:!1,fill:!1};oe.overrides={interaction:{mode:"point"},plugins:{tooltip:{callbacks:{title(){return""},label(i){return"("+i.label+", "+i.formattedValue+")"}}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var Rr=Object.freeze({__proto__:null,BarController:te,BubbleController:ee,DoughnutController:Dt,LineController:ie,PolarAreaController:se,PieController:Ce,RadarController:ne,ScatterController:oe});function Et(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}var De=class{constructor(t){this.options=t||{}}init(t){}formats(){return Et()}parse(t,e){return Et()}format(t,e){return Et()}add(t,e,s){return Et()}diff(t,e,s){return Et()}startOf(t,e,s){return Et()}endOf(t,e){return Et()}};De.override=function(i){Object.assign(De.prototype,i)};var Er={_date:De};function Fr(i,t,e,s){let{controller:n,data:o,_sorted:a}=i,r=n._cachedMeta.iScale;if(r&&t===r.axis&&t!=="r"&&a&&o.length){let l=r._reversePixels?qs:at;if(s){if(n._sharedOptions){let c=o[0],h=typeof c.getRange=="function"&&c.getRange(t);if(h){let d=l(o,t,e-h),u=l(o,t,e+h);return{lo:d.lo,hi:u.hi}}}}else return l(o,t,e)}return{lo:0,hi:o.length-1}}function Fe(i,t,e,s,n){let o=i.getSortedVisibleDatasetMetas(),a=e[t];for(let r=0,l=o.length;r{l[a](t[e],n)&&(o.push({element:l,datasetIndex:c,index:h}),r=r||l.inRange(t.x,t.y,n))}),s&&!r?[]:o}var Vr={evaluateInteractionItems:Fe,modes:{index(i,t,e,s){let n=St(t,i),o=e.axis||"x",a=e.includeInvisible||!1,r=e.intersect?rs(i,n,o,s,a):ls(i,n,o,!1,s,a),l=[];return r.length?(i.getSortedVisibleDatasetMetas().forEach(c=>{let h=r[0].index,d=c.data[h];d&&!d.skip&&l.push({element:d,datasetIndex:c.index,index:h})}),l):[]},dataset(i,t,e,s){let n=St(t,i),o=e.axis||"xy",a=e.includeInvisible||!1,r=e.intersect?rs(i,n,o,s,a):ls(i,n,o,!1,s,a);if(r.length>0){let l=r[0].datasetIndex,c=i.getDatasetMeta(l).data;r=[];for(let h=0;he.pos===t)}function In(i,t){return i.filter(e=>bo.indexOf(e.pos)===-1&&e.box.axis===t)}function we(i,t){return i.sort((e,s)=>{let n=t?s:e,o=t?e:s;return n.weight===o.weight?n.index-o.index:n.weight-o.weight})}function Wr(i){let t=[],e,s,n,o,a,r;for(e=0,s=(i||[]).length;ec.box.fullSize),!0),s=we(Me(t,"left"),!0),n=we(Me(t,"right")),o=we(Me(t,"top"),!0),a=we(Me(t,"bottom")),r=In(t,"x"),l=In(t,"y");return{fullSize:e,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Me(t,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}function zn(i,t,e,s){return Math.max(i[e],t[e])+Math.max(i[s],t[s])}function _o(i,t){i.top=Math.max(i.top,t.top),i.left=Math.max(i.left,t.left),i.bottom=Math.max(i.bottom,t.bottom),i.right=Math.max(i.right,t.right)}function $r(i,t,e,s){let{pos:n,box:o}=e,a=i.maxPadding;if(!D(n)){e.size&&(i[n]-=e.size);let d=s[e.stack]||{size:0,count:1};d.size=Math.max(d.size,e.horizontal?o.height:o.width),e.size=d.size/d.count,i[n]+=e.size}o.getPadding&&_o(a,o.getPadding());let r=Math.max(0,t.outerWidth-zn(a,i,"left","right")),l=Math.max(0,t.outerHeight-zn(a,i,"top","bottom")),c=r!==i.w,h=l!==i.h;return i.w=r,i.h=l,e.horizontal?{same:c,other:h}:{same:h,other:c}}function Yr(i){let t=i.maxPadding;function e(s){let n=Math.max(t[s]-i[s],0);return i[s]+=n,n}i.y+=e("top"),i.x+=e("left"),e("right"),e("bottom")}function Xr(i,t){let e=t.maxPadding;function s(n){let o={left:0,top:0,right:0,bottom:0};return n.forEach(a=>{o[a]=Math.max(t[a],e[a])}),o}return s(i?["left","right"]:["top","bottom"])}function Se(i,t,e,s){let n=[],o,a,r,l,c,h;for(o=0,a=i.length,c=0;o{typeof p.beforeLayout=="function"&&p.beforeLayout()});let h=l.reduce((p,m)=>m.box.options&&m.box.options.display===!1?p:p+1,0)||1,d=Object.freeze({outerWidth:t,outerHeight:e,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/h,hBoxMaxHeight:a/2}),u=Object.assign({},n);_o(u,U(s));let f=Object.assign({maxPadding:u,w:o,h:a,x:n.left,y:n.top},n),g=Hr(l.concat(c),d);Se(r.fullSize,f,d,g),Se(l,f,d,g),Se(c,f,d,g)&&Se(l,f,d,g),Yr(f),Bn(r.leftAndTop,f,d,g),f.x+=f.w,f.y+=f.h,Bn(r.rightAndBottom,f,d,g),i.chartArea={left:f.left,top:f.top,right:f.left+f.w,bottom:f.top+f.h,height:f.h,width:f.w},E(r.chartArea,p=>{let m=p.box;Object.assign(m,i.chartArea),m.update(f.w,f.h,{left:0,top:0,right:0,bottom:0})})}},hi=class{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,s){}removeEventListener(t,e,s){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,s,n){return e=Math.max(0,e||t.width),s=s||t.height,{width:e,height:Math.max(0,n?Math.floor(e/n):s)}}isAttached(t){return!0}updateConfig(t){}},ms=class extends hi{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}},li="$chartjs",Ur={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},Vn=i=>i===null||i==="";function Kr(i,t){let e=i.style,s=i.getAttribute("height"),n=i.getAttribute("width");if(i[li]={initial:{height:s,width:n,style:{display:e.display,height:e.height,width:e.width}}},e.display=e.display||"block",e.boxSizing=e.boxSizing||"border-box",Vn(n)){let o=Qi(i,"width");o!==void 0&&(i.width=o)}if(Vn(s))if(i.style.height==="")i.height=i.width/(t||2);else{let o=Qi(i,"height");o!==void 0&&(i.height=o)}return i}var xo=bn?{passive:!0}:!1;function qr(i,t,e){i.addEventListener(t,e,xo)}function Gr(i,t,e){i.canvas.removeEventListener(t,e,xo)}function Zr(i,t){let e=Ur[i.type]||i.type,{x:s,y:n}=St(i,t);return{type:e,chart:t,native:i,x:s!==void 0?s:null,y:n!==void 0?n:null}}function di(i,t){for(let e of i)if(e===t||e.contains(t))return!0}function Jr(i,t,e){let s=i.canvas,n=new MutationObserver(o=>{let a=!1;for(let r of o)a=a||di(r.addedNodes,s),a=a&&!di(r.removedNodes,s);a&&e()});return n.observe(document,{childList:!0,subtree:!0}),n}function Qr(i,t,e){let s=i.canvas,n=new MutationObserver(o=>{let a=!1;for(let r of o)a=a||di(r.removedNodes,s),a=a&&!di(r.addedNodes,s);a&&e()});return n.observe(document,{childList:!0,subtree:!0}),n}var Oe=new Map,Wn=0;function yo(){let i=window.devicePixelRatio;i!==Wn&&(Wn=i,Oe.forEach((t,e)=>{e.currentDevicePixelRatio!==i&&t()}))}function tl(i,t){Oe.size||window.addEventListener("resize",yo),Oe.set(i,t)}function el(i){Oe.delete(i),Oe.size||window.removeEventListener("resize",yo)}function il(i,t,e){let s=i.canvas,n=s&&ti(s);if(!n)return;let o=zi((r,l)=>{let c=n.clientWidth;e(r,l),c{let l=r[0],c=l.contentRect.width,h=l.contentRect.height;c===0&&h===0||o(c,h)});return a.observe(n),tl(i,o),a}function cs(i,t,e){e&&e.disconnect(),t==="resize"&&el(i)}function sl(i,t,e){let s=i.canvas,n=zi(o=>{i.ctx!==null&&e(Zr(o,i))},i,o=>{let a=o[0];return[a,a.offsetX,a.offsetY]});return qr(s,t,n),n}var bs=class extends hi{acquireContext(t,e){let s=t&&t.getContext&&t.getContext("2d");return s&&s.canvas===t?(Kr(t,e),s):null}releaseContext(t){let e=t.canvas;if(!e[li])return!1;let s=e[li].initial;["height","width"].forEach(o=>{let a=s[o];T(a)?e.removeAttribute(o):e.setAttribute(o,a)});let n=s.style||{};return Object.keys(n).forEach(o=>{e.style[o]=n[o]}),e.width=e.width,delete e[li],!0}addEventListener(t,e,s){this.removeEventListener(t,e);let n=t.$proxies||(t.$proxies={}),a={attach:Jr,detach:Qr,resize:il}[e]||sl;n[e]=a(t,e,s)}removeEventListener(t,e){let s=t.$proxies||(t.$proxies={}),n=s[e];if(!n)return;({attach:cs,detach:cs,resize:cs}[e]||Gr)(t,e,n),s[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,s,n){return mn(t,e,s,n)}isAttached(t){let e=ti(t);return!!(e&&e.isConnected)}};function nl(i){return!Zi()||typeof OffscreenCanvas<"u"&&i instanceof OffscreenCanvas?ms:bs}var _s=class{constructor(){this._init=[]}notify(t,e,s,n){e==="beforeInit"&&(this._init=this._createDescriptors(t,!0),this._notify(this._init,t,"install"));let o=n?this._descriptors(t).filter(n):this._descriptors(t),a=this._notify(o,t,e,s);return e==="afterDestroy"&&(this._notify(o,t,"stop"),this._notify(this._init,t,"uninstall")),a}_notify(t,e,s,n){n=n||{};for(let o of t){let a=o.plugin,r=a[s],l=[e,n,o.options];if(z(r,l,a)===!1&&n.cancelable)return!1}return!0}invalidate(){T(this._cache)||(this._oldCache=this._cache,this._cache=void 0)}_descriptors(t){if(this._cache)return this._cache;let e=this._cache=this._createDescriptors(t);return this._notifyStateChanges(t),e}_createDescriptors(t,e){let s=t&&t.config,n=C(s.options&&s.options.plugins,{}),o=ol(s);return n===!1&&!e?[]:rl(t,o,n,e)}_notifyStateChanges(t){let e=this._oldCache||[],s=this._cache,n=(o,a)=>o.filter(r=>!a.some(l=>r.plugin.id===l.plugin.id));this._notify(n(e,s),t,"stop"),this._notify(n(s,e),t,"start")}};function ol(i){let t={},e=[],s=Object.keys(ht.plugins.items);for(let o=0;o{let l=s[r];if(!D(l))return console.error(`Invalid scale configuration for scale: ${r}`);if(l._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${r}`);let c=ys(r,l),h=hl(c,n),d=e.scales||{};o[c]=o[c]||r,a[r]=Xt(Object.create(null),[{axis:c},l,d[c],d[h]])}),i.data.datasets.forEach(r=>{let l=r.type||i.type,c=r.indexAxis||xs(l,t),d=(vt[l]||{}).scales||{};Object.keys(d).forEach(u=>{let f=cl(u,c),g=r[f+"AxisID"]||o[f]||f;a[g]=a[g]||Object.create(null),Xt(a[g],[{axis:f},s[g],d[u]])})}),Object.keys(a).forEach(r=>{let l=a[r];Xt(l,[O.scales[l.type],O.scale])}),a}function vo(i){let t=i.options||(i.options={});t.plugins=C(t.plugins,{}),t.scales=ul(i,t)}function Mo(i){return i=i||{},i.datasets=i.datasets||[],i.labels=i.labels||[],i}function fl(i){return i=i||{},i.data=Mo(i.data),vo(i),i}var Nn=new Map,wo=new Set;function ni(i,t){let e=Nn.get(i);return e||(e=t(),Nn.set(i,e),wo.add(e)),e}var ke=(i,t,e)=>{let s=gt(t,e);s!==void 0&&i.add(s)},vs=class{constructor(t){this._config=fl(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Mo(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){let t=this._config;this.clearCache(),vo(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return ni(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,e){return ni(`${t}.transition.${e}`,()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,e){return ni(`${t}-${e}`,()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]])}pluginScopeKeys(t){let e=t.id,s=this.type;return ni(`${s}-plugin-${e}`,()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,e){let s=this._scopeCache,n=s.get(t);return(!n||e)&&(n=new Map,s.set(t,n)),n}getOptionScopes(t,e,s){let{options:n,type:o}=this,a=this._cachedScopes(t,s),r=a.get(e);if(r)return r;let l=new Set;e.forEach(h=>{t&&(l.add(t),h.forEach(d=>ke(l,t,d))),h.forEach(d=>ke(l,n,d)),h.forEach(d=>ke(l,vt[o]||{},d)),h.forEach(d=>ke(l,O,d)),h.forEach(d=>ke(l,Ge,d))});let c=Array.from(l);return c.length===0&&c.push(Object.create(null)),wo.has(e)&&a.set(e,c),c}chartOptionScopes(){let{options:t,type:e}=this;return[t,vt[e]||{},O.datasets[e]||{},{type:e},O,Ge]}resolveNamedOptions(t,e,s,n=[""]){let o={$shared:!0},{resolver:a,subPrefixes:r}=Hn(this._resolverCache,t,n),l=a;if(pl(a,e)){o.$shared=!1,s=ft(s)?s():s;let c=this.createResolver(t,s,r);l=Tt(a,s,c)}for(let c of e)o[c]=l[c];return o}createResolver(t,e,s=[""],n){let{resolver:o}=Hn(this._resolverCache,t,s);return D(e)?Tt(o,e,void 0,n):o}};function Hn(i,t,e){let s=i.get(t);s||(s=new Map,i.set(t,s));let n=e.join(),o=s.get(n);return o||(o={resolver:Qe(t,e),subPrefixes:e.filter(r=>!r.toLowerCase().includes("hover"))},s.set(n,o)),o}var gl=i=>D(i)&&Object.getOwnPropertyNames(i).reduce((t,e)=>t||ft(i[e]),!1);function pl(i,t){let{isScriptable:e,isIndexable:s}=Ui(i);for(let n of t){let o=e(n),a=s(n),r=(a||o)&&i[n];if(o&&(ft(r)||gl(r))||a&&I(r))return!0}return!1}var ml="3.9.1",bl=["top","bottom","left","right","chartArea"];function jn(i,t){return i==="top"||i==="bottom"||bl.indexOf(i)===-1&&t==="x"}function $n(i,t){return function(e,s){return e[i]===s[i]?e[t]-s[t]:e[i]-s[i]}}function Yn(i){let t=i.chart,e=t.options.animation;t.notifyPlugins("afterRender"),z(e&&e.onComplete,[i],t)}function _l(i){let t=i.chart,e=t.options.animation;z(e&&e.onProgress,[i],t)}function ko(i){return Zi()&&typeof i=="string"?i=document.getElementById(i):i&&i.length&&(i=i[0]),i&&i.canvas&&(i=i.canvas),i}var ui={},So=i=>{let t=ko(i);return Object.values(ui).filter(e=>e.canvas===t).pop()};function xl(i,t,e){let s=Object.keys(i);for(let n of s){let o=+n;if(o>=t){let a=i[n];delete i[n],(e>0||o>t)&&(i[o+e]=a)}}}function yl(i,t,e,s){return!e||i.type==="mouseout"?null:s?t:i}var It=class{constructor(t,e){let s=this.config=new vs(e),n=ko(t),o=So(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");let a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||nl(n)),this.platform.updateConfig(s);let r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,c=l&&l.height,h=l&&l.width;if(this.id=Hs(),this.ctx=r,this.canvas=l,this.width=h,this.height=c,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new _s,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=Qs(d=>this.update(d),a.resizeDelay||0),this._dataChanges=[],ui[this.id]=this,!r||!l){console.error("Failed to create chart: can't acquire context from the given item");return}mt.listen(this,"complete",Yn),mt.listen(this,"progress",_l),this._initialize(),this.attached&&this.update()}get aspectRatio(){let{options:{aspectRatio:t,maintainAspectRatio:e},width:s,height:n,_aspectRatio:o}=this;return T(t)?e&&o?o:n?s/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():Ji(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return $i(this.canvas,this.ctx),this}stop(){return mt.stop(this),this}resize(t,e){mt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){let s=this.options,n=this.canvas,o=s.maintainAspectRatio&&this.aspectRatio,a=this.platform.getMaximumSize(n,t,e,o),r=s.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=a.width,this.height=a.height,this._aspectRatio=this.aspectRatio,Ji(this,r,!0)&&(this.notifyPlugins("resize",{size:a}),z(s.onResize,[this,a],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){let e=this.options.scales||{};E(e,(s,n)=>{s.id=n})}buildOrUpdateScales(){let t=this.options,e=t.scales,s=this.scales,n=Object.keys(s).reduce((a,r)=>(a[r]=!1,a),{}),o=[];e&&(o=o.concat(Object.keys(e).map(a=>{let r=e[a],l=ys(a,r),c=l==="r",h=l==="x";return{options:r,dposition:c?"chartArea":h?"bottom":"left",dtype:c?"radialLinear":h?"category":"linear"}}))),E(o,a=>{let r=a.options,l=r.id,c=ys(l,r),h=C(r.type,a.dtype);(r.position===void 0||jn(r.position,c)!==jn(a.dposition))&&(r.position=a.dposition),n[l]=!0;let d=null;if(l in s&&s[l].type===h)d=s[l];else{let u=ht.getScale(h);d=new u({id:l,type:h,ctx:this.ctx,chart:this}),s[d.id]=d}d.init(r,t)}),E(n,(a,r)=>{a||delete s[r]}),E(s,a=>{K.configure(this,a,a.options),K.addBox(this,a)})}_updateMetasets(){let t=this._metasets,e=this.data.datasets.length,s=t.length;if(t.sort((n,o)=>n.index-o.index),s>e){for(let n=e;ne.length&&delete this._stacks,t.forEach((s,n)=>{e.filter(o=>o===s._dataset).length===0&&this._destroyDatasetMeta(n)})}buildOrUpdateControllers(){let t=[],e=this.data.datasets,s,n;for(this._removeUnreferencedMetasets(),s=0,n=e.length;s{this.getDatasetMeta(e).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){let e=this.config;e.update();let s=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),n=this._animationsDisabled=!s.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;let o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let a=0;for(let c=0,h=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort($n("z","_idx"));let{_active:r,_lastEvent:l}=this;l?this._eventHandler(l,!0):r.length&&this._updateHoverStyles(r,r,!0),this.render()}_updateScales(){E(this.scales,t=>{K.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){let t=this.options,e=new Set(Object.keys(this._listeners)),s=new Set(t.events);(!Oi(e,s)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){let{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(let{method:s,start:n,count:o}of e){let a=s==="_removeElements"?-o:o;xl(t,n,a)}}_getUniformDataChanges(){let t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];let e=this.data.datasets.length,s=o=>new Set(t.filter(a=>a[0]===o).map((a,r)=>r+","+a.splice(1).join(","))),n=s(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;K.update(this,this.width,this.height,t);let e=this.chartArea,s=e.width<=0||e.height<=0;this._layers=[],E(this.boxes,n=>{s&&n.position==="chartArea"||(n.configure&&n.configure(),this._layers.push(...n._layers()))},this),this._layers.forEach((n,o)=>{n._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let e=0,s=this.data.datasets.length;e=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){let e=this.ctx,s=t._clip,n=!s.disabled,o=this.chartArea,a={meta:t,index:t.index,cancelable:!0};this.notifyPlugins("beforeDatasetDraw",a)!==!1&&(n&&_e(e,{left:s.left===!1?0:o.left-s.left,right:s.right===!1?this.width:o.right+s.right,top:s.top===!1?0:o.top-s.top,bottom:s.bottom===!1?this.height:o.bottom+s.bottom}),t.controller.draw(),n&&xe(e),a.cancelable=!1,this.notifyPlugins("afterDatasetDraw",a))}isPointInArea(t){return $t(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,s,n){let o=Vr.modes[e];return typeof o=="function"?o(this,t,s,n):[]}getDatasetMeta(t){let e=this.data.datasets[t],s=this._metasets,n=s.filter(o=>o&&o._dataset===e).pop();return n||(n={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},s.push(n)),n}getContext(){return this.$context||(this.$context=pt(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){let e=this.data.datasets[t];if(!e)return!1;let s=this.getDatasetMeta(t);return typeof s.hidden=="boolean"?!s.hidden:!e.hidden}setDatasetVisibility(t,e){let s=this.getDatasetMeta(t);s.hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,s){let n=s?"show":"hide",o=this.getDatasetMeta(t),a=o.controller._resolveAnimations(void 0,n);J(e)?(o.data[e].hidden=!s,this.update()):(this.setDatasetVisibility(t,s),a.update(o,{visible:s}),this.update(r=>r.datasetIndex===t?n:void 0))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){let e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),mt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,o,a),t[o]=a},n=(o,a,r)=>{o.offsetX=a,o.offsetY=r,this._eventHandler(o)};E(this.options.events,o=>s(o,n))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});let t=this._responsiveListeners,e=this.platform,s=(l,c)=>{e.addEventListener(this,l,c),t[l]=c},n=(l,c)=>{t[l]&&(e.removeEventListener(this,l,c),delete t[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)},a,r=()=>{n("attach",r),this.attached=!0,this.resize(),s("resize",o),s("detach",a)};a=()=>{this.attached=!1,n("resize",o),this._stop(),this._resize(0,0),s("attach",r)},e.isAttached(this.canvas)?r():a()}unbindEvents(){E(this._listeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._listeners={},E(this._responsiveListeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,e,s){let n=s?"set":"remove",o,a,r,l;for(e==="dataset"&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+n+"DatasetHoverStyle"]()),r=0,l=t.length;r{let r=this.getDatasetMeta(o);if(!r)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:r.data[a],index:a}});!me(s,e)&&(this._active=s,this._lastEvent=null,this._updateHoverStyles(s,e))}notifyPlugins(t,e,s){return this._plugins.notify(this,t,e,s)}_updateHoverStyles(t,e,s){let n=this.options.hover,o=(l,c)=>l.filter(h=>!c.some(d=>h.datasetIndex===d.datasetIndex&&h.index===d.index)),a=o(e,t),r=s?t:o(t,e);a.length&&this.updateHoverStyle(a,n.mode,!1),r.length&&n.mode&&this.updateHoverStyle(r,n.mode,!0)}_eventHandler(t,e){let s={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},n=a=>(a.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",s,n)===!1)return;let o=this._handleEvent(t,e,s.inChartArea);return s.cancelable=!1,this.notifyPlugins("afterEvent",s,n),(o||s.changed)&&this.render(),this}_handleEvent(t,e,s){let{_active:n=[],options:o}=this,a=e,r=this._getActiveElements(t,n,s,a),l=Ys(t),c=yl(t,this._lastEvent,s,l);s&&(this._lastEvent=null,z(o.onHover,[t,r,this],this),l&&z(o.onClick,[t,r,this],this));let h=!me(r,n);return(h||e)&&(this._active=r,this._updateHoverStyles(r,n,e)),this._lastEvent=c,h}_getActiveElements(t,e,s,n){if(t.type==="mouseout")return[];if(!s)return e;let o=this.options.hover;return this.getElementsAtEventForMode(t,o.mode,o,n)}},Xn=()=>E(It.instances,i=>i._plugins.invalidate()),Pt=!0;Object.defineProperties(It,{defaults:{enumerable:Pt,value:O},instances:{enumerable:Pt,value:ui},overrides:{enumerable:Pt,value:vt},registry:{enumerable:Pt,value:ht},version:{enumerable:Pt,value:ml},getChart:{enumerable:Pt,value:So},register:{enumerable:Pt,value:(...i)=>{ht.add(...i),Xn()}},unregister:{enumerable:Pt,value:(...i)=>{ht.remove(...i),Xn()}}});function Po(i,t,e){let{startAngle:s,pixelMargin:n,x:o,y:a,outerRadius:r,innerRadius:l}=t,c=n/r;i.beginPath(),i.arc(o,a,r,s-c,e+c),l>n?(c=n/l,i.arc(o,a,l,e+c,s-c,!0)):i.arc(o,a,n,e+V,s-V),i.closePath(),i.clip()}function vl(i){return Je(i,["outerStart","outerEnd","innerStart","innerEnd"])}function Ml(i,t,e,s){let n=vl(i.options.borderRadius),o=(e-t)/2,a=Math.min(o,s*t/2),r=l=>{let c=(e-Math.min(o,l))*s/2;return Y(l,0,Math.min(o,c))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:Y(n.innerStart,0,a),innerEnd:Y(n.innerEnd,0,a)}}function Jt(i,t,e,s){return{x:e+i*Math.cos(t),y:s+i*Math.sin(t)}}function Ms(i,t,e,s,n,o){let{x:a,y:r,startAngle:l,pixelMargin:c,innerRadius:h}=t,d=Math.max(t.outerRadius+s+e-c,0),u=h>0?h+s+e+c:0,f=0,g=n-l;if(s){let P=h>0?h-s:0,j=d>0?d-s:0,N=(P+j)/2,Ot=N!==0?g*N/(N+s):g;f=(g-Ot)/2}let p=Math.max(.001,g*d-e/B)/d,m=(g-p)/2,b=l+m+f,_=n-m-f,{outerStart:v,outerEnd:y,innerStart:x,innerEnd:M}=Ml(t,u,d,_-b),w=d-v,S=d-y,k=b+v/w,L=_-y/S,R=u+x,A=u+M,H=b+x/R,q=_-M/A;if(i.beginPath(),o){if(i.arc(a,r,d,k,L),y>0){let N=Jt(S,L,a,r);i.arc(N.x,N.y,y,L,_+V)}let P=Jt(A,_,a,r);if(i.lineTo(P.x,P.y),M>0){let N=Jt(A,q,a,r);i.arc(N.x,N.y,M,_+V,q+Math.PI)}if(i.arc(a,r,u,_-M/u,b+x/u,!0),x>0){let N=Jt(R,H,a,r);i.arc(N.x,N.y,x,H+Math.PI,b-V)}let j=Jt(w,b,a,r);if(i.lineTo(j.x,j.y),v>0){let N=Jt(w,k,a,r);i.arc(N.x,N.y,v,b-V,k)}}else{i.moveTo(a,r);let P=Math.cos(k)*d+a,j=Math.sin(k)*d+r;i.lineTo(P,j);let N=Math.cos(L)*d+a,Ot=Math.sin(L)*d+r;i.lineTo(N,Ot)}i.closePath()}function wl(i,t,e,s,n){let{fullCircles:o,startAngle:a,circumference:r}=t,l=t.endAngle;if(o){Ms(i,t,e,s,a+F,n);for(let c=0;c=F||Kt(o,r,l),p=lt(a,c+u,h+u);return g&&p}getCenterPoint(t){let{x:e,y:s,startAngle:n,endAngle:o,innerRadius:a,outerRadius:r}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius","circumference"],t),{offset:l,spacing:c}=this.options,h=(n+o)/2,d=(a+r+c+l)/2;return{x:e+Math.cos(h)*d,y:s+Math.sin(h)*d}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){let{options:e,circumference:s}=this,n=(e.offset||0)/2,o=(e.spacing||0)/2,a=e.circular;if(this.pixelMargin=e.borderAlign==="inner"?.33:0,this.fullCircles=s>F?Math.floor(s/F):0,s===0||this.innerRadius<0||this.outerRadius<0)return;t.save();let r=0;if(n){r=n/2;let c=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(c)*r,Math.sin(c)*r),this.circumference>=B&&(r=n)}t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor;let l=wl(t,this,r,o,a);Sl(t,this,r,o,l,a),t.restore()}};ae.id="arc";ae.defaults={borderAlign:"center",borderColor:"#fff",borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0};ae.defaultRoutes={backgroundColor:"backgroundColor"};function Co(i,t,e=t){i.lineCap=C(e.borderCapStyle,t.borderCapStyle),i.setLineDash(C(e.borderDash,t.borderDash)),i.lineDashOffset=C(e.borderDashOffset,t.borderDashOffset),i.lineJoin=C(e.borderJoinStyle,t.borderJoinStyle),i.lineWidth=C(e.borderWidth,t.borderWidth),i.strokeStyle=C(e.borderColor,t.borderColor)}function Pl(i,t,e){i.lineTo(e.x,e.y)}function Cl(i){return i.stepped?ln:i.tension||i.cubicInterpolationMode==="monotone"?cn:Pl}function Do(i,t,e={}){let s=i.length,{start:n=0,end:o=s-1}=e,{start:a,end:r}=t,l=Math.max(n,a),c=Math.min(o,r),h=nr&&o>r;return{count:s,start:l,loop:t.loop,ilen:c(a+(c?r-y:y))%o,v=()=>{p!==m&&(i.lineTo(h,m),i.lineTo(h,p),i.lineTo(h,b))};for(l&&(f=n[_(0)],i.moveTo(f.x,f.y)),u=0;u<=r;++u){if(f=n[_(u)],f.skip)continue;let y=f.x,x=f.y,M=y|0;M===g?(xm&&(m=x),h=(d*h+y)/++d):(v(),i.lineTo(y,x),g=M,d=0,p=m=x),b=x}v()}function ws(i){let t=i.options,e=t.borderDash&&t.borderDash.length;return!i._decimated&&!i._loop&&!t.tension&&t.cubicInterpolationMode!=="monotone"&&!t.stepped&&!e?Ol:Dl}function Al(i){return i.stepped?_n:i.tension||i.cubicInterpolationMode==="monotone"?xn:_t}function Tl(i,t,e,s){let n=t._path;n||(n=t._path=new Path2D,t.path(n,e,s)&&n.closePath()),Co(i,t.options),i.stroke(n)}function Ll(i,t,e,s){let{segments:n,options:o}=t,a=ws(t);for(let r of n)Co(i,o,r.style),i.beginPath(),a(i,t,r,{start:e,end:e+s-1})&&i.closePath(),i.stroke()}var Rl=typeof Path2D=="function";function El(i,t,e,s){Rl&&!t.options.segment?Tl(i,t,e,s):Ll(i,t,e,s)}var dt=class extends it{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){let s=this.options;if((s.tension||s.cubicInterpolationMode==="monotone")&&!s.stepped&&!this._pointsUpdated){let n=s.spanGaps?this._loop:this._fullLoop;pn(this._points,s,t,n,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=vn(this,this.options.segment))}first(){let t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){let t=this.segments,e=this.points,s=t.length;return s&&e[t[s-1].end]}interpolate(t,e){let s=this.options,n=t[e],o=this.points,a=ss(this,{property:e,start:n,end:n});if(!a.length)return;let r=[],l=Al(s),c,h;for(c=0,h=a.length;ci!=="borderDash"&&i!=="fill"};function Un(i,t,e,s){let n=i.options,{[e]:o}=i.getProps([e],s);return Math.abs(t-o)=e)return i.slice(t,t+e);let a=[],r=(e-2)/(o-2),l=0,c=t+e-1,h=t,d,u,f,g,p;for(a[l++]=i[h],d=0;df&&(f=g,u=i[_],p=_);a[l++]=u,h=p}return a[l++]=i[c],a}function Hl(i,t,e,s){let n=0,o=0,a,r,l,c,h,d,u,f,g,p,m=[],b=t+e-1,_=i[t].x,y=i[b].x-_;for(a=t;ap&&(p=c,u=a),n=(o*n+r.x)/++o;else{let M=a-1;if(!T(d)&&!T(u)){let w=Math.min(d,u),S=Math.max(d,u);w!==f&&w!==M&&m.push({...i[w],x:n}),S!==f&&S!==M&&m.push({...i[S],x:n})}a>0&&M!==f&&m.push(i[M]),m.push(r),h=x,o=0,g=p=c,d=u=f=a}}return m}function Ao(i){if(i._decimated){let t=i._data;delete i._decimated,delete i._data,Object.defineProperty(i,"data",{value:t})}}function Kn(i){i.data.datasets.forEach(t=>{Ao(t)})}function jl(i,t){let e=t.length,s=0,n,{iScale:o}=i,{min:a,max:r,minDefined:l,maxDefined:c}=o.getUserBounds();return l&&(s=Y(at(t,o.axis,a).lo,0,e-1)),c?n=Y(at(t,o.axis,r).hi+1,s,e)-s:n=e-s,{start:s,count:n}}var $l={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(i,t,e)=>{if(!e.enabled){Kn(i);return}let s=i.width;i.data.datasets.forEach((n,o)=>{let{_data:a,indexAxis:r}=n,l=i.getDatasetMeta(o),c=a||n.data;if(Gt([r,i.options.indexAxis])==="y"||!l.controller.supportsDecimation)return;let h=i.scales[l.xAxisID];if(h.type!=="linear"&&h.type!=="time"||i.options.parsing)return;let{start:d,count:u}=jl(l,c),f=e.threshold||4*s;if(u<=f){Ao(n);return}T(a)&&(n._data=c,delete n.data,Object.defineProperty(n,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(p){this._data=p}}));let g;switch(e.algorithm){case"lttb":g=Nl(c,d,u,s,e);break;case"min-max":g=Hl(c,d,u,s);break;default:throw new Error(`Unsupported decimation algorithm '${e.algorithm}'`)}n._decimated=g})},destroy(i){Kn(i)}};function Yl(i,t,e){let s=i.segments,n=i.points,o=t.points,a=[];for(let r of s){let{start:l,end:c}=r;c=Ps(l,c,n);let h=ks(e,n[l],n[c],r.loop);if(!t.segments){a.push({source:r,target:h,start:n[l],end:n[c]});continue}let d=ss(t,h);for(let u of d){let f=ks(e,o[u.start],o[u.end],u.loop),g=is(r,n,f);for(let p of g)a.push({source:p,target:u,start:{[e]:qn(h,f,"start",Math.max)},end:{[e]:qn(h,f,"end",Math.min)}})}}return a}function ks(i,t,e,s){if(s)return;let n=t[i],o=e[i];return i==="angle"&&(n=G(n),o=G(o)),{property:i,start:n,end:o}}function Xl(i,t){let{x:e=null,y:s=null}=i||{},n=t.points,o=[];return t.segments.forEach(({start:a,end:r})=>{r=Ps(a,r,n);let l=n[a],c=n[r];s!==null?(o.push({x:l.x,y:s}),o.push({x:c.x,y:s})):e!==null&&(o.push({x:e,y:l.y}),o.push({x:e,y:c.y}))}),o}function Ps(i,t,e){for(;t>i;t--){let s=e[t];if(!isNaN(s.x)&&!isNaN(s.y))break}return t}function qn(i,t,e,s){return i&&t?s(i[e],t[e]):i?i[e]:t?t[e]:0}function To(i,t){let e=[],s=!1;return I(i)?(s=!0,e=i):e=Xl(i,t),e.length?new dt({points:e,options:{tension:0},_loop:s,_fullLoop:s}):null}function Gn(i){return i&&i.fill!==!1}function Ul(i,t,e){let n=i[t].fill,o=[t],a;if(!e)return n;for(;n!==!1&&o.indexOf(n)===-1;){if(!W(n))return n;if(a=i[n],!a)return!1;if(a.visible)return n;o.push(n),n=a.fill}return!1}function Kl(i,t,e){let s=Jl(i);if(D(s))return isNaN(s.value)?!1:s;let n=parseFloat(s);return W(n)&&Math.floor(n)===n?ql(s[0],t,n,e):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function ql(i,t,e,s){return(i==="-"||i==="+")&&(e=t+e),e===t||e<0||e>=s?!1:e}function Gl(i,t){let e=null;return i==="start"?e=t.bottom:i==="end"?e=t.top:D(i)?e=t.getPixelForValue(i.value):t.getBasePixel&&(e=t.getBasePixel()),e}function Zl(i,t,e){let s;return i==="start"?s=e:i==="end"?s=t.options.reverse?t.min:t.max:D(i)?s=i.value:s=t.getBaseValue(),s}function Jl(i){let t=i.options,e=t.fill,s=C(e&&e.target,e);return s===void 0&&(s=!!t.backgroundColor),s===!1||s===null?!1:s===!0?"origin":s}function Ql(i){let{scale:t,index:e,line:s}=i,n=[],o=s.segments,a=s.points,r=tc(t,e);r.push(To({x:null,y:t.bottom},s));for(let l=0;l=0;--a){let r=n[a].$filler;r&&(r.line.updateControlPoints(o,r.axis),s&&r.fill&&us(i.ctx,r,o))}},beforeDatasetsDraw(i,t,e){if(e.drawTime!=="beforeDatasetsDraw")return;let s=i.getSortedVisibleDatasetMetas();for(let n=s.length-1;n>=0;--n){let o=s[n].$filler;Gn(o)&&us(i.ctx,o,i.chartArea)}},beforeDatasetDraw(i,t,e){let s=t.meta.$filler;!Gn(s)||e.drawTime!=="beforeDatasetDraw"||us(i.ctx,s,i.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}},to=(i,t)=>{let{boxHeight:e=t,boxWidth:s=t}=i;return i.usePointStyle&&(e=Math.min(e,t),s=i.pointStyleWidth||Math.min(s,t)),{boxWidth:s,boxHeight:e,itemHeight:Math.max(t,e)}},dc=(i,t)=>i!==null&&t!==null&&i.datasetIndex===t.datasetIndex&&i.index===t.index,gi=class extends it{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,s){this.maxWidth=t,this.maxHeight=e,this._margins=s,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){let t=this.options.labels||{},e=z(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter(s=>t.filter(s,this.chart.data))),t.sort&&(e=e.sort((s,n)=>t.sort(s,n,this.chart.data))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){let{options:t,ctx:e}=this;if(!t.display){this.width=this.height=0;return}let s=t.labels,n=$(s.font),o=n.size,a=this._computeTitleHeight(),{boxWidth:r,itemHeight:l}=to(s,o),c,h;e.font=n.string,this.isHorizontal()?(c=this.maxWidth,h=this._fitRows(a,o,r,l)+10):(h=this.maxHeight,c=this._fitCols(a,o,r,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,s,n){let{ctx:o,maxWidth:a,options:{labels:{padding:r}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],h=n+r,d=t;o.textAlign="left",o.textBaseline="middle";let u=-1,f=-h;return this.legendItems.forEach((g,p)=>{let m=s+e/2+o.measureText(g.text).width;(p===0||c[c.length-1]+m+2*r>a)&&(d+=h,c[c.length-(p>0?0:1)]=0,f+=h,u++),l[p]={left:0,top:f,row:u,width:m,height:n},c[c.length-1]+=m+r}),d}_fitCols(t,e,s,n){let{ctx:o,maxHeight:a,options:{labels:{padding:r}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],h=a-t,d=r,u=0,f=0,g=0,p=0;return this.legendItems.forEach((m,b)=>{let _=s+e/2+o.measureText(m.text).width;b>0&&f+n+2*r>h&&(d+=u+r,c.push({width:u,height:f}),g+=u+r,p++,u=f=0),l[b]={left:g,top:f,col:p,width:_,height:n},u=Math.max(u,_),f+=n+r}),d+=u,c.push({width:u,height:f}),d}adjustHitBoxes(){if(!this.options.display)return;let t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:s,labels:{padding:n},rtl:o}}=this,a=Rt(o,this.left,this.width);if(this.isHorizontal()){let r=0,l=X(s,this.left+n,this.right-this.lineWidths[r]);for(let c of e)r!==c.row&&(r=c.row,l=X(s,this.left+n,this.right-this.lineWidths[r])),c.top+=this.top+t+n,c.left=a.leftForLtr(a.x(l),c.width),l+=c.width+n}else{let r=0,l=X(s,this.top+t+n,this.bottom-this.columnSizes[r].height);for(let c of e)c.col!==r&&(r=c.col,l=X(s,this.top+t+n,this.bottom-this.columnSizes[r].height)),c.top=l,c.left+=this.left+n,c.left=a.leftForLtr(a.x(c.left),c.width),l+=c.height+n}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){let t=this.ctx;_e(t,this),this._draw(),xe(t)}}_draw(){let{options:t,columnSizes:e,lineWidths:s,ctx:n}=this,{align:o,labels:a}=t,r=O.color,l=Rt(t.rtl,this.left,this.width),c=$(a.font),{color:h,padding:d}=a,u=c.size,f=u/2,g;this.drawTitle(),n.textAlign=l.textAlign("left"),n.textBaseline="middle",n.lineWidth=.5,n.font=c.string;let{boxWidth:p,boxHeight:m,itemHeight:b}=to(a,u),_=function(w,S,k){if(isNaN(p)||p<=0||isNaN(m)||m<0)return;n.save();let L=C(k.lineWidth,1);if(n.fillStyle=C(k.fillStyle,r),n.lineCap=C(k.lineCap,"butt"),n.lineDashOffset=C(k.lineDashOffset,0),n.lineJoin=C(k.lineJoin,"miter"),n.lineWidth=L,n.strokeStyle=C(k.strokeStyle,r),n.setLineDash(C(k.lineDash,[])),a.usePointStyle){let R={radius:m*Math.SQRT2/2,pointStyle:k.pointStyle,rotation:k.rotation,borderWidth:L},A=l.xPlus(w,p/2),H=S+f;Yi(n,R,A,H,a.pointStyleWidth&&p)}else{let R=S+Math.max((u-m)/2,0),A=l.leftForLtr(w,p),H=kt(k.borderRadius);n.beginPath(),Object.values(H).some(q=>q!==0)?qt(n,{x:A,y:R,w:p,h:m,radius:H}):n.rect(A,R,p,m),n.fill(),L!==0&&n.stroke()}n.restore()},v=function(w,S,k){wt(n,k.text,w,S+b/2,c,{strikethrough:k.hidden,textAlign:l.textAlign(k.textAlign)})},y=this.isHorizontal(),x=this._computeTitleHeight();y?g={x:X(o,this.left+d,this.right-s[0]),y:this.top+d+x,line:0}:g={x:this.left+d,y:X(o,this.top+x+d,this.bottom-e[0].height),line:0},ts(this.ctx,t.textDirection);let M=b+d;this.legendItems.forEach((w,S)=>{n.strokeStyle=w.fontColor||h,n.fillStyle=w.fontColor||h;let k=n.measureText(w.text).width,L=l.textAlign(w.textAlign||(w.textAlign=a.textAlign)),R=p+f+k,A=g.x,H=g.y;l.setWidth(this.width),y?S>0&&A+R+d>this.right&&(H=g.y+=M,g.line++,A=g.x=X(o,this.left+d,this.right-s[g.line])):S>0&&H+M>this.bottom&&(A=g.x=A+e[g.line].width+d,g.line++,H=g.y=X(o,this.top+x+d,this.bottom-e[g.line].height));let q=l.x(A);_(q,H,w),A=tn(L,A+p+f,y?A+R:this.right,t.rtl),v(l.x(A),H,w),y?g.x+=R+d:g.y+=M}),es(this.ctx,t.textDirection)}drawTitle(){let t=this.options,e=t.title,s=$(e.font),n=U(e.padding);if(!e.display)return;let o=Rt(t.rtl,this.left,this.width),a=this.ctx,r=e.position,l=s.size/2,c=n.top+l,h,d=this.left,u=this.width;if(this.isHorizontal())u=Math.max(...this.lineWidths),h=this.top+c,d=X(t.align,d,this.right-u);else{let g=this.columnSizes.reduce((p,m)=>Math.max(p,m.height),0);h=c+X(t.align,this.top,this.bottom-g-t.labels.padding-this._computeTitleHeight())}let f=X(r,d,d+u);a.textAlign=o.textAlign(qe(r)),a.textBaseline="middle",a.strokeStyle=e.color,a.fillStyle=e.color,a.font=s.string,wt(a,e.text,f,h,s)}_computeTitleHeight(){let t=this.options.title,e=$(t.font),s=U(t.padding);return t.display?e.lineHeight+s.height:0}_getLegendItemAt(t,e){let s,n,o;if(lt(t,this.left,this.right)&<(e,this.top,this.bottom)){for(o=this.legendHitBoxes,s=0;si.chart.options.color,boxWidth:40,padding:10,generateLabels(i){let t=i.data.datasets,{labels:{usePointStyle:e,pointStyle:s,textAlign:n,color:o}}=i.legend.options;return i._getSortedDatasetMetas().map(a=>{let r=a.controller.getStyle(e?0:void 0),l=U(r.borderWidth);return{text:t[a.index].label,fillStyle:r.backgroundColor,fontColor:o,hidden:!a.visible,lineCap:r.borderCapStyle,lineDash:r.borderDash,lineDashOffset:r.borderDashOffset,lineJoin:r.borderJoinStyle,lineWidth:(l.width+l.height)/4,strokeStyle:r.borderColor,pointStyle:s||r.pointStyle,rotation:r.rotation,textAlign:n||r.textAlign,borderRadius:0,datasetIndex:a.index}},this)}},title:{color:i=>i.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:i=>!i.startsWith("on"),labels:{_scriptable:i=>!["generateLabels","filter","sort"].includes(i)}}},Ae=class extends it{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){let s=this.options;if(this.left=0,this.top=0,!s.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=e;let n=I(s.text)?s.text.length:1;this._padding=U(s.padding);let o=n*$(s.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){let t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){let{top:e,left:s,bottom:n,right:o,options:a}=this,r=a.align,l=0,c,h,d;return this.isHorizontal()?(h=X(r,s,o),d=e+t,c=o-s):(a.position==="left"?(h=s+t,d=X(r,n,e),l=B*-.5):(h=o-t,d=X(r,e,n),l=B*.5),c=n-e),{titleX:h,titleY:d,maxWidth:c,rotation:l}}draw(){let t=this.ctx,e=this.options;if(!e.display)return;let s=$(e.font),o=s.lineHeight/2+this._padding.top,{titleX:a,titleY:r,maxWidth:l,rotation:c}=this._drawArgs(o);wt(t,e.text,0,0,s,{color:e.color,maxWidth:l,rotation:c,textAlign:qe(e.align),textBaseline:"middle",translation:[a,r]})}};function gc(i,t){let e=new Ae({ctx:i.ctx,options:t,chart:i});K.configure(i,e,t),K.addBox(i,e),i.titleBlock=e}var pc={id:"title",_element:Ae,start(i,t,e){gc(i,e)},stop(i){let t=i.titleBlock;K.removeBox(i,t),delete i.titleBlock},beforeUpdate(i,t,e){let s=i.titleBlock;K.configure(i,s,e),s.options=e},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}},oi=new WeakMap,mc={id:"subtitle",start(i,t,e){let s=new Ae({ctx:i.ctx,options:e,chart:i});K.configure(i,s,e),K.addBox(i,s),oi.set(i,s)},stop(i){K.removeBox(i,oi.get(i)),oi.delete(i)},beforeUpdate(i,t,e){let s=oi.get(i);K.configure(i,s,e),s.options=e},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}},Pe={average(i){if(!i.length)return!1;let t,e,s=0,n=0,o=0;for(t=0,e=i.length;t-1?i.split(` -`):i}function bc(i,t){let{element:e,datasetIndex:s,index:n}=t,o=i.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:i,label:a,parsed:o.getParsed(n),raw:i.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:e}}function eo(i,t){let e=i.chart.ctx,{body:s,footer:n,title:o}=i,{boxWidth:a,boxHeight:r}=t,l=$(t.bodyFont),c=$(t.titleFont),h=$(t.footerFont),d=o.length,u=n.length,f=s.length,g=U(t.padding),p=g.height,m=0,b=s.reduce((y,x)=>y+x.before.length+x.lines.length+x.after.length,0);if(b+=i.beforeBody.length+i.afterBody.length,d&&(p+=d*c.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),b){let y=t.displayColors?Math.max(r,l.lineHeight):l.lineHeight;p+=f*y+(b-f)*l.lineHeight+(b-1)*t.bodySpacing}u&&(p+=t.footerMarginTop+u*h.lineHeight+(u-1)*t.footerSpacing);let _=0,v=function(y){m=Math.max(m,e.measureText(y).width+_)};return e.save(),e.font=c.string,E(i.title,v),e.font=l.string,E(i.beforeBody.concat(i.afterBody),v),_=t.displayColors?a+2+t.boxPadding:0,E(s,y=>{E(y.before,v),E(y.lines,v),E(y.after,v)}),_=0,e.font=h.string,E(i.footer,v),e.restore(),m+=g.width,{width:m,height:p}}function _c(i,t){let{y:e,height:s}=t;return ei.height-s/2?"bottom":"center"}function xc(i,t,e,s){let{x:n,width:o}=s,a=e.caretSize+e.caretPadding;if(i==="left"&&n+o+a>t.width||i==="right"&&n-o-a<0)return!0}function yc(i,t,e,s){let{x:n,width:o}=e,{width:a,chartArea:{left:r,right:l}}=i,c="center";return s==="center"?c=n<=(r+l)/2?"left":"right":n<=o/2?c="left":n>=a-o/2&&(c="right"),xc(c,i,t,e)&&(c="center"),c}function io(i,t,e){let s=e.yAlign||t.yAlign||_c(i,e);return{xAlign:e.xAlign||t.xAlign||yc(i,t,e,s),yAlign:s}}function vc(i,t){let{x:e,width:s}=i;return t==="right"?e-=s:t==="center"&&(e-=s/2),e}function Mc(i,t,e){let{y:s,height:n}=i;return t==="top"?s+=e:t==="bottom"?s-=n+e:s-=n/2,s}function so(i,t,e,s){let{caretSize:n,caretPadding:o,cornerRadius:a}=i,{xAlign:r,yAlign:l}=e,c=n+o,{topLeft:h,topRight:d,bottomLeft:u,bottomRight:f}=kt(a),g=vc(t,r),p=Mc(t,l,c);return l==="center"?r==="left"?g+=c:r==="right"&&(g-=c):r==="left"?g-=Math.max(h,u)+n:r==="right"&&(g+=Math.max(d,f)+n),{x:Y(g,0,s.width-t.width),y:Y(p,0,s.height-t.height)}}function ai(i,t,e){let s=U(e.padding);return t==="center"?i.x+i.width/2:t==="right"?i.x+i.width-s.right:i.x+s.left}function no(i){return ct([],bt(i))}function wc(i,t,e){return pt(i,{tooltip:t,tooltipItems:e,type:"tooltip"})}function oo(i,t){let e=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return e?i.override(e):i}var Te=class extends it{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart||t._chart,this._chart=this.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){let t=this._cachedAnimations;if(t)return t;let e=this.chart,s=this.options.setContext(this.getContext()),n=s.enabled&&e.options.animation&&s.animations,o=new ci(this.chart,n);return n._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=wc(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,e){let{callbacks:s}=e,n=s.beforeTitle.apply(this,[t]),o=s.title.apply(this,[t]),a=s.afterTitle.apply(this,[t]),r=[];return r=ct(r,bt(n)),r=ct(r,bt(o)),r=ct(r,bt(a)),r}getBeforeBody(t,e){return no(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){let{callbacks:s}=e,n=[];return E(t,o=>{let a={before:[],lines:[],after:[]},r=oo(s,o);ct(a.before,bt(r.beforeLabel.call(this,o))),ct(a.lines,r.label.call(this,o)),ct(a.after,bt(r.afterLabel.call(this,o))),n.push(a)}),n}getAfterBody(t,e){return no(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){let{callbacks:s}=e,n=s.beforeFooter.apply(this,[t]),o=s.footer.apply(this,[t]),a=s.afterFooter.apply(this,[t]),r=[];return r=ct(r,bt(n)),r=ct(r,bt(o)),r=ct(r,bt(a)),r}_createItems(t){let e=this._active,s=this.chart.data,n=[],o=[],a=[],r=[],l,c;for(l=0,c=e.length;lt.filter(h,d,u,s))),t.itemSort&&(r=r.sort((h,d)=>t.itemSort(h,d,s))),E(r,h=>{let d=oo(t.callbacks,h);n.push(d.labelColor.call(this,h)),o.push(d.labelPointStyle.call(this,h)),a.push(d.labelTextColor.call(this,h))}),this.labelColors=n,this.labelPointStyles=o,this.labelTextColors=a,this.dataPoints=r,r}update(t,e){let s=this.options.setContext(this.getContext()),n=this._active,o,a=[];if(!n.length)this.opacity!==0&&(o={opacity:0});else{let r=Pe[s.position].call(this,n,this._eventPosition);a=this._createItems(s),this.title=this.getTitle(a,s),this.beforeBody=this.getBeforeBody(a,s),this.body=this.getBody(a,s),this.afterBody=this.getAfterBody(a,s),this.footer=this.getFooter(a,s);let l=this._size=eo(this,s),c=Object.assign({},r,l),h=io(this.chart,s,c),d=so(s,c,h,this.chart);this.xAlign=h.xAlign,this.yAlign=h.yAlign,o={opacity:1,x:d.x,y:d.y,width:l.width,height:l.height,caretX:r.x,caretY:r.y}}this._tooltipItems=a,this.$context=void 0,o&&this._resolveAnimations().update(this,o),t&&s.external&&s.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,s,n){let o=this.getCaretPosition(t,s,n);e.lineTo(o.x1,o.y1),e.lineTo(o.x2,o.y2),e.lineTo(o.x3,o.y3)}getCaretPosition(t,e,s){let{xAlign:n,yAlign:o}=this,{caretSize:a,cornerRadius:r}=s,{topLeft:l,topRight:c,bottomLeft:h,bottomRight:d}=kt(r),{x:u,y:f}=t,{width:g,height:p}=e,m,b,_,v,y,x;return o==="center"?(y=f+p/2,n==="left"?(m=u,b=m-a,v=y+a,x=y-a):(m=u+g,b=m+a,v=y-a,x=y+a),_=m):(n==="left"?b=u+Math.max(l,h)+a:n==="right"?b=u+g-Math.max(c,d)-a:b=this.caretX,o==="top"?(v=f,y=v-a,m=b-a,_=b+a):(v=f+p,y=v+a,m=b+a,_=b-a),x=v),{x1:m,x2:b,x3:_,y1:v,y2:y,y3:x}}drawTitle(t,e,s){let n=this.title,o=n.length,a,r,l;if(o){let c=Rt(s.rtl,this.x,this.width);for(t.x=ai(this,s.titleAlign,s),e.textAlign=c.textAlign(s.titleAlign),e.textBaseline="middle",a=$(s.titleFont),r=s.titleSpacing,e.fillStyle=s.titleColor,e.font=a.string,l=0;lv!==0)?(t.beginPath(),t.fillStyle=o.multiKeyBackground,qt(t,{x:m,y:p,w:c,h:l,radius:_}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),qt(t,{x:b,y:p+1,w:c-2,h:l-2,radius:_}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(m,p,c,l),t.strokeRect(m,p,c,l),t.fillStyle=a.backgroundColor,t.fillRect(b,p+1,c-2,l-2))}t.fillStyle=this.labelTextColors[s]}drawBody(t,e,s){let{body:n}=this,{bodySpacing:o,bodyAlign:a,displayColors:r,boxHeight:l,boxWidth:c,boxPadding:h}=s,d=$(s.bodyFont),u=d.lineHeight,f=0,g=Rt(s.rtl,this.x,this.width),p=function(S){e.fillText(S,g.x(t.x+f),t.y+u/2),t.y+=u+o},m=g.textAlign(a),b,_,v,y,x,M,w;for(e.textAlign=a,e.textBaseline="middle",e.font=d.string,t.x=ai(this,m,s),e.fillStyle=s.bodyColor,E(this.beforeBody,p),f=r&&m!=="right"?a==="center"?c/2+h:c+2+h:0,y=0,M=n.length;y0&&e.stroke()}_updateAnimationTarget(t){let e=this.chart,s=this.$animations,n=s&&s.x,o=s&&s.y;if(n||o){let a=Pe[t.position].call(this,this._active,this._eventPosition);if(!a)return;let r=this._size=eo(this,t),l=Object.assign({},a,this._size),c=io(e,t,l),h=so(t,l,c,e);(n._to!==h.x||o._to!==h.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=r.width,this.height=r.height,this.caretX=a.x,this.caretY=a.y,this._resolveAnimations().update(this,h))}}_willRender(){return!!this.opacity}draw(t){let e=this.options.setContext(this.getContext()),s=this.opacity;if(!s)return;this._updateAnimationTarget(e);let n={width:this.width,height:this.height},o={x:this.x,y:this.y};s=Math.abs(s)<.001?0:s;let a=U(e.padding),r=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&r&&(t.save(),t.globalAlpha=s,this.drawBackground(o,t,n,e),ts(t,e.textDirection),o.y+=a.top,this.drawTitle(o,t,e),this.drawBody(o,t,e),this.drawFooter(o,t,e),es(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){let s=this._active,n=t.map(({datasetIndex:r,index:l})=>{let c=this.chart.getDatasetMeta(r);if(!c)throw new Error("Cannot find a dataset at index "+r);return{datasetIndex:r,element:c.data[l],index:l}}),o=!me(s,n),a=this._positionChanged(n,e);(o||a)&&(this._active=n,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,s=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;let n=this.options,o=this._active||[],a=this._getActiveElements(t,o,e,s),r=this._positionChanged(a,t),l=e||!me(a,o)||r;return l&&(this._active=a,(n.enabled||n.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),l}_getActiveElements(t,e,s,n){let o=this.options;if(t.type==="mouseout")return[];if(!n)return e;let a=this.chart.getElementsAtEventForMode(t,o.mode,o,s);return o.reverse&&a.reverse(),a}_positionChanged(t,e){let{caretX:s,caretY:n,options:o}=this,a=Pe[o.position].call(this,t,e);return a!==!1&&(s!==a.x||n!==a.y)}};Te.positioners=Pe;var kc={id:"tooltip",_element:Te,positioners:Pe,afterInit(i,t,e){e&&(i.tooltip=new Te({chart:i,options:e}))},beforeUpdate(i,t,e){i.tooltip&&i.tooltip.initialize(e)},reset(i,t,e){i.tooltip&&i.tooltip.initialize(e)},afterDraw(i){let t=i.tooltip;if(t&&t._willRender()){let e={tooltip:t};if(i.notifyPlugins("beforeTooltipDraw",e)===!1)return;t.draw(i.ctx),i.notifyPlugins("afterTooltipDraw",e)}},afterEvent(i,t){if(i.tooltip){let e=t.replay;i.tooltip.handleEvent(t.event,e,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(i,t)=>t.bodyFont.size,boxWidth:(i,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:rt,title(i){if(i.length>0){let t=i[0],e=t.chart.data.labels,s=e?e.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(s>0&&t.dataIndexi!=="filter"&&i!=="itemSort"&&i!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},Sc=Object.freeze({__proto__:null,Decimation:$l,Filler:hc,Legend:fc,SubTitle:mc,Title:pc,Tooltip:kc}),Pc=(i,t,e,s)=>(typeof t=="string"?(e=i.push(t)-1,s.unshift({index:e,label:t})):isNaN(t)&&(e=null),e);function Cc(i,t,e,s){let n=i.indexOf(t);if(n===-1)return Pc(i,t,e,s);let o=i.lastIndexOf(t);return n!==o?e:n}var Dc=(i,t)=>i===null?null:Y(Math.round(i),0,t),ce=class extends Ft{constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){let e=this._addedLabels;if(e.length){let s=this.getLabels();for(let{index:n,label:o}of e)s[n]===o&&s.splice(n,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(T(t))return null;let s=this.getLabels();return e=isFinite(e)&&s[e]===t?e:Cc(s,t,C(e,t),this._addedLabels),Dc(e,s.length-1)}determineDataLimits(){let{minDefined:t,maxDefined:e}=this.getUserBounds(),{min:s,max:n}=this.getMinMax(!0);this.options.bounds==="ticks"&&(t||(s=0),e||(n=this.getLabels().length-1)),this.min=s,this.max=n}buildTicks(){let t=this.min,e=this.max,s=this.options.offset,n=[],o=this.getLabels();o=t===0&&e===o.length-1?o:o.slice(t,e+1),this._valueRange=Math.max(o.length-(s?0:1),1),this._startValue=this.min-(s?.5:0);for(let a=t;a<=e;a++)n.push({value:a});return n}getLabelForValue(t){let e=this.getLabels();return t>=0&&te.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}};ce.id="category";ce.defaults={ticks:{callback:ce.prototype.getLabelForValue}};function Oc(i,t){let e=[],{bounds:n,step:o,min:a,max:r,precision:l,count:c,maxTicks:h,maxDigits:d,includeBounds:u}=i,f=o||1,g=h-1,{min:p,max:m}=t,b=!T(a),_=!T(r),v=!T(c),y=(m-p)/(d+1),x=Ai((m-p)/g/f)*f,M,w,S,k;if(x<1e-14&&!b&&!_)return[{value:p},{value:m}];k=Math.ceil(m/x)-Math.floor(p/x),k>g&&(x=Ai(k*x/g/f)*f),T(l)||(M=Math.pow(10,l),x=Math.ceil(x*M)/M),n==="ticks"?(w=Math.floor(p/x)*x,S=Math.ceil(m/x)*x):(w=p,S=m),b&&_&&o&&Us((r-a)/o,x/1e3)?(k=Math.round(Math.min((r-a)/x,h)),x=(r-a)/k,w=a,S=r):v?(w=b?a:w,S=_?r:S,k=c-1,x=(S-w)/k):(k=(S-w)/x,Ut(k,Math.round(k),x/1e3)?k=Math.round(k):k=Math.ceil(k));let L=Math.max(Li(x),Li(w));M=Math.pow(10,T(l)?L:l),w=Math.round(w*M)/M,S=Math.round(S*M)/M;let R=0;for(b&&(u&&w!==a?(e.push({value:a}),wn=e?n:l,r=l=>o=s?o:l;if(t){let l=ot(n),c=ot(o);l<0&&c<0?r(0):l>0&&c>0&&a(0)}if(n===o){let l=1;(o>=Number.MAX_SAFE_INTEGER||n<=Number.MIN_SAFE_INTEGER)&&(l=Math.abs(o*.05)),r(o+l),t||a(n-l)}this.min=n,this.max=o}getTickLimit(){let t=this.options.ticks,{maxTicksLimit:e,stepSize:s}=t,n;return s?(n=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,n>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${n} ticks. Limiting to 1000.`),n=1e3)):(n=this.computeTickLimit(),e=e||11),e&&(n=Math.min(e,n)),n}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){let t=this.options,e=t.ticks,s=this.getTickLimit();s=Math.max(2,s);let n={maxTicks:s,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:e.includeBounds!==!1},o=this._range||this,a=Oc(n,o);return t.bounds==="ticks"&&Ti(a,this,"value"),t.reverse?(a.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),a}configure(){let t=this.ticks,e=this.min,s=this.max;if(super.configure(),this.options.offset&&t.length){let n=(s-e)/Math.max(t.length-1,1)/2;e-=n,s+=n}this._startValue=e,this._endValue=s,this._valueRange=s-e}getLabelForValue(t){return Zt(t,this.chart.options.locale,this.options.ticks.format)}},Le=class extends he{determineDataLimits(){let{min:t,max:e}=this.getMinMax(!0);this.min=W(t)?t:0,this.max=W(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){let t=this.isHorizontal(),e=t?this.width:this.height,s=nt(this.options.ticks.minRotation),n=(t?Math.sin(s):Math.cos(s))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,o.lineHeight/n))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}};Le.id="linear";Le.defaults={ticks:{callback:pi.formatters.numeric}};function ro(i){return i/Math.pow(10,Math.floor(tt(i)))===1}function Ac(i,t){let e=Math.floor(tt(t.max)),s=Math.ceil(t.max/Math.pow(10,e)),n=[],o=Q(i.min,Math.pow(10,Math.floor(tt(t.min)))),a=Math.floor(tt(o)),r=Math.floor(o/Math.pow(10,a)),l=a<0?Math.pow(10,Math.abs(a)):1;do n.push({value:o,major:ro(o)}),++r,r===10&&(r=1,++a,l=a>=0?1:l),o=Math.round(r*Math.pow(10,a)*l)/l;while(a0?s:null}determineDataLimits(){let{min:t,max:e}=this.getMinMax(!0);this.min=W(t)?Math.max(0,t):null,this.max=W(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this.handleTickRangeOptions()}handleTickRangeOptions(){let{minDefined:t,maxDefined:e}=this.getUserBounds(),s=this.min,n=this.max,o=l=>s=t?s:l,a=l=>n=e?n:l,r=(l,c)=>Math.pow(10,Math.floor(tt(l))+c);s===n&&(s<=0?(o(1),a(10)):(o(r(s,-1)),a(r(n,1)))),s<=0&&o(r(n,-1)),n<=0&&a(r(s,1)),this._zero&&this.min!==this._suggestedMin&&s===r(this.min,0)&&o(r(s,-1)),this.min=s,this.max=n}buildTicks(){let t=this.options,e={min:this._userMin,max:this._userMax},s=Ac(e,this);return t.bounds==="ticks"&&Ti(s,this,"value"),t.reverse?(s.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),s}getLabelForValue(t){return t===void 0?"0":Zt(t,this.chart.options.locale,this.options.ticks.format)}configure(){let t=this.min;super.configure(),this._startValue=tt(t),this._valueRange=tt(this.max)-tt(t)}getPixelForValue(t){return(t===void 0||t===0)&&(t=this.min),t===null||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(tt(t)-this._startValue)/this._valueRange)}getValueForPixel(t){let e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}};Re.id="logarithmic";Re.defaults={ticks:{callback:pi.formatters.logarithmic,major:{enabled:!0}}};function Ss(i){let t=i.ticks;if(t.display&&i.display){let e=U(t.backdropPadding);return C(t.font&&t.font.size,O.font.size)+e.height}return 0}function Tc(i,t,e){return e=I(e)?e:[e],{w:rn(i,t.string,e),h:e.length*t.lineHeight}}function lo(i,t,e,s,n){return i===s||i===n?{start:t-e/2,end:t+e/2}:in?{start:t-e,end:t}:{start:t,end:t+e}}function Lc(i){let t={l:i.left+i._padding.left,r:i.right-i._padding.right,t:i.top+i._padding.top,b:i.bottom-i._padding.bottom},e=Object.assign({},t),s=[],n=[],o=i._pointLabels.length,a=i.options.pointLabels,r=a.centerPointLabels?B/o:0;for(let l=0;lt.r&&(r=(s.end-t.r)/o,i.r=Math.max(i.r,t.r+r)),n.startt.b&&(l=(n.end-t.b)/a,i.b=Math.max(i.b,t.b+l))}function Ec(i,t,e){let s=[],n=i._pointLabels.length,o=i.options,a=Ss(o)/2,r=i.drawingArea,l=o.pointLabels.centerPointLabels?B/n:0;for(let c=0;c270||e<90)&&(i-=t),i}function Bc(i,t){let{ctx:e,options:{pointLabels:s}}=i;for(let n=t-1;n>=0;n--){let o=s.setContext(i.getPointLabelContext(n)),a=$(o.font),{x:r,y:l,textAlign:c,left:h,top:d,right:u,bottom:f}=i._pointLabelItems[n],{backdropColor:g}=o;if(!T(g)){let p=kt(o.borderRadius),m=U(o.backdropPadding);e.fillStyle=g;let b=h-m.left,_=d-m.top,v=u-h+m.width,y=f-d+m.height;Object.values(p).some(x=>x!==0)?(e.beginPath(),qt(e,{x:b,y:_,w:v,h:y,radius:p}),e.fill()):e.fillRect(b,_,v,y)}wt(e,i._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:c,textBaseline:"middle"})}}function Lo(i,t,e,s){let{ctx:n}=i;if(e)n.arc(i.xCenter,i.yCenter,t,0,F);else{let o=i.getPointPosition(0,t);n.moveTo(o.x,o.y);for(let a=1;a{let n=z(this.options.pointLabels.callback,[e,s],this);return n||n===0?n:""}).filter((e,s)=>this.chart.getDataVisibility(s))}fit(){let t=this.options;t.display&&t.pointLabels.display?Lc(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,s,n){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((s-n)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,s,n))}getIndexAngle(t){let e=F/(this._pointLabels.length||1),s=this.options.startAngle||0;return G(t*e+nt(s))}getDistanceFromCenterForValue(t){if(T(t))return NaN;let e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(T(t))return NaN;let e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){let e=this._pointLabels||[];if(t>=0&&t{if(h!==0){r=this.getDistanceFromCenterForValue(c.value);let d=n.setContext(this.getContext(h-1));Vc(this,d,r,o)}}),s.display){for(t.save(),a=o-1;a>=0;a--){let c=s.setContext(this.getPointLabelContext(a)),{color:h,lineWidth:d}=c;!d||!h||(t.lineWidth=d,t.strokeStyle=h,t.setLineDash(c.borderDash),t.lineDashOffset=c.borderDashOffset,r=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){let t=this.ctx,e=this.options,s=e.ticks;if(!s.display)return;let n=this.getIndexAngle(0),o,a;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(n),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach((r,l)=>{if(l===0&&!e.reverse)return;let c=s.setContext(this.getContext(l)),h=$(c.font);if(o=this.getDistanceFromCenterForValue(this.ticks[l].value),c.showLabelBackdrop){t.font=h.string,a=t.measureText(r.label).width,t.fillStyle=c.backdropColor;let d=U(c.backdropPadding);t.fillRect(-a/2-d.left,-o-h.size/2-d.top,a+d.width,h.size+d.height)}wt(t,r.label,0,-o,h,{color:c.color})}),t.restore()}drawTitle(){}};zt.id="radialLinear";zt.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:pi.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(i){return i},padding:5,centerPointLabels:!1}};zt.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};zt.descriptors={angleLines:{_fallback:"grid"}};var mi={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Z=Object.keys(mi);function Nc(i,t){return i-t}function co(i,t){if(T(t))return null;let e=i._adapter,{parser:s,round:n,isoWeekday:o}=i._parseOpts,a=t;return typeof s=="function"&&(a=s(a)),W(a)||(a=typeof s=="string"?e.parse(a,s):e.parse(a)),a===null?null:(n&&(a=n==="week"&&(Lt(o)||o===!0)?e.startOf(a,"isoWeek",o):e.startOf(a,n)),+a)}function ho(i,t,e,s){let n=Z.length;for(let o=Z.indexOf(i);o=Z.indexOf(e);o--){let a=Z[o];if(mi[a].common&&i._adapter.diff(n,s,a)>=t-1)return a}return Z[e?Z.indexOf(e):0]}function jc(i){for(let t=Z.indexOf(i)+1,e=Z.length;t=t?e[s]:e[n];i[o]=!0}}function $c(i,t,e,s){let n=i._adapter,o=+n.startOf(t[0].value,s),a=t[t.length-1].value,r,l;for(r=o;r<=a;r=+n.add(r,1,s))l=e[r],l>=0&&(t[l].major=!0);return t}function fo(i,t,e){let s=[],n={},o=t.length,a,r;for(a=0;a+t.value))}initOffsets(t){let e=0,s=0,n,o;this.options.offset&&t.length&&(n=this.getDecimalForValue(t[0]),t.length===1?e=1-n:e=(this.getDecimalForValue(t[1])-n)/2,o=this.getDecimalForValue(t[t.length-1]),t.length===1?s=o:s=(o-this.getDecimalForValue(t[t.length-2]))/2);let a=t.length<3?.5:.25;e=Y(e,0,a),s=Y(s,0,a),this._offsets={start:e,end:s,factor:1/(e+1+s)}}_generate(){let t=this._adapter,e=this.min,s=this.max,n=this.options,o=n.time,a=o.unit||ho(o.minUnit,e,s,this._getLabelCapacity(e)),r=C(o.stepSize,1),l=a==="week"?o.isoWeekday:!1,c=Lt(l)||l===!0,h={},d=e,u,f;if(c&&(d=+t.startOf(d,"isoWeek",l)),d=+t.startOf(d,c?"day":a),t.diff(s,e,a)>1e5*r)throw new Error(e+" and "+s+" are too far apart with stepSize of "+r+" "+a);let g=n.ticks.source==="data"&&this.getDataTimestamps();for(u=d,f=0;up-m).map(p=>+p)}getLabelForValue(t){let e=this._adapter,s=this.options.time;return s.tooltipFormat?e.format(t,s.tooltipFormat):e.format(t,s.displayFormats.datetime)}_tickFormatFunction(t,e,s,n){let o=this.options,a=o.time.displayFormats,r=this._unit,l=this._majorUnit,c=r&&a[r],h=l&&a[l],d=s[e],u=l&&h&&d&&d.major,f=this._adapter.format(t,n||(u?h:c)),g=o.ticks.callback;return g?z(g,[f,e,s],this):f}generateTickLabels(t){let e,s,n;for(e=0,s=t.length;e0?r:1}getDataTimestamps(){let t=this._cache.data||[],e,s;if(t.length)return t;let n=this.getMatchingVisibleMetas();if(this._normalized&&n.length)return this._cache.data=n[0].controller.getAllParsedValues(this);for(e=0,s=n.length;e=i[s].pos&&t<=i[n].pos&&({lo:s,hi:n}=at(i,"pos",t)),{pos:o,time:r}=i[s],{pos:a,time:l}=i[n]):(t>=i[s].time&&t<=i[n].time&&({lo:s,hi:n}=at(i,"time",t)),{time:o,pos:r}=i[s],{time:a,pos:l}=i[n]);let c=a-o;return c?r+(l-r)*(t-o)/c:r}var Ee=class extends Bt{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){let t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=ri(e,this.min),this._tableRange=ri(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){let{min:e,max:s}=this,n=[],o=[],a,r,l,c,h;for(a=0,r=t.length;a=e&&c<=s&&n.push(c);if(n.length<2)return[{time:e,pos:0},{time:s,pos:1}];for(a=0,r=n.length;a{Alpine.store("theme");let s=this.getChart();s&&s.destroy(),this.initChart()}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Alpine.store("theme")==="system"&&this.$nextTick(()=>{let s=this.getChart();s&&s.destroy(),this.initChart()})})},initChart:function(){if(!(!this.$refs.canvas||!this.$refs.backgroundColorElement||!this.$refs.borderColorElement))return new Cs(this.$refs.canvas,{type:"line",data:{labels:t,datasets:[{data:e,borderWidth:2,fill:"start",tension:.5,backgroundColor:getComputedStyle(this.$refs.backgroundColorElement).color,borderColor:getComputedStyle(this.$refs.borderColorElement).color}]},options:{animation:{duration:0},elements:{point:{radius:0}},maintainAspectRatio:!1,plugins:{legend:{display:!1}},scales:{x:{display:!1},y:{display:!1}},tooltips:{enabled:!1}}})},getChart:function(){return this.$refs.canvas?Cs.getChart(this.$refs.canvas):null}}}export{Xc as default}; -/*! Bundled license information: - -chart.js/dist/chunks/helpers.segment.mjs: -chart.js/dist/chart.mjs: - (*! - * Chart.js v3.9.1 - * https://www.chartjs.org - * (c) 2022 Chart.js Contributors - * Released under the MIT License - *) - -chart.js/dist/chunks/helpers.segment.mjs: - (*! - * @kurkle/color v0.2.1 - * https://github.com/kurkle/color#readme - * (c) 2022 Jukka Kurkela - * Released under the MIT License - *) -*/ diff --git a/resources/css/app.css b/resources/css/app.css index 7d769f1..abfc015 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,18 +1,41 @@ @import 'tailwindcss'; @theme { - /* Primary color palette - green theme */ - --color-primary-50: oklch(94.77% 0.075 181.53); - --color-primary-100: oklch(89.5% 0.159 181.34); - --color-primary-200: oklch(80.03% 0.142 181.59); - --color-primary-300: oklch(71.68% 0.127 181.62); - --color-primary-400: oklch(62.48% 0.111 181.51); - --color-primary-500: oklch(53.86% 0.096 181.61); - --color-primary-600: oklch(45.51% 0.081 181.98); - --color-primary-700: oklch(38.07% 0.068 181.7); - --color-primary-800: oklch(29.53% 0.053 180.86); - --color-primary-900: oklch(21.87% 0.039 183.18); - --color-primary-950: oklch(17.17% 0.031 183.02); + /* Primary ramp. Tailwind's own `gray` verbatim, a cool neutral, so an + installation that has not picked an accent colour looks deliberate + rather than like somebody else's brand. Setting one in + /manage > Settings derives a ramp from it and overrides these at + runtime; see App\Services\BrandingService::paletteVariables. */ + --color-primary-50: oklch(98.5% 0.002 247.839); + --color-primary-100: oklch(96.7% 0.003 264.542); + --color-primary-200: oklch(92.8% 0.006 264.531); + --color-primary-300: oklch(87.2% 0.01 258.338); + --color-primary-400: oklch(70.7% 0.022 261.325); + --color-primary-500: oklch(55.1% 0.027 264.364); + --color-primary-600: oklch(44.6% 0.03 256.802); + --color-primary-700: oklch(37.3% 0.034 259.733); + --color-primary-800: oklch(27.8% 0.033 256.848); + --color-primary-900: oklch(21% 0.034 264.665); + --color-primary-950: oklch(13% 0.028 261.692); + + /* Motion. One set of curves and durations for the whole app, so a tile, a + drawer and a status page all decelerate the same way. `--default-*` + override Tailwind's own, which means a bare `transition-colors` is + already on the house curve without the utility naming it. + There is no `--duration-*` theme namespace in Tailwind, so the durations + are plain custom properties, used as `duration-(--dur-base)`. */ + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in-quart: cubic-bezier(0.5, 0, 0.75, 0); + --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1); + + --dur-fast: 120ms; + --dur-base: 220ms; + --dur-slow: 420ms; + --dur-slower: 700ms; + + --default-transition-duration: 180ms; + --default-transition-timing-function: var(--ease-out-quart); --color-background: var(--background); --color-foreground: var(--foreground); @@ -22,6 +45,29 @@ --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); + + /* Content width cap for the public site (`max-w-page`). The tile grid is + auto-fill/210px, so an uncapped page puts ~15 tiles on one row at 3440px and + the grid stops reading as rows at all. 110rem holds it at 8. */ + --container-page: 110rem; + + /* Manage panel (Control Room). Surfaces stack from deepest to raised; state + colours are the only colour in the UI and are resolved from a tone name the + server sends, never derived on the client. See docs/admin/rebuild-plan.md 1.3. */ + --color-surface-0: var(--surface-0); + --color-surface-1: var(--surface-1); + --color-surface-2: var(--surface-2); + --color-surface-3: var(--surface-3); + --color-fg-1: var(--fg-1); + --color-fg-2: var(--fg-2); + --color-fg-3: var(--fg-3); + --color-hairline: var(--hairline); + --color-state-live: var(--state-live); + --color-state-ok: var(--state-ok); + --color-state-warn: var(--state-warn); + --color-state-idle: var(--state-idle); + --color-state-danger: var(--state-danger); + --color-state-info: var(--state-info); } :root { @@ -30,6 +76,25 @@ --border: oklch(0.922 0 0); --ring: oklch(0.708 0 0); --radius: 0.625rem; + + /* Manage panel surfaces are dark at the root, not behind a .dark class: this is a + control room, and dark is the only mode it ships. Deepest to raised, each step a + small lightness bump so a card reads against the page without a border doing all + the work. The faint 200-hue chroma keeps them from looking like dead grey. */ + --surface-0: oklch(0.16 0.008 200); + --surface-1: oklch(0.19 0.009 200); + --surface-2: oklch(0.22 0.01 200); + --surface-3: oklch(0.26 0.012 200); + --fg-1: oklch(0.97 0.005 200); + --fg-2: oklch(0.75 0.01 200); + --fg-3: oklch(0.58 0.012 200); + --hairline: oklch(0.28 0.012 200); + --state-live: oklch(0.75 0.12 181.61); + --state-ok: oklch(0.75 0.16 150); + --state-warn: oklch(0.8 0.15 78); + --state-idle: oklch(0.58 0.012 200); + --state-danger: oklch(0.68 0.19 25); + --state-info: oklch(0.72 0.13 245); } .dark { @@ -39,29 +104,44 @@ --ring: oklch(0.439 0 0); } +.manage-root { + color-scheme: dark; +} + +/* Kept so a light manage theme is a class away if it is ever wanted. Nothing sets it. */ +.manage-light { + --surface-0: oklch(0.985 0 0); + --surface-1: oklch(1 0 0); + --surface-2: oklch(0.97 0.003 200); + --surface-3: oklch(0.94 0.004 200); + --fg-1: oklch(0.2 0 0); + --fg-2: oklch(0.45 0 0); + --fg-3: oklch(0.6 0 0); + --hairline: oklch(0.9 0.003 200); + --state-live: oklch(0.5 0.096 181.61); + --state-ok: oklch(0.55 0.15 150); + --state-warn: oklch(0.62 0.15 75); + --state-idle: oklch(0.6 0 0); + --state-danger: oklch(0.55 0.2 25); + --state-info: oklch(0.55 0.13 245); +} + @layer base { * { border-color: var(--border); } + /* The public site is dark-only, so the page behind it is dark too. Without + this the white token background flashes through on overscroll and above + the sticky nav. The manage panel paints its own full-height surface on + top, so it is unaffected. */ body { - background-color: var(--background); + background-color: var(--color-primary-950); color: var(--foreground); } } /* Animation keyframes */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - @keyframes pulse { 0%, 100% { opacity: 1; @@ -86,9 +166,49 @@ } } -/* Page transition styles */ -.page * { - @apply transition-colors; +/* No universal transition here on purpose: `.page * { transition-colors }` made + every element on the page animate, which showed up as dropped frames while + scrolling. Components declare their own transitions on the few properties + that actually change. */ + +/* Tile grids (`.stream-grid` on browse and the archive). Shared here rather than + per page because all three grids use the same . + Enter is staggered off an inline `--stagger` the page sets from the loop index, + so a filter switch deals the tiles out instead of flashing the whole set in. + There is deliberately no leave transition: a leaving tile that holds its grid + slot pushes the incoming set down, and dropping it immediately is also what + lets `.tile-move` measure honestly, so a show that goes live slides to its new + position instead of jumping. */ +.tile-enter-active { + transition: + opacity var(--dur-slow) var(--ease-out-expo), + transform var(--dur-slow) var(--ease-out-expo); + transition-delay: calc(var(--stagger, 0) * 22ms); +} + +.tile-enter-from { + opacity: 0; + transform: translateY(12px); +} + +.tile-move { + transition: transform var(--dur-base) var(--ease-out-quart); +} + +/* One gate for every animation in the app. Components no longer carry their own + media query, and motion added later cannot ship without honouring the setting. + Durations collapse to ~0 rather than to `none` so Vue's still gets + its transitionend/animationend and never strands an element mid-enter. */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + transition-delay: 0ms !important; + scroll-behavior: auto !important; + } } /* Theater mode styles */ diff --git a/resources/js/Components/Chat/ChatBadges.vue b/resources/js/Components/Chat/ChatBadges.vue new file mode 100644 index 0000000..ec2c962 --- /dev/null +++ b/resources/js/Components/Chat/ChatBadges.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/Components/Chat/ChatInput.vue b/resources/js/Components/Chat/ChatInput.vue new file mode 100644 index 0000000..e56ba11 --- /dev/null +++ b/resources/js/Components/Chat/ChatInput.vue @@ -0,0 +1,306 @@ + + +