diff --git a/README.md b/README.md index 5499da3e0a..a137fb1b40 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DOMjudge [![Coverity Scan Status](https://img.shields.io/coverity/scan/671.svg)](https://scan.coverity.com/projects/domjudge) [![CodeQL alerts](https://github.com/DOMjudge/domjudge/actions/workflows/codeql-analysis.yml/badge.svg?branch=main&event=push)](https://github.com/DOMjudge/domjudge/actions/workflows/codeql-analysis.yml) -This is the Programming Contest Jury System "DOMjudge" version 8.3.0 +This is the Programming Contest Jury System "DOMjudge" version 8.3.0-0.4.2 DOMjudge is a system for running a programming contest, like the ICPC regional and world championship programming contests. diff --git a/composer.json b/composer.json index 33d952e20e..7ddc97eab2 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,11 @@ "homepage": "https://www.domjudge.org", "license": "GPL-2.0+", "repositories": [ - { + { + "type": "vcs", + "url": "https://github.com/matous-volf/editorjs-php" + }, + { "type": "package", "package": { "name": "fortawesome/font-awesome", @@ -68,6 +72,7 @@ "ircmaxell/password-compat": "*", "jms/serializer-bundle": "^5.2", "league/commonmark": "^2.3", + "liip/imagine-bundle": "^2.11", "mbostock/d3": "^3.5", "nelmio/api-doc-bundle": "^4.11", "nelmio/cors-bundle": "^2.4", @@ -79,6 +84,7 @@ "riverline/multipart-parser": "^2.1", "select2/select2": "4.*", "sentry/sentry-symfony": "^4.5", + "setono/editorjs-php": "0.1.x-dev", "symfony/asset": "6.4.*", "symfony/browser-kit": "6.4.*", "symfony/console": "6.4.*", diff --git a/composer.lock b/composer.lock index da5641cb62..b236ad8adb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d3b090ea4b3b394978b9c484208da019", + "content-hash": "c0c9896c4525611cae4f9383ba61b043", "packages": [ { "name": "apalfrey/select2-bootstrap-5-theme", @@ -1563,16 +1563,16 @@ }, { "name": "doctrine/lexer", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", "shasum": "" }, "require": { @@ -1580,11 +1580,11 @@ "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", + "doctrine/coding-standard": "^9 || ^12", "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" + "vimeo/psalm": "^4.11 || ^5.21" }, "type": "library", "autoload": { @@ -1621,7 +1621,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" + "source": "https://github.com/doctrine/lexer/tree/2.1.1" }, "funding": [ { @@ -1637,7 +1637,7 @@ "type": "tidelift" } ], - "time": "2022-12-14T08:49:07+00:00" + "time": "2024-02-05T11:35:39+00:00" }, { "name": "doctrine/migrations", @@ -2372,6 +2372,68 @@ }, "time": "2021-07-21T13:50:14+00:00" }, + { + "name": "imagine/imagine", + "version": "1.3.5", + "source": { + "type": "git", + "url": "https://github.com/php-imagine/Imagine.git", + "reference": "7151d553edec4dc2bbac60419f7a74ff34700e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/7151d553edec4dc2bbac60419f7a74ff34700e7f", + "reference": "7151d553edec4dc2bbac60419f7a74ff34700e7f", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3" + }, + "suggest": { + "ext-exif": "to read EXIF metadata", + "ext-gd": "to use the GD implementation", + "ext-gmagick": "to use the Gmagick implementation", + "ext-imagick": "to use the Imagick implementation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Imagine\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bulat Shakirzyanov", + "email": "mallluhuct@gmail.com", + "homepage": "http://avalanche123.com" + } + ], + "description": "Image processing for PHP 5.3", + "homepage": "http://imagine.readthedocs.org/", + "keywords": [ + "drawing", + "graphics", + "image manipulation", + "image processing" + ], + "support": { + "issues": "https://github.com/php-imagine/Imagine/issues", + "source": "https://github.com/php-imagine/Imagine/tree/1.3.5" + }, + "time": "2023-06-07T14:49:52+00:00" + }, { "name": "ircmaxell/password-compat", "version": "v1.0.4", @@ -3087,6 +3149,112 @@ ], "time": "2023-11-24T15:40:42+00:00" }, + { + "name": "liip/imagine-bundle", + "version": "2.13.1", + "source": { + "type": "git", + "url": "https://github.com/liip/LiipImagineBundle.git", + "reference": "f3b67426800a6a86840de7447bb552c61a79f4b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/f3b67426800a6a86840de7447bb552c61a79f4b6", + "reference": "f3b67426800a6a86840de7447bb552c61a79f4b6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "imagine/imagine": "^1.3.2", + "php": "^7.2|^8.0", + "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/finder": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0|^7.0", + "symfony/mime": "^4.4|^5.3|^6.0|^7.0", + "symfony/options-resolver": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/process": "^3.4|^4.4|^5.3|^6.0|^7.0", + "twig/twig": "^1.44|^2.9|^3.0" + }, + "require-dev": { + "amazonwebservices/aws-sdk-for-php": "^1.0", + "aws/aws-sdk-php": "^2.4|^3.0", + "doctrine/cache": "^1.11|^2.0", + "doctrine/persistence": "^1.3|^2.0", + "enqueue/enqueue-bundle": "^0.9|^0.10", + "ext-gd": "*", + "league/flysystem": "^1.0|^2.0|^3.0", + "phpstan/phpstan": "^1.10.0", + "psr/cache": "^1.0|^2.0|^3.0", + "psr/log": "^1.0", + "symfony/asset": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/cache": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/console": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/dependency-injection": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/form": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/messenger": "^4.4|^5.3|^6.0|^7.0", + "symfony/phpunit-bridge": "^7.0.2", + "symfony/templating": "^3.4|^4.4|^5.3|^6.0", + "symfony/validator": "^3.4|^4.4|^5.3|^6.0|^7.0", + "symfony/yaml": "^3.4|^4.4|^5.3|^6.0|^7.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "required for mongodb components", + "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver", + "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver", + "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components", + "enqueue/enqueue-bundle": "^0.9 add if you like to process images in background", + "ext-exif": "required to read EXIF metadata from images", + "ext-gd": "required to use gd driver", + "ext-gmagick": "required to use gmagick driver", + "ext-imagick": "required to use imagick driver", + "ext-json": "required to read JSON manifest versioning", + "ext-mongodb": "required for mongodb components", + "league/flysystem": "required to use FlySystem data loader or cache resolver", + "monolog/monolog": "A psr/log compatible logger is required to enable logging", + "rokka/imagine-vips": "required to use 'vips' driver", + "symfony/asset": "If you want to use asset versioning", + "symfony/messenger": "If you like to process images in background", + "symfony/templating": "required to use deprecated Templating component instead of Twig" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Liip\\ImagineBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Liip and other contributors", + "homepage": "https://github.com/liip/LiipImagineBundle/contributors" + } + ], + "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", + "homepage": "https://www.liip.ch", + "keywords": [ + "bundle", + "image", + "imagine", + "liip", + "manipulation", + "photos", + "pictures", + "symfony", + "transformation" + ], + "support": { + "issues": "https://github.com/liip/LiipImagineBundle/issues", + "source": "https://github.com/liip/LiipImagineBundle/tree/2.13.1" + }, + "time": "2024-07-03T13:28:14+00:00" + }, { "name": "masterminds/html5", "version": "2.8.1", @@ -5337,6 +5505,83 @@ ], "time": "2024-01-11T14:55:45+00:00" }, + { + "name": "setono/editorjs-php", + "version": "0.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/matous-volf/editorjs-php.git", + "reference": "29bdb51580e238f700ff16a3a77ca0fcb6a40b1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matous-volf/editorjs-php/zipball/29bdb51580e238f700ff16a3a77ca0fcb6a40b1a", + "reference": "29bdb51580e238f700ff16a3a77ca0fcb6a40b1a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.4", + "symfony/options-resolver": "^4.4 || ^5.0 || ^6.0", + "webmozart/assert": "^1.10" + }, + "require-dev": { + "infection/infection": "^0.26", + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.16.1", + "setono/code-quality-pack": "^2.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Setono\\EditorJS\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Setono\\EditorJS\\": "tests/" + } + }, + "scripts": { + "analyse": [ + "psalm" + ], + "check-style": [ + "ecs check" + ], + "fix-style": [ + "ecs check --fix" + ], + "phpunit": [ + "phpunit" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joachim Løvgaard", + "email": "joachim@loevgaard.dk" + } + ], + "description": "PHP library for handling data from the EditorJS", + "support": { + "source": "https://github.com/matous-volf/editorjs-php/tree/seminar" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/Setono" + } + ], + "time": "2023-07-16T11:59:53+00:00" + }, { "name": "symfony/asset", "version": "v6.4.3", @@ -8639,6 +8884,67 @@ ], "time": "2023-08-16T06:22:46+00:00" }, + { + "name": "symfony/process", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "31642b0818bfcff85930344ef93193f8c607e0a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/31642b0818bfcff85930344ef93193f8c607e0a3", + "reference": "31642b0818bfcff85930344ef93193f8c607e0a3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, { "name": "symfony/property-access", "version": "v6.4.3", @@ -13117,67 +13423,6 @@ ], "time": "2024-01-23T14:51:35+00:00" }, - { - "name": "symfony/process", - "version": "v6.4.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/31642b0818bfcff85930344ef93193f8c607e0a3", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "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": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v6.4.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-01-23T14:51:35+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.2", @@ -13233,7 +13478,8 @@ "minimum-stability": "stable", "stability-flags": { "datatables.net/datatables.net": 20, - "datatables.net/datatables.net-bs5": 20 + "datatables.net/datatables.net-bs5": 20, + "setono/editorjs-php": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/docker-compose.yml b/docker-compose.yml index 181596b96c..7e354308b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: mariadb: - image: docker.io/mariadb + image: docker.io/mariadb:11.1.2 environment: - MYSQL_ROOT_PASSWORD=domjudge - MYSQL_USER=domjudge @@ -19,7 +19,7 @@ services: volumes: - /var/lib/mysql domjudge: - image: docker.io/domjudge/domjudge-contributor + image: deltacs/seminar-domjudge-contributor:8.3.0 hostname: domjudge-contributor volumes: - /sys/fs/cgroup:/sys/fs/cgroup diff --git a/etc/db-config.yaml b/etc/db-config.yaml index 9c6b0866b4..15f1fa276c 100644 --- a/etc/db-config.yaml +++ b/etc/db-config.yaml @@ -125,6 +125,11 @@ 3: Only on request regex: /^[123]$/ error_message: A value between 1 and 3 is required. + - name: partial_points_scoring + type: bool + default_value: false + public: false + description: Use partial scoring? Enabling also requires setting "Lazy eval results" to "Full judging". If enabled, when scoring submissions, uses the testcase group percentages, otherwise ignores them and scores either 0 or full points. - name: judgehost_warning type: int default_value: 30 @@ -323,6 +328,21 @@ default_value: true public: true description: If disabled, no ranking information is shown to contestants. + - name: blog_posts_per_page + type: int + default_value: 10 + public: true + description: Maximum number of public blog posts on a single page. + - name: homepage_blog_post_count + type: int + default_value: 2 + public: true + description: Number of blog posts displayed on the homepage. + - name: welcome_message_body + type: textarea + default_value: "Welcome to the contest" + public: true + description: The body of the welcome message shown to teams after first visiting a contest. - category: Authentication description: Options related to authentication. items: @@ -340,6 +360,21 @@ default_value: false public: false description: Enable to skip the login page when using IP authentication. + - name: allow_create_affiliation_during_registration + type: bool + default_value: false + public: true + description: Allow creation of a new affiliation during self-registration? + - name: allow_registration_without_affiliation + type: bool + default_value: false + public: true + description: Allow self-registration without selecting an affiliation? + - name: allow_custom_team_name_registration + type: bool + default_value: false + public: true + description: Allow specifying a custom team name during self-registration? - category: External systems description: Miscellaneous configuration options. items: @@ -403,3 +438,18 @@ default_value: false public: false description: Is the Adminer Database Editor enabled? + - name: google_analytics_tracking_id + type: string + default_value: "" + public: false + description: The tracking ID for Google Analytics. + - name: hotjar_tracking_id + type: string + default_value: "" + public: false + description: The tracking ID for Hotjar. + - name: discord_invite_url + type: string + default_value: "" + public: true + description: The invite link to the contest Discord server. diff --git a/sql/files/defaultdata/csharp-dotnet/build b/sql/files/defaultdata/csharp-dotnet/build new file mode 100755 index 0000000000..3ad1806766 --- /dev/null +++ b/sql/files/defaultdata/csharp-dotnet/build @@ -0,0 +1,2 @@ +#!/bin/sh +# nothing to compile diff --git a/sql/files/defaultdata/csharp-dotnet/run b/sql/files/defaultdata/csharp-dotnet/run new file mode 100755 index 0000000000..006ec1170d --- /dev/null +++ b/sql/files/defaultdata/csharp-dotnet/run @@ -0,0 +1,81 @@ +#!/bin/sh + +# C# (dotnet) compile and run wrapper-script for .NET. +# This script compiles with the .NET C# compiler and generates +# a shell script to run it with the .NET runtime later. +# +# This script requires the .NET SDK to be installed. +# +# Inspired by +# - https://stackoverflow.com/questions/46065777/is-it-possible-to-compile-a-single-c-sharp-code-file-with-the-net-core-roslyn-c + +# https://github.com/dotnet/sdk/issues/31457 +export DOTNET_EnableWriteXorExecute=0 +export TMPDIR="$PWD"/tmp + +mkdir "$TMPDIR" + +DEST="$1" ; shift +MEMLIMIT="$1" ; shift +MAINSOURCE="$1" + +DESTCLI="${DEST}.exe" +DESTDIR=$(dirname "$DEST") + +SOURCEDIR="${MAINSOURCE%/*}" +[ "$SOURCEDIR" = "$MAINSOURCE" ] && SOURCEDIR='.' + +DOTNET_DIR=$(dirname $(dirname $(dotnet --info | grep "Base Path" | cut -d' ' -f 6))) +CSC_PATH=$(find "$DOTNET_DIR" -name csc.dll -print | sort | tail -n1) +REFS_PATH=$(find "$DOTNET_DIR" -path "*packs/Microsoft.NETCore.App.Ref/*/ref" -print | sort -V | tail -n1) +REFS=$(find "$REFS_PATH" -path '*.dll' -printf "-r:\"%p\" ") + +# Compile using the .NET CLI: +# You can add -define:ONLINE_JUDGE,DOMJUDGE if you need specific compilation symbols. +dotnet "$CSC_PATH" -nologo $REFS -o+ -out:"$DESTCLI" "$@" +EXITCODE=$? +[ "$EXITCODE" -ne 0 ] && exit $EXITCODE + +# Check for output file: +if [ ! -f "$DESTCLI" ]; then + echo "Error: compiled file '$DESTCLI' not found." + exit 1 +fi + +DOTNET_CSC_RUNTIME_CONFIG="$DESTDIR"/csc-console-app.runtimeconfig.json +DOTNET_RUNTIME_VERSION=$(dotnet --list-runtimes | grep 'Microsoft.NETCore.App' | tail -1 | cut -d' ' -f2) + +if [ -f "$DOTNET_CSC_RUNTIME_CONFIG" ]; then + rm "$DOTNET_CSC_RUNTIME_CONFIG" +fi + +cat << EOF > $DOTNET_CSC_RUNTIME_CONFIG +{ + "runtimeOptions": { + "framework": { + "name": "Microsoft.NETCore.App", + "version": "$DOTNET_RUNTIME_VERSION" + } + } +} +EOF + +# Write executing script, executes dotnet compiled program: +cat > "$DEST" < phpstan/phpstan ### phpstan.neon ###< phpstan/phpstan ### + +###> liip/imagine-bundle ### +/public/media/cache/ +###< liip/imagine-bundle ### diff --git a/webapp/config/bundles.php b/webapp/config/bundles.php index 2d4ec46fc9..706dd8d7c4 100644 --- a/webapp/config/bundles.php +++ b/webapp/config/bundles.php @@ -18,4 +18,5 @@ Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Sentry\SentryBundle\SentryBundle::class => ['prod' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], + Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], ]; diff --git a/webapp/config/packages/liip_imagine.yaml b/webapp/config/packages/liip_imagine.yaml new file mode 100644 index 0000000000..f2b2344212 --- /dev/null +++ b/webapp/config/packages/liip_imagine.yaml @@ -0,0 +1,17 @@ +# Documentation on how to configure the bundle can be found at: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html +liip_imagine: + # valid drivers options include "gd" or "gmagick" or "imagick" + driver: "gd" + + resolvers: + default: + web_path: ~ + + webp: + generate: true + + filter_sets: + cache: ~ + + default_filter_set_settings: + format: webp diff --git a/webapp/config/packages/security.yaml b/webapp/config/packages/security.yaml index 90be4c137e..cd814d503b 100644 --- a/webapp/config/packages/security.yaml +++ b/webapp/config/packages/security.yaml @@ -26,7 +26,7 @@ security: # disables authentication for assets and the profiler, adapt it according to your needs dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ + pattern: ^/(_(profiler|wdt)|css|images|media|js)/ security: false # SEE NOTE ABOVE IF CHANGING ANYTHING IN THIS SECTION @@ -71,7 +71,7 @@ security: logout: path: logout - target: /public + target: public_index access_control: - { path: ^/$, roles: PUBLIC_ACCESS } diff --git a/webapp/config/routes/liip_imagine.yaml b/webapp/config/routes/liip_imagine.yaml new file mode 100644 index 0000000000..201cbd5d47 --- /dev/null +++ b/webapp/config/routes/liip_imagine.yaml @@ -0,0 +1,2 @@ +_liip_imagine: + resource: "@LiipImagineBundle/Resources/config/routing.yaml" diff --git a/webapp/config/services.yaml b/webapp/config/services.yaml index 205c5ce8f7..45021d82ea 100644 --- a/webapp/config/services.yaml +++ b/webapp/config/services.yaml @@ -11,6 +11,19 @@ parameters: # Enable this to support removing time intervals from the contest. # This code is rarely tested and we discourage using it. removed_intervals: false + image_directory: '%kernel.project_dir%/public/media/images' + # The prefixes of requests, which should be contest-aware + # - This will ensure, that cid parameter is always added to the urls + # - - and if missing, sets the current contest id or the default contest id + contest_id_urls_prefixes: [ + # public actions + '/public/scoreboard', + '/public/problems', + # team actions + '/team/scoreboard', + '/team/problems', + '/team/editor', + ] services: # default configuration for services in *this* file @@ -27,3 +40,18 @@ services: - '../src/Entity' - '../src/Migrations' - '../src/Kernel.php' + + App\EventSubscriber\ContestIdSubscriber: + arguments: + $contestIdURLsPrefixes: '%contest_id_urls_prefixes%' + + App\EventListener\AddContentSecurityPolicyListener: + arguments: + $cspConfig: + defaultSrc: "'self'" + styleSrc: "'self' 'unsafe-inline'" + scriptSrc: "'self' 'unsafe-inline' https://*.googletagmanager.com https://*.hotjar.com https://*.hotjar.io wss://*.hotjar.com" + imgSrc: "'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com data: https://*.hotjar.com https://*.hotjar.io wss://*.hotjar.com" + connectSrc: "'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://*.hotjar.com https://*.hotjar.io wss://*.hotjar.com" + fontSrc: "'self' https://*.hotjar.com" + frameAncestors: "'self'" diff --git a/webapp/migrations/Version20230707160739.php b/webapp/migrations/Version20230707160739.php new file mode 100644 index 0000000000..6b085e3028 --- /dev/null +++ b/webapp/migrations/Version20230707160739.php @@ -0,0 +1,47 @@ +addSql(<<addSql('DROP TABLE blog_post'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/migrations/Version20230918185221.php b/webapp/migrations/Version20230918185221.php new file mode 100644 index 0000000000..fa318403a0 --- /dev/null +++ b/webapp/migrations/Version20230918185221.php @@ -0,0 +1,37 @@ +addSql(<<addSql(<<addSql('ALTER TABLE contest ADD COLUMN `ranknumber` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT \'Determines order of the contests\''); + $this->addSql(<<addSql('ALTER TABLE contest ADD CONSTRAINT `ranknumber_unique` UNIQUE (`ranknumber`)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE contest DROP INDEX `ranknumber_unique`'); + $this->addSql('ALTER TABLE contest DROP COLUMN `ranknumber`'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/migrations/Version20231108142925.php b/webapp/migrations/Version20231108142925.php new file mode 100644 index 0000000000..5308ca858b --- /dev/null +++ b/webapp/migrations/Version20231108142925.php @@ -0,0 +1,104 @@ +addSql(<<addSql(<<connection->fetchAllAssociative(<<addSql(<<addSql(<< $problem['probid'] + ]); + } + + $this->addSql(<<addSql(<<addSql(<<addSql(<<addSql(<<addSql(<<addSql(<<addSql(<< .image > img { + max-width: 100%; + height: auto; +} + +.blog-post-list-thumbnail, .blog-post-homepage-thumbnail { + width: 100%; + aspect-ratio: 3/2; + object-fit: contain; +} + +@media (min-width: 768px) { + .blog-post-list-thumbnail { + width: 360px; + } +} diff --git a/webapp/public/css/help.css b/webapp/public/css/help.css new file mode 100644 index 0000000000..192d7b9228 --- /dev/null +++ b/webapp/public/css/help.css @@ -0,0 +1,3 @@ +.help-img { + max-height: 400px; +} diff --git a/webapp/public/css/homepage.css b/webapp/public/css/homepage.css new file mode 100644 index 0000000000..58735c9182 --- /dev/null +++ b/webapp/public/css/homepage.css @@ -0,0 +1,85 @@ +h3, h4, h5, h6 { + text-align: left; +} + +.hp-cover { + margin-top: -15px; + + background: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)), url("/images/hacker/homepage-cover.webp") no-repeat; + background-size: cover; + background-position: top; + + color: white; +} + +.hp-logo-bar > .container > img { + max-width: 300px; +} + +.hp-cover-headline { + max-width: 600px !important; + margin: 80px 0; + + font-size: 1.5em; + text-shadow: black 0 4px 3px; +} + +.hp-cover-details > .d-flex { + gap: 30px !important; +} + +.hp-cover-details-segment { + flex-basis: 0; + flex-grow: 1; +} + +.hp-cover-details-segment-larger { + flex-grow: 2; +} + +.hp-cover-overlay { + background-color: rgba(0, 0, 0, 0.5); + padding: 15px 0; +} + +.hp-cover-details { + padding: 25px 0; +} + +@media (min-width: 992px) { + .hp-cover-headline { + margin-right: 100px; + } +} + +@media (min-width: 1200px) { + .hp-cover-headline { + margin-right: 200px; + } +} + +@media (min-width: 1380px) { + .container { + max-width: 1320px; + } +} + +@media (min-width: 1560px) { + .container { + max-width: 1500px; + } + + .hp-cover-headline { + margin-right: 300px; + } +} + +@media (min-width: 1740px) { + .container { + max-width: 1680px; + } + + .hp-cover { + background-position: top -50px center; + } +} diff --git a/webapp/public/images/hacker/homepage-cover.webp b/webapp/public/images/hacker/homepage-cover.webp new file mode 100644 index 0000000000..d8acc07a22 Binary files /dev/null and b/webapp/public/images/hacker/homepage-cover.webp differ diff --git a/webapp/public/images/hacker/logo-black.svg b/webapp/public/images/hacker/logo-black.svg new file mode 100644 index 0000000000..016fb0d862 --- /dev/null +++ b/webapp/public/images/hacker/logo-black.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webapp/public/images/hacker/logo-white.svg b/webapp/public/images/hacker/logo-white.svg new file mode 100644 index 0000000000..77be42846f --- /dev/null +++ b/webapp/public/images/hacker/logo-white.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webapp/public/images/hacker/school-logo.webp b/webapp/public/images/hacker/school-logo.webp new file mode 100644 index 0000000000..9b03a6012f Binary files /dev/null and b/webapp/public/images/hacker/school-logo.webp differ diff --git a/webapp/public/images/hacker/soc.webp b/webapp/public/images/hacker/soc.webp new file mode 100644 index 0000000000..5192d92a38 Binary files /dev/null and b/webapp/public/images/hacker/soc.webp differ diff --git a/webapp/public/images/help-jury/blog-thumbnail-3-to-2.png b/webapp/public/images/help-jury/blog-thumbnail-3-to-2.png new file mode 100644 index 0000000000..3aa4542058 Binary files /dev/null and b/webapp/public/images/help-jury/blog-thumbnail-3-to-2.png differ diff --git a/webapp/public/images/help-jury/blog-thumbnail-narrow.png b/webapp/public/images/help-jury/blog-thumbnail-narrow.png new file mode 100644 index 0000000000..e6e0581636 Binary files /dev/null and b/webapp/public/images/help-jury/blog-thumbnail-narrow.png differ diff --git a/webapp/public/images/help-jury/blog-thumbnail-wide.png b/webapp/public/images/help-jury/blog-thumbnail-wide.png new file mode 100644 index 0000000000..fa3d2c3e9a Binary files /dev/null and b/webapp/public/images/help-jury/blog-thumbnail-wide.png differ diff --git a/webapp/public/images/help/clarification-request.png b/webapp/public/images/help/clarification-request.png new file mode 100644 index 0000000000..a77a5b4bd7 Binary files /dev/null and b/webapp/public/images/help/clarification-request.png differ diff --git a/webapp/public/images/help/contests.png b/webapp/public/images/help/contests.png new file mode 100644 index 0000000000..05f589de0a Binary files /dev/null and b/webapp/public/images/help/contests.png differ diff --git a/webapp/public/images/help/diff-output.png b/webapp/public/images/help/diff-output.png new file mode 100644 index 0000000000..6109377537 Binary files /dev/null and b/webapp/public/images/help/diff-output.png differ diff --git a/webapp/public/images/help/messages-unread.png b/webapp/public/images/help/messages-unread.png new file mode 100644 index 0000000000..94527d3211 Binary files /dev/null and b/webapp/public/images/help/messages-unread.png differ diff --git a/webapp/public/images/help/messages.png b/webapp/public/images/help/messages.png new file mode 100644 index 0000000000..d6b52ba6ee Binary files /dev/null and b/webapp/public/images/help/messages.png differ diff --git a/webapp/public/images/help/problemset.png b/webapp/public/images/help/problemset.png new file mode 100644 index 0000000000..132f0df420 Binary files /dev/null and b/webapp/public/images/help/problemset.png differ diff --git a/webapp/public/images/help/register.png b/webapp/public/images/help/register.png new file mode 100644 index 0000000000..70fff1d2ee Binary files /dev/null and b/webapp/public/images/help/register.png differ diff --git a/webapp/public/images/help/scoreboard.png b/webapp/public/images/help/scoreboard.png new file mode 100644 index 0000000000..1a0326450f Binary files /dev/null and b/webapp/public/images/help/scoreboard.png differ diff --git a/webapp/public/images/help/submissions.png b/webapp/public/images/help/submissions.png new file mode 100644 index 0000000000..27779e6214 Binary files /dev/null and b/webapp/public/images/help/submissions.png differ diff --git a/webapp/public/images/help/submit.png b/webapp/public/images/help/submit.png new file mode 100644 index 0000000000..5cda858e45 Binary files /dev/null and b/webapp/public/images/help/submit.png differ diff --git a/webapp/public/js/bootstrap-auto-dark-mode.js b/webapp/public/js/bootstrap-auto-dark-mode.js new file mode 100644 index 0000000000..cc98922bd3 --- /dev/null +++ b/webapp/public/js/bootstrap-auto-dark-mode.js @@ -0,0 +1,31 @@ +/** + * Based on: + * + * Author and copyright: Stefan Haack (https://shaack.com) + * Repository: https://github.com/shaack/bootstrap-auto-dark-mode + * License: MIT, see file 'LICENSE' + */ + +window.updateTheme = function(theme) { + theme = theme || localStorage.getItem("theme") || "auto"; + + if (theme !== "dark" && theme !== "light") { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + localStorage.setItem("theme", theme); + document.querySelector("html").setAttribute("data-bs-theme", theme); + + editors = document.querySelectorAll(".editor"); + for (let i = 0; i < editors.length; i++) { + let editor = editors[i].editor; + editor.setTheme(theme === "dark" ? "ace/theme/tomorrow_night" : "ace/theme/eclipse"); + } +} + +;(function () { + if (document.querySelector("html").getAttribute("data-bs-theme") === 'auto') { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', window.updateTheme); + window.updateTheme(); + } +})(); diff --git a/webapp/public/js/editorjs/code.js b/webapp/public/js/editorjs/code.js new file mode 100644 index 0000000000..719daaf9ea --- /dev/null +++ b/webapp/public/js/editorjs/code.js @@ -0,0 +1,15 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/code@2.8.0/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CodeTool=t():e.CodeTool=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=4)}([function(e,t,n){var r=n(1),o=n(2);"string"==typeof(o=o.__esModule?o.default:o)&&(o=[[e.i,o,""]]);var a={insert:"head",singleton:!1};r(o,a);e.exports=o.locals||{}},function(e,t,n){"use strict";var r,o=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),i=[];function c(e){for(var t=-1,n=0;n0;)t-=1,n=e.substr(t,1);return"\n"===n&&(t+=1),t}(a,o);if(" "!==a.substr(i," ".length))return;n.value=a.substring(0,i)+a.substring(i+" ".length),t=o-" ".length}else t=o+" ".length,n.value=a.substring(0,o)+" "+a.substring(o);n.setSelectionRange(t,t)}},{key:"data",get:function(){return this._data},set:function(e){this._data=e,this.nodes.textarea&&(this.nodes.textarea.textContent=e.code)}}],[{key:"toolbox",get:function(){return{icon:'',title:"Code"}}},{key:"DEFAULT_PLACEHOLDER",get:function(){return"Enter a code"}},{key:"pasteConfig",get:function(){return{tags:["pre"]}}},{key:"sanitize",get:function(){return{code:!0}}}]),e}()}]).default})); diff --git a/webapp/public/js/editorjs/editorjs.js b/webapp/public/js/editorjs/editorjs.js new file mode 100644 index 0000000000..79bf9b37aa --- /dev/null +++ b/webapp/public/js/editorjs/editorjs.js @@ -0,0 +1,89 @@ +/** + * Failed to minify the file using Terser v5.17.1. Serving the original version. + * Original file: /npm/@editorjs/editorjs@2.27.2/dist/editorjs.umd.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +(function(ce,q){typeof exports=="object"&&typeof module<"u"?module.exports=q():typeof define=="function"&&define.amd?define(q):(ce=typeof globalThis<"u"?globalThis:ce||self,ce.EditorJS=q())})(this,function(){"use strict";var ce=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function q(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}function Be(){}Object.assign(Be,{default:Be,register:Be,revert:function(){},__esModule:!0}),Element.prototype.matches||(Element.prototype.matches=Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector||function(s){const e=(this.document||this.ownerDocument).querySelectorAll(s);let t=e.length;for(;--t>=0&&e.item(t)!==this;);return t>-1}),Element.prototype.closest||(Element.prototype.closest=function(s){let e=this;if(!document.documentElement.contains(e))return null;do{if(e.matches(s))return e;e=e.parentElement||e.parentNode}while(e!==null);return null}),Element.prototype.prepend||(Element.prototype.prepend=function(e){const t=document.createDocumentFragment();Array.isArray(e)||(e=[e]),e.forEach(o=>{const i=o instanceof Node;t.appendChild(i?o:document.createTextNode(o))}),this.insertBefore(t,this.firstChild)}),Element.prototype.scrollIntoViewIfNeeded||(Element.prototype.scrollIntoViewIfNeeded=function(s){s=arguments.length===0?!0:!!s;const e=this.parentNode,t=window.getComputedStyle(e,null),o=parseInt(t.getPropertyValue("border-top-width")),i=parseInt(t.getPropertyValue("border-left-width")),n=this.offsetTop-e.offsetTope.scrollTop+e.clientHeight,a=this.offsetLeft-e.offsetLefte.scrollLeft+e.clientWidth,c=n&&!r;(n||r)&&s&&(e.scrollTop=this.offsetTop-e.offsetTop-e.clientHeight/2-o+this.clientHeight/2),(a||l)&&s&&(e.scrollLeft=this.offsetLeft-e.offsetLeft-e.clientWidth/2-i+this.clientWidth/2),(n||r||a||l)&&!s&&this.scrollIntoView(c)});let Mt=(s=21)=>crypto.getRandomValues(new Uint8Array(s)).reduce((e,t)=>(t&=63,t<36?e+=t.toString(36):t<62?e+=(t-26).toString(36).toUpperCase():t>62?e+="-":e+="_",e),"");var Ve=(s=>(s.VERBOSE="VERBOSE",s.INFO="INFO",s.WARN="WARN",s.ERROR="ERROR",s))(Ve||{});const B={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,LEFT:37,UP:38,DOWN:40,RIGHT:39,DELETE:46,META:91},Lt={LEFT:0,WHEEL:1,RIGHT:2,BACKWARD:3,FORWARD:4};function de(s,e,t="log",o,i="color: inherit"){if(!("console"in window)||!window.console[t])return;const n=["info","log","warn","error"].includes(t),r=[];switch(de.logLevel){case"ERROR":if(t!=="error")return;break;case"WARN":if(!["error","warn"].includes(t))return;break;case"INFO":if(!n||s)return;break}o&&r.push(o);const a="Editor.js 2.27.2",l=`line-height: 1em; + color: #006FEA; + display: inline-block; + font-size: 11px; + line-height: 1em; + background-color: #fff; + padding: 4px 9px; + border-radius: 30px; + border: 1px solid rgba(56, 138, 229, 0.16); + margin: 4px 5px 4px 0;`;s&&(n?(r.unshift(l,i),e=`%c${a}%c ${e}`):e=`( ${a} )${e}`);try{n?o?console[t](`${e} %o`,...r):console[t](e,...r):console[t](e)}catch{}}de.logLevel="VERBOSE";function Ot(s){de.logLevel=s}const C=de.bind(window,!1),K=de.bind(window,!0);function ee(s){return Object.prototype.toString.call(s).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function D(s){return ee(s)==="function"||ee(s)==="asyncfunction"}function H(s){return ee(s)==="object"}function J(s){return ee(s)==="string"}function _t(s){return ee(s)==="boolean"}function Ze(s){return ee(s)==="number"}function Ge(s){return ee(s)==="undefined"}function X(s){return s?Object.keys(s).length===0&&s.constructor===Object:!0}function qe(s){return s>47&&s<58||s===32||s===13||s===229||s>64&&s<91||s>95&&s<112||s>185&&s<193||s>218&&s<223}async function Je(s,e=()=>{},t=()=>{}){async function o(i,n,r){try{await i.function(i.data),await n(Ge(i.data)?{}:i.data)}catch{r(Ge(i.data)?{}:i.data)}}return s.reduce(async(i,n)=>(await i,o(n,e,t)),Promise.resolve())}function Qe(s){return Array.prototype.slice.call(s)}function te(s,e){return function(){const t=this,o=arguments;window.setTimeout(()=>s.apply(t,o),e)}}function At(s){return s.name.split(".").pop()}function Nt(s){return/^[-\w]+\/([-+\w]+|\*)$/.test(s)}function Rt(s,e,t){let o;return(...i)=>{const n=this,r=()=>{o=null,t||s.apply(n,i)},a=t&&!o;window.clearTimeout(o),o=window.setTimeout(r,e),a&&s.apply(n,i)}}function Te(s,e,t=void 0){let o,i,n,r=null,a=0;t||(t={});const l=function(){a=t.leading===!1?0:Date.now(),r=null,n=s.apply(o,i),r||(o=i=null)};return function(){const c=Date.now();!a&&t.leading===!1&&(a=c);const u=e-(c-a);return o=this,i=arguments,u<=0||u>e?(r&&(clearTimeout(r),r=null),a=c,n=s.apply(o,i),r||(o=i=null)):!r&&t.trailing!==!1&&(r=setTimeout(l,u)),n}}function Dt(){const s={win:!1,mac:!1,x11:!1,linux:!1},e=Object.keys(s).find(t=>window.navigator.appVersion.toLowerCase().indexOf(t)!==-1);return e&&(s[e]=!0),s}function ke(s){return s[0].toUpperCase()+s.slice(1)}function Ce(s,...e){if(!e.length)return s;const t=e.shift();if(H(s)&&H(t))for(const o in t)H(t[o])?(s[o]||Object.assign(s,{[o]:{}}),Ce(s[o],t[o])):Object.assign(s,{[o]:t[o]});return Ce(s,...e)}function et(s){const e=Dt();return s=s.replace(/shift/gi,"⇧").replace(/backspace/gi,"⌫").replace(/enter/gi,"⏎").replace(/up/gi,"↑").replace(/left/gi,"→").replace(/down/gi,"↓").replace(/right/gi,"←").replace(/escape/gi,"⎋").replace(/insert/gi,"Ins").replace(/delete/gi,"␡").replace(/\+/gi," + "),e.mac?s=s.replace(/ctrl|cmd/gi,"⌘").replace(/alt/gi,"⌥"):s=s.replace(/cmd/gi,"Ctrl").replace(/windows/gi,"WIN"),s}function Pt(s){try{return new URL(s).href}catch{}return s.substring(0,2)==="//"?window.location.protocol+s:window.location.origin+s}function Ft(){return Mt(10)}function Ht(s){window.open(s,"_blank")}function zt(s=""){return`${s}${Math.floor(Math.random()*1e8).toString(16)}`}function Se(s,e,t){const o=`«${e}» is deprecated and will be removed in the next major release. Please use the «${t}» instead.`;s&&K(o,"warn")}function ne(s,e,t){const o=t.value?"value":"get",i=t[o],n=`#${e}Cache`;if(t[o]=function(...r){return this[n]===void 0&&(this[n]=i.apply(this,...r)),this[n]},o==="get"&&t.set){const r=t.set;t.set=function(a){delete s[n],r.apply(this,a)}}return t}const tt=650;function oe(){return window.matchMedia(`(max-width: ${tt}px)`).matches}const ot=typeof window<"u"&&window.navigator&&window.navigator.platform&&(/iP(ad|hone|od)/.test(window.navigator.platform)||window.navigator.platform==="MacIntel"&&window.navigator.maxTouchPoints>1);function jt(s,e){const t=Array.isArray(s)||H(s),o=Array.isArray(e)||H(e);return t||o?JSON.stringify(s)===JSON.stringify(e):s===e}class d{static isSingleTag(e){return e.tagName&&["AREA","BASE","BR","COL","COMMAND","EMBED","HR","IMG","INPUT","KEYGEN","LINK","META","PARAM","SOURCE","TRACK","WBR"].includes(e.tagName)}static isLineBreakTag(e){return e&&e.tagName&&["BR","WBR"].includes(e.tagName)}static make(e,t=null,o={}){const i=document.createElement(e);Array.isArray(t)?i.classList.add(...t):t&&i.classList.add(t);for(const n in o)Object.prototype.hasOwnProperty.call(o,n)&&(i[n]=o[n]);return i}static text(e){return document.createTextNode(e)}static append(e,t){Array.isArray(t)?t.forEach(o=>e.appendChild(o)):e.appendChild(t)}static prepend(e,t){Array.isArray(t)?(t=t.reverse(),t.forEach(o=>e.prepend(o))):e.prepend(t)}static swap(e,t){const o=document.createElement("div"),i=e.parentNode;i.insertBefore(o,e),i.insertBefore(e,t),i.insertBefore(t,o),i.removeChild(o)}static find(e=document,t){return e.querySelector(t)}static get(e){return document.getElementById(e)}static findAll(e=document,t){return e.querySelectorAll(t)}static get allInputsSelector(){return"[contenteditable=true], textarea, input:not([type]), "+["text","password","email","number","search","tel","url"].map(t=>`input[type="${t}"]`).join(", ")}static findAllInputs(e){return Qe(e.querySelectorAll(d.allInputsSelector)).reduce((t,o)=>d.isNativeInput(o)||d.containsOnlyInlineElements(o)?[...t,o]:[...t,...d.getDeepestBlockElements(o)],[])}static getDeepestNode(e,t=!1){const o=t?"lastChild":"firstChild",i=t?"previousSibling":"nextSibling";if(e&&e.nodeType===Node.ELEMENT_NODE&&e[o]){let n=e[o];if(d.isSingleTag(n)&&!d.isNativeInput(n)&&!d.isLineBreakTag(n))if(n[i])n=n[i];else if(n.parentNode[i])n=n.parentNode[i];else return n.parentNode;return this.getDeepestNode(n,t)}return e}static isElement(e){return Ze(e)?!1:e&&e.nodeType&&e.nodeType===Node.ELEMENT_NODE}static isFragment(e){return Ze(e)?!1:e&&e.nodeType&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE}static isContentEditable(e){return e.contentEditable==="true"}static isNativeInput(e){const t=["INPUT","TEXTAREA"];return e&&e.tagName?t.includes(e.tagName):!1}static canSetCaret(e){let t=!0;if(d.isNativeInput(e))switch(e.type){case"file":case"checkbox":case"radio":case"hidden":case"submit":case"button":case"image":case"reset":t=!1;break}else t=d.isContentEditable(e);return t}static isNodeEmpty(e){let t;return this.isSingleTag(e)&&!this.isLineBreakTag(e)?!1:(this.isElement(e)&&this.isNativeInput(e)?t=e.value:t=e.textContent.replace("​",""),t.trim().length===0)}static isLeaf(e){return e?e.childNodes.length===0:!1}static isEmpty(e){e.normalize();const t=[e];for(;t.length>0;)if(e=t.shift(),!!e){if(this.isLeaf(e)&&!this.isNodeEmpty(e))return!1;e.childNodes&&t.push(...Array.from(e.childNodes))}return!0}static isHTMLString(e){const t=d.make("div");return t.innerHTML=e,t.childElementCount>0}static getContentLength(e){return d.isNativeInput(e)?e.value.length:e.nodeType===Node.TEXT_NODE?e.length:e.textContent.length}static get blockElements(){return["address","article","aside","blockquote","canvas","div","dl","dt","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","li","main","nav","noscript","ol","output","p","pre","ruby","section","table","tbody","thead","tr","tfoot","ul","video"]}static containsOnlyInlineElements(e){let t;J(e)?(t=document.createElement("div"),t.innerHTML=e):t=e;const o=i=>!d.blockElements.includes(i.tagName.toLowerCase())&&Array.from(i.children).every(o);return Array.from(t.children).every(o)}static getDeepestBlockElements(e){return d.containsOnlyInlineElements(e)?[e]:Array.from(e.children).reduce((t,o)=>[...t,...d.getDeepestBlockElements(o)],[])}static getHolder(e){return J(e)?document.getElementById(e):e}static isAnchor(e){return e.tagName.toLowerCase()==="a"}static offset(e){const t=e.getBoundingClientRect(),o=window.pageXOffset||document.documentElement.scrollLeft,i=window.pageYOffset||document.documentElement.scrollTop,n=t.top+i,r=t.left+o;return{top:n,left:r,bottom:n+t.height,right:r+t.width}}}const it={ui:{blockTunes:{toggler:{"Click to tune":"","or drag to move":""}},inlineToolbar:{converter:{"Convert to":""}},toolbar:{toolbox:{Add:""}},popover:{Filter:"","Nothing found":""}},toolNames:{Text:"",Link:"",Bold:"",Italic:""},tools:{link:{"Add a link":""},stub:{"The block can not be displayed correctly.":""}},blockTunes:{delete:{Delete:"","Click to delete":""},moveUp:{"Move up":""},moveDown:{"Move down":""}}},se=class{static ui(s,e){return se._t(s,e)}static t(s,e){return se._t(s,e)}static setDictionary(s){se.currentDictionary=s}static _t(s,e){const t=se.getNamespace(s);return!t||!t[e]?e:t[e]}static getNamespace(s){return s.split(".").reduce((t,o)=>!t||!Object.keys(t).length?{}:t[o],se.currentDictionary)}};let $=se;$.currentDictionary=it;class nt extends Error{}class ve{constructor(){this.subscribers={}}on(e,t){e in this.subscribers||(this.subscribers[e]=[]),this.subscribers[e].push(t)}once(e,t){e in this.subscribers||(this.subscribers[e]=[]);const o=i=>{const n=t(i),r=this.subscribers[e].indexOf(o);return r!==-1&&this.subscribers[e].splice(r,1),n};this.subscribers[e].push(o)}emit(e,t){X(this.subscribers)||!this.subscribers[e]||this.subscribers[e].reduce((o,i)=>{const n=i(o);return n!==void 0?n:o},t)}off(e,t){for(let o=0;o{const l=this.allListeners.indexOf(n[a]);l>-1&&(this.allListeners.splice(l,1),r.element.removeEventListener(r.eventType,r.handler,r.options))})}offById(e){const t=this.findById(e);t&&t.element.removeEventListener(t.eventType,t.handler,t.options)}findOne(e,t,o){const i=this.findAll(e,t,o);return i.length>0?i[0]:null}findAll(e,t,o){let i;const n=e?this.findByEventTarget(e):[];return e&&t&&o?i=n.filter(r=>r.eventType===t&&r.handler===o):e&&t?i=n.filter(r=>r.eventType===t):i=n,i}removeAll(){this.allListeners.map(e=>{e.element.removeEventListener(e.eventType,e.handler,e.options)}),this.allListeners=[]}destroy(){this.removeAll()}findByEventTarget(e){return this.allListeners.filter(t=>{if(t.element===e)return t})}findByType(e){return this.allListeners.filter(t=>{if(t.eventType===e)return t})}findByHandler(e){return this.allListeners.filter(t=>{if(t.handler===e)return t})}findById(e){return this.allListeners.find(t=>t.id===e)}}class S{constructor({config:e,eventsDispatcher:t}){if(this.nodes={},this.listeners=new Ie,this.readOnlyMutableListeners={on:(o,i,n,r=!1)=>{this.mutableListenerIds.push(this.listeners.on(o,i,n,r))},clearAll:()=>{for(const o of this.mutableListenerIds)this.listeners.offById(o);this.mutableListenerIds=[]}},this.mutableListenerIds=[],new.target===S)throw new TypeError("Constructors for abstract class Module are not allowed.");this.config=e,this.eventsDispatcher=t}set state(e){this.Editor=e}removeAllNodes(){for(const e in this.nodes){const t=this.nodes[e];t instanceof HTMLElement&&t.remove()}}get isRtl(){return this.config.i18n.direction==="rtl"}}class m{constructor(){this.instance=null,this.selection=null,this.savedSelectionRange=null,this.isFakeBackgroundEnabled=!1,this.commandBackground="backColor",this.commandRemoveFormat="removeFormat"}static get CSS(){return{editorWrapper:"codex-editor",editorZone:"codex-editor__redactor"}}static get anchorNode(){const e=window.getSelection();return e?e.anchorNode:null}static get anchorElement(){const e=window.getSelection();if(!e)return null;const t=e.anchorNode;return t?d.isElement(t)?t:t.parentElement:null}static get anchorOffset(){const e=window.getSelection();return e?e.anchorOffset:null}static get isCollapsed(){const e=window.getSelection();return e?e.isCollapsed:null}static get isAtEditor(){return this.isSelectionAtEditor(m.get())}static isSelectionAtEditor(e){if(!e)return!1;let t=e.anchorNode||e.focusNode;t&&t.nodeType===Node.TEXT_NODE&&(t=t.parentNode);let o=null;return t&&t instanceof Element&&(o=t.closest(`.${m.CSS.editorZone}`)),o?o.nodeType===Node.ELEMENT_NODE:!1}static isRangeAtEditor(e){if(!e)return;let t=e.startContainer;t&&t.nodeType===Node.TEXT_NODE&&(t=t.parentNode);let o=null;return t&&t instanceof Element&&(o=t.closest(`.${m.CSS.editorZone}`)),o?o.nodeType===Node.ELEMENT_NODE:!1}static get isSelectionExists(){return!!m.get().anchorNode}static get range(){return this.getRangeFromSelection(this.get())}static getRangeFromSelection(e){return e&&e.rangeCount?e.getRangeAt(0):null}static get rect(){let e=document.selection,t,o={x:0,y:0,width:0,height:0};if(e&&e.type!=="Control")return e=e,t=e.createRange(),o.x=t.boundingLeft,o.y=t.boundingTop,o.width=t.boundingWidth,o.height=t.boundingHeight,o;if(!window.getSelection)return C("Method window.getSelection is not supported","warn"),o;if(e=window.getSelection(),e.rangeCount===null||isNaN(e.rangeCount))return C("Method SelectionUtils.rangeCount is not supported","warn"),o;if(e.rangeCount===0)return o;if(t=e.getRangeAt(0).cloneRange(),t.getBoundingClientRect&&(o=t.getBoundingClientRect()),o.x===0&&o.y===0){const i=document.createElement("span");if(i.getBoundingClientRect){i.appendChild(document.createTextNode("​")),t.insertNode(i),o=i.getBoundingClientRect();const n=i.parentNode;n.removeChild(i),n.normalize()}}return o}static get text(){return window.getSelection?window.getSelection().toString():""}static get(){return window.getSelection()}static setCursor(e,t=0){const o=document.createRange(),i=window.getSelection();return d.isNativeInput(e)?d.canSetCaret(e)?(e.focus(),e.selectionStart=e.selectionEnd=t,e.getBoundingClientRect()):void 0:(o.setStart(e,t),o.setEnd(e,t),i.removeAllRanges(),i.addRange(o),o.getBoundingClientRect())}static isRangeInsideContainer(e){const t=m.range;return t===null?!1:e.contains(t.startContainer)}static addFakeCursor(){const e=m.range;if(e===null)return;const t=d.make("span","codex-editor__fake-cursor");t.dataset.mutationFree="true",e.collapse(),e.insertNode(t)}static isFakeCursorInsideContainer(e){return d.find(e,".codex-editor__fake-cursor")!==null}static removeFakeCursor(e=document.body){const t=d.find(e,".codex-editor__fake-cursor");t&&t.remove()}removeFakeBackground(){this.isFakeBackgroundEnabled&&(this.isFakeBackgroundEnabled=!1,document.execCommand(this.commandRemoveFormat))}setFakeBackground(){document.execCommand(this.commandBackground,!1,"#a8d6ff"),this.isFakeBackgroundEnabled=!0}save(){this.savedSelectionRange=m.range}restore(){if(!this.savedSelectionRange)return;const e=window.getSelection();e.removeAllRanges(),e.addRange(this.savedSelectionRange)}clearSaved(){this.savedSelectionRange=null}collapseToEnd(){const e=window.getSelection(),t=document.createRange();t.selectNodeContents(e.focusNode),t.collapse(!1),e.removeAllRanges(),e.addRange(t)}findParentTag(e,t,o=10){const i=window.getSelection();let n=null;return!i||!i.anchorNode||!i.focusNode?null:([i.anchorNode,i.focusNode].forEach(a=>{let l=o;for(;l>0&&a.parentNode&&!(a.tagName===e&&(n=a,t&&a.classList&&!a.classList.contains(t)&&(n=null),n));)a=a.parentNode,l--}),n)}expandToTag(e){const t=window.getSelection();t.removeAllRanges();const o=document.createRange();o.selectNodeContents(e),t.addRange(o)}}function Ut(s,e){const{type:t,target:o,addedNodes:i,removedNodes:n}=s;if(o===e)return!0;if(["characterData","attributes"].includes(t)){const l=o.nodeType===Node.TEXT_NODE?o.parentNode:o;return e.contains(l)}const r=Array.from(i).some(l=>e.contains(l)),a=Array.from(n).some(l=>e.contains(l));return r||a}const Me="redactor dom changed",st="block changed",rt="fake cursor is about to be toggled",at="fake cursor have been set";var Q=(s=>(s.APPEND_CALLBACK="appendCallback",s.RENDERED="rendered",s.MOVED="moved",s.UPDATED="updated",s.REMOVED="removed",s.ON_PASTE="onPaste",s))(Q||{});class F extends ve{constructor({id:e=Ft(),data:t,tool:o,api:i,readOnly:n,tunesData:r},a){super(),this.cachedInputs=[],this.toolRenderedElement=null,this.tunesInstances=new Map,this.defaultTunesInstances=new Map,this.unavailableTunesData={},this.inputIndex=0,this.editorEventBus=null,this.handleFocus=()=>{this.dropInputsCache(),this.updateCurrentInput()},this.didMutated=(l=void 0)=>{const c=l===void 0,u=l instanceof InputEvent;!c&&!u&&this.detectToolRootChange(l);let h;c||u?h=!0:h=!(l.length>0&&l.every(v=>{const{addedNodes:p,removedNodes:k,target:_}=v;return[...Array.from(p),...Array.from(k),_].some(A=>d.isElement(A)?A.dataset.mutationFree==="true":!1)})),h&&(this.dropInputsCache(),this.updateCurrentInput(),this.call("updated"),this.emit("didMutated",this))},this.name=o.name,this.id=e,this.settings=o.settings,this.config=o.settings.config||{},this.api=i,this.editorEventBus=a||null,this.blockAPI=new he(this),this.tool=o,this.toolInstance=o.create(t,this.blockAPI,n),this.tunes=o.tunes,this.composeTunes(r),this.holder=this.compose(),this.watchBlockMutations(),this.addInputEvents()}static get CSS(){return{wrapper:"ce-block",wrapperStretched:"ce-block--stretched",content:"ce-block__content",focused:"ce-block--focused",selected:"ce-block--selected",dropTarget:"ce-block--drop-target"}}get inputs(){if(this.cachedInputs.length!==0)return this.cachedInputs;const e=d.findAllInputs(this.holder);return this.inputIndex>e.length-1&&(this.inputIndex=e.length-1),this.cachedInputs=e,e}get currentInput(){return this.inputs[this.inputIndex]}set currentInput(e){const t=this.inputs.findIndex(o=>o===e||o.contains(e));t!==-1&&(this.inputIndex=t)}get firstInput(){return this.inputs[0]}get lastInput(){const e=this.inputs;return e[e.length-1]}get nextInput(){return this.inputs[this.inputIndex+1]}get previousInput(){return this.inputs[this.inputIndex-1]}get data(){return this.save().then(e=>e&&!X(e.data)?e.data:{})}get sanitize(){return this.tool.sanitizeConfig}get mergeable(){return D(this.toolInstance.merge)}get isEmpty(){const e=d.isEmpty(this.pluginsContent),t=!this.hasMedia;return e&&t}get hasMedia(){const e=["img","iframe","video","audio","source","input","textarea","twitterwidget"];return!!this.holder.querySelector(e.join(","))}set focused(e){this.holder.classList.toggle(F.CSS.focused,e)}get focused(){return this.holder.classList.contains(F.CSS.focused)}set selected(e){var i,n;this.holder.classList.toggle(F.CSS.selected,e);const t=e===!0&&m.isRangeInsideContainer(this.holder),o=e===!1&&m.isFakeCursorInsideContainer(this.holder);(t||o)&&((i=this.editorEventBus)==null||i.emit(rt,{state:e}),t?m.addFakeCursor():m.removeFakeCursor(this.holder),(n=this.editorEventBus)==null||n.emit(at,{state:e}))}get selected(){return this.holder.classList.contains(F.CSS.selected)}set stretched(e){this.holder.classList.toggle(F.CSS.wrapperStretched,e)}get stretched(){return this.holder.classList.contains(F.CSS.wrapperStretched)}set dropTarget(e){this.holder.classList.toggle(F.CSS.dropTarget,e)}get pluginsContent(){return this.toolRenderedElement}call(e,t){if(D(this.toolInstance[e])){e==="appendCallback"&&C("`appendCallback` hook is deprecated and will be removed in the next major release. Use `rendered` hook instead","warn");try{this.toolInstance[e].call(this.toolInstance,t)}catch(o){C(`Error during '${e}' call: ${o.message}`,"error")}}}async mergeWith(e){await this.toolInstance.merge(e)}async save(){const e=await this.toolInstance.save(this.pluginsContent),t=this.unavailableTunesData;[...this.tunesInstances.entries(),...this.defaultTunesInstances.entries()].forEach(([n,r])=>{if(D(r.save))try{t[n]=r.save()}catch(a){C(`Tune ${r.constructor.name} save method throws an Error %o`,"warn",a)}});const o=window.performance.now();let i;return Promise.resolve(e).then(n=>(i=window.performance.now(),{id:this.id,tool:this.name,data:n,tunes:t,time:i-o})).catch(n=>{C(`Saving process for ${this.name} tool failed due to the ${n}`,"log","red")})}async validate(e){let t=!0;return this.toolInstance.validate instanceof Function&&(t=await this.toolInstance.validate(e)),t}getTunes(){const e=document.createElement("div"),t=[],o=typeof this.toolInstance.renderSettings=="function"?this.toolInstance.renderSettings():[],i=[...this.tunesInstances.values(),...this.defaultTunesInstances.values()].map(n=>n.render());return[o,i].flat().forEach(n=>{d.isElement(n)?e.appendChild(n):Array.isArray(n)?t.push(...n):t.push(n)}),[t,e]}updateCurrentInput(){this.currentInput=d.isNativeInput(document.activeElement)||!m.anchorNode?document.activeElement:m.anchorNode}dispatchChange(){this.didMutated()}destroy(){this.unwatchBlockMutations(),this.removeInputEvents(),super.destroy(),D(this.toolInstance.destroy)&&this.toolInstance.destroy()}async getActiveToolboxEntry(){const e=this.tool.toolbox;if(e.length===1)return Promise.resolve(this.tool.toolbox[0]);const t=await this.data;return e.find(i=>Object.entries(i.data).some(([n,r])=>t[n]&&jt(t[n],r)))}compose(){const e=d.make("div",F.CSS.wrapper),t=d.make("div",F.CSS.content),o=this.toolInstance.render();this.toolRenderedElement=o,t.appendChild(this.toolRenderedElement);let i=t;return[...this.tunesInstances.values(),...this.defaultTunesInstances.values()].forEach(n=>{if(D(n.wrap))try{i=n.wrap(i)}catch(r){C(`Tune ${n.constructor.name} wrap method throws an Error %o`,"warn",r)}}),e.appendChild(i),e}composeTunes(e){Array.from(this.tunes.values()).forEach(t=>{(t.isInternal?this.defaultTunesInstances:this.tunesInstances).set(t.name,t.create(e[t.name],this.blockAPI))}),Object.entries(e).forEach(([t,o])=>{this.tunesInstances.has(t)||(this.unavailableTunesData[t]=o)})}addInputEvents(){this.inputs.forEach(e=>{e.addEventListener("focus",this.handleFocus),d.isNativeInput(e)&&e.addEventListener("input",this.didMutated)})}removeInputEvents(){this.inputs.forEach(e=>{e.removeEventListener("focus",this.handleFocus),d.isNativeInput(e)&&e.removeEventListener("input",this.didMutated)})}watchBlockMutations(){var e;this.redactorDomChangedCallback=t=>{const{mutations:o}=t;o.some(n=>Ut(n,this.toolRenderedElement))&&this.didMutated(o)},(e=this.editorEventBus)==null||e.on(Me,this.redactorDomChangedCallback)}unwatchBlockMutations(){var e;(e=this.editorEventBus)==null||e.off(Me,this.redactorDomChangedCallback)}detectToolRootChange(e){e.forEach(t=>{if(Array.from(t.removedNodes).includes(this.toolRenderedElement)){const i=t.addedNodes[t.addedNodes.length-1];this.toolRenderedElement=i}})}dropInputsCache(){this.cachedInputs=[]}}class $t extends S{constructor(){super(...arguments),this.insert=(e=this.config.defaultBlock,t={},o={},i,n,r,a)=>{const l=this.Editor.BlockManager.insert({id:a,tool:e,data:t,index:i,needToFocus:n,replace:r});return new he(l)},this.composeBlockData=async e=>{const t=this.Editor.Tools.blockTools.get(e);return new F({tool:t,api:this.Editor.API,readOnly:!0,data:{},tunesData:{}}).data},this.update=(e,t)=>{const{BlockManager:o}=this.Editor,i=o.getBlockById(e);if(!i){C("blocks.update(): Block with passed id was not found","warn");return}const n=o.getBlockIndex(i);o.insert({id:i.id,tool:i.name,data:t,index:n,replace:!0,tunes:i.tunes})}}get methods(){return{clear:()=>this.clear(),render:e=>this.render(e),renderFromHTML:e=>this.renderFromHTML(e),delete:e=>this.delete(e),swap:(e,t)=>this.swap(e,t),move:(e,t)=>this.move(e,t),getBlockByIndex:e=>this.getBlockByIndex(e),getById:e=>this.getById(e),getCurrentBlockIndex:()=>this.getCurrentBlockIndex(),getBlockIndex:e=>this.getBlockIndex(e),getBlocksCount:()=>this.getBlocksCount(),stretchBlock:(e,t=!0)=>this.stretchBlock(e,t),insertNewBlock:()=>this.insertNewBlock(),insert:this.insert,update:this.update,composeBlockData:this.composeBlockData}}getBlocksCount(){return this.Editor.BlockManager.blocks.length}getCurrentBlockIndex(){return this.Editor.BlockManager.currentBlockIndex}getBlockIndex(e){const t=this.Editor.BlockManager.getBlockById(e);if(!t){K("There is no block with id `"+e+"`","warn");return}return this.Editor.BlockManager.getBlockIndex(t)}getBlockByIndex(e){const t=this.Editor.BlockManager.getBlockByIndex(e);if(t===void 0){K("There is no block at index `"+e+"`","warn");return}return new he(t)}getById(e){const t=this.Editor.BlockManager.getBlockById(e);return t===void 0?(K("There is no block with id `"+e+"`","warn"),null):new he(t)}swap(e,t){C("`blocks.swap()` method is deprecated and will be removed in the next major release. Use `block.move()` method instead","info"),this.Editor.BlockManager.swap(e,t)}move(e,t){this.Editor.BlockManager.move(e,t)}delete(e){try{this.Editor.BlockManager.removeBlock(e)}catch(t){K(t,"warn");return}this.Editor.BlockManager.blocks.length===0&&this.Editor.BlockManager.insert(),this.Editor.BlockManager.currentBlock&&this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock,this.Editor.Caret.positions.END),this.Editor.Toolbar.close()}clear(){this.Editor.BlockManager.clear(!0),this.Editor.InlineToolbar.close()}render(e){return this.Editor.BlockManager.clear(),this.Editor.Renderer.render(e.blocks)}renderFromHTML(e){return this.Editor.BlockManager.clear(),this.Editor.Paste.processText(e,!0)}stretchBlock(e,t=!0){Se(!0,"blocks.stretchBlock()","BlockAPI");const o=this.Editor.BlockManager.getBlockByIndex(e);o&&(o.stretched=t)}insertNewBlock(){C("Method blocks.insertNewBlock() is deprecated and it will be removed in the next major release. Use blocks.insert() instead.","warn"),this.insert()}}class Wt extends S{constructor(){super(...arguments),this.setToFirstBlock=(e=this.Editor.Caret.positions.DEFAULT,t=0)=>this.Editor.BlockManager.firstBlock?(this.Editor.Caret.setToBlock(this.Editor.BlockManager.firstBlock,e,t),!0):!1,this.setToLastBlock=(e=this.Editor.Caret.positions.DEFAULT,t=0)=>this.Editor.BlockManager.lastBlock?(this.Editor.Caret.setToBlock(this.Editor.BlockManager.lastBlock,e,t),!0):!1,this.setToPreviousBlock=(e=this.Editor.Caret.positions.DEFAULT,t=0)=>this.Editor.BlockManager.previousBlock?(this.Editor.Caret.setToBlock(this.Editor.BlockManager.previousBlock,e,t),!0):!1,this.setToNextBlock=(e=this.Editor.Caret.positions.DEFAULT,t=0)=>this.Editor.BlockManager.nextBlock?(this.Editor.Caret.setToBlock(this.Editor.BlockManager.nextBlock,e,t),!0):!1,this.setToBlock=(e,t=this.Editor.Caret.positions.DEFAULT,o=0)=>this.Editor.BlockManager.blocks[e]?(this.Editor.Caret.setToBlock(this.Editor.BlockManager.blocks[e],t,o),!0):!1,this.focus=(e=!1)=>e?this.setToLastBlock(this.Editor.Caret.positions.END):this.setToFirstBlock(this.Editor.Caret.positions.START)}get methods(){return{setToFirstBlock:this.setToFirstBlock,setToLastBlock:this.setToLastBlock,setToPreviousBlock:this.setToPreviousBlock,setToNextBlock:this.setToNextBlock,setToBlock:this.setToBlock,focus:this.focus}}}class Yt extends S{get methods(){return{emit:(e,t)=>this.emit(e,t),off:(e,t)=>this.off(e,t),on:(e,t)=>this.on(e,t)}}on(e,t){this.eventsDispatcher.on(e,t)}emit(e,t){this.eventsDispatcher.emit(e,t)}off(e,t){this.eventsDispatcher.off(e,t)}}class Le extends S{static getNamespace(e){return e.isTune()?`blockTunes.${e.name}`:`tools.${e.name}`}get methods(){return{t:()=>{K("I18n.t() method can be accessed only from Tools","warn")}}}getMethodsForTool(e){return Object.assign(this.methods,{t:t=>$.t(Le.getNamespace(e),t)})}}class Kt extends S{get methods(){return{blocks:this.Editor.BlocksAPI.methods,caret:this.Editor.CaretAPI.methods,events:this.Editor.EventsAPI.methods,listeners:this.Editor.ListenersAPI.methods,notifier:this.Editor.NotifierAPI.methods,sanitizer:this.Editor.SanitizerAPI.methods,saver:this.Editor.SaverAPI.methods,selection:this.Editor.SelectionAPI.methods,styles:this.Editor.StylesAPI.classes,toolbar:this.Editor.ToolbarAPI.methods,inlineToolbar:this.Editor.InlineToolbarAPI.methods,tooltip:this.Editor.TooltipAPI.methods,i18n:this.Editor.I18nAPI.methods,readOnly:this.Editor.ReadOnlyAPI.methods,ui:this.Editor.UiAPI.methods}}getMethodsForTool(e){return Object.assign(this.methods,{i18n:this.Editor.I18nAPI.getMethodsForTool(e)})}}class Xt extends S{get methods(){return{close:()=>this.close(),open:()=>this.open()}}open(){this.Editor.InlineToolbar.tryToShow()}close(){this.Editor.InlineToolbar.close()}}class Vt extends S{get methods(){return{on:(e,t,o,i)=>this.on(e,t,o,i),off:(e,t,o,i)=>this.off(e,t,o,i),offById:e=>this.offById(e)}}on(e,t,o,i){return this.listeners.on(e,t,o,i)}off(e,t,o,i){this.listeners.off(e,t,o,i)}offById(e){this.listeners.offById(e)}}var Oe={},Zt={get exports(){return Oe},set exports(s){Oe=s}};(function(s,e){(function(t,o){s.exports=o()})(window,function(){return function(t){var o={};function i(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=t,i.c=o,i.d=function(n,r,a){i.o(n,r)||Object.defineProperty(n,r,{enumerable:!0,get:a})},i.r=function(n){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,r){if(1&r&&(n=i(n)),8&r||4&r&&typeof n=="object"&&n&&n.__esModule)return n;var a=Object.create(null);if(i.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:n}),2&r&&typeof n!="string")for(var l in n)i.d(a,l,function(c){return n[c]}.bind(null,l));return a},i.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(r,"a",r),r},i.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},i.p="/",i(i.s=0)}([function(t,o,i){i(1),t.exports=function(){var n=i(6),r="cdx-notify--bounce-in",a=null;return{show:function(l){if(l.message){(function(){if(a)return!0;a=n.getWrapper(),document.body.appendChild(a)})();var c=null,u=l.time||8e3;switch(l.type){case"confirm":c=n.confirm(l);break;case"prompt":c=n.prompt(l);break;default:c=n.alert(l),window.setTimeout(function(){c.remove()},u)}a.appendChild(c),c.classList.add(r)}}}}()},function(t,o,i){var n=i(2);typeof n=="string"&&(n=[[t.i,n,""]]);var r={hmr:!0,transform:void 0,insertInto:void 0};i(4)(n,r),n.locals&&(t.exports=n.locals)},function(t,o,i){(t.exports=i(3)(!1)).push([t.i,`.cdx-notify--error{background:#fffbfb!important}.cdx-notify--error::before{background:#fb5d5d!important}.cdx-notify__input{max-width:130px;padding:5px 10px;background:#f7f7f7;border:0;border-radius:3px;font-size:13px;color:#656b7c;outline:0}.cdx-notify__input:-ms-input-placeholder{color:#656b7c}.cdx-notify__input::placeholder{color:#656b7c}.cdx-notify__input:focus:-ms-input-placeholder{color:rgba(101,107,124,.3)}.cdx-notify__input:focus::placeholder{color:rgba(101,107,124,.3)}.cdx-notify__button{border:none;border-radius:3px;font-size:13px;padding:5px 10px;cursor:pointer}.cdx-notify__button:last-child{margin-left:10px}.cdx-notify__button--cancel{background:#f2f5f7;box-shadow:0 2px 1px 0 rgba(16,19,29,0);color:#656b7c}.cdx-notify__button--cancel:hover{background:#eee}.cdx-notify__button--confirm{background:#34c992;box-shadow:0 1px 1px 0 rgba(18,49,35,.05);color:#fff}.cdx-notify__button--confirm:hover{background:#33b082}.cdx-notify__btns-wrapper{display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;margin-top:5px}.cdx-notify__cross{position:absolute;top:5px;right:5px;width:10px;height:10px;padding:5px;opacity:.54;cursor:pointer}.cdx-notify__cross::after,.cdx-notify__cross::before{content:'';position:absolute;left:9px;top:5px;height:12px;width:2px;background:#575d67}.cdx-notify__cross::before{transform:rotate(-45deg)}.cdx-notify__cross::after{transform:rotate(45deg)}.cdx-notify__cross:hover{opacity:1}.cdx-notifies{position:fixed;z-index:2;bottom:20px;left:20px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",sans-serif}.cdx-notify{position:relative;width:220px;margin-top:15px;padding:13px 16px;background:#fff;box-shadow:0 11px 17px 0 rgba(23,32,61,.13);border-radius:5px;font-size:14px;line-height:1.4em;word-wrap:break-word}.cdx-notify::before{content:'';position:absolute;display:block;top:0;left:0;width:3px;height:calc(100% - 6px);margin:3px;border-radius:5px;background:0 0}@keyframes bounceIn{0%{opacity:0;transform:scale(.3)}50%{opacity:1;transform:scale(1.05)}70%{transform:scale(.9)}100%{transform:scale(1)}}.cdx-notify--bounce-in{animation-name:bounceIn;animation-duration:.6s;animation-iteration-count:1}.cdx-notify--success{background:#fafffe!important}.cdx-notify--success::before{background:#41ffb1!important}`,""])},function(t,o){t.exports=function(i){var n=[];return n.toString=function(){return this.map(function(r){var a=function(l,c){var u=l[1]||"",h=l[3];if(!h)return u;if(c&&typeof btoa=="function"){var f=(p=h,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(p))))+" */"),v=h.sources.map(function(k){return"/*# sourceURL="+h.sourceRoot+k+" */"});return[u].concat(v).concat([f]).join(` +`)}var p;return[u].join(` +`)}(r,i);return r[2]?"@media "+r[2]+"{"+a+"}":a}).join("")},n.i=function(r,a){typeof r=="string"&&(r=[[null,r,""]]);for(var l={},c=0;c=0&&f.splice(g,1)}function A(b){var g=document.createElement("style");return b.attrs.type===void 0&&(b.attrs.type="text/css"),y(g,b.attrs),_(b,g),g}function y(b,g){Object.keys(g).forEach(function(E){b.setAttribute(E,g[E])})}function x(b,g){var E,T,O,I;if(g.transform&&b.css){if(!(I=g.transform(b.css)))return function(){};b.css=I}if(g.singleton){var j=h++;E=u||(u=A(g)),T=R.bind(null,E,j,!1),O=R.bind(null,E,j,!0)}else b.sourceMap&&typeof URL=="function"&&typeof URL.createObjectURL=="function"&&typeof URL.revokeObjectURL=="function"&&typeof Blob=="function"&&typeof btoa=="function"?(E=function(L){var W=document.createElement("link");return L.attrs.type===void 0&&(L.attrs.type="text/css"),L.attrs.rel="stylesheet",y(W,L.attrs),_(L,W),W}(g),T=function(L,W,me){var ie=me.css,Xe=me.sourceMap,ci=W.convertToAbsoluteUrls===void 0&&Xe;(W.convertToAbsoluteUrls||ci)&&(ie=v(ie)),Xe&&(ie+=` +/*# sourceMappingURL=data:application/json;base64,`+btoa(unescape(encodeURIComponent(JSON.stringify(Xe))))+" */");var di=new Blob([ie],{type:"text/css"}),It=L.href;L.href=URL.createObjectURL(di),It&&URL.revokeObjectURL(It)}.bind(null,E,g),O=function(){N(E),E.href&&URL.revokeObjectURL(E.href)}):(E=A(g),T=function(L,W){var me=W.css,ie=W.media;if(ie&&L.setAttribute("media",ie),L.styleSheet)L.styleSheet.cssText=me;else{for(;L.firstChild;)L.removeChild(L.firstChild);L.appendChild(document.createTextNode(me))}}.bind(null,E),O=function(){N(E)});return T(b),function(L){if(L){if(L.css===b.css&&L.media===b.media&&L.sourceMap===b.sourceMap)return;T(b=L)}else O()}}t.exports=function(b,g){if(typeof DEBUG<"u"&&DEBUG&&typeof document!="object")throw new Error("The style-loader cannot be used in a non-browser environment");(g=g||{}).attrs=typeof g.attrs=="object"?g.attrs:{},g.singleton||typeof g.singleton=="boolean"||(g.singleton=l()),g.insertInto||(g.insertInto="head"),g.insertAt||(g.insertAt="bottom");var E=k(b,g);return p(E,g),function(T){for(var O=[],I=0;Ithis.show(e)}}show(e){return this.notifier.show(e)}}class Qt extends S{get methods(){const e=()=>this.isEnabled;return{toggle:t=>this.toggle(t),get isEnabled(){return e()}}}toggle(e){return this.Editor.ReadOnly.toggle(e)}get isEnabled(){return this.Editor.ReadOnly.isEnabled}}var _e={},eo={get exports(){return _e},set exports(s){_e=s}};(function(s,e){(function(t,o){s.exports=o()})(ce,function(){function t(h){var f=h.tags,v=Object.keys(f),p=v.map(function(k){return typeof f[k]}).every(function(k){return k==="object"||k==="boolean"||k==="function"});if(!p)throw new Error("The configuration was invalid");this.config=h}var o=["P","LI","TD","TH","DIV","H1","H2","H3","H4","H5","H6","PRE"];function i(h){return o.indexOf(h.nodeName)!==-1}var n=["A","B","STRONG","I","EM","SUB","SUP","U","STRIKE"];function r(h){return n.indexOf(h.nodeName)!==-1}t.prototype.clean=function(h){const f=document.implementation.createHTMLDocument(),v=f.createElement("div");return v.innerHTML=h,this._sanitize(f,v),v.innerHTML},t.prototype._sanitize=function(h,f){var v=a(h,f),p=v.firstChild();if(p)do{if(p.nodeType===Node.TEXT_NODE)if(p.data.trim()===""&&(p.previousElementSibling&&i(p.previousElementSibling)||p.nextElementSibling&&i(p.nextElementSibling))){f.removeChild(p),this._sanitize(h,f);break}else continue;if(p.nodeType===Node.COMMENT_NODE){f.removeChild(p),this._sanitize(h,f);break}var k=r(p),_;k&&(_=Array.prototype.some.call(p.childNodes,i));var N=!!f.parentNode,A=i(f)&&i(p)&&N,y=p.nodeName.toLowerCase(),x=l(this.config,y,p),w=k&&_;if(w||c(p,x)||!this.config.keepNestedBlockElements&&A){if(!(p.nodeName==="SCRIPT"||p.nodeName==="STYLE"))for(;p.childNodes.length>0;)f.insertBefore(p.childNodes[0],p);f.removeChild(p),this._sanitize(h,f);break}for(var M=0;M"u"?!0:typeof f=="boolean"?!f:!1}function u(h,f,v){var p=h.name.toLowerCase();return f===!0?!1:typeof f[p]=="function"?!f[p](h.value,v):typeof f[p]>"u"||f[p]===!1?!0:typeof f[p]=="string"?f[p]!==h.value:!1}return t})})(eo);const to=_e;function lt(s,e){return s.map(t=>{const o=D(e)?e(t.tool):e;return X(o)||(t.data=Ae(t.data,o)),t})}function Z(s,e={}){const t={tags:e};return new to(t).clean(s)}function Ae(s,e){return Array.isArray(s)?oo(s,e):H(s)?io(s,e):J(s)?no(s,e):s}function oo(s,e){return s.map(t=>Ae(t,e))}function io(s,e){const t={};for(const o in s){if(!Object.prototype.hasOwnProperty.call(s,o))continue;const i=s[o],n=so(e[o])?e[o]:e;t[o]=Ae(i,n)}return t}function no(s,e){return H(e)?Z(s,e):e===!1?Z(s,{}):s}function so(s){return H(s)||_t(s)||D(s)}class ro extends S{get methods(){return{clean:(e,t)=>this.clean(e,t)}}clean(e,t){return Z(e,t)}}class ao extends S{get methods(){return{save:()=>this.save()}}save(){const e="Editor's content can not be saved in read-only mode";return this.Editor.ReadOnly.isEnabled?(K(e,"warn"),Promise.reject(new Error(e))):this.Editor.Saver.save()}}class lo extends S{get methods(){return{findParentTag:(e,t)=>this.findParentTag(e,t),expandToTag:e=>this.expandToTag(e)}}findParentTag(e,t){return new m().findParentTag(e,t)}expandToTag(e){new m().expandToTag(e)}}class co extends S{get classes(){return{block:"cdx-block",inlineToolButton:"ce-inline-tool",inlineToolButtonActive:"ce-inline-tool--active",input:"cdx-input",loader:"cdx-loader",button:"cdx-button",settingsButton:"cdx-settings-button",settingsButtonActive:"cdx-settings-button--active"}}}class ho extends S{get methods(){return{close:()=>this.close(),open:()=>this.open(),toggleBlockSettings:e=>this.toggleBlockSettings(e),toggleToolbox:e=>this.toggleToolbox(e)}}open(){this.Editor.Toolbar.moveAndOpen()}close(){this.Editor.Toolbar.close()}toggleBlockSettings(e){if(this.Editor.BlockManager.currentBlockIndex===-1){K("Could't toggle the Toolbar because there is no block selected ","warn");return}e??!this.Editor.BlockSettings.opened?(this.Editor.Toolbar.moveAndOpen(),this.Editor.BlockSettings.open()):this.Editor.BlockSettings.close()}toggleToolbox(e){if(this.Editor.BlockManager.currentBlockIndex===-1){K("Could't toggle the Toolbox because there is no block selected ","warn");return}e??!this.Editor.Toolbar.toolbox.opened?(this.Editor.Toolbar.moveAndOpen(),this.Editor.Toolbar.toolbox.open()):this.Editor.Toolbar.toolbox.close()}}var Ne={},uo={get exports(){return Ne},set exports(s){Ne=s}};/*! + * CodeX.Tooltips + * + * @version 1.0.5 + * + * @licence MIT + * @author CodeX + * + * + */(function(s,e){(function(t,o){s.exports=o()})(window,function(){return function(t){var o={};function i(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=t,i.c=o,i.d=function(n,r,a){i.o(n,r)||Object.defineProperty(n,r,{enumerable:!0,get:a})},i.r=function(n){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,r){if(1&r&&(n=i(n)),8&r||4&r&&typeof n=="object"&&n&&n.__esModule)return n;var a=Object.create(null);if(i.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:n}),2&r&&typeof n!="string")for(var l in n)i.d(a,l,function(c){return n[c]}.bind(null,l));return a},i.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(r,"a",r),r},i.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},i.p="",i(i.s=0)}([function(t,o,i){t.exports=i(1)},function(t,o,i){i.r(o),i.d(o,"default",function(){return n});class n{constructor(){this.nodes={wrapper:null,content:null},this.showed=!1,this.offsetTop=10,this.offsetLeft=10,this.offsetRight=10,this.hidingDelay=0,this.handleWindowScroll=()=>{this.showed&&this.hide(!0)},this.loadStyles(),this.prepare(),window.addEventListener("scroll",this.handleWindowScroll,{passive:!0})}get CSS(){return{tooltip:"ct",tooltipContent:"ct__content",tooltipShown:"ct--shown",placement:{left:"ct--left",bottom:"ct--bottom",right:"ct--right",top:"ct--top"}}}show(a,l,c){this.nodes.wrapper||this.prepare(),this.hidingTimeout&&clearTimeout(this.hidingTimeout);const u=Object.assign({placement:"bottom",marginTop:0,marginLeft:0,marginRight:0,marginBottom:0,delay:70,hidingDelay:0},c);if(u.hidingDelay&&(this.hidingDelay=u.hidingDelay),this.nodes.content.innerHTML="",typeof l=="string")this.nodes.content.appendChild(document.createTextNode(l));else{if(!(l instanceof Node))throw Error("[CodeX Tooltip] Wrong type of «content» passed. It should be an instance of Node or String. But "+typeof l+" given.");this.nodes.content.appendChild(l)}switch(this.nodes.wrapper.classList.remove(...Object.values(this.CSS.placement)),u.placement){case"top":this.placeTop(a,u);break;case"left":this.placeLeft(a,u);break;case"right":this.placeRight(a,u);break;case"bottom":default:this.placeBottom(a,u)}u&&u.delay?this.showingTimeout=setTimeout(()=>{this.nodes.wrapper.classList.add(this.CSS.tooltipShown),this.showed=!0},u.delay):(this.nodes.wrapper.classList.add(this.CSS.tooltipShown),this.showed=!0)}hide(a=!1){if(this.hidingDelay&&!a)return this.hidingTimeout&&clearTimeout(this.hidingTimeout),void(this.hidingTimeout=setTimeout(()=>{this.hide(!0)},this.hidingDelay));this.nodes.wrapper.classList.remove(this.CSS.tooltipShown),this.showed=!1,this.showingTimeout&&clearTimeout(this.showingTimeout)}onHover(a,l,c){a.addEventListener("mouseenter",()=>{this.show(a,l,c)}),a.addEventListener("mouseleave",()=>{this.hide()})}destroy(){this.nodes.wrapper.remove(),window.removeEventListener("scroll",this.handleWindowScroll)}prepare(){this.nodes.wrapper=this.make("div",this.CSS.tooltip),this.nodes.content=this.make("div",this.CSS.tooltipContent),this.append(this.nodes.wrapper,this.nodes.content),this.append(document.body,this.nodes.wrapper)}loadStyles(){const a="codex-tooltips-style";if(document.getElementById(a))return;const l=i(2),c=this.make("style",null,{textContent:l.toString(),id:a});this.prepend(document.head,c)}placeBottom(a,l){const c=a.getBoundingClientRect(),u=c.left+a.clientWidth/2-this.nodes.wrapper.offsetWidth/2,h=c.bottom+window.pageYOffset+this.offsetTop+l.marginTop;this.applyPlacement("bottom",u,h)}placeTop(a,l){const c=a.getBoundingClientRect(),u=c.left+a.clientWidth/2-this.nodes.wrapper.offsetWidth/2,h=c.top+window.pageYOffset-this.nodes.wrapper.clientHeight-this.offsetTop;this.applyPlacement("top",u,h)}placeLeft(a,l){const c=a.getBoundingClientRect(),u=c.left-this.nodes.wrapper.offsetWidth-this.offsetLeft-l.marginLeft,h=c.top+window.pageYOffset+a.clientHeight/2-this.nodes.wrapper.offsetHeight/2;this.applyPlacement("left",u,h)}placeRight(a,l){const c=a.getBoundingClientRect(),u=c.right+this.offsetRight+l.marginRight,h=c.top+window.pageYOffset+a.clientHeight/2-this.nodes.wrapper.offsetHeight/2;this.applyPlacement("right",u,h)}applyPlacement(a,l,c){this.nodes.wrapper.classList.add(this.CSS.placement[a]),this.nodes.wrapper.style.left=l+"px",this.nodes.wrapper.style.top=c+"px"}make(a,l=null,c={}){const u=document.createElement(a);Array.isArray(l)?u.classList.add(...l):l&&u.classList.add(l);for(const h in c)c.hasOwnProperty(h)&&(u[h]=c[h]);return u}append(a,l){Array.isArray(l)?l.forEach(c=>a.appendChild(c)):a.appendChild(l)}prepend(a,l){Array.isArray(l)?(l=l.reverse()).forEach(c=>a.prepend(c)):a.prepend(l)}}},function(t,o){t.exports=`.ct{z-index:999;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;-webkit-transition:opacity 50ms ease-in,-webkit-transform 70ms cubic-bezier(.215,.61,.355,1);transition:opacity 50ms ease-in,-webkit-transform 70ms cubic-bezier(.215,.61,.355,1);transition:opacity 50ms ease-in,transform 70ms cubic-bezier(.215,.61,.355,1);transition:opacity 50ms ease-in,transform 70ms cubic-bezier(.215,.61,.355,1),-webkit-transform 70ms cubic-bezier(.215,.61,.355,1);will-change:opacity,top,left;-webkit-box-shadow:0 8px 12px 0 rgba(29,32,43,.17),0 4px 5px -3px rgba(5,6,12,.49);box-shadow:0 8px 12px 0 rgba(29,32,43,.17),0 4px 5px -3px rgba(5,6,12,.49);border-radius:9px}.ct,.ct:before{position:absolute;top:0;left:0}.ct:before{content:"";bottom:0;right:0;background-color:#1d202b;z-index:-1;border-radius:4px}@supports(-webkit-mask-box-image:url("")){.ct:before{border-radius:0;-webkit-mask-box-image:url('data:image/svg+xml;charset=utf-8,') 48% 41% 37.9% 53.3%}}@media (--mobile){.ct{display:none}}.ct__content{padding:6px 10px;color:#cdd1e0;font-size:12px;text-align:center;letter-spacing:.02em;line-height:1em}.ct:after{content:"";width:8px;height:8px;position:absolute;background-color:#1d202b;z-index:-1}.ct--bottom{-webkit-transform:translateY(5px);transform:translateY(5px)}.ct--bottom:after{top:-3px;left:50%;-webkit-transform:translateX(-50%) rotate(-45deg);transform:translateX(-50%) rotate(-45deg)}.ct--top{-webkit-transform:translateY(-5px);transform:translateY(-5px)}.ct--top:after{top:auto;bottom:-3px;left:50%;-webkit-transform:translateX(-50%) rotate(-45deg);transform:translateX(-50%) rotate(-45deg)}.ct--left{-webkit-transform:translateX(-5px);transform:translateX(-5px)}.ct--left:after{top:50%;left:auto;right:0;-webkit-transform:translate(41.6%,-50%) rotate(-45deg);transform:translate(41.6%,-50%) rotate(-45deg)}.ct--right{-webkit-transform:translateX(5px);transform:translateX(5px)}.ct--right:after{top:50%;left:0;-webkit-transform:translate(-41.6%,-50%) rotate(-45deg);transform:translate(-41.6%,-50%) rotate(-45deg)}.ct--shown{opacity:1;-webkit-transform:none;transform:none}`}]).default})})(uo);const po=q(Ne);class Re{constructor(){this.lib=new po}destroy(){this.lib.destroy()}show(e,t,o){this.lib.show(e,t,o)}hide(e=!1){this.lib.hide(e)}onHover(e,t,o){this.lib.onHover(e,t,o)}}class fo extends S{constructor({config:e,eventsDispatcher:t}){super({config:e,eventsDispatcher:t}),this.tooltip=new Re}destroy(){this.tooltip.destroy()}get methods(){return{show:(e,t,o)=>this.show(e,t,o),hide:()=>this.hide(),onHover:(e,t,o)=>this.onHover(e,t,o)}}show(e,t,o){this.tooltip.show(e,t,o)}hide(){this.tooltip.hide()}onHover(e,t,o){this.tooltip.onHover(e,t,o)}}class go extends S{get methods(){return{nodes:this.editorNodes}}get editorNodes(){return{wrapper:this.Editor.UI.nodes.wrapper,redactor:this.Editor.UI.nodes.redactor}}}function ct(s,e){const t={};return Object.entries(s).forEach(([o,i])=>{if(H(i)){const n=e?`${e}.${o}`:o;Object.values(i).every(a=>J(a))?t[o]=n:t[o]=ct(i,n);return}t[o]=i}),t}const V=ct(it);function bo(s,e){const t={};return Object.keys(s).forEach(o=>{const i=e[o];i!==void 0?t[i]=s[o]:t[o]=s[o]}),t}const mo='',dt='',ko='',vo='',xo='',wo='',ht='',yo='',Eo='',Bo='',To='';class P{constructor(e){this.nodes={root:null,icon:null},this.confirmationState=null,this.removeSpecialFocusBehavior=()=>{this.nodes.root.classList.remove(P.CSS.noFocus)},this.removeSpecialHoverBehavior=()=>{this.nodes.root.classList.remove(P.CSS.noHover)},this.onErrorAnimationEnd=()=>{this.nodes.icon.classList.remove(P.CSS.wobbleAnimation),this.nodes.icon.removeEventListener("animationend",this.onErrorAnimationEnd)},this.params=e,this.nodes.root=this.make(e)}get isDisabled(){return this.params.isDisabled}get toggle(){return this.params.toggle}get title(){return this.params.title}get closeOnActivate(){return this.params.closeOnActivate}get isConfirmationStateEnabled(){return this.confirmationState!==null}get isFocused(){return this.nodes.root.classList.contains(P.CSS.focused)}static get CSS(){return{container:"ce-popover-item",title:"ce-popover-item__title",secondaryTitle:"ce-popover-item__secondary-title",icon:"ce-popover-item__icon",active:"ce-popover-item--active",disabled:"ce-popover-item--disabled",focused:"ce-popover-item--focused",hidden:"ce-popover-item--hidden",confirmationState:"ce-popover-item--confirmation",noHover:"ce-popover-item--no-hover",noFocus:"ce-popover-item--no-focus",wobbleAnimation:"wobble"}}getElement(){return this.nodes.root}handleClick(){if(this.isConfirmationStateEnabled){this.activateOrEnableConfirmationMode(this.confirmationState);return}this.activateOrEnableConfirmationMode(this.params)}toggleActive(e){this.nodes.root.classList.toggle(P.CSS.active,e)}toggleHidden(e){this.nodes.root.classList.toggle(P.CSS.hidden,e)}reset(){this.isConfirmationStateEnabled&&this.disableConfirmationMode()}onFocus(){this.disableSpecialHoverAndFocusBehavior()}make(e){const t=d.make("div",P.CSS.container);return e.name&&(t.dataset.itemName=e.name),this.nodes.icon=d.make("div",P.CSS.icon,{innerHTML:e.icon||xo}),t.appendChild(this.nodes.icon),t.appendChild(d.make("div",P.CSS.title,{innerHTML:e.title||""})),e.secondaryLabel&&t.appendChild(d.make("div",P.CSS.secondaryTitle,{textContent:e.secondaryLabel})),e.isActive&&t.classList.add(P.CSS.active),e.isDisabled&&t.classList.add(P.CSS.disabled),t}enableConfirmationMode(e){const t={...this.params,...e,confirmation:e.confirmation},o=this.make(t);this.nodes.root.innerHTML=o.innerHTML,this.nodes.root.classList.add(P.CSS.confirmationState),this.confirmationState=e,this.enableSpecialHoverAndFocusBehavior()}disableConfirmationMode(){const e=this.make(this.params);this.nodes.root.innerHTML=e.innerHTML,this.nodes.root.classList.remove(P.CSS.confirmationState),this.confirmationState=null,this.disableSpecialHoverAndFocusBehavior()}enableSpecialHoverAndFocusBehavior(){this.nodes.root.classList.add(P.CSS.noHover),this.nodes.root.classList.add(P.CSS.noFocus),this.nodes.root.addEventListener("mouseleave",this.removeSpecialHoverBehavior,{once:!0})}disableSpecialHoverAndFocusBehavior(){this.removeSpecialFocusBehavior(),this.removeSpecialHoverBehavior(),this.nodes.root.removeEventListener("mouseleave",this.removeSpecialHoverBehavior)}activateOrEnableConfirmationMode(e){if(e.confirmation===void 0)try{e.onActivate(e),this.disableConfirmationMode()}catch{this.animateError()}else this.enableConfirmationMode(e.confirmation)}animateError(){this.nodes.icon.classList.contains(P.CSS.wobbleAnimation)||(this.nodes.icon.classList.add(P.CSS.wobbleAnimation),this.nodes.icon.addEventListener("animationend",this.onErrorAnimationEnd))}}const ue=class{constructor(s,e){this.cursor=-1,this.items=[],this.items=s||[],this.focusedCssClass=e}get currentItem(){return this.cursor===-1?null:this.items[this.cursor]}setCursor(s){s=-1&&(this.dropCursor(),this.cursor=s,this.items[this.cursor].classList.add(this.focusedCssClass))}setItems(s){this.items=s}next(){this.cursor=this.leafNodesAndReturnIndex(ue.directions.RIGHT)}previous(){this.cursor=this.leafNodesAndReturnIndex(ue.directions.LEFT)}dropCursor(){this.cursor!==-1&&(this.items[this.cursor].classList.remove(this.focusedCssClass),this.cursor=-1)}leafNodesAndReturnIndex(s){if(this.items.length===0)return this.cursor;let e=this.cursor;return e===-1?e=s===ue.directions.RIGHT?-1:0:this.items[e].classList.remove(this.focusedCssClass),s===ue.directions.RIGHT?e=(e+1)%this.items.length:e=(this.items.length+e-1)%this.items.length,d.canSetCaret(this.items[e])&&te(()=>m.setCursor(this.items[e]),50)(),this.items[e].classList.add(this.focusedCssClass),e}};let re=ue;re.directions={RIGHT:"right",LEFT:"left"};class G{constructor(e){this.iterator=null,this.activated=!1,this.flipCallbacks=[],this.onKeyDown=t=>{if(this.isEventReadyForHandling(t))switch(G.usedKeys.includes(t.keyCode)&&t.preventDefault(),t.keyCode){case B.TAB:this.handleTabPress(t);break;case B.LEFT:case B.UP:this.flipLeft();break;case B.RIGHT:case B.DOWN:this.flipRight();break;case B.ENTER:this.handleEnterPress(t);break}},this.iterator=new re(e.items,e.focusedItemClass),this.activateCallback=e.activateCallback,this.allowedKeys=e.allowedKeys||G.usedKeys}get isActivated(){return this.activated}static get usedKeys(){return[B.TAB,B.LEFT,B.RIGHT,B.ENTER,B.UP,B.DOWN]}activate(e,t){this.activated=!0,e&&this.iterator.setItems(e),t!==void 0&&this.iterator.setCursor(t),document.addEventListener("keydown",this.onKeyDown,!0)}deactivate(){this.activated=!1,this.dropCursor(),document.removeEventListener("keydown",this.onKeyDown)}focusFirst(){this.dropCursor(),this.flipRight()}flipLeft(){this.iterator.previous(),this.flipCallback()}flipRight(){this.iterator.next(),this.flipCallback()}hasFocus(){return!!this.iterator.currentItem}onFlip(e){this.flipCallbacks.push(e)}removeOnFlip(e){this.flipCallbacks=this.flipCallbacks.filter(t=>t!==e)}dropCursor(){this.iterator.dropCursor()}isEventReadyForHandling(e){return this.activated&&this.allowedKeys.includes(e.keyCode)}handleTabPress(e){switch(e.shiftKey?re.directions.LEFT:re.directions.RIGHT){case re.directions.RIGHT:this.flipRight();break;case re.directions.LEFT:this.flipLeft();break}}handleEnterPress(e){this.activated&&(this.iterator.currentItem&&(e.stopPropagation(),e.preventDefault(),this.iterator.currentItem.click()),D(this.activateCallback)&&this.activateCallback(this.iterator.currentItem))}flipCallback(){this.iterator.currentItem&&this.iterator.currentItem.scrollIntoViewIfNeeded(),this.flipCallbacks.forEach(e=>e())}}class pe{static get CSS(){return{wrapper:"cdx-search-field",icon:"cdx-search-field__icon",input:"cdx-search-field__input"}}constructor({items:e,onSearch:t,placeholder:o}){this.listeners=new Ie,this.items=e,this.onSearch=t,this.render(o)}getElement(){return this.wrapper}focus(){this.input.focus()}clear(){this.input.value="",this.searchQuery="",this.onSearch("",this.foundItems)}destroy(){this.listeners.removeAll()}render(e){this.wrapper=d.make("div",pe.CSS.wrapper);const t=d.make("div",pe.CSS.icon,{innerHTML:Bo});this.input=d.make("input",pe.CSS.input,{placeholder:e}),this.wrapper.appendChild(t),this.wrapper.appendChild(this.input),this.listeners.on(this.input,"input",()=>{this.searchQuery=this.input.value,this.onSearch(this.searchQuery,this.foundItems)})}get foundItems(){return this.items.filter(e=>this.checkItem(e))}checkItem(e){var i;const t=((i=e.title)==null?void 0:i.toLowerCase())||"",o=this.searchQuery.toLowerCase();return t.includes(o)}}const fe=class{lock(){ot?this.lockHard():document.body.classList.add(fe.CSS.scrollLocked)}unlock(){ot?this.unlockHard():document.body.classList.remove(fe.CSS.scrollLocked)}lockHard(){this.scrollPosition=window.pageYOffset,document.documentElement.style.setProperty("--window-scroll-offset",`${this.scrollPosition}px`),document.body.classList.add(fe.CSS.scrollLockedHard)}unlockHard(){document.body.classList.remove(fe.CSS.scrollLockedHard),this.scrollPosition!==null&&window.scrollTo(0,this.scrollPosition),this.scrollPosition=null}};let ut=fe;ut.CSS={scrollLocked:"ce-scroll-locked",scrollLockedHard:"ce-scroll-locked--hard"};var Co=Object.defineProperty,So=Object.getOwnPropertyDescriptor,Io=(s,e,t,o)=>{for(var i=o>1?void 0:o?So(e,t):e,n=s.length-1,r;n>=0;n--)(r=s[n])&&(i=(o?r(e,t,i):r(i))||i);return o&&i&&Co(e,t,i),i},ge=(s=>(s.Close="close",s))(ge||{});const z=class extends ve{constructor(s){super(),this.scopeElement=document.body,this.listeners=new Ie,this.scrollLocker=new ut,this.nodes={wrapper:null,popover:null,nothingFoundMessage:null,customContent:null,items:null,overlay:null},this.messages={nothingFound:"Nothing found",search:"Search"},this.onFlip=()=>{this.items.find(t=>t.isFocused).onFocus()},this.items=s.items.map(e=>new P(e)),s.scopeElement!==void 0&&(this.scopeElement=s.scopeElement),s.messages&&(this.messages={...this.messages,...s.messages}),s.customContentFlippableItems&&(this.customContentFlippableItems=s.customContentFlippableItems),this.make(),s.customContent&&this.addCustomContent(s.customContent),s.searchable&&this.addSearch(),this.initializeFlipper()}static get CSS(){return{popover:"ce-popover",popoverOpenTop:"ce-popover--open-top",popoverOpened:"ce-popover--opened",search:"ce-popover__search",nothingFoundMessage:"ce-popover__nothing-found-message",nothingFoundMessageDisplayed:"ce-popover__nothing-found-message--displayed",customContent:"ce-popover__custom-content",customContentHidden:"ce-popover__custom-content--hidden",items:"ce-popover__items",overlay:"ce-popover__overlay",overlayHidden:"ce-popover__overlay--hidden"}}getElement(){return this.nodes.wrapper}hasFocus(){return this.flipper.hasFocus()}show(){this.shouldOpenBottom||(this.nodes.popover.style.setProperty("--popover-height",this.height+"px"),this.nodes.popover.classList.add(z.CSS.popoverOpenTop)),this.nodes.overlay.classList.remove(z.CSS.overlayHidden),this.nodes.popover.classList.add(z.CSS.popoverOpened),this.flipper.activate(this.flippableElements),this.search!==void 0&&setTimeout(()=>{this.search.focus()},100),oe()&&this.scrollLocker.lock()}hide(){this.nodes.popover.classList.remove(z.CSS.popoverOpened),this.nodes.popover.classList.remove(z.CSS.popoverOpenTop),this.nodes.overlay.classList.add(z.CSS.overlayHidden),this.flipper.deactivate(),this.items.forEach(s=>s.reset()),this.search!==void 0&&this.search.clear(),oe()&&this.scrollLocker.unlock(),this.emit("close")}destroy(){this.flipper.deactivate(),this.listeners.removeAll(),oe()&&this.scrollLocker.unlock()}make(){this.nodes.popover=d.make("div",[z.CSS.popover]),this.nodes.nothingFoundMessage=d.make("div",[z.CSS.nothingFoundMessage],{textContent:this.messages.nothingFound}),this.nodes.popover.appendChild(this.nodes.nothingFoundMessage),this.nodes.items=d.make("div",[z.CSS.items]),this.items.forEach(s=>{this.nodes.items.appendChild(s.getElement())}),this.nodes.popover.appendChild(this.nodes.items),this.listeners.on(this.nodes.popover,"click",s=>{const e=this.getTargetItem(s);e!==void 0&&this.handleItemClick(e)}),this.nodes.wrapper=d.make("div"),this.nodes.overlay=d.make("div",[z.CSS.overlay,z.CSS.overlayHidden]),this.listeners.on(this.nodes.overlay,"click",()=>{this.hide()}),this.nodes.wrapper.appendChild(this.nodes.overlay),this.nodes.wrapper.appendChild(this.nodes.popover)}addSearch(){this.search=new pe({items:this.items,placeholder:this.messages.search,onSearch:(e,t)=>{this.items.forEach(i=>{const n=!t.includes(i);i.toggleHidden(n)}),this.toggleNothingFoundMessage(t.length===0),this.toggleCustomContent(e!=="");const o=e===""?this.flippableElements:t.map(i=>i.getElement());this.flipper.isActivated&&(this.flipper.deactivate(),this.flipper.activate(o))}});const s=this.search.getElement();s.classList.add(z.CSS.search),this.nodes.popover.insertBefore(s,this.nodes.popover.firstChild)}addCustomContent(s){this.nodes.customContent=s,this.nodes.customContent.classList.add(z.CSS.customContent),this.nodes.popover.insertBefore(s,this.nodes.popover.firstChild)}getTargetItem(s){return this.items.find(e=>s.composedPath().includes(e.getElement()))}handleItemClick(s){s.isDisabled||(this.items.filter(e=>e!==s).forEach(e=>e.reset()),s.handleClick(),this.toggleItemActivenessIfNeeded(s),s.closeOnActivate&&this.hide())}initializeFlipper(){this.flipper=new G({items:this.flippableElements,focusedItemClass:P.CSS.focused,allowedKeys:[B.TAB,B.UP,B.DOWN,B.ENTER]}),this.flipper.onFlip(this.onFlip)}get flippableElements(){const s=this.items.map(t=>t.getElement());return(this.customContentFlippableItems||[]).concat(s)}get height(){let s=0;if(this.nodes.popover===null)return s;const e=this.nodes.popover.cloneNode(!0);return e.style.visibility="hidden",e.style.position="absolute",e.style.top="-1000px",e.classList.add(z.CSS.popoverOpened),document.body.appendChild(e),s=e.offsetHeight,e.remove(),s}get shouldOpenBottom(){const s=this.nodes.popover.getBoundingClientRect(),e=this.scopeElement.getBoundingClientRect(),t=this.height,o=s.top+t,i=s.top-t,n=Math.min(window.innerHeight,e.bottom);return it.toggle===s.toggle);if(e.length===1){s.toggleActive();return}e.forEach(t=>{t.toggleActive(t===s)})}}};let De=z;Io([ne],De.prototype,"height",1);class Mo extends S{constructor(){super(...arguments),this.opened=!1,this.selection=new m,this.onPopoverClose=()=>{this.close()}}get events(){return{opened:"block-settings-opened",closed:"block-settings-closed"}}get CSS(){return{settings:"ce-settings"}}get flipper(){var e;return(e=this.popover)==null?void 0:e.flipper}make(){this.nodes.wrapper=d.make("div",[this.CSS.settings])}destroy(){this.removeAllNodes()}open(e=this.Editor.BlockManager.currentBlock){this.opened=!0,this.selection.save(),e.selected=!0,this.Editor.BlockSelection.clearCache();const[t,o]=e.getTunes();this.eventsDispatcher.emit(this.events.opened),this.popover=new De({searchable:!0,items:t.map(i=>this.resolveTuneAliases(i)),customContent:o,customContentFlippableItems:this.getControls(o),scopeElement:this.Editor.API.methods.ui.nodes.redactor,messages:{nothingFound:$.ui(V.ui.popover,"Nothing found"),search:$.ui(V.ui.popover,"Filter")}}),this.popover.on(ge.Close,this.onPopoverClose),this.nodes.wrapper.append(this.popover.getElement()),this.popover.show()}getElement(){return this.nodes.wrapper}close(){this.opened=!1,m.isAtEditor||this.selection.restore(),this.selection.clearSaved(),!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted&&this.Editor.BlockManager.currentBlock&&(this.Editor.BlockManager.currentBlock.selected=!1),this.eventsDispatcher.emit(this.events.closed),this.popover&&(this.popover.off(ge.Close,this.onPopoverClose),this.popover.destroy(),this.popover.getElement().remove(),this.popover=null)}getControls(e){const{StylesAPI:t}=this.Editor,o=e.querySelectorAll(`.${t.classes.settingsButton}, ${d.allInputsSelector}`);return Array.from(o)}resolveTuneAliases(e){const t=bo(e,{label:"title"});return e.confirmation&&(t.confirmation=this.resolveTuneAliases(e.confirmation)),t}}class Y extends S{constructor(){super(...arguments),this.opened=!1,this.tools=[],this.flipper=null,this.togglingCallback=null}static get CSS(){return{conversionToolbarWrapper:"ce-conversion-toolbar",conversionToolbarShowed:"ce-conversion-toolbar--showed",conversionToolbarTools:"ce-conversion-toolbar__tools",conversionToolbarLabel:"ce-conversion-toolbar__label",conversionTool:"ce-conversion-tool",conversionToolHidden:"ce-conversion-tool--hidden",conversionToolIcon:"ce-conversion-tool__icon",conversionToolFocused:"ce-conversion-tool--focused",conversionToolActive:"ce-conversion-tool--active"}}make(){this.nodes.wrapper=d.make("div",[Y.CSS.conversionToolbarWrapper,...this.isRtl?[this.Editor.UI.CSS.editorRtlFix]:[]]),this.nodes.tools=d.make("div",Y.CSS.conversionToolbarTools);const e=d.make("div",Y.CSS.conversionToolbarLabel,{textContent:$.ui(V.ui.inlineToolbar.converter,"Convert to")});return this.addTools(),this.enableFlipper(),d.append(this.nodes.wrapper,e),d.append(this.nodes.wrapper,this.nodes.tools),this.nodes.wrapper}destroy(){this.flipper&&(this.flipper.deactivate(),this.flipper=null),this.removeAllNodes()}toggle(e){this.opened?this.close():this.open(),D(e)&&(this.togglingCallback=e)}open(){this.filterTools(),this.opened=!0,this.nodes.wrapper.classList.add(Y.CSS.conversionToolbarShowed),window.requestAnimationFrame(()=>{this.flipper.activate(this.tools.map(e=>e.button).filter(e=>!e.classList.contains(Y.CSS.conversionToolHidden))),this.flipper.focusFirst(),D(this.togglingCallback)&&this.togglingCallback(!0)})}close(){this.opened=!1,this.flipper.deactivate(),this.nodes.wrapper.classList.remove(Y.CSS.conversionToolbarShowed),D(this.togglingCallback)&&this.togglingCallback(!1)}hasTools(){return this.tools.length===1?this.tools[0].name!==this.config.defaultBlock:!0}async replaceWithBlock(e,t){const o=this.Editor.BlockManager.currentBlock.tool,n=(await this.Editor.BlockManager.currentBlock.save()).data,r=this.Editor.Tools.blockTools.get(e);let a="";const l=o.conversionConfig.export;if(D(l))a=l(n);else if(J(l))a=n[l];else{C("Conversion «export» property must be a string or function. String means key of saved data object to export. Function should export processed string to export.");return}const c=Z(a,r.sanitizeConfig);let u={};const h=r.conversionConfig.import;if(D(h))u=h(c);else if(J(h))u[h]=c;else{C("Conversion «import» property must be a string or function. String means key of tool data to import. Function accepts a imported string and return composed tool data.");return}t&&(u=Object.assign(u,t)),this.Editor.BlockManager.replace({tool:e,data:u}),this.Editor.BlockSelection.clearSelection(),this.close(),this.Editor.InlineToolbar.close(),te(()=>{this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock)},10)()}addTools(){const e=this.Editor.Tools.blockTools;Array.from(e.entries()).forEach(([t,o])=>{const i=o.conversionConfig;!i||!i.import||o.toolbox.forEach(n=>this.addToolIfValid(t,n))})}addToolIfValid(e,t){X(t)||!t.icon||this.addTool(e,t)}addTool(e,t){const o=d.make("div",[Y.CSS.conversionTool]),i=d.make("div",[Y.CSS.conversionToolIcon]);o.dataset.tool=e,i.innerHTML=t.icon,d.append(o,i),d.append(o,d.text($.t(V.toolNames,t.title||ke(e)))),d.append(this.nodes.tools,o),this.tools.push({name:e,button:o,toolboxItem:t}),this.listeners.on(o,"click",async()=>{await this.replaceWithBlock(e,t.data)})}async filterTools(){const{currentBlock:e}=this.Editor.BlockManager,t=await e.getActiveToolboxEntry();function o(i,n){return i.icon===n.icon&&i.title===n.title}this.tools.forEach(i=>{let n=!1;if(t){const r=o(t,i.toolboxItem);n=i.button.dataset.tool===e.name&&r}i.button.hidden=n,i.button.classList.toggle(Y.CSS.conversionToolHidden,n)})}enableFlipper(){this.flipper=new G({focusedItemClass:Y.CSS.conversionToolFocused})}}var Pe={},Lo={get exports(){return Pe},set exports(s){Pe=s}};/*! + * Library for handling keyboard shortcuts + * @copyright CodeX (https://codex.so) + * @license MIT + * @author CodeX (https://codex.so) + * @version 1.2.0 + */(function(s,e){(function(t,o){s.exports=o()})(window,function(){return function(t){var o={};function i(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=t,i.c=o,i.d=function(n,r,a){i.o(n,r)||Object.defineProperty(n,r,{enumerable:!0,get:a})},i.r=function(n){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,r){if(1&r&&(n=i(n)),8&r||4&r&&typeof n=="object"&&n&&n.__esModule)return n;var a=Object.create(null);if(i.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:n}),2&r&&typeof n!="string")for(var l in n)i.d(a,l,function(c){return n[c]}.bind(null,l));return a},i.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(r,"a",r),r},i.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},i.p="",i(i.s=0)}([function(t,o,i){function n(l,c){for(var u=0;un!==o))}findShortcut(e,t){return(this.registeredShortcuts.get(e)||[]).find(({name:i})=>i===t)}}const ae=new _o;var Ao=Object.defineProperty,No=Object.getOwnPropertyDescriptor,pt=(s,e,t,o)=>{for(var i=o>1?void 0:o?No(e,t):e,n=s.length-1,r;n>=0;n--)(r=s[n])&&(i=(o?r(e,t,i):r(i))||i);return o&&i&&Ao(e,t,i),i},xe=(s=>(s.Opened="toolbox-opened",s.Closed="toolbox-closed",s.BlockAdded="toolbox-block-added",s))(xe||{});const ft=class extends ve{constructor({api:s,tools:e,i18nLabels:t}){super(),this.opened=!1,this.nodes={toolbox:null},this.onPopoverClose=()=>{this.opened=!1,this.emit("toolbox-closed")},this.api=s,this.tools=e,this.i18nLabels=t}get isEmpty(){return this.toolsToBeDisplayed.length===0}static get CSS(){return{toolbox:"ce-toolbox"}}make(){return this.popover=new De({scopeElement:this.api.ui.nodes.redactor,searchable:!0,messages:{nothingFound:this.i18nLabels.nothingFound,search:this.i18nLabels.filter},items:this.toolboxItemsToBeDisplayed}),this.popover.on(ge.Close,this.onPopoverClose),this.enableShortcuts(),this.nodes.toolbox=this.popover.getElement(),this.nodes.toolbox.classList.add(ft.CSS.toolbox),this.nodes.toolbox}hasFocus(){var s;return(s=this.popover)==null?void 0:s.hasFocus()}destroy(){var s;super.destroy(),this.nodes&&this.nodes.toolbox&&(this.nodes.toolbox.remove(),this.nodes.toolbox=null),this.removeAllShortcuts(),(s=this.popover)==null||s.off(ge.Close,this.onPopoverClose)}toolButtonActivated(s,e){this.insertNewBlock(s,e)}open(){var s;this.isEmpty||((s=this.popover)==null||s.show(),this.opened=!0,this.emit("toolbox-opened"))}close(){var s;(s=this.popover)==null||s.hide(),this.opened=!1,this.emit("toolbox-closed")}toggle(){this.opened?this.close():this.open()}get toolsToBeDisplayed(){const s=[];return this.tools.forEach(e=>{e.toolbox&&s.push(e)}),s}get toolboxItemsToBeDisplayed(){const s=(e,t)=>({icon:e.icon,title:$.t(V.toolNames,e.title||ke(t.name)),name:t.name,onActivate:()=>{this.toolButtonActivated(t.name,e.data)},secondaryLabel:t.shortcut?et(t.shortcut):""});return this.toolsToBeDisplayed.reduce((e,t)=>(Array.isArray(t.toolbox)?t.toolbox.forEach(o=>{e.push(s(o,t))}):t.toolbox!==void 0&&e.push(s(t.toolbox,t)),e),[])}enableShortcuts(){this.toolsToBeDisplayed.forEach(s=>{const e=s.shortcut;e&&this.enableShortcutForTool(s.name,e)})}enableShortcutForTool(s,e){ae.add({name:e,on:this.api.ui.nodes.redactor,handler:t=>{t.preventDefault(),this.insertNewBlock(s)}})}removeAllShortcuts(){this.toolsToBeDisplayed.forEach(s=>{const e=s.shortcut;e&&ae.remove(this.api.ui.nodes.redactor,e)})}async insertNewBlock(s,e){const t=this.api.blocks.getCurrentBlockIndex(),o=this.api.blocks.getBlockByIndex(t);if(!o)return;const i=o.isEmpty?t:t+1;let n;if(e){const a=await this.api.blocks.composeBlockData(s);n=Object.assign(a,e)}const r=this.api.blocks.insert(s,n,void 0,i,void 0,o.isEmpty);r.call(Q.APPEND_CALLBACK),this.api.caret.setToBlock(i),this.emit("toolbox-block-added",{block:r}),this.api.toolbar.close()}};let Fe=ft;pt([ne],Fe.prototype,"toolsToBeDisplayed",1),pt([ne],Fe.prototype,"toolboxItemsToBeDisplayed",1);const gt="block hovered";class Ro extends S{constructor({config:e,eventsDispatcher:t}){super({config:e,eventsDispatcher:t}),this.tooltip=new Re}get CSS(){return{toolbar:"ce-toolbar",content:"ce-toolbar__content",actions:"ce-toolbar__actions",actionsOpened:"ce-toolbar__actions--opened",toolbarOpened:"ce-toolbar--opened",openedToolboxHolderModifier:"codex-editor--toolbox-opened",plusButton:"ce-toolbar__plus",plusButtonShortcut:"ce-toolbar__plus-shortcut",settingsToggler:"ce-toolbar__settings-btn",settingsTogglerHidden:"ce-toolbar__settings-btn--hidden"}}get opened(){return this.nodes.wrapper.classList.contains(this.CSS.toolbarOpened)}get toolbox(){return{opened:this.toolboxInstance.opened,close:()=>{this.toolboxInstance.close()},open:()=>{this.Editor.BlockManager.currentBlock=this.hoveredBlock,this.toolboxInstance.open()},toggle:()=>this.toolboxInstance.toggle(),hasFocus:()=>this.toolboxInstance.hasFocus()}}get blockActions(){return{hide:()=>{this.nodes.actions.classList.remove(this.CSS.actionsOpened)},show:()=>{this.nodes.actions.classList.add(this.CSS.actionsOpened)}}}get blockTunesToggler(){return{hide:()=>this.nodes.settingsToggler.classList.add(this.CSS.settingsTogglerHidden),show:()=>this.nodes.settingsToggler.classList.remove(this.CSS.settingsTogglerHidden)}}toggleReadOnly(e){e?(this.destroy(),this.Editor.BlockSettings.destroy(),this.disableModuleBindings()):(this.drawUI(),this.enableModuleBindings())}moveAndOpen(e=this.Editor.BlockManager.currentBlock){if(this.toolboxInstance.opened&&this.toolboxInstance.close(),this.Editor.BlockSettings.opened&&this.Editor.BlockSettings.close(),!e)return;this.hoveredBlock=e;const t=e.holder,{isMobile:o}=this.Editor.UI,i=e.pluginsContent,n=window.getComputedStyle(i),r=parseInt(n.paddingTop,10),a=t.offsetHeight;let l;o?l=t.offsetTop+a:l=t.offsetTop+r,this.nodes.wrapper.style.top=`${Math.floor(l)}px`,this.Editor.BlockManager.blocks.length===1&&e.isEmpty?this.blockTunesToggler.hide():this.blockTunesToggler.show(),this.open()}close(){this.Editor.ReadOnly.isEnabled||(this.nodes.wrapper.classList.remove(this.CSS.toolbarOpened),this.blockActions.hide(),this.toolboxInstance.close(),this.Editor.BlockSettings.close())}open(e=!0){te(()=>{this.nodes.wrapper.classList.add(this.CSS.toolbarOpened),e?this.blockActions.show():this.blockActions.hide()},50)()}make(){this.nodes.wrapper=d.make("div",this.CSS.toolbar),["content","actions"].forEach(t=>{this.nodes[t]=d.make("div",this.CSS[t])}),d.append(this.nodes.wrapper,this.nodes.content),d.append(this.nodes.content,this.nodes.actions),this.nodes.plusButton=d.make("div",this.CSS.plusButton,{innerHTML:Eo}),d.append(this.nodes.actions,this.nodes.plusButton),this.readOnlyMutableListeners.on(this.nodes.plusButton,"click",()=>{this.tooltip.hide(!0),this.plusButtonClicked()},!1);const e=d.make("div");e.appendChild(document.createTextNode($.ui(V.ui.toolbar.toolbox,"Add"))),e.appendChild(d.make("div",this.CSS.plusButtonShortcut,{textContent:"⇥ Tab"})),this.tooltip.onHover(this.nodes.plusButton,e,{hidingDelay:400}),this.nodes.settingsToggler=d.make("span",this.CSS.settingsToggler,{innerHTML:yo}),d.append(this.nodes.actions,this.nodes.settingsToggler),this.tooltip.onHover(this.nodes.settingsToggler,$.ui(V.ui.blockTunes.toggler,"Click to tune"),{hidingDelay:400}),d.append(this.nodes.actions,this.makeToolbox()),d.append(this.nodes.actions,this.Editor.BlockSettings.getElement()),d.append(this.Editor.UI.nodes.wrapper,this.nodes.wrapper)}makeToolbox(){return this.toolboxInstance=new Fe({api:this.Editor.API.methods,tools:this.Editor.Tools.blockTools,i18nLabels:{filter:$.ui(V.ui.popover,"Filter"),nothingFound:$.ui(V.ui.popover,"Nothing found")}}),this.toolboxInstance.on(xe.Opened,()=>{this.Editor.UI.nodes.wrapper.classList.add(this.CSS.openedToolboxHolderModifier)}),this.toolboxInstance.on(xe.Closed,()=>{this.Editor.UI.nodes.wrapper.classList.remove(this.CSS.openedToolboxHolderModifier)}),this.toolboxInstance.on(xe.BlockAdded,({block:e})=>{const{BlockManager:t,Caret:o}=this.Editor,i=t.getBlockById(e.id);i.inputs.length===0&&(i===t.lastBlock?(t.insertAtEnd(),o.setToBlock(t.lastBlock)):o.setToBlock(t.nextBlock))}),this.toolboxInstance.make()}plusButtonClicked(){this.Editor.BlockManager.currentBlock=this.hoveredBlock,this.toolboxInstance.toggle()}enableModuleBindings(){this.readOnlyMutableListeners.on(this.nodes.settingsToggler,"mousedown",e=>{e.stopPropagation(),this.settingsTogglerClicked(),this.toolboxInstance.opened&&this.toolboxInstance.close(),this.tooltip.hide(!0)},!0),oe()||this.eventsDispatcher.on(gt,e=>{this.Editor.BlockSettings.opened||this.toolboxInstance.opened||this.moveAndOpen(e.block)})}disableModuleBindings(){this.readOnlyMutableListeners.clearAll()}settingsTogglerClicked(){this.Editor.BlockManager.currentBlock=this.hoveredBlock,this.Editor.BlockSettings.opened?this.Editor.BlockSettings.close():this.Editor.BlockSettings.open(this.hoveredBlock)}drawUI(){this.Editor.BlockSettings.make(),this.make()}destroy(){this.removeAllNodes(),this.toolboxInstance&&this.toolboxInstance.destroy(),this.tooltip.destroy()}}var we=(s=>(s[s.Block=0]="Block",s[s.Inline=1]="Inline",s[s.Tune=2]="Tune",s))(we||{}),ye=(s=>(s.Shortcut="shortcut",s.Toolbox="toolbox",s.EnabledInlineTools="inlineToolbar",s.EnabledBlockTunes="tunes",s.Config="config",s))(ye||{}),bt=(s=>(s.Shortcut="shortcut",s.SanitizeConfig="sanitize",s))(bt||{}),le=(s=>(s.IsEnabledLineBreaks="enableLineBreaks",s.Toolbox="toolbox",s.ConversionConfig="conversionConfig",s.IsReadOnlySupported="isReadOnlySupported",s.PasteConfig="pasteConfig",s))(le||{}),He=(s=>(s.IsInline="isInline",s.Title="title",s))(He||{}),mt=(s=>(s.IsTune="isTune",s))(mt||{});class ze{constructor({name:e,constructable:t,config:o,api:i,isDefault:n,isInternal:r=!1,defaultPlaceholder:a}){this.api=i,this.name=e,this.constructable=t,this.config=o,this.isDefault=n,this.isInternal=r,this.defaultPlaceholder=a}get settings(){const e=this.config.config||{};return this.isDefault&&!("placeholder"in e)&&this.defaultPlaceholder&&(e.placeholder=this.defaultPlaceholder),e}reset(){if(D(this.constructable.reset))return this.constructable.reset()}prepare(){if(D(this.constructable.prepare))return this.constructable.prepare({toolName:this.name,config:this.settings})}get shortcut(){const e=this.constructable.shortcut;return this.config.shortcut||e}get sanitizeConfig(){return this.constructable.sanitize||{}}isInline(){return this.type===1}isBlock(){return this.type===0}isTune(){return this.type===2}}class Do extends S{constructor({config:e,eventsDispatcher:t}){super({config:e,eventsDispatcher:t}),this.CSS={inlineToolbar:"ce-inline-toolbar",inlineToolbarShowed:"ce-inline-toolbar--showed",inlineToolbarLeftOriented:"ce-inline-toolbar--left-oriented",inlineToolbarRightOriented:"ce-inline-toolbar--right-oriented",inlineToolbarShortcut:"ce-inline-toolbar__shortcut",buttonsWrapper:"ce-inline-toolbar__buttons",actionsWrapper:"ce-inline-toolbar__actions",inlineToolButton:"ce-inline-tool",inputField:"cdx-input",focusedButton:"ce-inline-tool--focused",conversionToggler:"ce-inline-toolbar__dropdown",conversionTogglerArrow:"ce-inline-toolbar__dropdown-arrow",conversionTogglerHidden:"ce-inline-toolbar__dropdown--hidden",conversionTogglerContent:"ce-inline-toolbar__dropdown-content",togglerAndButtonsWrapper:"ce-inline-toolbar__toggler-and-button-wrapper"},this.opened=!1,this.toolbarVerticalMargin=oe()?20:6,this.buttonsList=null,this.width=0,this.flipper=null,this.tooltip=new Re}toggleReadOnly(e){e?(this.destroy(),this.Editor.ConversionToolbar.destroy()):this.make()}tryToShow(e=!1,t=!0){if(!this.allowedToShow()){e&&this.close();return}this.move(),this.open(t),this.Editor.Toolbar.close()}move(){const e=m.rect,t=this.Editor.UI.nodes.wrapper.getBoundingClientRect(),o={x:e.x-t.left,y:e.y+e.height-t.top+this.toolbarVerticalMargin};e.width&&(o.x+=Math.floor(e.width/2));const i=o.x-this.width/2,n=o.x+this.width/2;this.nodes.wrapper.classList.toggle(this.CSS.inlineToolbarLeftOriented,ithis.Editor.UI.contentRect.right),this.nodes.wrapper.style.left=Math.floor(o.x)+"px",this.nodes.wrapper.style.top=Math.floor(o.y)+"px"}close(){this.opened&&(this.Editor.ReadOnly.isEnabled||(this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed),Array.from(this.toolsInstances.entries()).forEach(([e,t])=>{const o=this.getToolShortcut(e);o&&ae.remove(this.Editor.UI.nodes.redactor,o),D(t.clear)&&t.clear()}),this.opened=!1,this.flipper.deactivate(),this.Editor.ConversionToolbar.close()))}open(e=!0){if(this.opened)return;this.addToolsFiltered(),this.nodes.wrapper.classList.add(this.CSS.inlineToolbarShowed),this.buttonsList=this.nodes.buttons.querySelectorAll(`.${this.CSS.inlineToolButton}`),this.opened=!0,e&&this.Editor.ConversionToolbar.hasTools()?this.setConversionTogglerContent():this.nodes.conversionToggler.hidden=!0;let t=Array.from(this.buttonsList);t.unshift(this.nodes.conversionToggler),t=t.filter(o=>!o.hidden),this.flipper.activate(t)}containsNode(e){return this.nodes.wrapper.contains(e)}destroy(){this.flipper&&(this.flipper.deactivate(),this.flipper=null),this.removeAllNodes(),this.tooltip.destroy()}make(){this.nodes.wrapper=d.make("div",[this.CSS.inlineToolbar,...this.isRtl?[this.Editor.UI.CSS.editorRtlFix]:[]]),this.nodes.togglerAndButtonsWrapper=d.make("div",this.CSS.togglerAndButtonsWrapper),this.nodes.buttons=d.make("div",this.CSS.buttonsWrapper),this.nodes.actions=d.make("div",this.CSS.actionsWrapper),this.listeners.on(this.nodes.wrapper,"mousedown",e=>{e.target.closest(`.${this.CSS.actionsWrapper}`)||e.preventDefault()}),d.append(this.nodes.wrapper,[this.nodes.togglerAndButtonsWrapper,this.nodes.actions]),d.append(this.Editor.UI.nodes.wrapper,this.nodes.wrapper),this.addConversionToggler(),d.append(this.nodes.togglerAndButtonsWrapper,this.nodes.buttons),this.prepareConversionToolbar(),this.recalculateWidth(),this.enableFlipper()}allowedToShow(){const e=["IMG","INPUT"],t=m.get(),o=m.text;if(!t||!t.anchorNode||t.isCollapsed||o.length<1)return!1;const i=d.isElement(t.anchorNode)?t.anchorNode:t.anchorNode.parentElement;if(t&&e.includes(i.tagName)||i.closest('[contenteditable="true"]')===null)return!1;const r=this.Editor.BlockManager.getBlock(t.anchorNode);return r?r.tool.inlineTools.size!==0:!1}recalculateWidth(){this.width=this.nodes.wrapper.offsetWidth}addConversionToggler(){this.nodes.conversionToggler=d.make("div",this.CSS.conversionToggler),this.nodes.conversionTogglerContent=d.make("div",this.CSS.conversionTogglerContent);const e=d.make("div",this.CSS.conversionTogglerArrow,{innerHTML:dt});this.nodes.conversionToggler.appendChild(this.nodes.conversionTogglerContent),this.nodes.conversionToggler.appendChild(e),this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler),this.listeners.on(this.nodes.conversionToggler,"click",()=>{this.Editor.ConversionToolbar.toggle(t=>{!t&&this.opened?this.flipper.activate():this.opened&&this.flipper.deactivate()})}),oe()===!1&&this.tooltip.onHover(this.nodes.conversionToggler,$.ui(V.ui.inlineToolbar.converter,"Convert to"),{placement:"top",hidingDelay:100})}async setConversionTogglerContent(){const{BlockManager:e}=this.Editor,{currentBlock:t}=e,o=t.name,i=t.tool.conversionConfig,n=i&&i.export;this.nodes.conversionToggler.hidden=!n,this.nodes.conversionToggler.classList.toggle(this.CSS.conversionTogglerHidden,!n);const r=await t.getActiveToolboxEntry()||{};this.nodes.conversionTogglerContent.innerHTML=r.icon||r.title||ke(o)}prepareConversionToolbar(){const e=this.Editor.ConversionToolbar.make();d.append(this.nodes.wrapper,e)}addToolsFiltered(){const e=m.get(),t=this.Editor.BlockManager.getBlock(e.anchorNode);this.nodes.buttons.innerHTML="",this.nodes.actions.innerHTML="",this.toolsInstances=new Map,Array.from(t.tool.inlineTools.values()).forEach(o=>{this.addTool(o)}),this.recalculateWidth()}addTool(e){const t=e.create(),o=t.render();if(!o){C("Render method must return an instance of Node","warn",e.name);return}if(o.dataset.tool=e.name,this.nodes.buttons.appendChild(o),this.toolsInstances.set(e.name,t),D(t.renderActions)){const a=t.renderActions();this.nodes.actions.appendChild(a)}this.listeners.on(o,"click",a=>{this.toolClicked(t),a.preventDefault()});const i=this.getToolShortcut(e.name);if(i)try{this.enableShortcuts(t,i)}catch{}const n=d.make("div"),r=$.t(V.toolNames,e.title||ke(e.name));n.appendChild(d.text(r)),i&&n.appendChild(d.make("div",this.CSS.inlineToolbarShortcut,{textContent:et(i)})),oe()===!1&&this.tooltip.onHover(o,n,{placement:"top",hidingDelay:100}),t.checkState(m.get())}getToolShortcut(e){const{Tools:t}=this.Editor,o=t.inlineTools.get(e),i=t.internal.inlineTools;return Array.from(i.keys()).includes(e)?this.inlineTools[e][bt.Shortcut]:o.shortcut}enableShortcuts(e,t){ae.add({name:t,handler:o=>{const{currentBlock:i}=this.Editor.BlockManager;i&&i.tool.enabledInlineTools&&(o.preventDefault(),this.toolClicked(e))},on:this.Editor.UI.nodes.redactor})}toolClicked(e){const t=m.range;e.surround(t),this.checkToolsState(),e.renderActions!==void 0&&this.flipper.deactivate()}checkToolsState(){this.toolsInstances.forEach(e=>{e.checkState(m.get())})}get inlineTools(){const e={};return Array.from(this.Editor.Tools.inlineTools.entries()).forEach(([t,o])=>{e[t]=o.create()}),e}enableFlipper(){this.flipper=new G({focusedItemClass:this.CSS.focusedButton,allowedKeys:[B.ENTER,B.TAB]})}}class Po extends S{keydown(e){switch(this.beforeKeydownProcessing(e),e.keyCode){case B.BACKSPACE:this.backspace(e);break;case B.ENTER:this.enter(e);break;case B.DOWN:case B.RIGHT:this.arrowRightAndDown(e);break;case B.UP:case B.LEFT:this.arrowLeftAndUp(e);break;case B.TAB:this.tabPressed(e);break}}beforeKeydownProcessing(e){this.needToolbarClosing(e)&&qe(e.keyCode)&&(this.Editor.Toolbar.close(),this.Editor.ConversionToolbar.close(),e.ctrlKey||e.metaKey||e.altKey||e.shiftKey||(this.Editor.BlockManager.clearFocused(),this.Editor.BlockSelection.clearSelection(e)))}keyup(e){e.shiftKey||this.Editor.UI.checkEmptiness()}tabPressed(e){this.Editor.BlockSelection.clearSelection(e);const{BlockManager:t,InlineToolbar:o,ConversionToolbar:i}=this.Editor,n=t.currentBlock;if(!n)return;const r=n.isEmpty,a=n.tool.isDefault&&r,l=!r&&i.opened,c=!r&&!m.isCollapsed&&o.opened;a?this.activateToolbox():!l&&!c&&this.activateBlockSettings()}dragOver(e){const t=this.Editor.BlockManager.getBlockByChildNode(e.target);t.dropTarget=!0}dragLeave(e){const t=this.Editor.BlockManager.getBlockByChildNode(e.target);t.dropTarget=!1}handleCommandC(e){const{BlockSelection:t}=this.Editor;t.anyBlockSelected&&t.copySelectedBlocks(e)}handleCommandX(e){const{BlockSelection:t,BlockManager:o,Caret:i}=this.Editor;t.anyBlockSelected&&t.copySelectedBlocks(e).then(()=>{const n=o.removeSelectedBlocks(),r=o.insertDefaultBlockAtIndex(n,!0);i.setToBlock(r,i.positions.START),t.clearSelection(e)})}enter(e){const{BlockManager:t,UI:o}=this.Editor;if(t.currentBlock.tool.isLineBreaksEnabled||o.someToolbarOpened&&o.someFlipperButtonFocused||e.shiftKey)return;let n=this.Editor.BlockManager.currentBlock;this.Editor.Caret.isAtStart&&!this.Editor.BlockManager.currentBlock.hasMedia?this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex):this.Editor.Caret.isAtEnd?n=this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex+1):n=this.Editor.BlockManager.split(),this.Editor.Caret.setToBlock(n),this.Editor.Toolbar.moveAndOpen(n),e.preventDefault()}backspace(e){const{BlockManager:t,BlockSelection:o,Caret:i}=this.Editor,n=t.currentBlock,r=n.tool;if(n.selected||n.isEmpty&&n.currentInput===n.firstInput){e.preventDefault();const c=t.currentBlockIndex;t.previousBlock&&t.previousBlock.inputs.length===0?t.removeBlock(c-1):t.removeBlock(),i.setToBlock(t.currentBlock,c?i.positions.END:i.positions.START),this.Editor.Toolbar.close(),o.clearSelection(e);return}if(r.isLineBreaksEnabled&&!i.isAtStart)return;const a=t.currentBlockIndex===0;i.isAtStart&&m.isCollapsed&&n.currentInput===n.firstInput&&!a&&(e.preventDefault(),this.mergeBlocks())}mergeBlocks(){const{BlockManager:e,Caret:t,Toolbar:o}=this.Editor,i=e.previousBlock,n=e.currentBlock;if(n.name!==i.name||!i.mergeable){if(i.inputs.length===0||i.isEmpty){e.removeBlock(e.currentBlockIndex-1),t.setToBlock(e.currentBlock),o.close();return}t.navigatePrevious()&&o.close();return}t.createShadow(i.pluginsContent),e.mergeBlocks(i,n).then(()=>{t.restoreCaret(i.pluginsContent),i.pluginsContent.normalize(),o.close()})}arrowRightAndDown(e){const t=G.usedKeys.includes(e.keyCode)&&(!e.shiftKey||e.keyCode===B.TAB);if(this.Editor.UI.someToolbarOpened&&t)return;this.Editor.BlockManager.clearFocused(),this.Editor.Toolbar.close();const o=this.Editor.Caret.isAtEnd||this.Editor.BlockSelection.anyBlockSelected;if(e.shiftKey&&e.keyCode===B.DOWN&&o){this.Editor.CrossBlockSelection.toggleBlockSelectedState();return}(e.keyCode===B.DOWN||e.keyCode===B.RIGHT&&!this.isRtl?this.Editor.Caret.navigateNext():this.Editor.Caret.navigatePrevious())?e.preventDefault():te(()=>{this.Editor.BlockManager.currentBlock&&this.Editor.BlockManager.currentBlock.updateCurrentInput()},20)(),this.Editor.BlockSelection.clearSelection(e)}arrowLeftAndUp(e){if(this.Editor.UI.someToolbarOpened){if(G.usedKeys.includes(e.keyCode)&&(!e.shiftKey||e.keyCode===B.TAB))return;this.Editor.UI.closeAllToolbars()}this.Editor.BlockManager.clearFocused(),this.Editor.Toolbar.close();const t=this.Editor.Caret.isAtStart||this.Editor.BlockSelection.anyBlockSelected;if(e.shiftKey&&e.keyCode===B.UP&&t){this.Editor.CrossBlockSelection.toggleBlockSelectedState(!1);return}(e.keyCode===B.UP||e.keyCode===B.LEFT&&!this.isRtl?this.Editor.Caret.navigatePrevious():this.Editor.Caret.navigateNext())?e.preventDefault():te(()=>{this.Editor.BlockManager.currentBlock&&this.Editor.BlockManager.currentBlock.updateCurrentInput()},20)(),this.Editor.BlockSelection.clearSelection(e)}needToolbarClosing(e){const t=e.keyCode===B.ENTER&&this.Editor.Toolbar.toolbox.opened,o=e.keyCode===B.ENTER&&this.Editor.BlockSettings.opened,i=e.keyCode===B.ENTER&&this.Editor.InlineToolbar.opened,n=e.keyCode===B.ENTER&&this.Editor.ConversionToolbar.opened,r=e.keyCode===B.TAB;return!(e.shiftKey||r||t||o||i||n)}activateToolbox(){this.Editor.Toolbar.opened||this.Editor.Toolbar.moveAndOpen(),this.Editor.Toolbar.toolbox.open()}activateBlockSettings(){this.Editor.Toolbar.opened||(this.Editor.BlockManager.currentBlock.focused=!0,this.Editor.Toolbar.moveAndOpen()),this.Editor.BlockSettings.opened||this.Editor.BlockSettings.open()}}class je{constructor(e){this.blocks=[],this.workingArea=e}get length(){return this.blocks.length}get array(){return this.blocks}get nodes(){return Qe(this.workingArea.children)}static set(e,t,o){return isNaN(Number(t))?(Reflect.set(e,t,o),!0):(e.insert(+t,o),!0)}static get(e,t){return isNaN(Number(t))?Reflect.get(e,t):e.get(+t)}push(e){this.blocks.push(e),this.insertToDOM(e)}swap(e,t){const o=this.blocks[t];d.swap(this.blocks[e].holder,o.holder),this.blocks[t]=this.blocks[e],this.blocks[e]=o}move(e,t){const o=this.blocks.splice(t,1)[0],i=e-1,n=Math.max(0,i),r=this.blocks[n];e>0?this.insertToDOM(o,"afterend",r):this.insertToDOM(o,"beforebegin",r),this.blocks.splice(e,0,o);const a=this.composeBlockEvent("move",{fromIndex:t,toIndex:e});o.call(Q.MOVED,a)}insert(e,t,o=!1){if(!this.length){this.push(t);return}e>this.length&&(e=this.length),o&&(this.blocks[e].holder.remove(),this.blocks[e].call(Q.REMOVED));const i=o?1:0;if(this.blocks.splice(e,i,t),e>0){const n=this.blocks[e-1];this.insertToDOM(t,"afterend",n)}else{const n=this.blocks[e+1];n?this.insertToDOM(t,"beforebegin",n):this.insertToDOM(t)}}remove(e){isNaN(e)&&(e=this.length-1),this.blocks[e].holder.remove(),this.blocks[e].call(Q.REMOVED),this.blocks.splice(e,1)}removeAll(){this.workingArea.innerHTML="",this.blocks.forEach(e=>e.call(Q.REMOVED)),this.blocks.length=0}insertAfter(e,t){const o=this.blocks.indexOf(e);this.insert(o+1,t)}get(e){return this.blocks[e]}indexOf(e){return this.blocks.indexOf(e)}insertToDOM(e,t,o){t?o.holder.insertAdjacentElement(t,e.holder):this.workingArea.appendChild(e.holder),e.call(Q.RENDERED)}composeBlockEvent(e,t){return new CustomEvent(e,{detail:t})}}const kt="block-removed",vt="block-added",Fo="block-moved",Ho="block-changed";class zo extends S{constructor(){super(...arguments),this._currentBlockIndex=-1,this._blocks=null}get currentBlockIndex(){return this._currentBlockIndex}set currentBlockIndex(e){this._currentBlockIndex=e}get firstBlock(){return this._blocks[0]}get lastBlock(){return this._blocks[this._blocks.length-1]}get currentBlock(){return this._blocks[this.currentBlockIndex]}set currentBlock(e){this.currentBlockIndex=this.getBlockIndex(e)}get nextBlock(){return this.currentBlockIndex===this._blocks.length-1?null:this._blocks[this.currentBlockIndex+1]}get nextContentfulBlock(){return this.blocks.slice(this.currentBlockIndex+1).find(t=>!!t.inputs.length)}get previousContentfulBlock(){return this.blocks.slice(0,this.currentBlockIndex).reverse().find(t=>!!t.inputs.length)}get previousBlock(){return this.currentBlockIndex===0?null:this._blocks[this.currentBlockIndex-1]}get blocks(){return this._blocks.array}get isEditorEmpty(){return this.blocks.every(e=>e.isEmpty)}prepare(){const e=new je(this.Editor.UI.nodes.redactor);this._blocks=new Proxy(e,{set:je.set,get:je.get}),this.listeners.on(document,"copy",t=>this.Editor.BlockEvents.handleCommandC(t))}toggleReadOnly(e){e?this.disableModuleBindings():this.enableModuleBindings()}composeBlock({tool:e,data:t={},id:o=void 0,tunes:i={}}){const n=this.Editor.ReadOnly.isEnabled,r=this.Editor.Tools.blockTools.get(e),a=new F({id:o,data:t,tool:r,api:this.Editor.API,readOnly:n,tunesData:i},this.eventsDispatcher);return n||this.bindBlockEvents(a),a}insert({id:e=void 0,tool:t=this.config.defaultBlock,data:o={},index:i,needToFocus:n=!0,replace:r=!1,tunes:a={}}={}){let l=i;l===void 0&&(l=this.currentBlockIndex+(r?0:1));const c=this.composeBlock({id:e,tool:t,data:o,tunes:a});return r&&this.blockDidMutated(kt,this.getBlockByIndex(l),{index:l}),this._blocks.insert(l,c,r),this.blockDidMutated(vt,c,{index:l}),n?this.currentBlockIndex=l:l<=this.currentBlockIndex&&this.currentBlockIndex++,c}replace({tool:e=this.config.defaultBlock,data:t={}}){return this.insert({tool:e,data:t,index:this.currentBlockIndex,replace:!0})}paste(e,t,o=!1){const i=this.insert({tool:e,replace:o});try{i.call(Q.ON_PASTE,t)}catch(n){C(`${e}: onPaste callback call is failed`,"error",n)}return i}insertDefaultBlockAtIndex(e,t=!1){const o=this.composeBlock({tool:this.config.defaultBlock});return this._blocks[e]=o,this.blockDidMutated(vt,o,{index:e}),t?this.currentBlockIndex=e:e<=this.currentBlockIndex&&this.currentBlockIndex++,o}insertAtEnd(){return this.currentBlockIndex=this.blocks.length-1,this.insert()}async mergeBlocks(e,t){const o=this._blocks.indexOf(t);if(t.isEmpty)return;const i=await t.data;X(i)||await e.mergeWith(i),this.removeBlock(o),this.currentBlockIndex=this._blocks.indexOf(e)}removeBlock(e=this.currentBlockIndex){if(!this.validateIndex(e))throw new Error("Can't find a Block to remove");const t=this._blocks[e];t.destroy(),this._blocks.remove(e),this.blockDidMutated(kt,t,{index:e}),this.currentBlockIndex>=e&&this.currentBlockIndex--,this.blocks.length?e===0&&(this.currentBlockIndex=0):(this.currentBlockIndex=-1,this.insert())}removeSelectedBlocks(){let e;for(let t=this.blocks.length-1;t>=0;t--)this.blocks[t].selected&&(this.removeBlock(t),e=t);return e}removeAllBlocks(){for(let e=this.blocks.length-1;e>=0;e--)this._blocks.remove(e);this.currentBlockIndex=-1,this.insert(),this.currentBlock.firstInput.focus()}split(){const e=this.Editor.Caret.extractFragmentFromCaretPosition(),t=d.make("div");t.appendChild(e);const o={text:d.isEmpty(t)?"":t.innerHTML};return this.insert({data:o})}getBlockByIndex(e){return e===-1&&(e=this._blocks.length-1),this._blocks[e]}getBlockIndex(e){return this._blocks.indexOf(e)}getBlockById(e){return this._blocks.array.find(t=>t.id===e)}getBlock(e){d.isElement(e)||(e=e.parentNode);const t=this._blocks.nodes,o=e.closest(`.${F.CSS.wrapper}`),i=t.indexOf(o);if(i>=0)return this._blocks[i]}highlightCurrentNode(){this.clearFocused(),this.currentBlock.focused=!0}clearFocused(){this.blocks.forEach(e=>{e.focused=!1})}setCurrentBlockByChildNode(e){d.isElement(e)||(e=e.parentNode);const t=e.closest(`.${F.CSS.wrapper}`);if(!t)return;const o=t.closest(`.${this.Editor.UI.CSS.editorWrapper}`);if(o!=null&&o.isEqualNode(this.Editor.UI.nodes.wrapper))return this.currentBlockIndex=this._blocks.nodes.indexOf(t),this.currentBlock.updateCurrentInput(),this.currentBlock}getBlockByChildNode(e){d.isElement(e)||(e=e.parentNode);const t=e.closest(`.${F.CSS.wrapper}`);return this.blocks.find(o=>o.holder===t)}swap(e,t){this._blocks.swap(e,t),this.currentBlockIndex=t}move(e,t=this.currentBlockIndex){if(isNaN(e)||isNaN(t)){C("Warning during 'move' call: incorrect indices provided.","warn");return}if(!this.validateIndex(e)||!this.validateIndex(t)){C("Warning during 'move' call: indices cannot be lower than 0 or greater than the amount of blocks.","warn");return}this._blocks.move(e,t),this.currentBlockIndex=e,this.blockDidMutated(Fo,this.currentBlock,{fromIndex:t,toIndex:e})}dropPointer(){this.currentBlockIndex=-1,this.clearFocused()}clear(e=!1){this._blocks.removeAll(),this.dropPointer(),e&&this.insert(),this.Editor.UI.checkEmptiness()}async destroy(){await Promise.all(this.blocks.map(e=>e.destroy()))}bindBlockEvents(e){const{BlockEvents:t}=this.Editor;this.readOnlyMutableListeners.on(e.holder,"keydown",o=>{t.keydown(o)}),this.readOnlyMutableListeners.on(e.holder,"keyup",o=>{t.keyup(o)}),this.readOnlyMutableListeners.on(e.holder,"dragover",o=>{t.dragOver(o)}),this.readOnlyMutableListeners.on(e.holder,"dragleave",o=>{t.dragLeave(o)}),e.on("didMutated",o=>this.blockDidMutated(Ho,o,{index:this.getBlockIndex(o)}))}disableModuleBindings(){this.readOnlyMutableListeners.clearAll()}enableModuleBindings(){this.readOnlyMutableListeners.on(document,"cut",e=>this.Editor.BlockEvents.handleCommandX(e)),this.blocks.forEach(e=>{this.bindBlockEvents(e)})}validateIndex(e){return!(e<0||e>=this._blocks.length)}blockDidMutated(e,t,o){const i=new CustomEvent(e,{detail:{target:new he(t),...o}});return this.eventsDispatcher.emit(st,{event:i}),t}}class jo extends S{constructor(){super(...arguments),this.anyBlockSelectedCache=null,this.needToSelectAll=!1,this.nativeInputSelected=!1,this.readyToBlockSelection=!1}get sanitizerConfig(){return{p:{},h1:{},h2:{},h3:{},h4:{},h5:{},h6:{},ol:{},ul:{},li:{},br:!0,img:{src:!0,width:!0,height:!0},a:{href:!0},b:{},i:{},u:{}}}get allBlocksSelected(){const{BlockManager:e}=this.Editor;return e.blocks.every(t=>t.selected===!0)}set allBlocksSelected(e){const{BlockManager:t}=this.Editor;t.blocks.forEach(o=>{o.selected=e}),this.clearCache()}get anyBlockSelected(){const{BlockManager:e}=this.Editor;return this.anyBlockSelectedCache===null&&(this.anyBlockSelectedCache=e.blocks.some(t=>t.selected===!0)),this.anyBlockSelectedCache}get selectedBlocks(){return this.Editor.BlockManager.blocks.filter(e=>e.selected)}prepare(){this.selection=new m,ae.add({name:"CMD+A",handler:e=>{const{BlockManager:t,ReadOnly:o}=this.Editor;if(o.isEnabled){e.preventDefault(),this.selectAllBlocks();return}t.currentBlock&&this.handleCommandA(e)},on:this.Editor.UI.nodes.redactor})}toggleReadOnly(){m.get().removeAllRanges(),this.allBlocksSelected=!1}unSelectBlockByIndex(e){const{BlockManager:t}=this.Editor;let o;isNaN(e)?o=t.currentBlock:o=t.getBlockByIndex(e),o.selected=!1,this.clearCache()}clearSelection(e,t=!1){const{BlockManager:o,Caret:i,RectangleSelection:n}=this.Editor;this.needToSelectAll=!1,this.nativeInputSelected=!1,this.readyToBlockSelection=!1;const r=e&&e instanceof KeyboardEvent,a=r&&qe(e.keyCode);if(this.anyBlockSelected&&r&&a&&!m.isSelectionExists){const l=o.removeSelectedBlocks();o.insertDefaultBlockAtIndex(l,!0),i.setToBlock(o.currentBlock),te(()=>{const c=e.key;i.insertContentAtCaretPosition(c.length>1?"":c)},20)()}if(this.Editor.CrossBlockSelection.clear(e),!this.anyBlockSelected||n.isRectActivated()){this.Editor.RectangleSelection.clearSelection();return}t&&this.selection.restore(),this.allBlocksSelected=!1}copySelectedBlocks(e){e.preventDefault();const t=d.make("div");this.selectedBlocks.forEach(n=>{const r=Z(n.holder.innerHTML,this.sanitizerConfig),a=d.make("p");a.innerHTML=r,t.appendChild(a)});const o=Array.from(t.childNodes).map(n=>n.textContent).join(` + +`),i=t.innerHTML;return e.clipboardData.setData("text/plain",o),e.clipboardData.setData("text/html",i),Promise.all(this.selectedBlocks.map(n=>n.save())).then(n=>{try{e.clipboardData.setData(this.Editor.Paste.MIME_TYPE,JSON.stringify(n))}catch{}})}selectBlockByIndex(e){const{BlockManager:t}=this.Editor;t.clearFocused();let o;isNaN(e)?o=t.currentBlock:o=t.getBlockByIndex(e),this.selection.save(),m.get().removeAllRanges(),o.selected=!0,this.clearCache(),this.Editor.InlineToolbar.close()}clearCache(){this.anyBlockSelectedCache=null}destroy(){ae.remove(this.Editor.UI.nodes.redactor,"CMD+A")}handleCommandA(e){if(this.Editor.RectangleSelection.clearSelection(),d.isNativeInput(e.target)&&!this.readyToBlockSelection){this.readyToBlockSelection=!0;return}const o=this.Editor.BlockManager.getBlock(e.target).inputs;if(o.length>1&&!this.readyToBlockSelection){this.readyToBlockSelection=!0;return}if(o.length===1&&!this.needToSelectAll){this.needToSelectAll=!0;return}this.needToSelectAll?(e.preventDefault(),this.selectAllBlocks(),this.needToSelectAll=!1,this.readyToBlockSelection=!1,this.Editor.ConversionToolbar.close()):this.readyToBlockSelection&&(e.preventDefault(),this.selectBlockByIndex(),this.needToSelectAll=!0)}selectAllBlocks(){this.selection.save(),m.get().removeAllRanges(),this.allBlocksSelected=!0,this.Editor.InlineToolbar.close()}}class Ee extends S{get positions(){return{START:"start",END:"end",DEFAULT:"default"}}static get CSS(){return{shadowCaret:"cdx-shadow-caret"}}get isAtStart(){const e=m.get(),t=d.getDeepestNode(this.Editor.BlockManager.currentBlock.currentInput);let o=e.focusNode;if(d.isNativeInput(t))return t.selectionEnd===0;if(!e.anchorNode)return!1;let i=o.textContent.search(/\S/);i===-1&&(i=0);let n=e.focusOffset;return o.nodeType!==Node.TEXT_NODE&&o.childNodes.length&&(o.childNodes[n]?(o=o.childNodes[n],n=0):(o=o.childNodes[n-1],n=o.textContent.length)),(d.isLineBreakTag(t)||d.isEmpty(t))&&this.getHigherLevelSiblings(o,"left").every(l=>{const c=d.isLineBreakTag(l),u=l.children.length===1&&d.isLineBreakTag(l.children[0]),h=c||u;return d.isEmpty(l)&&!h})&&n===i?!0:t===null||o===t&&n<=i}get isAtEnd(){const e=m.get();let t=e.focusNode;const o=d.getDeepestNode(this.Editor.BlockManager.currentBlock.currentInput,!0);if(d.isNativeInput(o))return o.selectionEnd===o.value.length;if(!e.focusNode)return!1;let i=e.focusOffset;if(t.nodeType!==Node.TEXT_NODE&&t.childNodes.length&&(t.childNodes[i-1]?(t=t.childNodes[i-1],i=t.textContent.length):(t=t.childNodes[0],i=0)),d.isLineBreakTag(o)||d.isEmpty(o)){const r=this.getHigherLevelSiblings(t,"right");if(r.every((l,c)=>c===r.length-1&&d.isLineBreakTag(l)||d.isEmpty(l)&&!d.isLineBreakTag(l))&&i===t.textContent.length)return!0}const n=o.textContent.replace(/\s+$/,"");return t===o&&i>=n.length}setToBlock(e,t=this.positions.DEFAULT,o=0){const{BlockManager:i}=this.Editor;let n;switch(t){case this.positions.START:n=e.firstInput;break;case this.positions.END:n=e.lastInput;break;default:n=e.currentInput}if(!n)return;const r=d.getDeepestNode(n,t===this.positions.END),a=d.getContentLength(r);switch(!0){case t===this.positions.START:o=0;break;case t===this.positions.END:case o>a:o=a;break}te(()=>{this.set(r,o)},20)(),i.setCurrentBlockByChildNode(e.holder),i.currentBlock.currentInput=n}setToInput(e,t=this.positions.DEFAULT,o=0){const{currentBlock:i}=this.Editor.BlockManager,n=d.getDeepestNode(e);switch(t){case this.positions.START:this.set(n,0);break;case this.positions.END:this.set(n,d.getContentLength(n));break;default:o&&this.set(n,o)}i.currentInput=e}set(e,t=0){const{top:o,bottom:i}=m.setCursor(e,t),{innerHeight:n}=window;o<0&&window.scrollBy(0,o),i>n&&window.scrollBy(0,i-n)}setToTheLastBlock(){const e=this.Editor.BlockManager.lastBlock;if(e)if(e.tool.isDefault&&e.isEmpty)this.setToBlock(e);else{const t=this.Editor.BlockManager.insertAtEnd();this.setToBlock(t)}}extractFragmentFromCaretPosition(){const e=m.get();if(e.rangeCount){const t=e.getRangeAt(0),o=this.Editor.BlockManager.currentBlock.currentInput;if(t.deleteContents(),o)if(d.isNativeInput(o)){const i=o,n=document.createDocumentFragment(),r=i.value.substring(0,i.selectionStart),a=i.value.substring(i.selectionStart);return n.textContent=a,i.value=r,n}else{const i=t.cloneRange();return i.selectNodeContents(o),i.setStart(t.endContainer,t.endOffset),i.extractContents()}}}navigateNext(){const{BlockManager:e}=this.Editor,{currentBlock:t,nextContentfulBlock:o}=e,{nextInput:i}=t,n=this.isAtEnd;let r=o;if(!r&&!i){if(t.tool.isDefault||!n)return!1;r=e.insertAtEnd()}return n?(i?this.setToInput(i,this.positions.START):this.setToBlock(r,this.positions.START),!0):!1}navigatePrevious(){const{currentBlock:e,previousContentfulBlock:t}=this.Editor.BlockManager;if(!e)return!1;const{previousInput:o}=e;return!t&&!o?!1:this.isAtStart?(o?this.setToInput(o,this.positions.END):this.setToBlock(t,this.positions.END),!0):!1}createShadow(e){const t=document.createElement("span");t.classList.add(Ee.CSS.shadowCaret),e.insertAdjacentElement("beforeend",t)}restoreCaret(e){const t=e.querySelector(`.${Ee.CSS.shadowCaret}`);if(!t)return;new m().expandToTag(t),setTimeout(()=>{const i=document.createRange();i.selectNode(t),i.extractContents()},50)}insertContentAtCaretPosition(e){const t=document.createDocumentFragment(),o=document.createElement("div"),i=m.get(),n=m.range;o.innerHTML=e,Array.from(o.childNodes).forEach(l=>t.appendChild(l)),t.childNodes.length===0&&t.appendChild(new Text);const r=t.lastChild;n.deleteContents(),n.insertNode(t);const a=document.createRange();a.setStart(r,r.textContent.length),i.removeAllRanges(),i.addRange(a)}getHigherLevelSiblings(e,t){let o=e;const i=[];for(;o.parentNode&&o.parentNode.contentEditable!=="true";)o=o.parentNode;const n=t==="left"?"previousSibling":"nextSibling";for(;o[n];)o=o[n],i.push(o);return i}}class Uo extends S{constructor(){super(...arguments),this.onMouseUp=()=>{this.listeners.off(document,"mouseover",this.onMouseOver),this.listeners.off(document,"mouseup",this.onMouseUp)},this.onMouseOver=e=>{const{BlockManager:t,BlockSelection:o}=this.Editor,i=t.getBlockByChildNode(e.relatedTarget)||this.lastSelectedBlock,n=t.getBlockByChildNode(e.target);if(!(!i||!n)&&n!==i){if(i===this.firstSelectedBlock){m.get().removeAllRanges(),i.selected=!0,n.selected=!0,o.clearCache();return}if(n===this.firstSelectedBlock){i.selected=!1,n.selected=!1,o.clearCache();return}this.Editor.InlineToolbar.close(),this.toggleBlocksSelectedState(i,n),this.lastSelectedBlock=n}}}async prepare(){this.listeners.on(document,"mousedown",e=>{this.enableCrossBlockSelection(e)})}watchSelection(e){if(e.button!==Lt.LEFT)return;const{BlockManager:t}=this.Editor;this.firstSelectedBlock=t.getBlock(e.target),this.lastSelectedBlock=this.firstSelectedBlock,this.listeners.on(document,"mouseover",this.onMouseOver),this.listeners.on(document,"mouseup",this.onMouseUp)}get isCrossBlockSelectionStarted(){return!!this.firstSelectedBlock&&!!this.lastSelectedBlock}toggleBlockSelectedState(e=!0){const{BlockManager:t,BlockSelection:o}=this.Editor;this.lastSelectedBlock||(this.lastSelectedBlock=this.firstSelectedBlock=t.currentBlock),this.firstSelectedBlock===this.lastSelectedBlock&&(this.firstSelectedBlock.selected=!0,o.clearCache(),m.get().removeAllRanges());const i=t.blocks.indexOf(this.lastSelectedBlock)+(e?1:-1),n=t.blocks[i];n&&(this.lastSelectedBlock.selected!==n.selected?(n.selected=!0,o.clearCache()):(this.lastSelectedBlock.selected=!1,o.clearCache()),this.lastSelectedBlock=n,this.Editor.InlineToolbar.close(),n.holder.scrollIntoView({block:"nearest"}))}clear(e){const{BlockManager:t,BlockSelection:o,Caret:i}=this.Editor,n=t.blocks.indexOf(this.firstSelectedBlock),r=t.blocks.indexOf(this.lastSelectedBlock);if(o.anyBlockSelected&&n>-1&&r>-1)if(e&&e instanceof KeyboardEvent)switch(e.keyCode){case B.DOWN:case B.RIGHT:i.setToBlock(t.blocks[Math.max(n,r)],i.positions.END);break;case B.UP:case B.LEFT:i.setToBlock(t.blocks[Math.min(n,r)],i.positions.START);break;default:i.setToBlock(t.blocks[Math.max(n,r)],i.positions.END)}else i.setToBlock(t.blocks[Math.max(n,r)],i.positions.END);this.firstSelectedBlock=this.lastSelectedBlock=null}enableCrossBlockSelection(e){const{UI:t}=this.Editor;m.isCollapsed||this.Editor.BlockSelection.clearSelection(e),t.nodes.redactor.contains(e.target)?this.watchSelection(e):this.Editor.BlockSelection.clearSelection(e)}toggleBlocksSelectedState(e,t){const{BlockManager:o,BlockSelection:i}=this.Editor,n=o.blocks.indexOf(e),r=o.blocks.indexOf(t),a=e.selected!==t.selected;for(let l=Math.min(n,r);l<=Math.max(n,r);l++){const c=o.blocks[l];c!==this.firstSelectedBlock&&c!==(a?e:t)&&(o.blocks[l].selected=!o.blocks[l].selected,i.clearCache())}}}class $o extends S{constructor(){super(...arguments),this.isStartedAtEditor=!1}toggleReadOnly(e){e?this.disableModuleBindings():this.enableModuleBindings()}enableModuleBindings(){const{UI:e}=this.Editor;this.readOnlyMutableListeners.on(e.nodes.holder,"drop",async t=>{await this.processDrop(t)},!0),this.readOnlyMutableListeners.on(e.nodes.holder,"dragstart",()=>{this.processDragStart()}),this.readOnlyMutableListeners.on(e.nodes.holder,"dragover",t=>{this.processDragOver(t)},!0)}disableModuleBindings(){this.readOnlyMutableListeners.clearAll()}async processDrop(e){const{BlockManager:t,Caret:o,Paste:i}=this.Editor;e.preventDefault(),t.blocks.forEach(r=>{r.dropTarget=!1}),m.isAtEditor&&!m.isCollapsed&&this.isStartedAtEditor&&document.execCommand("delete"),this.isStartedAtEditor=!1;const n=t.setCurrentBlockByChildNode(e.target);if(n)this.Editor.Caret.setToBlock(n,o.positions.END);else{const r=t.setCurrentBlockByChildNode(t.lastBlock.holder);this.Editor.Caret.setToBlock(r,o.positions.END)}await i.processDataTransfer(e.dataTransfer,!0)}processDragStart(){m.isAtEditor&&!m.isCollapsed&&(this.isStartedAtEditor=!0),this.Editor.InlineToolbar.close()}processDragOver(e){e.preventDefault()}}class Wo extends S{constructor({config:e,eventsDispatcher:t}){super({config:e,eventsDispatcher:t}),this.disabled=!1,this.batchingTimeout=null,this.batchingOnChangeQueue=new Map,this.batchTime=400,this.mutationObserver=new MutationObserver(o=>{this.redactorChanged(o)}),this.eventsDispatcher.on(st,o=>{this.particularBlockChanged(o.event)}),this.eventsDispatcher.on(rt,()=>{this.disable()}),this.eventsDispatcher.on(at,()=>{this.enable()})}enable(){this.mutationObserver.observe(this.Editor.UI.nodes.redactor,{childList:!0,subtree:!0,characterData:!0,attributes:!0}),this.disabled=!1}disable(){this.mutationObserver.disconnect(),this.disabled=!0}particularBlockChanged(e){this.disabled||!D(this.config.onChange)||(this.batchingOnChangeQueue.set(`block:${e.detail.target.id}:event:${e.type}`,e),this.batchingTimeout&&clearTimeout(this.batchingTimeout),this.batchingTimeout=setTimeout(()=>{let t;this.batchingOnChangeQueue.size===1?t=this.batchingOnChangeQueue.values().next().value:t=Array.from(this.batchingOnChangeQueue.values()),this.config.onChange&&this.config.onChange(this.Editor.API.methods,t),this.batchingOnChangeQueue.clear()},this.batchTime))}redactorChanged(e){this.eventsDispatcher.emit(Me,{mutations:e})}}const xt=class extends S{constructor(){super(...arguments),this.MIME_TYPE="application/x-editor-js",this.toolsTags={},this.tagsByTool={},this.toolsPatterns=[],this.toolsFiles={},this.exceptionList=[],this.processTool=s=>{try{const e=s.create({},{},!1);if(s.pasteConfig===!1){this.exceptionList.push(s.name);return}if(!D(e.onPaste))return;this.getTagsConfig(s),this.getFilesConfig(s),this.getPatternsConfig(s)}catch(e){C(`Paste handling for «${s.name}» Tool hasn't been set up because of the error`,"warn",e)}},this.handlePasteEvent=async s=>{const{BlockManager:e,Toolbar:t}=this.Editor;!e.currentBlock||this.isNativeBehaviour(s.target)&&!s.clipboardData.types.includes("Files")||e.currentBlock&&this.exceptionList.includes(e.currentBlock.name)||(s.preventDefault(),this.processDataTransfer(s.clipboardData),e.clearFocused(),t.close())}}async prepare(){this.processTools()}toggleReadOnly(s){s?this.unsetCallback():this.setCallback()}async processDataTransfer(s,e=!1){const{Tools:t}=this.Editor,o=s.types;if((o.includes?o.includes("Files"):o.contains("Files"))&&!X(this.toolsFiles)){await this.processFiles(s.files);return}const n=s.getData(this.MIME_TYPE),r=s.getData("text/plain");let a=s.getData("text/html");if(n)try{this.insertEditorJSData(JSON.parse(n));return}catch{}e&&r.trim()&&a.trim()&&(a="

"+(a.trim()?a:r)+"

");const l=Object.keys(this.toolsTags).reduce((h,f)=>(h[f.toLowerCase()]=this.toolsTags[f].sanitizationConfig??{},h),{}),c=Object.assign({},l,t.getAllInlineToolsSanitizeConfig(),{br:{}}),u=Z(a,c);!u.trim()||u.trim()===r||!d.isHTMLString(u)?await this.processText(r):await this.processText(u,!0)}async processText(s,e=!1){const{Caret:t,BlockManager:o}=this.Editor,i=e?this.processHTML(s):this.processPlain(s);if(!i.length)return;if(i.length===1){i[0].isBlock?this.processSingleBlock(i.pop()):this.processInlinePaste(i.pop());return}const r=o.currentBlock&&o.currentBlock.tool.isDefault&&o.currentBlock.isEmpty;i.map(async(a,l)=>this.insertBlock(a,l===0&&r)),o.currentBlock&&t.setToBlock(o.currentBlock,t.positions.END)}setCallback(){this.listeners.on(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}unsetCallback(){this.listeners.off(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}processTools(){const s=this.Editor.Tools.blockTools;Array.from(s.values()).forEach(this.processTool)}collectTagNames(s){return J(s)?[s]:H(s)?Object.keys(s):[]}getTagsConfig(s){if(s.pasteConfig===!1)return;const e=s.pasteConfig.tags||[],t=[];e.forEach(o=>{const i=this.collectTagNames(o);t.push(...i),i.forEach(n=>{if(Object.prototype.hasOwnProperty.call(this.toolsTags,n)){C(`Paste handler for «${s.name}» Tool on «${n}» tag is skipped because it is already used by «${this.toolsTags[n].tool.name}» Tool.`,"warn");return}const r=H(o)?o[n]:null;this.toolsTags[n.toUpperCase()]={tool:s,sanitizationConfig:r}})}),this.tagsByTool[s.name]=t.map(o=>o.toUpperCase())}getFilesConfig(s){if(s.pasteConfig===!1)return;const{files:e={}}=s.pasteConfig;let{extensions:t,mimeTypes:o}=e;!t&&!o||(t&&!Array.isArray(t)&&(C(`«extensions» property of the onDrop config for «${s.name}» Tool should be an array`),t=[]),o&&!Array.isArray(o)&&(C(`«mimeTypes» property of the onDrop config for «${s.name}» Tool should be an array`),o=[]),o&&(o=o.filter(i=>Nt(i)?!0:(C(`MIME type value «${i}» for the «${s.name}» Tool is not a valid MIME type`,"warn"),!1))),this.toolsFiles[s.name]={extensions:t||[],mimeTypes:o||[]})}getPatternsConfig(s){s.pasteConfig===!1||!s.pasteConfig.patterns||X(s.pasteConfig.patterns)||Object.entries(s.pasteConfig.patterns).forEach(([e,t])=>{t instanceof RegExp||C(`Pattern ${t} for «${s.name}» Tool is skipped because it should be a Regexp instance.`,"warn"),this.toolsPatterns.push({key:e,pattern:t,tool:s})})}isNativeBehaviour(s){return d.isNativeInput(s)}async processFiles(s){const{BlockManager:e}=this.Editor;let t;t=await Promise.all(Array.from(s).map(n=>this.processFile(n))),t=t.filter(n=>!!n);const i=e.currentBlock.tool.isDefault&&e.currentBlock.isEmpty;t.forEach((n,r)=>{e.paste(n.type,n.event,r===0&&i)})}async processFile(s){const e=At(s),t=Object.entries(this.toolsFiles).find(([n,{mimeTypes:r,extensions:a}])=>{const[l,c]=s.type.split("/"),u=a.find(f=>f.toLowerCase()===e.toLowerCase()),h=r.find(f=>{const[v,p]=f.split("/");return v===l&&(p===c||p==="*")});return!!u||!!h});if(!t)return;const[o]=t;return{event:this.composePasteEvent("file",{file:s}),type:o}}processHTML(s){const{Tools:e}=this.Editor,t=d.make("DIV");return t.innerHTML=s,this.getNodes(t).map(i=>{let n,r=e.defaultTool,a=!1;switch(i.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:n=d.make("div"),n.appendChild(i);break;case Node.ELEMENT_NODE:n=i,a=!0,this.toolsTags[n.tagName]&&(r=this.toolsTags[n.tagName].tool);break}const{tags:l}=r.pasteConfig||{tags:[]},c=l.reduce((f,v)=>(this.collectTagNames(v).forEach(k=>{const _=H(v)?v[k]:null;f[k.toLowerCase()]=_||{}}),f),{}),u=Object.assign({},c,r.baseSanitizeConfig);if(n.tagName.toLowerCase()==="table"){const f=Z(n.outerHTML,u);n=d.make("div",void 0,{innerHTML:f}).firstChild}else n.innerHTML=Z(n.innerHTML,u);const h=this.composePasteEvent("tag",{data:n});return{content:n,isBlock:a,tool:r.name,event:h}}).filter(i=>{const n=d.isEmpty(i.content),r=d.isSingleTag(i.content);return!n||r})}processPlain(s){const{defaultBlock:e}=this.config;if(!s)return[];const t=e;return s.split(/\r?\n/).filter(o=>o.trim()).map(o=>{const i=d.make("div");i.textContent=o;const n=this.composePasteEvent("tag",{data:i});return{content:i,tool:t,isBlock:!1,event:n}})}async processSingleBlock(s){const{Caret:e,BlockManager:t}=this.Editor,{currentBlock:o}=t;if(!o||s.tool!==o.name||!d.containsOnlyInlineElements(s.content.innerHTML)){this.insertBlock(s,(o==null?void 0:o.tool.isDefault)&&o.isEmpty);return}e.insertContentAtCaretPosition(s.content.innerHTML)}async processInlinePaste(s){const{BlockManager:e,Caret:t}=this.Editor,{content:o}=s;if(e.currentBlock&&e.currentBlock.tool.isDefault&&o.textContent.length{const i=o.pattern.exec(s);return i?s===i.shift():!1});return e?{event:this.composePasteEvent("pattern",{key:e.key,data:s}),tool:e.tool.name}:void 0}insertBlock(s,e=!1){const{BlockManager:t,Caret:o}=this.Editor,{currentBlock:i}=t;let n;if(e&&i&&i.isEmpty){n=t.paste(s.tool,s.event,!0),o.setToBlock(n,o.positions.END);return}n=t.paste(s.tool,s.event),o.setToBlock(n,o.positions.END)}insertEditorJSData(s){const{BlockManager:e,Caret:t,Tools:o}=this.Editor;lt(s,n=>o.blockTools.get(n).sanitizeConfig).forEach(({tool:n,data:r},a)=>{let l=!1;a===0&&(l=e.currentBlock&&e.currentBlock.tool.isDefault&&e.currentBlock.isEmpty);const c=e.insert({tool:n,data:r,replace:l});t.setToBlock(c,t.positions.END)})}processElementNode(s,e,t){const o=Object.keys(this.toolsTags),i=s,{tool:n}=this.toolsTags[i.tagName]||{},r=this.tagsByTool[n==null?void 0:n.name]||[],a=o.includes(i.tagName),l=d.blockElements.includes(i.tagName.toLowerCase()),c=Array.from(i.children).some(({tagName:h})=>o.includes(h)&&!r.includes(h)),u=Array.from(i.children).some(({tagName:h})=>d.blockElements.includes(h.toLowerCase()));if(!l&&!a&&!c)return t.appendChild(i),[...e,t];if(a&&!c||l&&!u&&!c)return[...e,t,i]}getNodes(s){const e=Array.from(s.childNodes);let t;const o=(i,n)=>{if(d.isEmpty(n)&&!d.isSingleTag(n))return i;const r=i[i.length-1];let a=new DocumentFragment;switch(r&&d.isFragment(r)&&(a=i.pop()),n.nodeType){case Node.ELEMENT_NODE:if(t=this.processElementNode(n,i,a),t)return t;break;case Node.TEXT_NODE:return a.appendChild(n),[...i,a];default:return[...i,a]}return[...i,...Array.from(n.childNodes).reduce(o,[])]};return e.reduce(o,[])}composePasteEvent(s,e){return new CustomEvent(s,{detail:e})}};let wt=xt;wt.PATTERN_PROCESSING_MAX_LENGTH=450;class Yo extends S{constructor(){super(...arguments),this.toolsDontSupportReadOnly=[],this.readOnlyEnabled=!1}get isEnabled(){return this.readOnlyEnabled}async prepare(){const{Tools:e}=this.Editor,{blockTools:t}=e,o=[];Array.from(t.entries()).forEach(([i,n])=>{n.isReadOnlySupported||o.push(i)}),this.toolsDontSupportReadOnly=o,this.config.readOnly&&o.length>0&&this.throwCriticalError(),this.toggle(this.config.readOnly)}async toggle(e=!this.readOnlyEnabled){e&&this.toolsDontSupportReadOnly.length>0&&this.throwCriticalError();const t=this.readOnlyEnabled;this.readOnlyEnabled=e;for(const i in this.Editor)this.Editor[i].toggleReadOnly&&this.Editor[i].toggleReadOnly(e);if(t===e)return this.readOnlyEnabled;const o=await this.Editor.Saver.save();return await this.Editor.BlockManager.clear(),await this.Editor.Renderer.render(o.blocks),this.readOnlyEnabled}throwCriticalError(){throw new nt(`To enable read-only mode all connected tools should support it. Tools ${this.toolsDontSupportReadOnly.join(", ")} don't support read-only mode.`)}}class be extends S{constructor(){super(...arguments),this.isRectSelectionActivated=!1,this.SCROLL_SPEED=3,this.HEIGHT_OF_SCROLL_ZONE=40,this.BOTTOM_SCROLL_ZONE=1,this.TOP_SCROLL_ZONE=2,this.MAIN_MOUSE_BUTTON=0,this.mousedown=!1,this.isScrolling=!1,this.inScrollZone=null,this.startX=0,this.startY=0,this.mouseX=0,this.mouseY=0,this.stackOfSelected=[],this.listenerIds=[]}static get CSS(){return{overlay:"codex-editor-overlay",overlayContainer:"codex-editor-overlay__container",rect:"codex-editor-overlay__rectangle",topScrollZone:"codex-editor-overlay__scroll-zone--top",bottomScrollZone:"codex-editor-overlay__scroll-zone--bottom"}}prepare(){this.enableModuleBindings()}startSelection(e,t){const o=document.elementFromPoint(e-window.pageXOffset,t-window.pageYOffset);o.closest(`.${this.Editor.Toolbar.CSS.toolbar}`)||(this.Editor.BlockSelection.allBlocksSelected=!1,this.clearSelection(),this.stackOfSelected=[]);const n=[`.${F.CSS.content}`,`.${this.Editor.Toolbar.CSS.toolbar}`,`.${this.Editor.InlineToolbar.CSS.inlineToolbar}`],r=o.closest("."+this.Editor.UI.CSS.editorWrapper),a=n.some(l=>!!o.closest(l));!r||a||(this.mousedown=!0,this.startX=e,this.startY=t)}endSelection(){this.mousedown=!1,this.startX=0,this.startY=0,this.overlayRectangle.style.display="none"}isRectActivated(){return this.isRectSelectionActivated}clearSelection(){this.isRectSelectionActivated=!1}enableModuleBindings(){const{container:e}=this.genHTML();this.listeners.on(e,"mousedown",t=>{this.processMouseDown(t)},!1),this.listeners.on(document.body,"mousemove",Te(t=>{this.processMouseMove(t)},10),{passive:!0}),this.listeners.on(document.body,"mouseleave",()=>{this.processMouseLeave()}),this.listeners.on(window,"scroll",Te(t=>{this.processScroll(t)},10),{passive:!0}),this.listeners.on(document.body,"mouseup",()=>{this.processMouseUp()},!1)}processMouseDown(e){if(e.button!==this.MAIN_MOUSE_BUTTON)return;e.target.closest(d.allInputsSelector)!==null||this.startSelection(e.pageX,e.pageY)}processMouseMove(e){this.changingRectangle(e),this.scrollByZones(e.clientY)}processMouseLeave(){this.clearSelection(),this.endSelection()}processScroll(e){this.changingRectangle(e)}processMouseUp(){this.clearSelection(),this.endSelection()}scrollByZones(e){if(this.inScrollZone=null,e<=this.HEIGHT_OF_SCROLL_ZONE&&(this.inScrollZone=this.TOP_SCROLL_ZONE),document.documentElement.clientHeight-e<=this.HEIGHT_OF_SCROLL_ZONE&&(this.inScrollZone=this.BOTTOM_SCROLL_ZONE),!this.inScrollZone){this.isScrolling=!1;return}this.isScrolling||(this.scrollVertical(this.inScrollZone===this.TOP_SCROLL_ZONE?-this.SCROLL_SPEED:this.SCROLL_SPEED),this.isScrolling=!0)}genHTML(){const{UI:e}=this.Editor,t=e.nodes.holder.querySelector("."+e.CSS.editorWrapper),o=d.make("div",be.CSS.overlay,{}),i=d.make("div",be.CSS.overlayContainer,{}),n=d.make("div",be.CSS.rect,{});return i.appendChild(n),o.appendChild(i),t.appendChild(o),this.overlayRectangle=n,{container:t,overlay:o}}scrollVertical(e){if(!(this.inScrollZone&&this.mousedown))return;const t=window.pageYOffset;window.scrollBy(0,e),this.mouseY+=window.pageYOffset-t,setTimeout(()=>{this.scrollVertical(e)},0)}changingRectangle(e){if(!this.mousedown)return;e.pageY!==void 0&&(this.mouseX=e.pageX,this.mouseY=e.pageY);const{rightPos:t,leftPos:o,index:i}=this.genInfoForMouseSelection(),n=this.startX>t&&this.mouseX>t,r=this.startX=this.startY?(this.overlayRectangle.style.top=`${this.startY-window.pageYOffset}px`,this.overlayRectangle.style.bottom=`calc(100% - ${this.mouseY-window.pageYOffset}px`):(this.overlayRectangle.style.bottom=`calc(100% - ${this.startY-window.pageYOffset}px`,this.overlayRectangle.style.top=`${this.mouseY-window.pageYOffset}px`),this.mouseX>=this.startX?(this.overlayRectangle.style.left=`${this.startX-window.pageXOffset}px`,this.overlayRectangle.style.right=`calc(100% - ${this.mouseX-window.pageXOffset}px`):(this.overlayRectangle.style.right=`calc(100% - ${this.startX-window.pageXOffset}px`,this.overlayRectangle.style.left=`${this.mouseX-window.pageXOffset}px`)}genInfoForMouseSelection(){const t=document.body.offsetWidth/2,o=this.mouseY-window.pageYOffset,i=document.elementFromPoint(t,o),n=this.Editor.BlockManager.getBlockByChildNode(i);let r;n!==void 0&&(r=this.Editor.BlockManager.blocks.findIndex(h=>h.holder===n.holder));const a=this.Editor.BlockManager.lastBlock.holder.querySelector("."+F.CSS.content),l=Number.parseInt(window.getComputedStyle(a).width,10)/2,c=t-l,u=t+l;return{index:r,leftPos:c,rightPos:u}}addBlockInSelection(e){this.rectCrossesBlocks&&this.Editor.BlockSelection.selectBlockByIndex(e),this.stackOfSelected.push(e)}trySelectNextBlock(e){const t=this.stackOfSelected[this.stackOfSelected.length-1]===e,o=this.stackOfSelected.length,i=1,n=-1,r=0;if(t)return;const a=this.stackOfSelected[o-1]-this.stackOfSelected[o-2]>0;let l=r;o>1&&(l=a?i:n);const c=e>this.stackOfSelected[o-1]&&l===i,u=ethis.stackOfSelected[o-1]||this.stackOfSelected[o-1]===void 0)){let k=this.stackOfSelected[o-1]+1||e;for(k;k<=e;k++)this.addBlockInSelection(k);return}if(!f&&e=e;k--)this.addBlockInSelection(k);return}if(!f)return;let v=o-1,p;for(e>this.stackOfSelected[o-1]?p=()=>e>this.stackOfSelected[v]:p=()=>e({function:()=>this.insertBlock(i)}));this.Editor.ModificationsObserver.disable();const o=await Je(t);return this.Editor.ModificationsObserver.enable(),this.Editor.UI.checkEmptiness(),o}async insertBlock(e){var l;const{Tools:t,BlockManager:o}=this.Editor,{type:i,data:n,tunes:r,id:a}=e;if(t.available.has(i))try{o.insert({id:a,tool:i,data:n,tunes:r})}catch(c){throw C(`Block «${i}» skipped because of plugins error`,"warn",{data:n,error:c}),Error(c)}else{const c={savedData:{id:a,type:i,data:n},title:i};if(t.unavailable.has(i)){const f=(l=t.unavailable.get(i).toolbox[0])==null?void 0:l.title;c.title=f||c.title}const u=o.insert({id:a,tool:t.stubTool,data:c});u.stretched=!0,C(`Tool «${i}» is not found. Check 'tools' property at your initial Editor.js config.`,"warn")}}}class Xo extends S{async save(){const{BlockManager:e,Tools:t}=this.Editor,o=e.blocks,i=[];try{o.forEach(a=>{i.push(this.getSavedData(a))});const n=await Promise.all(i),r=await lt(n,a=>t.blockTools.get(a).sanitizeConfig);return this.makeOutput(r)}catch(n){K("Saving failed due to the Error %o","error",n)}}async getSavedData(e){const t=await e.save(),o=t&&await e.validate(t.data);return{...t,isValid:o}}makeOutput(e){let t=0;const o=[];return C("[Editor.js saving]:","groupCollapsed"),e.forEach(({id:i,tool:n,data:r,tunes:a,time:l,isValid:c})=>{if(t+=l,C(`${n.charAt(0).toUpperCase()+n.slice(1)}`,"group"),c)C(r),C(void 0,"groupEnd");else{C(`Block «${n}» skipped because saved data is invalid`),C(void 0,"groupEnd");return}if(n===this.Editor.Tools.stubTool){o.push(r);return}const u={id:i,type:n,data:r,...!X(a)&&{tunes:a}};o.push(u)}),C("Total","log",t),C(void 0,"groupEnd"),{time:+new Date,blocks:o,version:"2.27.2"}}}var Ue={},Vo={get exports(){return Ue},set exports(s){Ue=s}};(function(s,e){(function(t,o){s.exports=o()})(window,function(){return function(t){var o={};function i(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=t,i.c=o,i.d=function(n,r,a){i.o(n,r)||Object.defineProperty(n,r,{enumerable:!0,get:a})},i.r=function(n){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,r){if(1&r&&(n=i(n)),8&r||4&r&&typeof n=="object"&&n&&n.__esModule)return n;var a=Object.create(null);if(i.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:n}),2&r&&typeof n!="string")for(var l in n)i.d(a,l,function(c){return n[c]}.bind(null,l));return a},i.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(r,"a",r),r},i.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},i.p="/",i(i.s=4)}([function(t,o,i){var n=i(1),r=i(2);typeof(r=r.__esModule?r.default:r)=="string"&&(r=[[t.i,r,""]]);var a={insert:"head",singleton:!1};n(r,a),t.exports=r.locals||{}},function(t,o,i){var n,r=function(){return n===void 0&&(n=!!(window&&document&&document.all&&!window.atob)),n},a=function(){var y={};return function(x){if(y[x]===void 0){var w=document.querySelector(x);if(window.HTMLIFrameElement&&w instanceof window.HTMLIFrameElement)try{w=w.contentDocument.head}catch{w=null}y[x]=w}return y[x]}}(),l=[];function c(y){for(var x=-1,w=0;w',title:"Text"}}}]),l}()}]).default})})(Vo);const Zo=q(Ue);class $e{constructor(){this.commandName="bold",this.CSS={button:"ce-inline-tool",buttonActive:"ce-inline-tool--active",buttonModifier:"ce-inline-tool--bold"},this.nodes={button:void 0}}static get sanitize(){return{b:{}}}render(){return this.nodes.button=document.createElement("button"),this.nodes.button.type="button",this.nodes.button.classList.add(this.CSS.button,this.CSS.buttonModifier),this.nodes.button.innerHTML=mo,this.nodes.button}surround(){document.execCommand(this.commandName)}checkState(){const e=document.queryCommandState(this.commandName);return this.nodes.button.classList.toggle(this.CSS.buttonActive,e),e}get shortcut(){return"CMD+B"}}$e.isInline=!0,$e.title="Bold";class We{constructor(){this.commandName="italic",this.CSS={button:"ce-inline-tool",buttonActive:"ce-inline-tool--active",buttonModifier:"ce-inline-tool--italic"},this.nodes={button:null}}static get sanitize(){return{i:{}}}render(){return this.nodes.button=document.createElement("button"),this.nodes.button.type="button",this.nodes.button.classList.add(this.CSS.button,this.CSS.buttonModifier),this.nodes.button.innerHTML=wo,this.nodes.button}surround(){document.execCommand(this.commandName)}checkState(){const e=document.queryCommandState(this.commandName);return this.nodes.button.classList.toggle(this.CSS.buttonActive,e),e}get shortcut(){return"CMD+I"}}We.isInline=!0,We.title="Italic";class Ye{constructor({api:e}){this.commandLink="createLink",this.commandUnlink="unlink",this.ENTER_KEY=13,this.CSS={button:"ce-inline-tool",buttonActive:"ce-inline-tool--active",buttonModifier:"ce-inline-tool--link",buttonUnlink:"ce-inline-tool--unlink",input:"ce-inline-tool-input",inputShowed:"ce-inline-tool-input--showed"},this.nodes={button:null,input:null},this.inputOpened=!1,this.toolbar=e.toolbar,this.inlineToolbar=e.inlineToolbar,this.notifier=e.notifier,this.i18n=e.i18n,this.selection=new m}static get sanitize(){return{a:{href:!0,target:"_blank",rel:"nofollow"}}}render(){return this.nodes.button=document.createElement("button"),this.nodes.button.type="button",this.nodes.button.classList.add(this.CSS.button,this.CSS.buttonModifier),this.nodes.button.innerHTML=ht,this.nodes.button}renderActions(){return this.nodes.input=document.createElement("input"),this.nodes.input.placeholder=this.i18n.t("Add a link"),this.nodes.input.classList.add(this.CSS.input),this.nodes.input.addEventListener("keydown",e=>{e.keyCode===this.ENTER_KEY&&this.enterPressed(e)}),this.nodes.input}surround(e){if(e){this.inputOpened?(this.selection.restore(),this.selection.removeFakeBackground()):(this.selection.setFakeBackground(),this.selection.save());const t=this.selection.findParentTag("A");if(t){this.selection.expandToTag(t),this.unlink(),this.closeActions(),this.checkState(),this.toolbar.close();return}}this.toggleActions()}checkState(){const e=this.selection.findParentTag("A");if(e){this.nodes.button.innerHTML=To,this.nodes.button.classList.add(this.CSS.buttonUnlink),this.nodes.button.classList.add(this.CSS.buttonActive),this.openActions();const t=e.getAttribute("href");this.nodes.input.value=t!=="null"?t:"",this.selection.save()}else this.nodes.button.innerHTML=ht,this.nodes.button.classList.remove(this.CSS.buttonUnlink),this.nodes.button.classList.remove(this.CSS.buttonActive);return!!e}clear(){this.closeActions()}get shortcut(){return"CMD+K"}toggleActions(){this.inputOpened?this.closeActions(!1):this.openActions(!0)}openActions(e=!1){this.nodes.input.classList.add(this.CSS.inputShowed),e&&this.nodes.input.focus(),this.inputOpened=!0}closeActions(e=!0){if(this.selection.isFakeBackgroundEnabled){const t=new m;t.save(),this.selection.restore(),this.selection.removeFakeBackground(),t.restore()}this.nodes.input.classList.remove(this.CSS.inputShowed),this.nodes.input.value="",e&&this.selection.clearSaved(),this.inputOpened=!1}enterPressed(e){let t=this.nodes.input.value||"";if(!t.trim()){this.selection.restore(),this.unlink(),e.preventDefault(),this.closeActions();return}if(!this.validateURL(t)){this.notifier.show({message:"Pasted link is not valid.",style:"error"}),C("Incorrect Link pasted","warn",t);return}t=this.prepareLink(t),this.selection.restore(),this.selection.removeFakeBackground(),this.insertLink(t),e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.selection.collapseToEnd(),this.inlineToolbar.close()}validateURL(e){return!/\s/.test(e)}prepareLink(e){return e=e.trim(),e=this.addProtocol(e),e}addProtocol(e){if(/^(\w+):(\/\/)?/.test(e))return e;const t=/^\/[^/\s]/.test(e),o=e.substring(0,1)==="#",i=/^\/\/[^/\s]/.test(e);return!t&&!o&&!i&&(e="http://"+e),e}insertLink(e){const t=this.selection.findParentTag("A");t&&this.selection.expandToTag(t),document.execCommand(this.commandLink,!1,e)}unlink(){document.execCommand(this.commandUnlink)}}Ye.isInline=!0,Ye.title="Link";class yt{constructor({data:e,api:t}){this.CSS={wrapper:"ce-stub",info:"ce-stub__info",title:"ce-stub__title",subtitle:"ce-stub__subtitle"},this.api=t,this.title=e.title||this.api.i18n.t("Error"),this.subtitle=this.api.i18n.t("The block can not be displayed correctly."),this.savedData=e.savedData,this.wrapper=this.make()}render(){return this.wrapper}save(){return this.savedData}make(){const e=d.make("div",this.CSS.wrapper),t='',o=d.make("div",this.CSS.info),i=d.make("div",this.CSS.title,{textContent:this.title}),n=d.make("div",this.CSS.subtitle,{textContent:this.subtitle});return e.innerHTML=t,o.appendChild(i),o.appendChild(n),e.appendChild(o),e}}yt.isReadOnlySupported=!0;class Go extends ze{constructor(){super(...arguments),this.type=we.Inline}get title(){return this.constructable[He.Title]}create(){return new this.constructable({api:this.api.getMethodsForTool(this),config:this.settings})}}class qo extends ze{constructor(){super(...arguments),this.type=we.Tune}create(e,t){return new this.constructable({api:this.api.getMethodsForTool(this),config:this.settings,block:t,data:e})}}class U extends Map{get blockTools(){const e=Array.from(this.entries()).filter(([,t])=>t.isBlock());return new U(e)}get inlineTools(){const e=Array.from(this.entries()).filter(([,t])=>t.isInline());return new U(e)}get blockTunes(){const e=Array.from(this.entries()).filter(([,t])=>t.isTune());return new U(e)}get internalTools(){const e=Array.from(this.entries()).filter(([,t])=>t.isInternal);return new U(e)}get externalTools(){const e=Array.from(this.entries()).filter(([,t])=>!t.isInternal);return new U(e)}}var Jo=Object.defineProperty,Qo=Object.getOwnPropertyDescriptor,Et=(s,e,t,o)=>{for(var i=o>1?void 0:o?Qo(e,t):e,n=s.length-1,r;n>=0;n--)(r=s[n])&&(i=(o?r(e,t,i):r(i))||i);return o&&i&&Jo(e,t,i),i};class Ke extends ze{constructor(){super(...arguments),this.type=we.Block,this.inlineTools=new U,this.tunes=new U}create(e,t,o){return new this.constructable({data:e,block:t,readOnly:o,api:this.api.getMethodsForTool(this),config:this.settings})}get isReadOnlySupported(){return this.constructable[le.IsReadOnlySupported]===!0}get isLineBreaksEnabled(){return this.constructable[le.IsEnabledLineBreaks]}get toolbox(){const e=this.constructable[le.Toolbox],t=this.config[ye.Toolbox];if(!X(e)&&t!==!1)return t?Array.isArray(e)?Array.isArray(t)?t.map((o,i)=>{const n=e[i];return n?{...n,...o}:o}):[t]:Array.isArray(t)?t:[{...e,...t}]:Array.isArray(e)?e:[e]}get conversionConfig(){return this.constructable[le.ConversionConfig]}get enabledInlineTools(){return this.config[ye.EnabledInlineTools]||!1}get enabledBlockTunes(){return this.config[ye.EnabledBlockTunes]}get pasteConfig(){return this.constructable[le.PasteConfig]??{}}get sanitizeConfig(){const e=super.sanitizeConfig,t=this.baseSanitizeConfig;if(X(e))return t;const o={};for(const i in e)if(Object.prototype.hasOwnProperty.call(e,i)){const n=e[i];H(n)?o[i]=Object.assign({},t,n):o[i]=n}return o}get baseSanitizeConfig(){const e={};return Array.from(this.inlineTools.values()).forEach(t=>Object.assign(e,t.sanitizeConfig)),Array.from(this.tunes.values()).forEach(t=>Object.assign(e,t.sanitizeConfig)),e}}Et([ne],Ke.prototype,"sanitizeConfig",1),Et([ne],Ke.prototype,"baseSanitizeConfig",1);class ei{constructor(e,t,o){this.api=o,this.config=e,this.editorConfig=t}get(e){const{class:t,isInternal:o=!1,...i}=this.config[e],n=this.getConstructor(t);return new n({name:e,constructable:t,config:i,api:this.api,isDefault:e===this.editorConfig.defaultBlock,defaultPlaceholder:this.editorConfig.placeholder,isInternal:o})}getConstructor(e){switch(!0){case e[He.IsInline]:return Go;case e[mt.IsTune]:return qo;default:return Ke}}}class Bt{constructor({api:e}){this.CSS={animation:"wobble"},this.api=e}render(){return{icon:dt,title:this.api.i18n.t("Move down"),onActivate:()=>this.handleClick(),name:"move-down"}}handleClick(){const e=this.api.blocks.getCurrentBlockIndex(),t=this.api.blocks.getBlockByIndex(e+1);if(!t)throw new Error("Unable to move Block down since it is already the last");const o=t.holder,i=o.getBoundingClientRect();let n=Math.abs(window.innerHeight-o.offsetHeight);i.topthis.handleClick()}}}handleClick(){this.api.blocks.delete()}}Tt.isTune=!0;class Ct{constructor({api:e}){this.CSS={animation:"wobble"},this.api=e}render(){return{icon:ko,title:this.api.i18n.t("Move up"),onActivate:()=>this.handleClick(),name:"move-up"}}handleClick(){const e=this.api.blocks.getCurrentBlockIndex(),t=this.api.blocks.getBlockByIndex(e),o=this.api.blocks.getBlockByIndex(e-1);if(e===0||!t||!o)throw new Error("Unable to move Block up since it is already the first");const i=t.holder,n=o.holder,r=i.getBoundingClientRect(),a=n.getBoundingClientRect();let l;a.top>0?l=Math.abs(r.top)-Math.abs(a.top):l=Math.abs(r.top)+a.height,window.scrollBy(0,-1*l),this.api.blocks.move(e-1),this.api.toolbar.toggleBlockSettings(!0)}}Ct.isTune=!0;var ti=Object.defineProperty,oi=Object.getOwnPropertyDescriptor,ii=(s,e,t,o)=>{for(var i=o>1?void 0:o?oi(e,t):e,n=s.length-1,r;n>=0;n--)(r=s[n])&&(i=(o?r(e,t,i):r(i))||i);return o&&i&&ti(e,t,i),i};class St extends S{constructor(){super(...arguments),this.stubTool="stub",this.toolsAvailable=new U,this.toolsUnavailable=new U}get available(){return this.toolsAvailable}get unavailable(){return this.toolsUnavailable}get inlineTools(){return this.available.inlineTools}get blockTools(){return this.available.blockTools}get blockTunes(){return this.available.blockTunes}get defaultTool(){return this.blockTools.get(this.config.defaultBlock)}get internal(){return this.available.internalTools}async prepare(){if(this.validateTools(),this.config.tools=Ce({},this.internalTools,this.config.tools),!Object.prototype.hasOwnProperty.call(this.config,"tools")||Object.keys(this.config.tools).length===0)throw Error("Can't start without tools");const e=this.prepareConfig();this.factory=new ei(e,this.config,this.Editor.API);const t=this.getListOfPrepareFunctions(e);if(t.length===0)return Promise.resolve();await Je(t,o=>{this.toolPrepareMethodSuccess(o)},o=>{this.toolPrepareMethodFallback(o)}),this.prepareBlockTools()}getAllInlineToolsSanitizeConfig(){const e={};return Array.from(this.inlineTools.values()).forEach(t=>{Object.assign(e,t.sanitizeConfig)}),e}destroy(){Object.values(this.available).forEach(async e=>{D(e.reset)&&await e.reset()})}get internalTools(){return{bold:{class:$e,isInternal:!0},italic:{class:We,isInternal:!0},link:{class:Ye,isInternal:!0},paragraph:{class:Zo,inlineToolbar:!0,isInternal:!0},stub:{class:yt,isInternal:!0},moveUp:{class:Ct,isInternal:!0},delete:{class:Tt,isInternal:!0},moveDown:{class:Bt,isInternal:!0}}}toolPrepareMethodSuccess(e){const t=this.factory.get(e.toolName);if(t.isInline()){const i=["render","surround","checkState"].filter(n=>!t.create()[n]);if(i.length){C(`Incorrect Inline Tool: ${t.name}. Some of required methods is not implemented %o`,"warn",i),this.toolsUnavailable.set(t.name,t);return}}this.toolsAvailable.set(t.name,t)}toolPrepareMethodFallback(e){this.toolsUnavailable.set(e.toolName,this.factory.get(e.toolName))}getListOfPrepareFunctions(e){const t=[];return Object.entries(e).forEach(([o,i])=>{t.push({function:D(i.class.prepare)?i.class.prepare:()=>{},data:{toolName:o,config:i.config}})}),t}prepareBlockTools(){Array.from(this.blockTools.values()).forEach(e=>{this.assignInlineToolsToBlockTool(e),this.assignBlockTunesToBlockTool(e)})}assignInlineToolsToBlockTool(e){if(this.config.inlineToolbar!==!1){if(e.enabledInlineTools===!0){e.inlineTools=new U(Array.isArray(this.config.inlineToolbar)?this.config.inlineToolbar.map(t=>[t,this.inlineTools.get(t)]):Array.from(this.inlineTools.entries()));return}Array.isArray(e.enabledInlineTools)&&(e.inlineTools=new U(e.enabledInlineTools.map(t=>[t,this.inlineTools.get(t)])))}}assignBlockTunesToBlockTool(e){if(e.enabledBlockTunes!==!1){if(Array.isArray(e.enabledBlockTunes)){const t=new U(e.enabledBlockTunes.map(o=>[o,this.blockTunes.get(o)]));e.tunes=new U([...t,...this.blockTunes.internalTools]);return}if(Array.isArray(this.config.tunes)){const t=new U(this.config.tunes.map(o=>[o,this.blockTunes.get(o)]));e.tunes=new U([...t,...this.blockTunes.internalTools]);return}e.tunes=this.blockTunes.internalTools}}validateTools(){for(const e in this.config.tools)if(Object.prototype.hasOwnProperty.call(this.config.tools,e)){if(e in this.internalTools)return;const t=this.config.tools[e];if(!D(t)&&!D(t.class))throw Error(`Tool «${e}» must be a constructor function or an object with function in the «class» property`)}}prepareConfig(){const e={};for(const t in this.config.tools)H(this.config.tools[t])?e[t]=this.config.tools[t]:e[t]={class:this.config.tools[t]};return e}}ii([ne],St.prototype,"getAllInlineToolsSanitizeConfig",1);const ni=`:root{--selectionColor: #e1f2ff;--inlineSelectionColor: #d4ecff;--bg-light: #eff2f5;--grayText: #707684;--color-dark: #1D202B;--color-active-icon: #388AE5;--color-gray-border: rgba(201, 201, 204, .48);--content-width: 650px;--narrow-mode-right-padding: 50px;--toolbox-buttons-size: 26px;--toolbox-buttons-size--mobile: 36px;--icon-size: 20px;--icon-size--mobile: 28px;--block-padding-vertical: .4em;--color-line-gray: #EFF0F1 }.codex-editor{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:1}.codex-editor .hide,.codex-editor__redactor--hidden{display:none}.codex-editor__redactor [contenteditable]:empty:after{content:"\\feff"}@media (min-width: 651px){.codex-editor--narrow .codex-editor__redactor{margin-right:50px}}@media (min-width: 651px){.codex-editor--narrow.codex-editor--rtl .codex-editor__redactor{margin-left:50px;margin-right:0}}@media (min-width: 651px){.codex-editor--narrow .ce-toolbar__actions{right:-5px}}.codex-editor__loader{position:relative;height:30vh}.codex-editor__loader:before{content:"";position:absolute;left:50%;top:50%;width:30px;height:30px;margin-top:-15px;margin-left:-15px;border-radius:50%;border:2px solid rgba(201,201,204,.48);border-top-color:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-animation:editor-loader-spin .8s infinite linear;animation:editor-loader-spin .8s infinite linear;will-change:transform}.codex-editor-copyable{position:absolute;height:1px;width:1px;top:-400%;opacity:.001}.codex-editor-overlay{position:fixed;top:0px;left:0px;right:0px;bottom:0px;z-index:999;pointer-events:none;overflow:hidden}.codex-editor-overlay__container{position:relative;pointer-events:auto;z-index:0}.codex-editor-overlay__rectangle{position:absolute;pointer-events:none;background-color:#2eaadc33;border:1px solid transparent}.codex-editor svg{max-height:100%}.codex-editor path{stroke:currentColor}::-moz-selection{background-color:#d4ecff}::selection{background-color:#d4ecff}.codex-editor--toolbox-opened [contentEditable=true][data-placeholder]:focus:before{opacity:0!important}@-webkit-keyframes editor-loader-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes editor-loader-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ce-scroll-locked{overflow:hidden}.ce-scroll-locked--hard{overflow:hidden;top:calc(-1 * var(--window-scroll-offset));position:fixed;width:100%}.ce-toolbar{position:absolute;left:0;right:0;top:0;-webkit-transition:opacity .1s ease;transition:opacity .1s ease;will-change:opacity,top;display:none}.ce-toolbar--opened{display:block}.ce-toolbar__content{max-width:650px;margin:0 auto;position:relative}.ce-toolbar__plus{color:#1d202b;cursor:pointer;width:26px;height:26px;border-radius:7px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-ms-flex-negative:0;flex-shrink:0}@media (max-width: 650px){.ce-toolbar__plus{width:36px;height:36px}}@media (hover: hover){.ce-toolbar__plus:hover{background-color:#eff2f5}}.ce-toolbar__plus--active{background-color:#eff2f5;-webkit-animation:bounceIn .75s 1;animation:bounceIn .75s 1;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.ce-toolbar__plus-shortcut{opacity:.6;word-spacing:-2px;margin-top:5px}@media (max-width: 650px){.ce-toolbar__plus{position:absolute;background-color:#fff;border:1px solid #E8E8EB;-webkit-box-shadow:0 3px 15px -3px rgba(13,20,33,.13);box-shadow:0 3px 15px -3px #0d142121;border-radius:6px;z-index:2;position:static}.ce-toolbar__plus--left-oriented:before{left:15px;margin-left:0}.ce-toolbar__plus--right-oriented:before{left:auto;right:15px;margin-left:0}}.ce-toolbar__actions{position:absolute;right:100%;opacity:0;display:-webkit-box;display:-ms-flexbox;display:flex;padding-right:5px}.ce-toolbar__actions--opened{opacity:1}@media (max-width: 650px){.ce-toolbar__actions{right:auto}}.ce-toolbar__settings-btn{color:#1d202b;width:26px;height:26px;border-radius:7px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;margin-left:3px;cursor:pointer;user-select:none}@media (max-width: 650px){.ce-toolbar__settings-btn{width:36px;height:36px}}@media (hover: hover){.ce-toolbar__settings-btn:hover{background-color:#eff2f5}}.ce-toolbar__settings-btn--active{background-color:#eff2f5;-webkit-animation:bounceIn .75s 1;animation:bounceIn .75s 1;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@media (min-width: 651px){.ce-toolbar__settings-btn{width:24px}}.ce-toolbar__settings-btn--hidden{display:none}@media (max-width: 650px){.ce-toolbar__settings-btn{position:absolute;background-color:#fff;border:1px solid #E8E8EB;-webkit-box-shadow:0 3px 15px -3px rgba(13,20,33,.13);box-shadow:0 3px 15px -3px #0d142121;border-radius:6px;z-index:2;position:static}.ce-toolbar__settings-btn--left-oriented:before{left:15px;margin-left:0}.ce-toolbar__settings-btn--right-oriented:before{left:auto;right:15px;margin-left:0}}.ce-toolbar__plus svg,.ce-toolbar__settings-btn svg{width:24px;height:24px}@media (min-width: 651px){.codex-editor--narrow .ce-toolbar__plus{left:5px}}@media (min-width: 651px){.codex-editor--narrow .ce-toolbox .ce-popover{right:0;left:auto;left:initial}}.ce-inline-toolbar{--y-offset: 8px;position:absolute;background-color:#fff;border:1px solid #E8E8EB;-webkit-box-shadow:0 3px 15px -3px rgba(13,20,33,.13);box-shadow:0 3px 15px -3px #0d142121;border-radius:6px;z-index:2;-webkit-transform:translateX(-50%) translateY(8px) scale(.94);transform:translate(-50%) translateY(8px) scale(.94);opacity:0;visibility:hidden;-webkit-transition:opacity .25s ease,-webkit-transform .15s ease;transition:opacity .25s ease,-webkit-transform .15s ease;transition:transform .15s ease,opacity .25s ease;transition:transform .15s ease,opacity .25s ease,-webkit-transform .15s ease;will-change:transform,opacity;top:0;left:0;z-index:3}.ce-inline-toolbar--left-oriented:before{left:15px;margin-left:0}.ce-inline-toolbar--right-oriented:before{left:auto;right:15px;margin-left:0}.ce-inline-toolbar--showed{opacity:1;visibility:visible;-webkit-transform:translateX(-50%);transform:translate(-50%)}.ce-inline-toolbar--left-oriented{-webkit-transform:translateX(-23px) translateY(8px) scale(.94);transform:translate(-23px) translateY(8px) scale(.94)}.ce-inline-toolbar--left-oriented.ce-inline-toolbar--showed{-webkit-transform:translateX(-23px);transform:translate(-23px)}.ce-inline-toolbar--right-oriented{-webkit-transform:translateX(-100%) translateY(8px) scale(.94);transform:translate(-100%) translateY(8px) scale(.94);margin-left:23px}.ce-inline-toolbar--right-oriented.ce-inline-toolbar--showed{-webkit-transform:translateX(-100%);transform:translate(-100%)}.ce-inline-toolbar [hidden]{display:none!important}.ce-inline-toolbar__toggler-and-button-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;padding:0 6px}.ce-inline-toolbar__buttons{display:-webkit-box;display:-ms-flexbox;display:flex}.ce-inline-toolbar__dropdown{display:-webkit-box;display:-ms-flexbox;display:flex;padding:6px;margin:0 6px 0 -6px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;border-right:1px solid rgba(201,201,204,.48);-webkit-box-sizing:border-box;box-sizing:border-box}@media (hover: hover){.ce-inline-toolbar__dropdown:hover{background:#eff2f5}}.ce-inline-toolbar__dropdown--hidden{display:none}.ce-inline-toolbar__dropdown-content,.ce-inline-toolbar__dropdown-arrow{display:-webkit-box;display:-ms-flexbox;display:flex}.ce-inline-toolbar__dropdown-content svg,.ce-inline-toolbar__dropdown-arrow svg{width:20px;height:20px}.ce-inline-toolbar__shortcut{opacity:.6;word-spacing:-3px;margin-top:3px}.ce-inline-tool{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:6px 1px;cursor:pointer;border:0;outline:none;background-color:transparent;vertical-align:bottom;color:inherit;margin:0;border-radius:0;line-height:normal}.ce-inline-tool svg{width:20px;height:20px}@media (max-width: 650px){.ce-inline-tool svg{width:28px;height:28px}}@media (hover: hover){.ce-inline-tool:hover{background-color:#eff2f5}}.ce-inline-tool--active{color:#388ae5}.ce-inline-tool--focused{background:rgba(34,186,255,.08)!important}.ce-inline-tool--focused{-webkit-box-shadow:inset 0 0 0px 1px rgba(7,161,227,.08);box-shadow:inset 0 0 0 1px #07a1e314}.ce-inline-tool--focused-animated{-webkit-animation-name:buttonClicked;animation-name:buttonClicked;-webkit-animation-duration:.25s;animation-duration:.25s}.ce-inline-tool--link .icon--unlink,.ce-inline-tool--unlink .icon--link{display:none}.ce-inline-tool--unlink .icon--unlink{display:inline-block;margin-bottom:-1px}.ce-inline-tool-input{outline:none;border:0;border-radius:0 0 4px 4px;margin:0;font-size:13px;padding:10px;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;display:none;font-weight:500;border-top:1px solid rgba(201,201,204,.48);-webkit-appearance:none;font-family:inherit}@media (max-width: 650px){.ce-inline-tool-input{font-size:15px;font-weight:500}}.ce-inline-tool-input::-webkit-input-placeholder{color:#707684}.ce-inline-tool-input::-moz-placeholder{color:#707684}.ce-inline-tool-input:-ms-input-placeholder{color:#707684}.ce-inline-tool-input::-ms-input-placeholder{color:#707684}.ce-inline-tool-input::placeholder{color:#707684}.ce-inline-tool-input--showed{display:block}.ce-conversion-toolbar{position:absolute;background-color:#fff;border:1px solid #E8E8EB;-webkit-box-shadow:0 3px 15px -3px rgba(13,20,33,.13);box-shadow:0 3px 15px -3px #0d142121;border-radius:6px;z-index:2;opacity:0;visibility:hidden;will-change:transform,opacity;-webkit-transition:opacity .1s ease,-webkit-transform .1s ease;transition:opacity .1s ease,-webkit-transform .1s ease;transition:transform .1s ease,opacity .1s ease;transition:transform .1s ease,opacity .1s ease,-webkit-transform .1s ease;-webkit-transform:translateY(-8px);transform:translateY(-8px);left:-1px;width:150px;margin-top:5px;-webkit-box-sizing:content-box;box-sizing:content-box}.ce-conversion-toolbar--left-oriented:before{left:15px;margin-left:0}.ce-conversion-toolbar--right-oriented:before{left:auto;right:15px;margin-left:0}.ce-conversion-toolbar--showed{opacity:1;visibility:visible;-webkit-transform:none;transform:none}.ce-conversion-toolbar [hidden]{display:none!important}.ce-conversion-toolbar__buttons{display:-webkit-box;display:-ms-flexbox;display:flex}.ce-conversion-toolbar__label{color:#707684;font-size:11px;font-weight:500;letter-spacing:.33px;padding:10px 10px 5px;text-transform:uppercase}.ce-conversion-tool{display:-webkit-box;display:-ms-flexbox;display:flex;padding:5px 10px;font-size:14px;line-height:20px;font-weight:500;cursor:pointer;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ce-conversion-tool--hidden{display:none}.ce-conversion-tool--focused{background:rgba(34,186,255,.08)!important}.ce-conversion-tool--focused{-webkit-box-shadow:inset 0 0 0px 1px rgba(7,161,227,.08);box-shadow:inset 0 0 0 1px #07a1e314}.ce-conversion-tool--focused-animated{-webkit-animation-name:buttonClicked;animation-name:buttonClicked;-webkit-animation-duration:.25s;animation-duration:.25s}.ce-conversion-tool:hover{background:#eff2f5}.ce-conversion-tool__icon{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;width:26px;height:26px;-webkit-box-shadow:0 0 0 1px rgba(201,201,204,.48);box-shadow:0 0 0 1px #c9c9cc7a;border-radius:5px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;background:#fff;-webkit-box-sizing:content-box;box-sizing:content-box;-ms-flex-negative:0;flex-shrink:0;margin-right:10px}.ce-conversion-tool__icon svg{width:20px;height:20px}@media (max-width: 650px){.ce-conversion-tool__icon{width:36px;height:36px;border-radius:8px}.ce-conversion-tool__icon svg{width:28px;height:28px}}.ce-conversion-tool--last{margin-right:0!important}.ce-conversion-tool--active{color:#388ae5!important}.ce-conversion-tool--active{-webkit-animation:bounceIn .75s 1;animation:bounceIn .75s 1;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.ce-settings__button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:6px 1px;border-radius:3px;cursor:pointer;border:0;outline:none;background-color:transparent;vertical-align:bottom;color:inherit;margin:0;line-height:32px}.ce-settings__button svg{width:20px;height:20px}@media (max-width: 650px){.ce-settings__button svg{width:28px;height:28px}}@media (hover: hover){.ce-settings__button:hover{background-color:#eff2f5}}.ce-settings__button--active{color:#388ae5}.ce-settings__button--focused{background:rgba(34,186,255,.08)!important}.ce-settings__button--focused{-webkit-box-shadow:inset 0 0 0px 1px rgba(7,161,227,.08);box-shadow:inset 0 0 0 1px #07a1e314}.ce-settings__button--focused-animated{-webkit-animation-name:buttonClicked;animation-name:buttonClicked;-webkit-animation-duration:.25s;animation-duration:.25s}.ce-settings__button:not(:nth-child(3n+3)){margin-right:3px}.ce-settings__button:nth-child(n+4){margin-top:3px}.ce-settings__button--disabled{cursor:not-allowed!important}.ce-settings__button--disabled{opacity:.3}.ce-settings__button--selected{color:#388ae5}@media (min-width: 651px){.codex-editor--narrow .ce-settings .ce-popover{right:0;left:auto;left:initial}}@-webkit-keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.ce-block{-webkit-animation:fade-in .3s ease;animation:fade-in .3s ease;-webkit-animation-fill-mode:none;animation-fill-mode:none;-webkit-animation-fill-mode:initial;animation-fill-mode:initial}.ce-block:first-of-type{margin-top:0}.ce-block--selected .ce-block__content{background:#e1f2ff}.ce-block--selected .ce-block__content [contenteditable]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ce-block--selected .ce-block__content img,.ce-block--selected .ce-block__content .ce-stub{opacity:.55}.ce-block--stretched .ce-block__content{max-width:none}.ce-block__content{position:relative;max-width:650px;margin:0 auto;-webkit-transition:background-color .15s ease;transition:background-color .15s ease}.ce-block--drop-target .ce-block__content:before{content:"";position:absolute;top:100%;left:-20px;margin-top:-1px;height:8px;width:8px;border:solid #388AE5;border-width:1px 1px 0 0;-webkit-transform-origin:right;transform-origin:right;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.ce-block--drop-target .ce-block__content:after{content:"";position:absolute;top:100%;height:1px;width:100%;color:#388ae5;background:repeating-linear-gradient(90deg,#388AE5,#388AE5 1px,#fff 1px,#fff 6px)}.ce-block a{cursor:pointer;-webkit-text-decoration:underline;text-decoration:underline}.ce-block b{font-weight:700}.ce-block i{font-style:italic}@media (min-width: 651px){.codex-editor--narrow .ce-block--focused{margin-right:-50px;padding-right:50px}}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}20%{-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}60%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}20%{-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}60%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}@-webkit-keyframes selectionBounce{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}50%{-webkit-transform:scale3d(1.01,1.01,1.01);transform:scale3d(1.01,1.01,1.01)}70%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}@keyframes selectionBounce{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}50%{-webkit-transform:scale3d(1.01,1.01,1.01);transform:scale3d(1.01,1.01,1.01)}70%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}@-webkit-keyframes buttonClicked{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.95,.95,.95);transform:scale3d(.95,.95,.95)}60%{-webkit-transform:scale3d(1.02,1.02,1.02);transform:scale3d(1.02,1.02,1.02)}80%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}@keyframes buttonClicked{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.95,.95,.95);transform:scale3d(.95,.95,.95)}60%{-webkit-transform:scale3d(1.02,1.02,1.02);transform:scale3d(1.02,1.02,1.02)}80%{-webkit-transform:scale3d(1,1,1);transform:scaleZ(1)}}.cdx-block{padding:.4em 0}.cdx-block::-webkit-input-placeholder{line-height:normal!important}.cdx-input{border:1px solid rgba(201,201,204,.48);-webkit-box-shadow:inset 0 1px 2px 0 rgba(35,44,72,.06);box-shadow:inset 0 1px 2px #232c480f;border-radius:3px;padding:10px 12px;outline:none;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.cdx-input[data-placeholder]:before{position:static!important}.cdx-input[data-placeholder]:before{display:inline-block;width:0;white-space:nowrap;pointer-events:none}.cdx-settings-button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:6px 1px;border-radius:3px;cursor:pointer;border:0;outline:none;background-color:transparent;vertical-align:bottom;color:inherit;margin:0;min-width:26px;min-height:26px}.cdx-settings-button svg{width:20px;height:20px}@media (max-width: 650px){.cdx-settings-button svg{width:28px;height:28px}}@media (hover: hover){.cdx-settings-button:hover{background-color:#eff2f5}}.cdx-settings-button--focused{background:rgba(34,186,255,.08)!important}.cdx-settings-button--focused{-webkit-box-shadow:inset 0 0 0px 1px rgba(7,161,227,.08);box-shadow:inset 0 0 0 1px #07a1e314}.cdx-settings-button--focused-animated{-webkit-animation-name:buttonClicked;animation-name:buttonClicked;-webkit-animation-duration:.25s;animation-duration:.25s}.cdx-settings-button--active{color:#388ae5}.cdx-settings-button svg{width:auto;height:auto}@media (max-width: 650px){.cdx-settings-button{width:36px;height:36px;border-radius:8px}}.cdx-loader{position:relative;border:1px solid rgba(201,201,204,.48)}.cdx-loader:before{content:"";position:absolute;left:50%;top:50%;width:18px;height:18px;margin:-11px 0 0 -11px;border:2px solid rgba(201,201,204,.48);border-left-color:#388ae5;border-radius:50%;-webkit-animation:cdxRotation 1.2s infinite linear;animation:cdxRotation 1.2s infinite linear}@-webkit-keyframes cdxRotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes cdxRotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.cdx-button{padding:13px;border-radius:3px;border:1px solid rgba(201,201,204,.48);font-size:14.9px;background:#fff;-webkit-box-shadow:0 2px 2px 0 rgba(18,30,57,.04);box-shadow:0 2px 2px #121e390a;color:#707684;text-align:center;cursor:pointer}@media (hover: hover){.cdx-button:hover{background:#FBFCFE;-webkit-box-shadow:0 1px 3px 0 rgba(18,30,57,.08);box-shadow:0 1px 3px #121e3914}}.cdx-button svg{height:20px;margin-right:.2em;margin-top:-2px}.ce-stub{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:100%;padding:3.5em 0;margin:17px 0;border-radius:3px;background:#fcf7f7;color:#b46262}.ce-stub__info{margin-left:20px}.ce-stub__title{margin-bottom:3px;font-weight:600;font-size:18px;text-transform:capitalize}.ce-stub__subtitle{font-size:16px}.codex-editor.codex-editor--rtl{direction:rtl}.codex-editor.codex-editor--rtl .cdx-list{padding-left:0;padding-right:40px}.codex-editor.codex-editor--rtl .ce-toolbar__plus{right:-26px;left:auto}.codex-editor.codex-editor--rtl .ce-toolbar__actions{right:auto;left:-26px}@media (max-width: 650px){.codex-editor.codex-editor--rtl .ce-toolbar__actions{margin-left:0;margin-right:auto;padding-right:0;padding-left:10px}}.codex-editor.codex-editor--rtl .ce-settings{left:5px;right:auto}.codex-editor.codex-editor--rtl .ce-settings:before{right:auto;left:25px}.codex-editor.codex-editor--rtl .ce-settings__button:not(:nth-child(3n+3)){margin-left:3px;margin-right:0}.codex-editor.codex-editor--rtl .ce-conversion-tool__icon{margin-right:0;margin-left:10px}.codex-editor.codex-editor--rtl .ce-inline-toolbar__dropdown{border-right:0px solid transparent;border-left:1px solid rgba(201,201,204,.48);margin:0 -6px 0 6px}.codex-editor.codex-editor--rtl .ce-inline-toolbar__dropdown .icon--toggler-down{margin-left:0;margin-right:4px}@media (min-width: 651px){.codex-editor--narrow.codex-editor--rtl .ce-toolbar__plus{left:0px;right:5px}}@media (min-width: 651px){.codex-editor--narrow.codex-editor--rtl .ce-toolbar__actions{left:-5px}}.cdx-search-field{--icon-margin-right: 10px;background:rgba(232,232,235,.49);border:1px solid rgba(226,226,229,.2);border-radius:6px;padding:2px;display:grid;grid-template-columns:auto auto 1fr;grid-template-rows:auto}.cdx-search-field__icon{width:26px;height:26px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-right:var(--icon-margin-right)}.cdx-search-field__icon svg{width:20px;height:20px;color:#707684}.cdx-search-field__input{font-size:14px;outline:none;font-weight:500;font-family:inherit;border:0;background:transparent;margin:0;padding:0;line-height:22px;min-width:calc(100% - 26px - var(--icon-margin-right))}.cdx-search-field__input::-webkit-input-placeholder{color:#707684;font-weight:500}.cdx-search-field__input::-moz-placeholder{color:#707684;font-weight:500}.cdx-search-field__input:-ms-input-placeholder{color:#707684;font-weight:500}.cdx-search-field__input::-ms-input-placeholder{color:#707684;font-weight:500}.cdx-search-field__input::placeholder{color:#707684;font-weight:500}.ce-popover{--border-radius: 6px;--width: 200px;--max-height: 270px;--padding: 6px;--offset-from-target: 8px;--color-border: #e8e8eb;--color-shadow: rgba(13,20,33,.13);--color-background: white;--color-text-primary: black;--color-text-secondary: #707684;--color-border-icon: rgba(201, 201, 204, .48);--color-border-icon-disabled: #EFF0F1;--color-text-icon-active: #388AE5;--color-background-icon-active: rgba(56, 138, 229, .1);--color-background-item-focus: rgba(34, 186, 255, .08);--color-shadow-item-focus: rgba(7, 161, 227, .08);--color-background-item-hover: #eff2f5;--color-background-item-confirm: #E24A4A;--color-background-item-confirm-hover: #CE4343;min-width:var(--width);width:var(--width);max-height:var(--max-height);border-radius:var(--border-radius);overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 3px 15px -3px var(--color-shadow);box-shadow:0 3px 15px -3px var(--color-shadow);position:absolute;left:0;top:calc(100% + var(--offset-from-target));background:var(--color-background);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;z-index:4;opacity:0;max-height:0;pointer-events:none;padding:0;border:none}.ce-popover--opened{opacity:1;padding:var(--padding);max-height:var(--max-height);pointer-events:auto;-webkit-animation:panelShowing .1s ease;animation:panelShowing .1s ease;border:1px solid var(--color-border)}@media (max-width: 650px){.ce-popover--opened{-webkit-animation:panelShowingMobile .25s ease;animation:panelShowingMobile .25s ease}}.ce-popover__items{overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:contain}@media (max-width: 650px){.ce-popover__overlay{position:fixed;top:0;bottom:0;left:0;right:0;background:#1D202B;z-index:3;opacity:.5;-webkit-transition:opacity .12s ease-in;transition:opacity .12s ease-in;will-change:opacity;visibility:visible}}.ce-popover__overlay--hidden{display:none}.ce-popover--open-top{top:calc(-1 * (var(--offset-from-target) + var(--popover-height)))}@media (max-width: 650px){.ce-popover{--offset: 5px;position:fixed;max-width:none;min-width:calc(100% - var(--offset) * 2);left:var(--offset);right:var(--offset);bottom:calc(var(--offset) + env(safe-area-inset-bottom));top:auto;border-radius:10px}.ce-popover .ce-popover__search{display:none}}.ce-popover__search,.ce-popover__custom-content:not(:empty){margin-bottom:5px}.ce-popover__nothing-found-message{color:#707684;display:none;cursor:default;padding:3px;font-size:14px;line-height:20px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ce-popover__nothing-found-message--displayed{display:block}.ce-popover__custom-content:not(:empty){padding:4px}@media (min-width: 651px){.ce-popover__custom-content:not(:empty){padding:0}}.ce-popover__custom-content--hidden{display:none}.ce-popover-item{--border-radius: 6px;--icon-size: 20px;--icon-size-mobile: 28px;border-radius:var(--border-radius);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:3px;color:var(--color-text-primary);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (max-width: 650px){.ce-popover-item{padding:4px}}.ce-popover-item:not(:last-of-type){margin-bottom:1px}.ce-popover-item__icon{border-radius:5px;width:26px;height:26px;-webkit-box-shadow:0 0 0 1px var(--color-border-icon);box-shadow:0 0 0 1px var(--color-border-icon);background:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-right:10px}.ce-popover-item__icon svg{width:20px;height:20px}@media (max-width: 650px){.ce-popover-item__icon{width:36px;height:36px;border-radius:8px}.ce-popover-item__icon svg{width:var(--icon-size-mobile);height:var(--icon-size-mobile)}}.ce-popover-item__title{font-size:14px;line-height:20px;font-weight:500;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media (max-width: 650px){.ce-popover-item__title{font-size:16px}}.ce-popover-item__secondary-title{color:var(--color-text-secondary);font-size:12px;margin-left:auto;white-space:nowrap;letter-spacing:-.1em;padding-right:5px;margin-bottom:-2px;opacity:.6}@media (max-width: 650px){.ce-popover-item__secondary-title{display:none}}.ce-popover-item--active{background:var(--color-background-icon-active);color:var(--color-text-icon-active)}.ce-popover-item--active .ce-popover-item__icon{-webkit-box-shadow:none;box-shadow:none}.ce-popover-item--disabled{color:var(--color-text-secondary);cursor:default;pointer-events:none}.ce-popover-item--disabled .ce-popover-item__icon{-webkit-box-shadow:0 0 0 1px var(--color-border-icon-disabled);box-shadow:0 0 0 1px var(--color-border-icon-disabled)}.ce-popover-item--focused:not(.ce-popover-item--no-focus){background:var(--color-background-item-focus)!important}.ce-popover-item--focused:not(.ce-popover-item--no-focus){-webkit-box-shadow:inset 0 0 0px 1px var(--color-shadow-item-focus);box-shadow:inset 0 0 0 1px var(--color-shadow-item-focus)}.ce-popover-item--hidden{display:none}@media (hover: hover){.ce-popover-item:hover{cursor:pointer}.ce-popover-item:hover:not(.ce-popover-item--no-hover){background-color:var(--color-background-item-hover)}.ce-popover-item:hover .ce-popover-item__icon{-webkit-box-shadow:none;box-shadow:none}}.ce-popover-item--confirmation{background:var(--color-background-item-confirm)}.ce-popover-item--confirmation .ce-popover-item__icon{color:var(--color-background-item-confirm)}.ce-popover-item--confirmation .ce-popover-item__title{color:#fff}@media (hover: hover){.ce-popover-item--confirmation:not(.ce-popover-item--no-hover):hover{background:var(--color-background-item-confirm-hover)}}.ce-popover-item--confirmation:not(.ce-popover-item--no-focus).ce-popover-item--focused{background:var(--color-background-item-confirm-hover)!important}.ce-popover-item--confirmation .ce-popover-item__icon,.ce-popover-item--active .ce-popover-item__icon,.ce-popover-item--focused .ce-popover-item__icon{-webkit-box-shadow:none;box-shadow:none}@-webkit-keyframes panelShowing{0%{opacity:0;-webkit-transform:translateY(-8px) scale(.9);transform:translateY(-8px) scale(.9)}70%{opacity:1;-webkit-transform:translateY(2px);transform:translateY(2px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes panelShowing{0%{opacity:0;-webkit-transform:translateY(-8px) scale(.9);transform:translateY(-8px) scale(.9)}70%{opacity:1;-webkit-transform:translateY(2px);transform:translateY(2px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes panelShowingMobile{0%{opacity:0;-webkit-transform:translateY(14px) scale(.98);transform:translateY(14px) scale(.98)}70%{opacity:1;-webkit-transform:translateY(-4px);transform:translateY(-4px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes panelShowingMobile{0%{opacity:0;-webkit-transform:translateY(14px) scale(.98);transform:translateY(14px) scale(.98)}70%{opacity:1;-webkit-transform:translateY(-4px);transform:translateY(-4px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}.wobble{-webkit-animation-name:wobble;animation-name:wobble;-webkit-animation-duration:.4s;animation-duration:.4s}@-webkit-keyframes wobble{0%{-webkit-transform:translate3d(0,0,0);transform:translateZ(0)}15%{-webkit-transform:translate3d(-9%,0,0);transform:translate3d(-9%,0,0)}30%{-webkit-transform:translate3d(9%,0,0);transform:translate3d(9%,0,0)}45%{-webkit-transform:translate3d(-4%,0,0);transform:translate3d(-4%,0,0)}60%{-webkit-transform:translate3d(4%,0,0);transform:translate3d(4%,0,0)}75%{-webkit-transform:translate3d(-1%,0,0);transform:translate3d(-1%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translateZ(0)}}@keyframes wobble{0%{-webkit-transform:translate3d(0,0,0);transform:translateZ(0)}15%{-webkit-transform:translate3d(-9%,0,0);transform:translate3d(-9%,0,0)}30%{-webkit-transform:translate3d(9%,0,0);transform:translate3d(9%,0,0)}45%{-webkit-transform:translate3d(-4%,0,0);transform:translate3d(-4%,0,0)}60%{-webkit-transform:translate3d(4%,0,0);transform:translate3d(4%,0,0)}75%{-webkit-transform:translate3d(-1%,0,0);transform:translate3d(-1%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translateZ(0)}} +`;class si extends S{constructor(){super(...arguments),this.isMobile=!1,this.contentRectCache=void 0,this.resizeDebouncer=Rt(()=>{this.windowResize()},200)}get CSS(){return{editorWrapper:"codex-editor",editorWrapperNarrow:"codex-editor--narrow",editorZone:"codex-editor__redactor",editorZoneHidden:"codex-editor__redactor--hidden",editorLoader:"codex-editor__loader",editorEmpty:"codex-editor--empty",editorRtlFix:"codex-editor--rtl"}}get contentRect(){if(this.contentRectCache)return this.contentRectCache;const e=this.nodes.wrapper.querySelector(`.${F.CSS.content}`);return e?(this.contentRectCache=e.getBoundingClientRect(),this.contentRectCache):{width:650,left:0,right:0}}addLoader(){this.nodes.loader=d.make("div",this.CSS.editorLoader),this.nodes.wrapper.prepend(this.nodes.loader),this.nodes.redactor.classList.add(this.CSS.editorZoneHidden)}removeLoader(){this.nodes.loader.remove(),this.nodes.redactor.classList.remove(this.CSS.editorZoneHidden)}async prepare(){this.checkIsMobile(),this.make(),this.addLoader(),this.loadStyles()}toggleReadOnly(e){e?this.disableModuleBindings():this.enableModuleBindings()}checkEmptiness(){const{BlockManager:e}=this.Editor;this.nodes.wrapper.classList.toggle(this.CSS.editorEmpty,e.isEditorEmpty)}get someToolbarOpened(){const{Toolbar:e,BlockSettings:t,InlineToolbar:o,ConversionToolbar:i}=this.Editor;return t.opened||o.opened||i.opened||e.toolbox.opened}get someFlipperButtonFocused(){return this.Editor.Toolbar.toolbox.hasFocus()?!0:Object.entries(this.Editor).filter(([e,t])=>t.flipper instanceof G).some(([e,t])=>t.flipper.hasFocus())}destroy(){this.nodes.holder.innerHTML=""}closeAllToolbars(){const{Toolbar:e,BlockSettings:t,InlineToolbar:o,ConversionToolbar:i}=this.Editor;t.close(),o.close(),i.close(),e.toolbox.close()}checkIsMobile(){this.isMobile=window.innerWidth{this.redactorClicked(e)},!1),this.readOnlyMutableListeners.on(this.nodes.redactor,"mousedown",e=>{this.documentTouched(e)},!0),this.readOnlyMutableListeners.on(this.nodes.redactor,"touchstart",e=>{this.documentTouched(e)},!0),this.readOnlyMutableListeners.on(document,"keydown",e=>{this.documentKeydown(e)},!0),this.readOnlyMutableListeners.on(document,"mousedown",e=>{this.documentClicked(e)},!0),this.readOnlyMutableListeners.on(document,"selectionchange",()=>{this.selectionChanged()},!0),this.readOnlyMutableListeners.on(window,"resize",()=>{this.resizeDebouncer()},{passive:!0}),this.watchBlockHoveredEvents()}watchBlockHoveredEvents(){let e;this.readOnlyMutableListeners.on(this.nodes.redactor,"mousemove",Te(t=>{const o=t.target.closest(".ce-block");this.Editor.BlockSelection.anyBlockSelected||o&&e!==o&&(e=o,this.eventsDispatcher.emit(gt,{block:this.Editor.BlockManager.getBlockByChildNode(o)}))},20),{passive:!0})}disableModuleBindings(){this.readOnlyMutableListeners.clearAll()}windowResize(){this.contentRectCache=null,this.checkIsMobile()}documentKeydown(e){switch(e.keyCode){case B.ENTER:this.enterPressed(e);break;case B.BACKSPACE:this.backspacePressed(e);break;case B.ESC:this.escapePressed(e);break;default:this.defaultBehaviour(e);break}}defaultBehaviour(e){const{currentBlock:t}=this.Editor.BlockManager,o=e.target.closest(`.${this.CSS.editorWrapper}`),i=e.altKey||e.ctrlKey||e.metaKey||e.shiftKey;if(t!==void 0&&o===null){this.Editor.BlockEvents.keydown(e);return}o||t&&i||(this.Editor.BlockManager.dropPointer(),this.Editor.Toolbar.close())}backspacePressed(e){const{BlockManager:t,BlockSelection:o,Caret:i}=this.Editor;if(o.anyBlockSelected&&!m.isSelectionExists){const n=t.removeSelectedBlocks();i.setToBlock(t.insertDefaultBlockAtIndex(n,!0),i.positions.START),o.clearSelection(e),e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()}}escapePressed(e){this.Editor.BlockSelection.clearSelection(e),this.Editor.Toolbar.toolbox.opened?(this.Editor.Toolbar.toolbox.close(),this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock)):this.Editor.BlockSettings.opened?this.Editor.BlockSettings.close():this.Editor.ConversionToolbar.opened?this.Editor.ConversionToolbar.close():this.Editor.InlineToolbar.opened?this.Editor.InlineToolbar.close():this.Editor.Toolbar.close()}enterPressed(e){const{BlockManager:t,BlockSelection:o}=this.Editor,i=t.currentBlockIndex>=0;if(o.anyBlockSelected&&!m.isSelectionExists){o.clearSelection(e),e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation();return}if(!this.someToolbarOpened&&i&&e.target.tagName==="BODY"){const n=this.Editor.BlockManager.insert();this.Editor.Caret.setToBlock(n),this.Editor.BlockManager.highlightCurrentNode(),this.Editor.Toolbar.moveAndOpen(n)}this.Editor.BlockSelection.clearSelection(e)}documentClicked(e){if(!e.isTrusted)return;const t=e.target;this.nodes.holder.contains(t)||m.isAtEditor||(this.Editor.BlockManager.dropPointer(),this.Editor.Toolbar.close());const i=this.Editor.BlockSettings.nodes.wrapper.contains(t),n=this.Editor.Toolbar.nodes.settingsToggler.contains(t),r=i||n;if(this.Editor.BlockSettings.opened&&!r){this.Editor.BlockSettings.close();const a=this.Editor.BlockManager.getBlockByChildNode(t);this.Editor.Toolbar.moveAndOpen(a)}this.Editor.BlockSelection.clearSelection(e)}documentTouched(e){let t=e.target;if(t===this.nodes.redactor){const o=e instanceof MouseEvent?e.clientX:e.touches[0].clientX,i=e instanceof MouseEvent?e.clientY:e.touches[0].clientY;t=document.elementFromPoint(o,i)}try{this.Editor.BlockManager.setCurrentBlockByChildNode(t),this.Editor.BlockManager.highlightCurrentNode()}catch{this.Editor.RectangleSelection.isRectActivated()||this.Editor.Caret.setToTheLastBlock()}this.Editor.Toolbar.moveAndOpen()}redactorClicked(e){const{BlockSelection:t}=this.Editor;if(!m.isCollapsed)return;const o=()=>{e.stopImmediatePropagation(),e.stopPropagation()},i=e.target,n=e.metaKey||e.ctrlKey;if(d.isAnchor(i)&&n){o();const u=i.getAttribute("href"),h=Pt(u);Ht(h);return}const r=this.Editor.BlockManager.getBlockByIndex(-1),a=d.offset(r.holder).bottom,l=e.pageY;if(e.target instanceof Element&&e.target.isEqualNode(this.nodes.redactor)&&!t.anyBlockSelected&&a{t=i,o=n}),Promise.resolve().then(async()=>{this.configuration=e,await this.validate(),await this.init(),await this.start(),K("I'm ready! (ノ◕ヮ◕)ノ*:・゚✧","log","","color: #E24A75"),setTimeout(async()=>{if(await this.render(),this.configuration.autofocus){const{BlockManager:i,Caret:n}=this.moduleInstances;n.setToBlock(i.blocks[0],n.positions.START),i.highlightCurrentNode()}this.moduleInstances.UI.removeLoader(),t()},500)}).catch(i=>{C(`Editor.js is not ready because of ${i}`,"error"),o(i)})}set configuration(e){var o,i;H(e)?this.config={...e}:this.config={holder:e},Se(!!this.config.holderId,"config.holderId","config.holder"),this.config.holderId&&!this.config.holder&&(this.config.holder=this.config.holderId,this.config.holderId=null),this.config.holder==null&&(this.config.holder="editorjs"),this.config.logLevel||(this.config.logLevel=Ve.VERBOSE),Ot(this.config.logLevel),Se(!!this.config.initialBlock,"config.initialBlock","config.defaultBlock"),this.config.defaultBlock=this.config.defaultBlock||this.config.initialBlock||"paragraph",this.config.minHeight=this.config.minHeight!==void 0?this.config.minHeight:300;const t={type:this.config.defaultBlock,data:{}};this.config.placeholder=this.config.placeholder||!1,this.config.sanitizer=this.config.sanitizer||{p:!0,b:!0,a:!0},this.config.hideToolbar=this.config.hideToolbar?this.config.hideToolbar:!1,this.config.tools=this.config.tools||{},this.config.i18n=this.config.i18n||{},this.config.data=this.config.data||{blocks:[]},this.config.onReady=this.config.onReady||(()=>{}),this.config.onChange=this.config.onChange||(()=>{}),this.config.inlineToolbar=this.config.inlineToolbar!==void 0?this.config.inlineToolbar:!0,(X(this.config.data)||!this.config.data.blocks||this.config.data.blocks.length===0)&&(this.config.data={blocks:[t]}),this.config.readOnly=this.config.readOnly||!1,(o=this.config.i18n)!=null&&o.messages&&$.setDictionary(this.config.i18n.messages),this.config.i18n.direction=((i=this.config.i18n)==null?void 0:i.direction)||"ltr"}get configuration(){return this.config}async validate(){const{holderId:e,holder:t}=this.config;if(e&&t)throw Error("«holderId» and «holder» param can't assign at the same time.");if(J(t)&&!d.get(t))throw Error(`element with ID «${t}» is missing. Pass correct holder's ID.`);if(t&&H(t)&&!d.isElement(t))throw Error("«holder» value must be an Element node")}init(){this.constructModules(),this.configureModules()}async start(){await["Tools","UI","BlockManager","Paste","BlockSelection","RectangleSelection","CrossBlockSelection","ReadOnly"].reduce((t,o)=>t.then(async()=>{try{await this.moduleInstances[o].prepare()}catch(i){if(i instanceof nt)throw new Error(i.message);C(`Module ${o} was skipped because of %o`,"warn",i)}}),Promise.resolve())}render(){return this.moduleInstances.Renderer.render(this.config.data.blocks)}constructModules(){Object.entries(ri).forEach(([e,t])=>{try{this.moduleInstances[e]=new t({config:this.configuration,eventsDispatcher:this.eventsDispatcher})}catch(o){C("[constructModules]",`Module ${e} skipped because`,"error",o)}})}configureModules(){for(const e in this.moduleInstances)Object.prototype.hasOwnProperty.call(this.moduleInstances,e)&&(this.moduleInstances[e].state=this.getModulesDiff(e))}getModulesDiff(e){const t={};for(const o in this.moduleInstances)o!==e&&(t[o]=this.moduleInstances[o]);return t}}/** + * Editor.js + * + * @license Apache-2.0 + * @see Editor.js + * @author CodeX Team + */class li{static get version(){return"2.27.2"}constructor(e){let t=()=>{};H(e)&&D(e.onReady)&&(t=e.onReady);const o=new ai(e);this.isReady=o.isReady.then(()=>{this.exportAPI(o),t()})}exportAPI(e){const t=["configuration"],o=()=>{Object.values(e.moduleInstances).forEach(n=>{D(n.destroy)&&n.destroy(),n.listeners.removeAll()}),e=null;for(const n in this)Object.prototype.hasOwnProperty.call(this,n)&&delete this[n];Object.setPrototypeOf(this,null)};t.forEach(n=>{this[n]=e[n]}),this.destroy=o,Object.setPrototypeOf(this,e.moduleInstances.API.methods),delete this.exportAPI,Object.entries({blocks:{clear:"clear",render:"render"},caret:{focus:"focus"},events:{on:"on",off:"off",emit:"emit"},saver:{save:"save"}}).forEach(([n,r])=>{Object.entries(r).forEach(([a,l])=>{this[l]=e.moduleInstances.API.methods[n][a]})})}}return li}); diff --git a/webapp/public/js/editorjs/header.js b/webapp/public/js/editorjs/header.js new file mode 100644 index 0000000000..23a373b660 --- /dev/null +++ b/webapp/public/js/editorjs/header.js @@ -0,0 +1,16 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/header@2.7.0/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Header=t():e.Header=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=5)}([function(e,t,n){var r=n(1);"string"==typeof r&&(r=[[e.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(2)(!1)).push([e.i,"/**\n * Plugin styles\n */\n.ce-header {\n padding: 0.6em 0 3px;\n margin: 0;\n line-height: 1.25em;\n outline: none;\n}\n\n.ce-header p,\n.ce-header div{\n padding: 0 !important;\n margin: 0 !important;\n}\n\n/**\n * Styles for Plugin icon in Toolbar\n */\n.ce-header__icon {}\n\n.ce-header[contentEditable=true][data-placeholder]::before{\n position: absolute;\n content: attr(data-placeholder);\n color: #707684;\n font-weight: normal;\n display: none;\n cursor: text;\n}\n\n.ce-header[contentEditable=true][data-placeholder]:empty::before {\n display: block;\n}\n\n.ce-header[contentEditable=true][data-placeholder]:empty:focus::before {\n display: none;\n}\n",""])},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(a=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),i=r.sources.map((function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"}));return[n].concat(i).concat([o]).join("\n")}var a;return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n})).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&f.splice(t,1)}function b(e){var t=document.createElement("style");return void 0===e.attrs.type&&(e.attrs.type="text/css"),m(t,e.attrs),v(e,t),t}function m(e,t){Object.keys(t).forEach((function(n){e.setAttribute(n,t[n])}))}function y(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i=t.transform(e.css)))return function(){};e.css=i}if(t.singleton){var a=c++;n=u||(u=b(t)),r=L.bind(null,n,a,!1),o=L.bind(null,n,a,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",m(t,e.attrs),v(e,t),t}(t),r=x.bind(null,n,t),o=function(){g(n),n.href&&URL.revokeObjectURL(n.href)}):(n=b(t),r=M.bind(null,n),o=function(){g(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=a()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=h(e,t);return p(n,t),function(e){for(var r=[],o=0;o',title:"Heading"}}}],(n=[{key:"normalizeData",value:function(e){var t={};return"object"!==r(e)&&(e={}),t.text=e.text||"",t.level=parseInt(e.level)||this.defaultLevel.number,t}},{key:"render",value:function(){return this._element}},{key:"renderSettings",value:function(){var e=this;return this.levels.map((function(t){return{icon:t.svg,label:e.api.i18n.t("Heading ".concat(t.number)),onActivate:function(){return e.setLevel(t.number)},closeOnActivate:!0,isActive:e.currentLevel.number===t.number}}))}},{key:"setLevel",value:function(e){this.data={level:e,text:this.data.text}}},{key:"merge",value:function(e){var t={text:this.data.text+e.text,level:this.data.level};this.data=t}},{key:"validate",value:function(e){return""!==e.text.trim()}},{key:"save",value:function(e){return{text:e.innerHTML,level:this.currentLevel.number}}},{key:"getTag",value:function(){var e=document.createElement(this.currentLevel.tag);return e.innerHTML=this._data.text||"",e.classList.add(this._CSS.wrapper),e.contentEditable=this.readOnly?"false":"true",e.dataset.placeholder=this.api.i18n.t(this._settings.placeholder||""),e}},{key:"onPaste",value:function(e){var t=e.detail.data,n=this.defaultLevel.number;switch(t.tagName){case"H1":n=1;break;case"H2":n=2;break;case"H3":n=3;break;case"H4":n=4;break;case"H5":n=5;break;case"H6":n=6}this._settings.levels&&(n=this._settings.levels.reduce((function(e,t){return Math.abs(t-n)'},{number:2,tag:"H2",svg:''},{number:3,tag:"H3",svg:''},{number:4,tag:"H4",svg:''},{number:5,tag:"H5",svg:''},{number:6,tag:"H6",svg:''}];return this._settings.levels?t.filter((function(t){return e._settings.levels.includes(t.number)})):t}}])&&o(t.prototype,n),i&&o(t,i),e}()}]).default})); diff --git a/webapp/public/js/editorjs/image.js b/webapp/public/js/editorjs/image.js new file mode 100644 index 0000000000..c505125b26 --- /dev/null +++ b/webapp/public/js/editorjs/image.js @@ -0,0 +1,47 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/image@2.8.1/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +/*! + * Image tool + * + * @version 2.8.1 + * + * @package https://github.com/editor-js/image + * @licence MIT + * @author CodeX + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ImageTool=t():e.ImageTool=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=9)}([function(e,t){function n(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};if(e.url&&"string"!=typeof e.url)throw new Error("Url must be a string");if(e.url=e.url||"",e.method&&"string"!=typeof e.method)throw new Error("`method` must be a string or null");if(e.method=e.method?e.method.toUpperCase():"GET",e.headers&&"object"!==r(e.headers))throw new Error("`headers` must be an object or null");if(e.headers=e.headers||{},e.type&&("string"!=typeof e.type||!Object.values(o).includes(e.type)))throw new Error("`type` must be taken from module's «contentType» library");if(e.progress&&"function"!=typeof e.progress)throw new Error("`progress` must be a function or null");if(e.progress=e.progress||function(e){},e.beforeSend=e.beforeSend||function(e){},e.ratio&&"number"!=typeof e.ratio)throw new Error("`ratio` must be a number");if(e.ratio<0||e.ratio>100)throw new Error("`ratio` must be in a 0-100 interval");if(e.ratio=e.ratio||90,e.accept&&"string"!=typeof e.accept)throw new Error("`accept` must be a string with a list of allowed mime-types");if(e.accept=e.accept||"*/*",e.multiple&&"boolean"!=typeof e.multiple)throw new Error("`multiple` must be a true or false");if(e.multiple=e.multiple||!1,e.fieldName&&"string"!=typeof e.fieldName)throw new Error("`fieldName` must be a string");return e.fieldName=e.fieldName||"files",e},c=function(e){switch(e.method){case"GET":var t=s(e.data,o.URLENCODED);delete e.data,e.url=/\?/.test(e.url)?e.url+"&"+t:e.url+"?"+t;break;case"POST":case"PUT":case"DELETE":case"UPDATE":var n=function(){return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).type||o.JSON}(e);(d.isFormData(e.data)||d.isFormElement(e.data))&&(n=o.FORM),e.data=s(e.data,n),n!==f.contentType.FORM&&(e.headers["content-type"]=n)}return e},s=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};switch(arguments.length>1?arguments[1]:void 0){case o.URLENCODED:return d.urlEncode(e);case o.JSON:return d.jsonEncode(e);case o.FORM:return d.formEncode(e);default:return e}},l=function(e){return e>=200&&e<300},{contentType:o={URLENCODED:"application/x-www-form-urlencoded; charset=utf-8",FORM:"multipart/form-data",JSON:"application/json; charset=utf-8"},request:i,get:function(e){return e.method="GET",i(e)},post:a,transport:function(e){return e=u(e),d.selectFiles(e).then((function(t){for(var n=new FormData,r=0;r=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(6),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(0))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,u,c=1,s={},l=!1,d=e.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(e);f=f&&f.setTimeout?f:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){h(e)}))}:function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?(a="setImmediate$"+Math.random()+"$",u=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&h(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",u,!1):e.attachEvent("onmessage",u),r=function(t){e.postMessage(a+t,"*")}):e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){h(e.data)},r=function(e){i.port2.postMessage(e)}):d&&"onreadystatechange"in d.createElement("script")?(o=d.documentElement,r=function(e){var t=d.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;n HTMLElement")}},{key:"isObject",value:function(e){return"[object Object]"===Object.prototype.toString.call(e)}},{key:"isFormData",value:function(e){return e instanceof FormData}},{key:"isFormElement",value:function(e){return e instanceof HTMLFormElement}},{key:"selectFiles",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return new Promise((function(t,n){var r=document.createElement("INPUT");r.type="file",e.multiple&&r.setAttribute("multiple","multiple"),e.accept&&r.setAttribute("accept",e.accept),r.style.display="none",document.body.appendChild(r),r.addEventListener("change",(function(e){var n=e.target.files;t(n),document.body.removeChild(r)}),!1),r.click()}))}},{key:"parseHeaders",value:function(e){var t=e.trim().split(/[\r\n]+/),n={};return t.forEach((function(e){var t=e.split(": "),r=t.shift(),o=t.join(": ");r&&(n[r]=o)})),n}}])&&r(t,n),e}()},function(e,t){var n=function(e){return encodeURIComponent(e).replace(/[!'()*]/g,escape).replace(/%20/g,"+")},r=function(e,t,o,i){return t=t||null,o=o||"&",i=i||null,e?function(e){for(var t=new Array,n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var u=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(u&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),k(n),s}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var o=r.arg;k(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:x(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),s}},e}(e.exports);try{regeneratorRuntime=r}catch(e){Function("r","regeneratorRuntime = r")(r)}},function(e,t,n){var r=n(12),o=n(13);"string"==typeof(o=o.__esModule?o.default:o)&&(o=[[e.i,o,""]]);var i={insert:"head",singleton:!1},a=(r(o,i),o.locals?o.locals:{});e.exports=a},function(e,t,n){"use strict";var r,o=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},i=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),a=[];function u(e){for(var t=-1,n=0;n1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=document.createElement(e);Array.isArray(n)?(t=o.classList).add.apply(t,p()(n)):n&&o.classList.add(n);for(var i in r)o[i]=r[i];return o}var m=function(){function e(t){var n=t.api,r=t.config,o=t.onSelectFile,i=t.readOnly;c()(this,e),this.api=n,this.config=r,this.onSelectFile=o,this.readOnly=i,this.nodes={wrapper:h("div",[this.CSS.baseClass,this.CSS.wrapper]),imageContainer:h("div",[this.CSS.imageContainer]),fileButton:this.createFileButton(),imageEl:void 0,imagePreloader:h("div",this.CSS.imagePreloader),caption:h("div",[this.CSS.input,this.CSS.caption],{contentEditable:!this.readOnly})},this.nodes.caption.dataset.placeholder=this.config.captionPlaceholder,this.nodes.imageContainer.appendChild(this.nodes.imagePreloader),this.nodes.wrapper.appendChild(this.nodes.imageContainer),this.nodes.wrapper.appendChild(this.nodes.caption),this.nodes.wrapper.appendChild(this.nodes.fileButton)}return l()(e,[{key:"render",value:function(t){return t.file&&0!==Object.keys(t.file).length?this.toggleStatus(e.status.UPLOADING):this.toggleStatus(e.status.EMPTY),this.nodes.wrapper}},{key:"createFileButton",value:function(){var e=this,t=h("div",[this.CSS.button]);return t.innerHTML=this.config.buttonContent||"".concat(d," ").concat(this.api.i18n.t("Select an Image")),t.addEventListener("click",(function(){e.onSelectFile()})),t}},{key:"showPreloader",value:function(t){this.nodes.imagePreloader.style.backgroundImage="url(".concat(t,")"),this.toggleStatus(e.status.UPLOADING)}},{key:"hidePreloader",value:function(){this.nodes.imagePreloader.style.backgroundImage="",this.toggleStatus(e.status.EMPTY)}},{key:"fillImage",value:function(t){var n=this,r=/\.mp4$/.test(t)?"VIDEO":"IMG",o={src:t},i="load";"VIDEO"===r&&(o.autoplay=!0,o.loop=!0,o.muted=!0,o.playsinline=!0,i="loadeddata"),this.nodes.imageEl=h(r,this.CSS.imageEl,o),this.nodes.imageEl.addEventListener(i,(function(){n.toggleStatus(e.status.FILLED),n.nodes.imagePreloader&&(n.nodes.imagePreloader.style.backgroundImage="")})),this.nodes.imageContainer.appendChild(this.nodes.imageEl)}},{key:"fillCaption",value:function(e){this.nodes.caption&&(this.nodes.caption.innerHTML=e)}},{key:"toggleStatus",value:function(t){for(var n in e.status)Object.prototype.hasOwnProperty.call(e.status,n)&&this.nodes.wrapper.classList.toggle("".concat(this.CSS.wrapper,"--").concat(e.status[n]),t===e.status[n])}},{key:"applyTune",value:function(e,t){this.nodes.wrapper.classList.toggle("".concat(this.CSS.wrapper,"--").concat(e),t)}},{key:"CSS",get:function(){return{baseClass:this.api.styles.block,loading:this.api.styles.loader,input:this.api.styles.input,button:this.api.styles.button,wrapper:"image-tool",imageContainer:"image-tool__image",imagePreloader:"image-tool__image-preloader",imageEl:"image-tool__image-picture",caption:"image-tool__caption"}}}],[{key:"status",get:function(){return{EMPTY:"empty",UPLOADING:"loading",FILLED:"filled"}}}]),e}(),g=n(8),y=n.n(g),v=n(1),b=n.n(v);function w(e){return e&&"function"==typeof e.then}var k=function(){function e(t){var n=t.config,r=t.onUpload,o=t.onError;c()(this,e),this.config=n,this.onUpload=r,this.onError=o}return l()(e,[{key:"uploadSelectedFile",value:function(e){var t=this,n=e.onPreview,r=function(e){var t=new FileReader;t.readAsDataURL(e),t.onload=function(e){n(e.target.result)}};(this.config.uploader&&"function"==typeof this.config.uploader.uploadByFile?b.a.selectFiles({accept:this.config.types}).then((function(e){r(e[0]);var n=t.config.uploader.uploadByFile(e[0]);return w(n)||console.warn("Custom uploader method uploadByFile should return a Promise"),n})):b.a.transport({url:this.config.endpoints.byFile,data:this.config.additionalRequestData,accept:this.config.types,headers:this.config.additionalRequestHeaders,beforeSend:function(e){r(e[0])},fieldName:this.config.field}).then((function(e){return e.body}))).then((function(e){t.onUpload(e)})).catch((function(e){t.onError(e)}))}},{key:"uploadByUrl",value:function(e){var t,n=this;this.config.uploader&&"function"==typeof this.config.uploader.uploadByUrl?w(t=this.config.uploader.uploadByUrl(e))||console.warn("Custom uploader method uploadByUrl should return a Promise"):t=b.a.post({url:this.config.endpoints.byUrl,data:Object.assign({url:e},this.config.additionalRequestData),type:b.a.contentType.JSON,headers:this.config.additionalRequestHeaders}).then((function(e){return e.body})),t.then((function(e){n.onUpload(e)})).catch((function(e){n.onError(e)}))}},{key:"uploadByFile",value:function(e,t){var n,r=this,o=t.onPreview,i=new FileReader;if(i.readAsDataURL(e),i.onload=function(e){o(e.target.result)},this.config.uploader&&"function"==typeof this.config.uploader.uploadByFile)w(n=this.config.uploader.uploadByFile(e))||console.warn("Custom uploader method uploadByFile should return a Promise");else{var a=new FormData;a.append(this.config.field,e),this.config.additionalRequestData&&Object.keys(this.config.additionalRequestData).length&&Object.entries(this.config.additionalRequestData).forEach((function(e){var t=y()(e,2),n=t[0],r=t[1];a.append(n,r)})),n=b.a.post({url:this.config.endpoints.byFile,data:a,type:b.a.contentType.JSON,headers:this.config.additionalRequestHeaders}).then((function(e){return e.body}))}n.then((function(e){r.onUpload(e)})).catch((function(e){r.onError(e)}))}}]),e}(),_=function(){function e(t){var n=this,r=t.data,o=t.config,i=t.api,a=t.readOnly;c()(this,e),this.api=i,this.readOnly=a,this.config={endpoints:o.endpoints||"",additionalRequestData:o.additionalRequestData||{},additionalRequestHeaders:o.additionalRequestHeaders||{},field:o.field||"image",types:o.types||"image/*",captionPlaceholder:this.api.i18n.t(o.captionPlaceholder||"Caption"),buttonContent:o.buttonContent||"",uploader:o.uploader||void 0,actions:o.actions||[]},this.uploader=new k({config:this.config,onUpload:function(e){return n.onUpload(e)},onError:function(e){return n.uploadingFailed(e)}}),this.ui=new m({api:i,config:this.config,onSelectFile:function(){n.uploader.uploadSelectedFile({onPreview:function(e){n.ui.showPreloader(e)}})},readOnly:a}),this._data={},this.data=r}var t;return l()(e,null,[{key:"isReadOnlySupported",get:function(){return!0}},{key:"toolbox",get:function(){return{icon:d,title:"Image"}}},{key:"tunes",get:function(){return[{name:"withBorder",icon:'',title:"With border",toggle:!0},{name:"stretched",icon:'',title:"Stretch image",toggle:!0},{name:"withBackground",icon:'',title:"With background",toggle:!0}]}}]),l()(e,[{key:"render",value:function(){return this.ui.render(this.data)}},{key:"validate",value:function(e){return e.file&&e.file.url}},{key:"save",value:function(){var e=this.ui.nodes.caption;return this._data.caption=e.innerHTML,this.data}},{key:"renderSettings",value:function(){var t=this;return e.tunes.concat(this.config.actions).map((function(e){return{icon:e.icon,label:t.api.i18n.t(e.title),name:e.name,toggle:e.toggle,isActive:t.data[e.name],onActivate:function(){"function"!=typeof e.action?t.tuneToggled(e.name):e.action(e.name)}}}))}},{key:"appendCallback",value:function(){this.ui.nodes.fileButton.click()}},{key:"onPaste",value:(t=a()(o.a.mark((function e(t){var n,r,i,a,u;return o.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:e.t0=t.type,e.next="tag"===e.t0?3:"pattern"===e.t0?15:"file"===e.t0?18:21;break;case 3:if(n=t.detail.data,!/^blob:/.test(n.src)){e.next=13;break}return e.next=7,fetch(n.src);case 7:return r=e.sent,e.next=10,r.blob();case 10:return i=e.sent,this.uploadFile(i),e.abrupt("break",21);case 13:return this.uploadUrl(n.src),e.abrupt("break",21);case 15:return a=t.detail.data,this.uploadUrl(a),e.abrupt("break",21);case 18:return u=t.detail.file,this.uploadFile(u),e.abrupt("break",21);case 21:case"end":return e.stop()}}),e,this)}))),function(e){return t.apply(this,arguments)})},{key:"onUpload",value:function(e){e.success&&e.file?this.image=e.file:this.uploadingFailed("incorrect response: "+JSON.stringify(e))}},{key:"uploadingFailed",value:function(e){console.log("Image Tool: uploading failed because of",e),this.api.notifier.show({message:this.api.i18n.t("Couldn’t upload image. Please try another."),style:"error"}),this.ui.hidePreloader()}},{key:"tuneToggled",value:function(e){this.setTune(e,!this._data[e])}},{key:"setTune",value:function(e,t){var n=this;this._data[e]=t,this.ui.applyTune(e,t),"stretched"===e&&Promise.resolve().then((function(){var e=n.api.blocks.getCurrentBlockIndex();n.api.blocks.stretchBlock(e,t)})).catch((function(e){console.error(e)}))}},{key:"uploadFile",value:function(e){var t=this;this.uploader.uploadByFile(e,{onPreview:function(e){t.ui.showPreloader(e)}})}},{key:"uploadUrl",value:function(e){this.ui.showPreloader(e),this.uploader.uploadByUrl(e)}},{key:"data",set:function(t){var n=this;this.image=t.file,this._data.caption=t.caption||"",this.ui.fillCaption(this._data.caption),e.tunes.forEach((function(e){var r=e.name,o=void 0!==t[r]&&(!0===t[r]||"true"===t[r]);n.setTune(r,o)}))},get:function(){return this._data}},{key:"image",set:function(e){this._data.file=e||{},e&&e.url&&this.ui.fillImage(e.url)}}],[{key:"pasteConfig",get:function(){return{tags:[{img:{src:!0}}],patterns:{image:/https?:\/\/\S+\.(gif|jpe?g|tiff|png|svg|webp)(\?[a-z0-9=]*)?$/i},files:{mimeTypes:["image/*"]}}}}]),e}(); + /** + * Image Tool for the Editor.js + * + * @author CodeX + * @license MIT + * @see {@link https://github.com/editor-js/image} + * + * To developers. + * To simplify Tool structure, we split it to 4 parts: + * 1) index.js — main Tool's interface, public API and methods for working with data + * 2) uploader.js — module that has methods for sending files via AJAX: from device, by URL or File pasting + * 3) ui.js — module for UI manipulations: render, showing preloader, etc + * 4) tunes.js — working with Block Tunes: render buttons, handle clicks + * + * For debug purposes there is a testing server + * that can save uploaded files and return a Response {@link UploadResponseFormat} + * + * $ node dev/server.js + * + * It will expose 8008 port, so you can pass http://localhost:8008 with the Tools config: + * + * image: { + * class: ImageTool, + * config: { + * endpoints: { + * byFile: 'http://localhost:8008/uploadFile', + * byUrl: 'http://localhost:8008/fetchUrl', + * } + * }, + * }, + */}]).default})); diff --git a/webapp/public/js/editorjs/inline-code.js b/webapp/public/js/editorjs/inline-code.js new file mode 100644 index 0000000000..ab404691f4 --- /dev/null +++ b/webapp/public/js/editorjs/inline-code.js @@ -0,0 +1,7 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/inline-code@1.4.0/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.InlineCode=e():t.InlineCode=e()}(window,function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/",n(n.s=5)}([function(t,e,n){var r=n(1);"string"==typeof r&&(r=[[t.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(t.exports=r.locals)},function(t,e,n){(t.exports=n(2)(!1)).push([t.i,".inline-code {\n background: rgba(250, 239, 240, 0.78);\n color: #b44437;\n padding: 3px 4px;\n border-radius: 5px;\n margin: 0 1px;\n font-family: inherit;\n font-size: 0.86em;\n font-weight: 500;\n letter-spacing: 0.3px;\n}\n",""])},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n=t[1]||"",r=t[3];if(!r)return n;if(e&&"function"==typeof btoa){var o=(s=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(s))))+" */"),i=r.sources.map(function(t){return"/*# sourceURL="+r.sourceRoot+t+" */"});return[n].concat(i).concat([o]).join("\n")}var s;return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},o=0;o=0&&f.splice(e,1)}function b(t){var e=document.createElement("style");return void 0===t.attrs.type&&(t.attrs.type="text/css"),y(e,t.attrs),h(t,e),e}function y(t,e){Object.keys(e).forEach(function(n){t.setAttribute(n,e[n])})}function g(t,e){var n,r,o,i;if(e.transform&&t.css){if(!(i=e.transform(t.css)))return function(){};t.css=i}if(e.singleton){var s=c++;n=u||(u=b(e)),r=x.bind(null,n,s,!1),o=x.bind(null,n,s,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(t){var e=document.createElement("link");return void 0===t.attrs.type&&(t.attrs.type="text/css"),t.attrs.rel="stylesheet",y(e,t.attrs),h(t,e),e}(e),r=function(t,e,n){var r=n.css,o=n.sourceMap,i=void 0===e.convertToAbsoluteUrls&&o;(e.convertToAbsoluteUrls||i)&&(r=l(r));o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var s=new Blob([r],{type:"text/css"}),a=t.href;t.href=URL.createObjectURL(s),a&&URL.revokeObjectURL(a)}.bind(null,n,e),o=function(){v(n),n.href&&URL.revokeObjectURL(n.href)}):(n=b(e),r=function(t,e){var n=e.css,r=e.media;r&&t.setAttribute("media",r);if(t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){v(n)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else o()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=s()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var n=d(t,e);return p(n,e),function(t){for(var r=[],o=0;o'}}],[{key:"isInline",get:function(){return!0}},{key:"sanitize",get:function(){return{code:{class:t.CSS}}}}]),t}()}]).default}); diff --git a/webapp/public/js/editorjs/list.js b/webapp/public/js/editorjs/list.js new file mode 100644 index 0000000000..514c62c193 --- /dev/null +++ b/webapp/public/js/editorjs/list.js @@ -0,0 +1,7 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/list@1.8.0/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.List=t():e.List=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=4)}([function(e,t,n){var r=n(1),i=n(2);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var o={insert:"head",singleton:!1};r(i,o);e.exports=i.locals||{}},function(e,t,n){"use strict";var r,i=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),a=[];function s(e){for(var t=-1,n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n',default:"ordered"===i.defaultStyle||!0}],this._data={style:this.settings.find((function(e){return!0===e.default})).name,items:[]},this.data=n}return u(e,null,[{key:"isReadOnlySupported",get:function(){return!0}},{key:"enableLineBreaks",get:function(){return!0}},{key:"toolbox",get:function(){return{icon:r,title:"List"}}}]),u(e,[{key:"render",value:function(){var e=this;return this._elements.wrapper=this.makeMainTag(this._data.style),this._data.items.length?this._data.items.forEach((function(t){e._elements.wrapper.appendChild(e._make("li",e.CSS.item,{innerHTML:t}))})):this._elements.wrapper.appendChild(this._make("li",this.CSS.item)),this.readOnly||this._elements.wrapper.addEventListener("keydown",(function(t){switch(t.keyCode){case 13:e.getOutofList(t);break;case 8:e.backspace(t)}}),!1),this._elements.wrapper}},{key:"save",value:function(){return this.data}},{key:"renderSettings",value:function(){var e=this;return this.settings.map((function(t){return s(s({},t),{},{isActive:e._data.style===t.name,closeOnActivate:!0,onActivate:function(){return e.toggleTune(t.name)}})}))}},{key:"onPaste",value:function(e){var t=e.detail.data;this.data=this.pasteHandler(t)}},{key:"makeMainTag",value:function(e){var t="ordered"===e?this.CSS.wrapperOrdered:this.CSS.wrapperUnordered,n="ordered"===e?"ol":"ul";return this._make(n,[this.CSS.baseBlock,this.CSS.wrapper,t],{contentEditable:!this.readOnly})}},{key:"toggleTune",value:function(e){for(var t=this.makeMainTag(e);this._elements.wrapper.hasChildNodes();)t.appendChild(this._elements.wrapper.firstChild);this._elements.wrapper.replaceWith(t),this._elements.wrapper=t,this._data.style=e}},{key:"_make",value:function(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=document.createElement(e);Array.isArray(n)?(t=o.classList).add.apply(t,i(n)):n&&o.classList.add(n);for(var a in r)o[a]=r[a];return o}},{key:"getOutofList",value:function(e){var t=this._elements.wrapper.querySelectorAll("."+this.CSS.item);if(!(t.length<2)){var n=t[t.length-1],r=this.currentItem;r!==n||n.textContent.trim().length||(r.parentElement.removeChild(r),this.api.blocks.insert(),this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex()),e.preventDefault(),e.stopPropagation())}}},{key:"backspace",value:function(e){var t=this._elements.wrapper.querySelectorAll("."+this.CSS.item),n=t[0];n&&t.length<2&&!n.innerHTML.replace("
"," ").trim()&&e.preventDefault()}},{key:"selectItem",value:function(e){e.preventDefault();var t=window.getSelection(),n=t.anchorNode.parentNode.closest("."+this.CSS.item),r=new Range;r.selectNodeContents(n),t.removeAllRanges(),t.addRange(r)}},{key:"pasteHandler",value:function(e){var t,n=e.tagName;switch(n){case"OL":t="ordered";break;case"UL":case"LI":t="unordered"}var r={style:t,items:[]};if("LI"===n)r.items=[e.innerHTML];else{var i=Array.from(e.querySelectorAll("LI"));r.items=i.map((function(e){return e.innerHTML})).filter((function(e){return!!e.trim()}))}return r}},{key:"CSS",get:function(){return{baseBlock:this.api.styles.block,wrapper:"cdx-list",wrapperOrdered:"cdx-list--ordered",wrapperUnordered:"cdx-list--unordered",item:"cdx-list__item"}}},{key:"data",set:function(e){e||(e={}),this._data.style=e.style||this.settings.find((function(e){return!0===e.default})).name,this._data.items=e.items||[];var t=this._elements.wrapper;t&&t.parentNode.replaceChild(this.render(),t)},get:function(){this._data.items=[];for(var e=this._elements.wrapper.querySelectorAll(".".concat(this.CSS.item)),t=0;t"," ").trim()&&this._data.items.push(e[t].innerHTML)}return this._data}},{key:"currentItem",get:function(){var e=window.getSelection().anchorNode;return e.nodeType!==Node.ELEMENT_NODE&&(e=e.parentNode),e.closest(".".concat(this.CSS.item))}}],[{key:"conversionConfig",get:function(){return{export:function(e){return e.items.join(". ")},import:function(e){return{items:[e],style:"unordered"}}}}},{key:"sanitize",get:function(){return{style:{},items:{br:!0}}}},{key:"pasteConfig",get:function(){return{tags:["OL","UL","LI"]}}}]),e}()}]).default})); diff --git a/webapp/public/js/editorjs/table.js b/webapp/public/js/editorjs/table.js new file mode 100644 index 0000000000..06ea5832b3 --- /dev/null +++ b/webapp/public/js/editorjs/table.js @@ -0,0 +1,7 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/table@2.2.2/dist/table.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Table=e():t.Table=e()}(window,(function(){return function(t){var e={};function o(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,o),i.l=!0,i.exports}return o.m=t,o.c=e,o.d=function(t,e,r){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)o.d(r,i,function(e){return t[e]}.bind(null,i));return r},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="/",o(o.s=6)}([function(t,e){t.exports=''},function(t,e,o){var r=o(2);"string"==typeof r&&(r=[[t.i,r,""]]);var i={hmr:!0,transform:void 0,insertInto:void 0};o(4)(r,i);r.locals&&(t.exports=r.locals)},function(t,e,o){(t.exports=o(3)(!1)).push([t.i,'.tc-wrap{--color-background:#f9f9fb;--color-text-secondary:#7b7e89;--color-border:#e8e8eb;--cell-size:34px;--toolbox-icon-size:18px;--toolbox-padding:6px;--toolbox-aiming-field-size:calc(var(--toolbox-icon-size) + var(--toolbox-padding)*2);border-left:0;position:relative;height:100%;width:100%;margin-top:var(--toolbox-icon-size);box-sizing:border-box;display:grid;grid-template-columns:calc(100% - var(--cell-size)) var(--cell-size);}.tc-wrap--readonly{grid-template-columns:100% var(--cell-size)}.tc-wrap svg{vertical-align:top}@media print{.tc-wrap{border-left-color:var(--color-border);border-left-style:solid;border-left-width:1px;grid-template-columns:100% var(--cell-size)}}@media print{.tc-wrap .tc-row:after{display:none}}.tc-table{position:relative;width:100%;height:100%;display:grid;font-size:14px;border-top:1px solid var(--color-border);line-height:1.4;}.tc-table:after{width:calc(var(--cell-size));height:100%;left:calc(var(--cell-size)*-1);top:0}.tc-table:after,.tc-table:before{position:absolute;content:""}.tc-table:before{width:100%;height:var(--toolbox-aiming-field-size);top:calc(var(--toolbox-aiming-field-size)*-1);left:0}.tc-table--heading .tc-row:first-child{font-weight:600;border-bottom:2px solid var(--color-border);}.tc-table--heading .tc-row:first-child [contenteditable]:empty:before{content:attr(heading);color:var(--color-text-secondary)}.tc-table--heading .tc-row:first-child:after{bottom:-2px;border-bottom:2px solid var(--color-border)}.tc-add-column,.tc-add-row{display:flex;color:var(--color-text-secondary)}@media print{.tc-add{display:none}}.tc-add-column{padding:4px 0;justify-content:center;border-top:1px solid var(--color-border);}@media print{.tc-add-column{display:none}}.tc-add-row{height:var(--cell-size);align-items:center;padding-left:4px;position:relative;}.tc-add-row:before{content:"";position:absolute;right:calc(var(--cell-size)*-1);width:var(--cell-size);height:100%}@media print{.tc-add-row{display:none}}.tc-add-column,.tc-add-row{transition:0s;cursor:pointer;will-change:background-color;}.tc-add-column:hover,.tc-add-row:hover{transition:background-color .1s ease;background-color:var(--color-background)}.tc-add-row{margin-top:1px;}.tc-add-row:hover:before{transition:.1s;background-color:var(--color-background)}.tc-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(10px,1fr));position:relative;border-bottom:1px solid var(--color-border);}.tc-row:after{content:"";pointer-events:none;position:absolute;width:var(--cell-size);height:100%;bottom:-1px;right:calc(var(--cell-size)*-1);border-bottom:1px solid var(--color-border)}.tc-row--selected{background:var(--color-background)}.tc-row--selected:after{background:var(--color-background)}.tc-cell{border-right:1px solid var(--color-border);padding:6px 12px;overflow:hidden;outline:none;line-break:normal;}.tc-cell--selected{background:var(--color-background)}.tc-wrap--readonly .tc-row:after{display:none}.tc-toolbox{--toolbox-padding:6px;--popover-margin:30px;--toggler-click-zone-size:30px;--toggler-dots-color:#7b7e89;--toggler-dots-color-hovered:#1d202b;position:absolute;cursor:pointer;z-index:1;opacity:0;transition:opacity .1s;will-change:left,opacity;}.tc-toolbox--column{top:calc(var(--toggler-click-zone-size)*-1);transform:translateX(calc(var(--toggler-click-zone-size)*-1/2));will-change:left,opacity}.tc-toolbox--row{left:calc(var(--popover-margin)*-1);transform:translateY(calc(var(--toggler-click-zone-size)*-1/2));margin-top:-1px;will-change:top,opacity}.tc-toolbox--showed{opacity:1}.tc-toolbox .tc-popover{position:absolute;top:0;left:var(--popover-margin)}.tc-toolbox__toggler{display:flex;align-items:center;justify-content:center;width:var(--toggler-click-zone-size);height:var(--toggler-click-zone-size);color:var(--toggler-dots-color);opacity:0;transition:opacity .15s ease;will-change:opacity;}.tc-toolbox__toggler:hover{color:var(--toggler-dots-color-hovered)}.tc-toolbox__toggler svg{fill:currentColor}.tc-wrap:hover .tc-toolbox__toggler{opacity:1}.tc-settings .cdx-settings-button{width:50%;margin:0}.tc-popover{--color-border:#eaeaea;--color-background:#fff;--color-background-hover:rgba(232,232,235,0.49);--color-background-confirm:#e24a4a;--color-background-confirm-hover:#d54040;--color-text-confirm:#fff;background:var(--color-background);border:1px solid var(--color-border);box-shadow:0 3px 15px -3px rgba(13,20,33,.13);border-radius:6px;padding:6px;display:none;will-change:opacity,transform;}.tc-popover--opened{display:block;animation:menuShowing .1s cubic-bezier(.215,.61,.355,1) forwards}.tc-popover__item{display:flex;align-items:center;padding:2px 14px 2px 2px;border-radius:5px;cursor:pointer;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.tc-popover__item:hover{background:var(--color-background-hover)}.tc-popover__item:not(:last-of-type){margin-bottom:2px}.tc-popover__item-icon{display:inline-flex;width:26px;height:26px;align-items:center;justify-content:center;background:var(--color-background);border-radius:5px;border:1px solid var(--color-border);margin-right:8px}.tc-popover__item-label{line-height:22px;font-size:14px;font-weight:500}.tc-popover__item--confirm{background:var(--color-background-confirm);color:var(--color-text-confirm);}.tc-popover__item--confirm:hover{background-color:var(--color-background-confirm-hover)}.tc-popover__item--confirm .tc-popover__item-icon{background:var(--color-background-confirm);border-color:rgba(0,0,0,.1);}.tc-popover__item--confirm .tc-popover__item-icon svg{transition:transform .2s ease-in;transform:rotate(90deg) scale(1.2)}.tc-popover__item--hidden{display:none}@keyframes menuShowing{0%{opacity:0;transform:translateY(-8px) scale(.9)}70%{opacity:1;transform:translateY(2px)}to{transform:translateY(0)}}',""])},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var o=function(t,e){var o=t[1]||"",r=t[3];if(!r)return o;if(e&&"function"==typeof btoa){var i=(s=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(s))))+" */"),n=r.sources.map((function(t){return"/*# sourceURL="+r.sourceRoot+t+" */"}));return[o].concat(n).concat([i]).join("\n")}var s;return[o].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+o+"}":o})).join("")},e.i=function(t,o){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},i=0;i=0&&h.splice(e,1)}function w(t){var e=document.createElement("style");if(void 0===t.attrs.type&&(t.attrs.type="text/css"),void 0===t.attrs.nonce){var r=function(){0;return o.nc}();r&&(t.attrs.nonce=r)}return b(e,t.attrs),g(t,e),e}function b(t,e){Object.keys(e).forEach((function(o){t.setAttribute(o,e[o])}))}function v(t,e){var o,r,i,n;if(e.transform&&t.css){if(!(n="function"==typeof e.transform?e.transform(t.css):e.transform.default(t.css)))return function(){};t.css=n}if(e.singleton){var s=d++;o=c||(c=w(e)),r=y.bind(null,o,s,!1),i=y.bind(null,o,s,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(o=function(t){var e=document.createElement("link");return void 0===t.attrs.type&&(t.attrs.type="text/css"),t.attrs.rel="stylesheet",b(e,t.attrs),g(t,e),e}(e),r=R.bind(null,o,e),i=function(){m(o),o.href&&URL.revokeObjectURL(o.href)}):(o=w(e),r=k.bind(null,o),i=function(){m(o)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else i()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=s()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var o=f(t,e);return u(o,e),function(t){for(var r=[],i=0;i{const o=r("div",a.CSS.item),i=r("div",a.CSS.itemIcon,{innerHTML:t.icon}),n=r("div",a.CSS.itemLabel,{textContent:t.label});o.dataset.index=e,o.appendChild(i),o.appendChild(n),this.wrapper.appendChild(o),this.itemEls.push(o)}),this.wrapper.addEventListener("click",t=>{this.popoverClicked(t)}),this.wrapper}popoverClicked(t){const e=t.target.closest("."+a.CSS.item);if(!e)return;const o=e.dataset.index,r=this.items[o];!r.confirmationRequired||this.hasConfirmationState(e)?r.onClick():this.setConfirmationState(e)}setConfirmationState(t){t.classList.add(a.CSS.itemConfirmState)}clearConfirmationState(t){t.classList.remove(a.CSS.itemConfirmState)}hasConfirmationState(t){return t.classList.contains(a.CSS.itemConfirmState)}get opened(){return this.wrapper.classList.contains(a.CSS.popoverOpened)}open(){this.items.forEach((t,e)=>{"function"==typeof t.hideIf&&this.itemEls[e].classList.toggle(a.CSS.itemHidden,t.hideIf())}),this.wrapper.classList.add(a.CSS.popoverOpened)}close(){this.wrapper.classList.remove(a.CSS.popoverOpened),this.itemEls.forEach(t=>{this.clearConfirmationState(t)})}}var c=o(0),d=o.n(c);class h{constructor({api:t,items:e,onOpen:o,onClose:r,cssModifier:i=""}){this.api=t,this.items=e,this.onOpen=o,this.onClose=r,this.cssModifier=i,this.popover=null,this.wrapper=this.createToolbox()}static get CSS(){return{toolbox:"tc-toolbox",toolboxShowed:"tc-toolbox--showed",toggler:"tc-toolbox__toggler"}}get element(){return this.wrapper}createToolbox(){const t=r("div",[h.CSS.toolbox,this.cssModifier?`${h.CSS.toolbox}--${this.cssModifier}`:""]);t.dataset.mutationFree="true";const e=this.createPopover(),o=this.createToggler();return t.appendChild(o),t.appendChild(e),t}createToggler(){const t=r("div",h.CSS.toggler,{innerHTML:d.a});return t.addEventListener("click",()=>{this.togglerClicked()}),t}createPopover(){return this.popover=new a({items:this.items}),this.popover.render()}togglerClicked(){this.popover.opened?(this.popover.close(),this.onClose()):(this.popover.open(),this.onOpen())}show(t){const e=t();Object.entries(e).forEach(([t,e])=>{this.wrapper.style[t]=e}),this.wrapper.classList.add(h.CSS.toolboxShowed)}hide(){this.popover.close(),this.wrapper.classList.remove(h.CSS.toolboxShowed)}}const p='',u='',f="tc-wrap",g="tc-wrap--readonly",m="tc-table",w="tc-row",b="tc-table--heading",v="tc-row--selected",x="tc-cell",C="tc-cell--selected",y="tc-add-row",k="tc-add-column";class R{constructor(t,e,o,r){this.readOnly=t,this.api=e,this.data=o,this.config=r,this.wrapper=null,this.table=null,this.toolboxColumn=this.createColumnToolbox(),this.toolboxRow=this.createRowToolbox(),this.createTableWrapper(),this.hoveredRow=0,this.hoveredColumn=0,this.selectedRow=0,this.selectedColumn=0,this.tunes={withHeadings:!1},this.resize(),this.fill(),this.focusedCell={row:0,column:0},this.documentClicked=t=>{const e=null!==t.target.closest("."+m),o=null===t.target.closest("."+f);(e||o)&&this.hideToolboxes();const r=t.target.closest("."+y),i=t.target.closest("."+k);r&&r.parentNode===this.wrapper?(this.addRow(void 0,!0),this.hideToolboxes()):i&&i.parentNode===this.wrapper&&(this.addColumn(void 0,!0),this.hideToolboxes())},this.readOnly||this.bindEvents()}getWrapper(){return this.wrapper}bindEvents(){document.addEventListener("click",this.documentClicked),this.table.addEventListener("mousemove",function(t,e){let o=0;return function(...r){const i=(new Date).getTime();if(!(i-othis.onMouseMoveInTable(t)),{passive:!0}),this.table.onkeypress=t=>this.onKeyPressListener(t),this.table.addEventListener("keydown",t=>this.onKeyDownListener(t)),this.table.addEventListener("focusin",t=>this.focusInTableListener(t))}createColumnToolbox(){return new h({api:this.api,cssModifier:"column",items:[{label:this.api.i18n.t("Add column to left"),icon:'',onClick:()=>{this.addColumn(this.selectedColumn,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Add column to right"),icon:'',onClick:()=>{this.addColumn(this.selectedColumn+1,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Delete column"),icon:p,hideIf:()=>1===this.numberOfColumns,confirmationRequired:!0,onClick:()=>{this.deleteColumn(this.selectedColumn),this.hideToolboxes()}}],onOpen:()=>{this.selectColumn(this.hoveredColumn),this.hideRowToolbox()},onClose:()=>{this.unselectColumn()}})}createRowToolbox(){return new h({api:this.api,cssModifier:"row",items:[{label:this.api.i18n.t("Add row above"),icon:'',onClick:()=>{this.addRow(this.selectedRow,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Add row below"),icon:'',onClick:()=>{this.addRow(this.selectedRow+1,!0),this.hideToolboxes()}},{label:this.api.i18n.t("Delete row"),icon:p,hideIf:()=>1===this.numberOfRows,confirmationRequired:!0,onClick:()=>{this.deleteRow(this.selectedRow),this.hideToolboxes()}}],onOpen:()=>{this.selectRow(this.hoveredRow),this.hideColumnToolbox()},onClose:()=>{this.unselectRow()}})}moveCursorToNextRow(){this.focusedCell.row!==this.numberOfRows?(this.focusedCell.row+=1,this.focusCell(this.focusedCell)):(this.addRow(),this.focusedCell.row+=1,this.focusCell(this.focusedCell),this.updateToolboxesPosition(0,0))}getCell(t,e){return this.table.querySelectorAll(`.${w}:nth-child(${t}) .${x}`)[e-1]}getRow(t){return this.table.querySelector(`.${w}:nth-child(${t})`)}getRowByCell(t){return t.parentElement}getRowFirstCell(t){return t.querySelector(`.${x}:first-child`)}setCellContent(t,e,o){this.getCell(t,e).innerHTML=o}addColumn(t=-1,e=!1){let o=this.numberOfColumns;for(let r=1;r<=this.numberOfRows;r++){let i;const n=this.createCell();if(t>0&&t<=o?(i=this.getCell(r,t),s(n,i)):i=this.getRow(r).appendChild(n),1===r){const i=this.getCell(r,t>0?t:o+1);i&&e&&l(i)}}this.addHeadingAttrToFirstRow()}addRow(t=-1,e=!1){let o,i=r("div",w);this.tunes.withHeadings&&this.removeHeadingAttrFromFirstRow();let n=this.numberOfColumns;if(t>0&&t<=this.numberOfRows){o=s(i,this.getRow(t))}else o=this.table.appendChild(i);this.fillRow(o,n),this.tunes.withHeadings&&this.addHeadingAttrToFirstRow();const a=this.getRowFirstCell(o);return a&&e&&l(a),o}deleteColumn(t){for(let e=1;e<=this.numberOfRows;e++){const o=this.getCell(e,t);if(!o)return;o.remove()}}deleteRow(t){this.getRow(t).remove(),this.addHeadingAttrToFirstRow()}createTableWrapper(){if(this.wrapper=r("div",f),this.table=r("div",m),this.readOnly&&this.wrapper.classList.add(g),this.wrapper.appendChild(this.toolboxRow.element),this.wrapper.appendChild(this.toolboxColumn.element),this.wrapper.appendChild(this.table),!this.readOnly){const t=r("div",k,{innerHTML:u}),e=r("div",y,{innerHTML:u});this.wrapper.appendChild(t),this.wrapper.appendChild(e)}}computeInitialSize(){const t=this.data&&this.data.content,e=Array.isArray(t),o=!!e&&t.length,r=e?t.length:void 0,i=o?t[0].length:void 0,n=Number.parseInt(this.config&&this.config.rows),s=Number.parseInt(this.config&&this.config.cols),l=!isNaN(n)&&n>0?n:void 0,a=!isNaN(s)&&s>0?s:void 0;return{rows:r||l||2,cols:i||a||2}}resize(){const{rows:t,cols:e}=this.computeInitialSize();for(let e=0;e0&&e<=this.numberOfColumns&&this.toolboxColumn.show(()=>({left:`calc((100% - var(--cell-size)) / (${this.numberOfColumns} * 2) * (1 + (${e} - 1) * 2))`})),this.isRowMenuShowing||t>0&&t<=this.numberOfRows&&this.toolboxRow.show(()=>{const e=this.getRow(t),{fromTopBorder:o}=n(this.table,e),{height:r}=e.getBoundingClientRect();return{top:Math.ceil(o+r/2)+"px"}})}setHeadingsSetting(t){this.tunes.withHeadings=t,t?(this.table.classList.add(b),this.addHeadingAttrToFirstRow()):(this.table.classList.remove(b),this.removeHeadingAttrFromFirstRow())}addHeadingAttrToFirstRow(){for(let t=1;t<=this.numberOfColumns;t++){let e=this.getCell(1,t);e&&e.setAttribute("heading",this.api.i18n.t("Heading"))}}removeHeadingAttrFromFirstRow(){for(let t=1;t<=this.numberOfColumns;t++){let e=this.getCell(1,t);e&&e.removeAttribute("heading")}}selectRow(t){const e=this.getRow(t);e&&(this.selectedRow=t,e.classList.add(v))}unselectRow(){if(this.selectedRow<=0)return;const t=this.table.querySelector("."+v);t&&t.classList.remove(v),this.selectedRow=0}selectColumn(t){for(let e=1;e<=this.numberOfRows;e++){const o=this.getCell(e,t);o&&o.classList.add(C)}this.selectedColumn=t}unselectColumn(){if(this.selectedColumn<=0)return;let t=this.table.querySelectorAll("."+C);Array.from(t).forEach(t=>{t.classList.remove(C)}),this.selectedColumn=0}getHoveredCell(t){let e=this.hoveredRow,o=this.hoveredColumn;const{width:r,height:i,x:n,y:s}=function(t,e){const o=t.getBoundingClientRect(),{width:r,height:i,x:n,y:s}=o,{clientX:l,clientY:a}=e;return{width:r,height:i,x:l-n,y:a-s}}(this.table,t);return n>=0&&(o=this.binSearch(this.numberOfColumns,t=>this.getCell(1,t),({fromLeftBorder:t})=>nn>r-t)),s>=0&&(e=this.binSearch(this.numberOfRows,t=>this.getCell(t,1),({fromTopBorder:t})=>ss>i-t)),{row:e||this.hoveredRow,column:o||this.hoveredColumn}}binSearch(t,e,o,r){let i,s=0,l=t+1,a=0;for(;s!t.textContent.trim())||t.push(r.map(t=>t.innerHTML))}return t}destroy(){document.removeEventListener("click",this.documentClicked)}}o(1),e.default=class{static get isReadOnlySupported(){return!0}static get enableLineBreaks(){return!0}constructor({data:t,config:e,api:o,readOnly:r}){this.api=o,this.readOnly=r,this.config=e,this.data={withHeadings:this.getConfig("withHeadings",!1,t),content:t&&t.content?t.content:[]},this.table=null}static get toolbox(){return{icon:'',title:"Table"}}render(){return this.table=new R(this.readOnly,this.api,this.data,this.config),this.container=r("div",this.api.styles.block),this.container.appendChild(this.table.getWrapper()),this.table.setHeadingsSetting(this.data.withHeadings),this.container}renderSettings(){return[{label:this.api.i18n.t("With headings"),icon:'',isActive:this.data.withHeadings,closeOnActivate:!0,toggle:!0,onActivate:()=>{this.data.withHeadings=!0,this.table.setHeadingsSetting(this.data.withHeadings)}},{label:this.api.i18n.t("Without headings"),icon:'',isActive:!this.data.withHeadings,closeOnActivate:!0,toggle:!0,onActivate:()=>{this.data.withHeadings=!1,this.table.setHeadingsSetting(this.data.withHeadings)}}]}save(){const t=this.table.getData();return{withHeadings:this.data.withHeadings,content:t}}destroy(){this.table.destroy()}getConfig(t,e,o){const r=this.data||o;return r?r[t]?r[t]:e:this.config&&this.config[t]?this.config[t]:e}static get pasteConfig(){return{tags:["TABLE","TR","TH","TD"]}}onPaste(t){const e=t.detail.data,o=e.querySelector(":scope > thead, tr:first-of-type th"),r=Array.from(e.querySelectorAll("tr")).map(t=>Array.from(t.querySelectorAll("th, td")).map(t=>t.innerHTML));this.data={withHeadings:null!==o,content:r},this.table.wrapper&&this.table.wrapper.replaceWith(this.render())}}}]).default})); diff --git a/webapp/public/js/editorjs/underline.js b/webapp/public/js/editorjs/underline.js new file mode 100644 index 0000000000..774561471e --- /dev/null +++ b/webapp/public/js/editorjs/underline.js @@ -0,0 +1,7 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/@editorjs/underline@1.1.0/dist/bundle.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Underline=t():e.Underline=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=4)}([function(e,t,n){var r=n(1),o=n(2);"string"==typeof(o=o.__esModule?o.default:o)&&(o=[[e.i,o,""]]);var i={insert:"head",singleton:!1};r(o,i);e.exports=o.locals||{}},function(e,t,n){"use strict";var r,o=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},i=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),a=[];function u(e){for(var t=-1,n=0;n'}}])&&o(t.prototype,n),r&&o(t,r),Object.defineProperty(t,"prototype",{writable:!1}),e}()}]).default})); diff --git a/webapp/public/media/.gitignore b/webapp/public/media/.gitignore new file mode 100644 index 0000000000..c87b89035f --- /dev/null +++ b/webapp/public/media/.gitignore @@ -0,0 +1,4 @@ +* +!/**/ +!/**/*.gitkeep + diff --git a/webapp/public/media/images/blog/in-article/.gitkeep b/webapp/public/media/images/blog/in-article/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/webapp/public/media/images/blog/thumbnails/.gitkeep b/webapp/public/media/images/blog/thumbnails/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/webapp/public/robots.txt b/webapp/public/robots.txt index 4665fcae34..983a071b48 100644 --- a/webapp/public/robots.txt +++ b/webapp/public/robots.txt @@ -2,4 +2,7 @@ # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 User-agent: * -Disallow: +Disallow: /public/*/attachment/* +Disallow: /public/team/* +Disallow: /public/problems/*/text +Disallow: /public/*/samples.zip diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index cd8235219a..d718dc8eca 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -1,7 +1,7 @@ :root { /** Specify the BG color in one place. **/ - --background-color: white; - --text-color: black; + /*--background-color: white;*/ + /*--text-color: black;*/ } /** WCAG fixes start **/ @@ -17,10 +17,9 @@ /** WCAG fixes end **/ body { - color: var(--text-color); - background-color: var(--background-color); + /*color: var(--text-color);*/ + /*background-color: var(--background-color);*/ font-family: Roboto, sans-serif; - padding-bottom: 4em; padding-top: 4.5rem; } @@ -199,6 +198,11 @@ del { min-width: 4.2em; border-right: none; } + +html[data-bs-theme="dark"] .scoreboard a { + color: var(--bs-light); +} + .scoreboard a { display: block; padding: 2px 1px 2px 1px; @@ -229,6 +233,11 @@ del { } .scoreboard .scoreaf { white-space: nowrap; border: 0; padding-left: 2px; text-align: center; } .scoreboard .scoreaf img { vertical-align: middle; } + +html[data-bs-theme="dark"] .univ { + color: var(--bs-gray-400); +} + .univ { font-size: 80%; font-weight: normal; @@ -246,12 +255,17 @@ img.affiliation-logo { border-bottom: 2px solid black; white-space: nowrap; } + +html[data-bs-theme="dark"] .scoreheader th { + background: var(--bs-dark); +} + .scoreheader th { text-align: center; box-shadow: -1px 0 0 0 silver inset, 0 2px 0 0 black; border: none; - background: var(--background-color); + background: var(--bs-light); position: sticky; top: 0; z-index: 1; @@ -260,7 +274,7 @@ img.affiliation-logo { .problempoints { font-size: smaller; font-weight: normal; - color: #303030; + /*color: #303030;*/ padding-left: 3pt; padding-right: 3pt; } @@ -434,6 +448,10 @@ tr.ignore td, td.ignore, span.ignore { font-weight: normal; } +html[data-bs-theme="dark"] .teamoverview { + background-color: var(--bs-dark); +} + .teamoverview { border-top: solid 1px darkgray; border-bottom: solid 1px darkgray; @@ -622,6 +640,8 @@ tr.ignore td, td.ignore, span.ignore { --badge-padding-x: 0.5em; font-size: 1em; min-width: 2em; + white-space: normal; + text-wrap: nowrap; } .tooltip .tooltip-inner { @@ -635,7 +655,7 @@ blockquote { } .category-best { - margin-right: 2em; + /*margin-right: 2em;*/ font-weight: normal; } @@ -699,3 +719,43 @@ blockquote { padding: 3px; border-radius: 5px; } + +.faicon-badge-container { + position: relative; + display: inline-block; +} + +.faicon-badge-dot-red { + position: absolute; + top: 0; + right: 0; + width: 10px; + height: 10px; + background-color: #e43636; + border-radius: 50%; +} + +.faicon-badge-dot-green { + position: absolute; + top: 0; + right: 0; + width: 10px; + height: 10px; + background-color: #28a745; + border-radius: 50%; +} + +.faicon-dot-red { + color: #e43636; +} +.faicon-dot-green { + color: #28a745; +} + +/* xs to (not including) lg */ +@media (max-width: 1200px) { + nav.navbar { + max-height: 100vh; + overflow-y: scroll; + } +} diff --git a/webapp/public/style_jury.css b/webapp/public/style_jury.css index c07b7da6c7..135696ec41 100644 --- a/webapp/public/style_jury.css +++ b/webapp/public/style_jury.css @@ -243,3 +243,9 @@ table.table-full-clickable-cell tr .table-button-head-right-right{ .timebutton { min-width: 9em; } + +#currentContests .collapsing { + -webkit-transition: none; + transition: none; + display: none; +} diff --git a/webapp/public/style_login.css b/webapp/public/style_login.css index 70dd686980..4c6c6be7d0 100644 --- a/webapp/public/style_login.css +++ b/webapp/public/style_login.css @@ -19,8 +19,8 @@ body { align-items: center; justify-content: center; padding-top: 40px; - padding-bottom: 40px; - background-color: #f5f5f5; + /*padding-bottom: 40px;*/ + /*background-color: #f5f5f5;*/ } .form-signin { diff --git a/webapp/src/Controller/API/AbstractApiController.php b/webapp/src/Controller/API/AbstractApiController.php index 163f000bba..4db784f71a 100644 --- a/webapp/src/Controller/API/AbstractApiController.php +++ b/webapp/src/Controller/API/AbstractApiController.php @@ -43,6 +43,7 @@ protected function getContestQueryBuilder(bool $onlyActive = false): QueryBuilde ->from(Contest::class, 'c') ->select('c') ->andWhere('c.enabled = 1') + ->orderBy('c.ranknumber') ->orderBy('c.activatetime'); if ($onlyActive || !$this->dj->checkrole('api_reader')) { diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 276519a9ea..d064f0aefd 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -24,6 +24,7 @@ use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use App\Service\EventLogService; +use App\Service\PointsScoreService; use App\Service\RejudgingService; use App\Service\ScoreboardService; use App\Service\SubmissionService; @@ -69,7 +70,8 @@ public function __construct( protected readonly SubmissionService $submissionService, protected readonly BalloonService $balloonService, protected readonly RejudgingService $rejudgingService, - protected readonly LoggerInterface $logger + protected readonly LoggerInterface $logger, + protected readonly PointsScoreService $pointsScoreService ) {} /** @@ -986,15 +988,15 @@ private function addSingleJudgingRun( $runs = $this->em->createQueryBuilder() ->from(JudgeTask::class, 'jt') ->leftJoin(JudgingRun::class, 'jr', Join::WITH, 'jt.testcase_id = jr.testcase AND jr.judging = :judgingid') - ->select('jr.runresult') + ->select('jr') ->andWhere('jt.jobid = :judgingid') ->andWhere('jr.judging = :judgingid') ->andWhere('jt.testcase_id = jr.testcase') ->orderBy('jt.judgetaskid') ->setParameter('judgingid', $judging->getJudgingid()) ->getQuery() - ->getArrayResult(); - $runresults = array_column($runs, 'runresult'); + ->getResult(); + $runresults = array_map(fn($run) => $run->getRunresult(), $runs); $oldResult = $judging->getResult(); @@ -1009,6 +1011,11 @@ private function addSingleJudgingRun( $judging->setResult($result); + if ($lazyEval === DOMJudgeService::EVAL_FULL) { + $pointsScored = $this->pointsScoreService->getScoredPoints($judging, $runs); + $judging->setPointsScored($pointsScored); + } + $hasNullResults = false; foreach ($runresults as $runresult) { if ($runresult === null) { diff --git a/webapp/src/Controller/BlogController.php b/webapp/src/Controller/BlogController.php new file mode 100644 index 0000000000..8d2d08005f --- /dev/null +++ b/webapp/src/Controller/BlogController.php @@ -0,0 +1,119 @@ +postsPerPage = $this->config->get('blog_posts_per_page'); + } + + #[Route("", name: "public_blog_list")] + public function listAction(Request $request): Response + { + $totalPosts = $this->em->getRepository(BlogPost::class) + ->createQueryBuilder('bp') + ->select('count(bp.blogpostid)') + ->where('bp.publishtime <= :now') + ->getQuery() + ->setParameter('now', new DateTime()) + ->getSingleScalarResult(); + + $totalPages = ceil($totalPosts / $this->postsPerPage); + + $page = (int)min($request->query->getInt('page', 1), $totalPages); + $page = (int)max($page, 1); + + $blogPosts = $this->em->getRepository(BlogPost::class) + ->createQueryBuilder('bp') + ->orderBy('bp.publishtime', 'DESC') + ->where('bp.publishtime <= :now') + ->setFirstResult(($page - 1) * $this->postsPerPage) + ->setMaxResults($this->postsPerPage) + ->getQuery() + ->setParameter('now', new DateTime()) + ->getResult(); + + return $this->render('public/blog_list.html.twig', [ + 'posts' => $blogPosts, + 'page' => $page, + 'totalPages' => $totalPages, + ]); + } + + #[Route("/{slug}", name: "public_blog_post")] + public function viewAction(string $slug): Response + { + /** @var BlogPost $blogPost */ + $blogPost = $this->em->getRepository(BlogPost::class)->findOneBy(['slug' => $slug]); + + if (!$blogPost || !$blogPost->isPublished()) { + throw new NotFoundHttpException(sprintf('Blog post with slug %s not found', $slug)); + } + + $parser = new Parser(); + $parser->addBlockParser(new ParagraphBlockParser()); + $parser->addBlockParser(new HeaderBlockParser()); + $parser->addBlockParser(new ListBlockParser()); + $parser->addBlockParser(new ImageBlockParser()); + $parser->addBlockParser(new CodeBlockParser()); + $parser->addBlockParser(new TableBlockParser()); + $parserResult = $parser->parse($blogPost->getBody()); + + $renderer = new Renderer(); + $renderer->addBlockRenderer(new ParagraphBlockRenderer()); + $renderer->addBlockRenderer(new HeaderBlockRenderer()); + $renderer->addBlockRenderer(new ListBlockRenderer()); + $renderer->addBlockRenderer(new ImageBlockRenderer()); + $renderer->addBlockRenderer(new CodeBlockRenderer()); + $renderer->addBlockRenderer(new TableBlockRenderer()); + + $body = $renderer->render($parserResult); + + $blogPostData = [ + 'title' => $blogPost->getTitle(), + 'subtitle' => $blogPost->getSubtitle(), + 'author' => $blogPost->getAuthor(), + 'body' => $body, + 'publishTime' => $blogPost->getPublishtime(), + ]; + + return $this->render('public/blog_post.html.twig', + $blogPostData + ); + } +} diff --git a/webapp/src/Controller/HelpController.php b/webapp/src/Controller/HelpController.php new file mode 100644 index 0000000000..adcd1fac71 --- /dev/null +++ b/webapp/src/Controller/HelpController.php @@ -0,0 +1,16 @@ +render('public/help.html.twig'); + } +} diff --git a/webapp/src/Controller/Jury/BlogController.php b/webapp/src/Controller/Jury/BlogController.php new file mode 100644 index 0000000000..c5ddf9f392 --- /dev/null +++ b/webapp/src/Controller/Jury/BlogController.php @@ -0,0 +1,246 @@ +em = $em; + $this->dj = $dj; + $this->config = $config; + $this->kernel = $kernel; + $this->eventLogService = $eventLogService; + $this->slugger = new AsciiSlugger(); + } + + #[Route(path: '', name: 'jury_blog', methods: ['GET'])] + public function indexAction(): Response + { + /** @var BlogPost[] $blogPosts */ + $blogPosts = $this->em->getRepository(BlogPost::class)->findAll(); + + $table_fields = [ + 'title' => ['title' => 'title', 'sort' => true], + 'author' => ['title' => 'author', 'sort' => true], + 'publishtime' => ['title' => 'publish time', 'sort' => true, + 'default_sort' => true], + 'ispublished' => ['title' => 'published', 'sort' => true], + ]; + + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $blogPostsTable = []; + foreach ($blogPosts as $b) { + /** @var BlogPost $b */ + $blogPostData = []; + $blogPostActions = []; + + // Get whatever fields we can from the user object itself. + foreach ($table_fields as $k => $v) { + if ($propertyAccessor->isReadable($b, $k)) { + $value = $propertyAccessor->getValue($b, $k); + + switch ($k) { + case 'ispublished': + $value = $value ? 'yes' : 'no'; + break; + + case 'publishtime': + $value = $value->format('Y-m-d H:i:s'); + break; + } + + $blogPostData[$k] = ['value' => $value]; + } + } + + if ($this->isGranted('ROLE_ADMIN')) { + $blogPostActions[] = [ + 'icon' => 'edit', + 'title' => 'edit this blog post', + 'link' => $this->generateUrl('jury_blog_post_edit', [ + 'id' => $b->getBlogpostid(), + ]) + ]; + $blogPostActions[] = [ + 'icon' => 'trash-alt', + 'title' => 'delete this blog post', + 'link' => $this->generateUrl('jury_blog_post_delete', [ + 'id' => $b->getBlogpostid(), + ]), + 'ajaxModal' => true, + ]; + } + + // Save this to our list of rows. + $blogPostsTable[] = [ + 'data' => $blogPostData, + 'actions' => $blogPostActions, + 'link' => $this->generateUrl('jury_blog_post_edit', ['id' => $b->getBlogpostid()]), + ]; + } + + return $this->render('jury/blog.html.twig', [ + 'blog_posts' => $blogPostsTable, + 'table_fields' => $table_fields, + 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, + ]); + } + + #[Route(path: '/send/image-upload', name: 'jury_blog_image_upload', methods: ['POST'])] + #[IsGranted('ROLE_ADMIN')] + public function uploadPostImageAction(Request $request): JsonResponse + { + /** @var UploadedFile|null $imageFile */ + $imageFile = $request->files->get('image'); + + if (!$imageFile) { + return new JsonResponse( + ['success' => 0, 'error' => 'No image file found.'], + Response::HTTP_BAD_REQUEST + ); + } + + try { + $fileName = $this->saveImage($imageFile, self::IN_ARTICLE_IMAGES_DIRECTORY); + } catch (FileException $e) { + return new JsonResponse( + ['success' => 0, 'error' => 'Error uploading the image.'], + Response::HTTP_INTERNAL_SERVER_ERROR + ); + } + + return new JsonResponse([ + 'success' => 1, + 'file' => [ + 'url' => self::EDITORJS_IMAGE_BASE_URL . $fileName, + ] + ]); + } + + #[Route(path: '/send', name: 'jury_blog_post_send', methods: ['GET', 'POST'])] + #[Route(path: '/{id<\d+>}/edit', name: 'jury_blog_post_edit', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_ADMIN')] + public function sendBlogPostAction(Request $request, ?int $id = null): Response + { + $editing = $id !== null; + + if ($editing) { + $blogPost = $this->em->getRepository(BlogPost::class)->find($id); + if (!$blogPost) { + throw $this->createNotFoundException('No blog post found for id ' . $id); + } + } else { + $blogPost = new BlogPost(); + } + + $form = $this->createForm(BlogPostType::class, $blogPost, [ + 'thumbnail_required' => !$editing, + ]); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + /** @var BlogPost $blogPost */ + $blogPost = $form->getData(); + + $slug = strtolower($this->slugger->slug($blogPost->getTitle())->toString()); + $duplicate = $this->em->getRepository(BlogPost::class)->findOneBy(['slug' => $slug]); + if (!$editing) { + if ($duplicate) { + $slug .= '-' . uniqid(); + } + $blogPost->setSlug($slug); + } + + if ($form['thumbnail']->getData()) { + $thumbnailFileName = $this->saveImage( + $form['thumbnail']->getData(), + self::THUMBNAILS_DIRECTORY + ); + $blogPost->setThumbnailFileName($thumbnailFileName); + } + + $this->em->persist($blogPost); + $this->em->flush(); + + $blogpostId = $blogPost->getBlogpostid(); + $this->dj->auditlog('blog_post', $blogpostId, $editing ? 'edited' : 'added'); + $this->eventLogService->log('blog_post', $blogpostId, $editing ? 'edited' : 'added'); + + if (!$blogPost->isPublished()) { + return $this->redirectToRoute('jury_blog'); + } + + return $this->redirectToRoute('public_blog_post', ['slug' => $blogPost->getSlug()]); + } + + return $this->renderForm('jury/blog_post_send.html.twig', [ + 'form' => $form, + 'action' => $editing ? 'edit' : 'send' + ]); + } + + #[Route(path: '/{id<\d+>}/delete', name: 'jury_blog_post_delete', methods: ['POST'])] + #[IsGranted('ROLE_ADMIN')] + public function deleteBlogPostAction(Request $request, int $id): Response + { + /** @var BlogPost $blogPost */ + $blogPost = $this->em->getRepository(BlogPost::class)->find($id); + if (!$blogPost) { + throw new NotFoundHttpException(sprintf('Blog post with ID %s not found', $id)); + } + + return $this->deleteEntities($request, $this->em, $this->dj, $this->eventLogService, $this->kernel, + [$blogPost], $this->generateUrl('jury_blog')); + } + + private function saveImage(UploadedFile $file, string $directory): string + { + $fileName = md5(uniqid()) . '.' . $file->guessExtension(); + + $file->move( + join('/', [$this->getParameter('image_directory'), $directory]), + $fileName + ); + + return join('/', [$directory, $fileName]); + } +} diff --git a/webapp/src/Controller/Jury/ContestController.php b/webapp/src/Controller/Jury/ContestController.php index 2fc04de7b4..323803fb68 100644 --- a/webapp/src/Controller/Jury/ContestController.php +++ b/webapp/src/Controller/Jury/ContestController.php @@ -75,17 +75,18 @@ public function indexAction(Request $request): Response $contests = $em->createQueryBuilder() ->select('c') ->from(Contest::class, 'c') - ->orderBy('c.starttime', 'DESC') + ->orderBy('c.ranknumber') ->groupBy('c.cid') ->getQuery()->getResult(); $table_fields = [ + 'rank' => ['title' => 'rank', 'sort' => true, + 'default_sort' => true, 'default_sort_order' => 'asc'], 'cid' => ['title' => 'CID', 'sort' => true], 'shortname' => ['title' => 'shortname', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true], 'activatetime' => ['title' => 'activate', 'sort' => true], - 'starttime' => ['title' => 'start', 'sort' => true, - 'default_sort' => true, 'default_sort_order' => 'desc'], + 'starttime' => ['title' => 'start', 'sort' => true], 'endtime' => ['title' => 'end', 'sort' => true], ]; @@ -168,6 +169,22 @@ public function indexAction(Request $request): Response $contestactions[] = []; $contestactions[] = []; } else { + $contestactions[] = [ + 'icon' => 'caret-up', + 'title' => 'move up this contest', + 'link' => $this->generateUrl('jury_contest_move', [ + 'contestId' => $contest->getCid(), + 'direction' => 'up' + ]) + ]; + $contestactions[] = [ + 'icon' => 'caret-down', + 'title' => 'move down this contest', + 'link' => $this->generateUrl('jury_contest_move', [ + 'contestId' => $contest->getCid(), + 'direction' => 'down' + ]) + ]; $contestactions[] = [ 'icon' => 'edit', 'title' => 'edit this contest', @@ -295,7 +312,7 @@ public function indexAction(Request $request): Response ->andWhere('c.activatetime > :now') ->andWhere('c.enabled = 1') ->setParameter('now', Utils::now()) - ->orderBy('c.activatetime') + ->orderBy('c.ranknumber') ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(); @@ -595,8 +612,25 @@ public function deleteAction(Request $request, int $contestId): Response return $this->redirectToRoute('jury_contest', ['contestId' => $contestId]); } - return $this->deleteEntities($request, $this->em, $this->dj, $this->eventLogService, $this->kernel, + $oldRank = $contest->getRank(); + $response = $this->deleteEntities($request, $this->em, $this->dj, $this->eventLogService, $this->kernel, [$contest], $this->generateUrl('jury_contests')); + + if (!$request->isMethod('POST')) { + return $response; + } + + $this->em->clear(); + /** @var Contest[] $contests */ + $contests = $this->em->getRepository(Contest::class)->findBy([], ['ranknumber' => 'ASC']); + foreach ($contests as $contest) { + if ($contest->getRank() > $oldRank) { + $contest->setRank($contest->getRank() - 1); + } + } + $this->em->flush(); + + return $response; } #[IsGranted('ROLE_ADMIN')] @@ -642,6 +676,9 @@ public function addAction(Request $request): Response } $this->em->wrapInTransaction(function () use ($contest) { + $contestsCount = $this->em->getRepository(Contest::class)->count([]); + $contest->setRank($contestsCount + 1); + // A little 'hack': we need to first persist and save the // contest, before we can persist and save the problem, // because we need a contest ID. @@ -1067,4 +1104,66 @@ public function viewProblemsetAction(int $cid): StreamedResponse return $contest->getContestProblemsetStreamedResponse(); } + + #[Route( + '/{contestId<\d+>}/move/{direction}', + name: 'jury_contest_move' + )] + #[IsGranted('ROLE_ADMIN')] + public function moveContestAction(int $contestId, string $direction): Response + { + /** @var Contest $contest */ + $contest = $this->em->getRepository(Contest::class)->find($contestId); + if (!$contest) { + throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); + } + + /** @var Contest[] $contests */ + $contests = $this->em->createQueryBuilder() + ->from(Contest::class, 'c') + ->select('c') + ->orderBy('c.ranknumber') + ->getQuery() + ->getResult(); + + // First find contest to switch with. + /** @var Contest|null $last */ + $last = null; + /** @var Contest|null $other */ + $other = null; + /** @var Contest|null $current */ + $current = $contest; + + $numContests = count($contests); + + foreach ($contests as $currentContest) { + if ($currentContest->getCid() === $contestId && $direction === 'up') { + $other = $last; + break; + } + if ($last !== null && $contestId === $last->getCid() && $direction === 'down') { + $other = $currentContest; + break; + } + $last = $currentContest; + } + + if ($other !== null) { + // (rank) is a unique key, so we must switch via a temporary rank, and use a transaction. + $this->em->wrapInTransaction(function () use ($current, $other, $numContests) { + $otherRank = $other->getRank(); + $currentRank = $current->getRank(); + $other->setRank($numContests + 1); + $current->setRank($numContests + 2); + $this->em->flush(); + $current->setRank($otherRank); + $other->setRank($currentRank); + }); + + $this->dj->auditlog('contest', $contestId, 'switch rank', + sprintf("%d <=> %d", $current->getRank(), $other->getRank())); + } + + return $this->redirect($this->generateUrl('jury_contests')); + } } diff --git a/webapp/src/Controller/Jury/JuryMiscController.php b/webapp/src/Controller/Jury/JuryMiscController.php index d24863f573..c15aba37be 100644 --- a/webapp/src/Controller/Jury/JuryMiscController.php +++ b/webapp/src/Controller/Jury/JuryMiscController.php @@ -16,6 +16,7 @@ use App\Service\ScoreboardService; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\Expr\Join; +use GuzzleHttp\Psr7\Uri; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\RequestStack; @@ -323,7 +324,8 @@ public function judgingVerifierAction(Request $request): Response public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response { if ($this->isLocalReferer($router, $request)) { - $response = new RedirectResponse($request->headers->get('referer')); + $uri = new Uri($request->headers->get('referer')); + $response = new RedirectResponse((string)$uri->withQuery('')); } else { $response = $this->redirectToRoute('jury_index'); } diff --git a/webapp/src/Controller/Jury/ProblemController.php b/webapp/src/Controller/Jury/ProblemController.php index 56ed94d5f8..89b7ca1871 100644 --- a/webapp/src/Controller/Jury/ProblemController.php +++ b/webapp/src/Controller/Jury/ProblemController.php @@ -7,6 +7,7 @@ use App\Entity\Contest; use App\Entity\ContestProblem; use App\Entity\Judging; +use App\Entity\Language; use App\Entity\Problem; use App\Entity\ProblemAttachment; use App\Entity\ProblemAttachmentContent; @@ -14,9 +15,11 @@ use App\Entity\SubmissionFile; use App\Entity\Testcase; use App\Entity\TestcaseContent; +use App\Entity\TestcaseGroup; use App\Form\Type\ProblemAttachmentType; use App\Form\Type\ProblemType; use App\Form\Type\ProblemUploadType; +use App\Form\Type\TestcaseGroupType; use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use App\Service\EventLogService; @@ -460,7 +463,14 @@ public function viewAction(Request $request, SubmissionService $submissionServic $name = $file->getClientOriginalName(); $fileParts = explode('.', $name); if (count($fileParts) > 1) { - $type = $fileParts[count($fileParts) - 1]; + $extension = $fileParts[count($fileParts) - 1]; + /** @var Language|null $language */ + $language = $problemAttachmentForm->get('language')->getData(); + if ($language) { + $type = $language->getLangid(); + } else { + $type = $extension; + } } else { $type = 'txt'; } @@ -582,6 +592,14 @@ public function testcasesAction(Request $request, int $probId): Response $messages[] = sprintf('Updated description of testcase %d ', $rank); } + $newGroupId = intval($request->request->all('group')[$rank]); + $oldGroupId = $testcase->getTestcaseGroup() === null ? -1 : $testcase->getTestcaseGroup()->getTestcasegroupid(); + if ($oldGroupId !== $newGroupId) { + $newGroup = $newGroupId === -1 ? null : $this->em->getRepository(TestcaseGroup::class)->find($newGroupId); + $testcase->setTestcaseGroup($newGroup); + $messages[] = sprintf('Updated group of testcase %d ', $rank); + } + foreach (['input', 'output', 'image'] as $type) { /** @var UploadedFile $file */ if ($file = $request->files->all('update_' . $type)[$rank]) { @@ -673,10 +691,26 @@ public function testcasesAction(Request $request, int $probId): Response if ($inputOrOutputSpecified && $allOk) { $newTestcase = new Testcase(); $newTestcaseContent = new TestcaseContent(); + + $testcaseGroupId = intval($request->request->get('add_group')); + if ($testcaseGroupId === -1) { + $testcaseGroup = new TestcaseGroup(); + $testcaseGroup->setName('default'); + $testcaseGroup->setPointsPercentage(1); + $this->em->persist($testcaseGroup); + } + else { + $testcaseGroup = $this->em->getRepository(TestcaseGroup::class)->find($testcaseGroupId); + if (!$testcaseGroup) { + throw new NotFoundHttpException(sprintf('Testcase group with ID %s not found', $probId)); + } + } + $newTestcase ->setContent($newTestcaseContent) ->setRank($maxrank) ->setProblem($problem) + ->setTestcaseGroup($testcaseGroup) ->setDescription($request->request->get('add_desc')) ->setSample($request->request->has('add_sample')); foreach (['input', 'output'] as $type) { @@ -771,8 +805,18 @@ public function testcasesAction(Request $request, int $probId): Response . join($lockedContests) . ', disallowing editing.'); } + + $emptyTestcaseGroups = $this->em->createQueryBuilder() + ->select('tg') + ->from(TestcaseGroup::class, 'tg') + ->leftJoin(Testcase::class, 'tc', Join::WITH, 'tc.testcase_group = tg') + ->where('tc.testcase_group IS NULL') + ->getQuery() + ->getResult(); + $data = [ 'problem' => $problem, + 'emptyTestcaseGroups' => $emptyTestcaseGroups, 'testcases' => $testcases, 'testcaseData' => $testcaseData, 'extensionMapping' => Testcase::EXTENSION_MAPPING, @@ -1061,6 +1105,7 @@ public function deleteTestcaseAction(Request $request, int $testcaseId): Respons } $testcase->setDeleted(true); $testcase->setProblem(null); + $testcase->setTestcaseGroup(null); $oldRank = $testcase->getRank(); /** @var Testcase[] $testcases */ @@ -1087,7 +1132,6 @@ public function addAction(Request $request): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $this->em->persist($problem); $this->saveEntity($this->em, $this->eventLogService, $this->dj, $problem, null, true); return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); } diff --git a/webapp/src/Controller/Jury/ProblemTestcaseGroupController.php b/webapp/src/Controller/Jury/ProblemTestcaseGroupController.php new file mode 100644 index 0000000000..8fb7850431 --- /dev/null +++ b/webapp/src/Controller/Jury/ProblemTestcaseGroupController.php @@ -0,0 +1,267 @@ +}/testcase-groups', name: 'jury_problem_testcase_groups')] + public function indexAction(Request $request, int $probId): Response + { + /** @var Problem $problem */ + $problem = $this->em->getRepository(Problem::class)->find($probId); + if (!$problem) { + throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); + } + + $lockedContests = []; + foreach ($problem->getContestProblems() as $contestproblem) { + /** @var ContestProblem $contestproblem */ + if ($contestproblem->getContest()->isLocked()) { + $lockedContests[] = 'c' . $contestproblem->getContest()->getCid(); + break; + } + } + $problemIsLocked = !empty($lockedContests); + + if ($problemIsLocked) { + $this->addFlash('warning', + 'Problem belongs to locked contest (' + . join($lockedContests) + . ', disallowing editing.'); + } + + $pointsPercentageSum = array_reduce($problem->getTestcaseGroups()->toArray(), + fn($carry, $group) => $carry + $group->getPointsPercentage(), 0); + if (abs($pointsPercentageSum - 1) > 0.001) { + $this->addFlash('warning', "The problem's groups point percentages sum to $pointsPercentageSum, not 1."); + } + + $tableFields = [ + 'testcasegroupid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true], + 'name' => ['title' => 'name', 'sort' => true], + 'points_percentage' => ['title' => '% of points', 'sort' => true], + ]; + + $testcaseGroupsTable = $this->generateTestcaseGroupsTable( + $problem->getTestcaseGroups(), + $tableFields, + false, + $problemIsLocked, + $problem->getProbid() + ); + + $emptyTestcaseGroups = $this->em->createQueryBuilder() + ->select('tg') + ->from(TestcaseGroup::class, 'tg') + ->leftJoin(Testcase::class, 'tc', Join::WITH, 'tc.testcase_group = tg') + ->where('tc.testcase_group IS NULL') + ->getQuery() + ->getResult(); + + $emptyTestcaseGroupsTable = $this->generateTestcaseGroupsTable( + $emptyTestcaseGroups, + $tableFields, + true, + $problemIsLocked, + $problem->getProbid() + ); + + $data = [ + 'problem' => $problem, + 'testcaseGroups' => $testcaseGroupsTable, + 'emptyTestcaseGroups' => $emptyTestcaseGroupsTable, + 'tableFields' => $tableFields, + 'numActions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, + 'allowEdit' => $this->isGranted('ROLE_ADMIN') && empty($lockedContest), + ]; + + return $this->render('jury/problem_testcase_groups.html.twig', $data); + } + + private function generateTestcaseGroupsTable( + $testcaseGroups, + array $tableFields, + bool $allowDeletion, + bool $problemIsLocked, + int $problemId): array + { + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $testcaseGroupsTable = []; + foreach ($testcaseGroups as $tgp) { + $testcaseGroupData = []; + $testCaseGroupActions = []; + // Get whatever fields we can from the problem object itself. + foreach ($tableFields as $k => $v) { + if ($propertyAccessor->isReadable($tgp, $k)) { + $testcaseGroupData[$k] = ['value' => $propertyAccessor->getValue($tgp, $k)]; + } + } + + // Create action links + if ($this->isGranted('ROLE_ADMIN')) { + $testCaseGroupActions[] = [ + 'icon' => 'edit', + 'title' => 'edit this testcase group', + 'link' => $this->generateUrl('jury_problem_testcase_group_edit', [ + 'probId' => $problemId, + 'testcaseGroupId' => $tgp->getTestcasegroupid(), + ]), + ]; + + if ($allowDeletion) { + $deleteAction = [ + 'icon' => 'trash-alt', + 'title' => 'delete this testcase group', + 'link' => $this->generateUrl('jury_problem_testcase_group_delete', [ + 'probId' => $problemId, + 'testcaseGroupId' => $tgp->getTestcasegroupid(), + ]), + 'ajaxModal' => true, + ]; + if ($problemIsLocked) { + $deleteAction['title'] .= ' - problem belongs to a locked contest'; + $deleteAction['disabled'] = true; + unset($deleteAction['link']); + } + $testCaseGroupActions[] = $deleteAction; + } + } + + // Save this to our list of rows + $testcaseGroupsTable[] = [ + 'data' => $testcaseGroupData, + 'actions' => $testCaseGroupActions, + 'link' => $this->generateUrl('jury_problem_testcase_group_edit', [ + 'probId' => $problemId, + 'testcaseGroupId' => $tgp->getTestcasegroupid(), + ]), + ]; + } + + return $testcaseGroupsTable; + } + + #[Route('/{probId<\d+>}/testcase-groups/{testcaseGroupId<\d+>}/delete', name: 'jury_problem_testcase_group_delete')] + #[IsGranted('ROLE_ADMIN')] + public function deleteAction(Request $request, int $probId, int $testcaseGroupId): Response + { + $testcaseGroup = $this->em->getRepository(TestcaseGroup::class)->find($testcaseGroupId); + if (!$testcaseGroup) { + throw new NotFoundHttpException(sprintf('Testcase group with ID %s not found', $testcaseGroupId)); + } + + if (!$testcaseGroup->getTestcases()->isEmpty()) { + throw new BadRequestHttpException(sprintf('Testcase group with ID %s is not empty and so cannot be deleted', $testcaseGroupId)); + } + + return $this->deleteEntities($request, $this->em, $this->dj, $this->eventLogService, $this->kernel, + [$testcaseGroup], $this->generateUrl('jury_problem_testcase_groups', ['probId' => $probId])); + } + + #[Route('/{probId<\d+>}/testcase-groups/add', name: 'jury_problem_testcase_group_add')] + #[Route('/{probId<\d+>}/testcase-groups/{testcaseGroupId<\d+>}/edit', name: 'jury_problem_testcase_group_edit')] + #[IsGranted('ROLE_ADMIN')] + public function addEditAction(Request $request, int $probId, ?int $testcaseGroupId = null): Response + { + $editing = $testcaseGroupId !== null; + + /** @var Problem $problem */ + $problem = $this->em->getRepository(Problem::class)->find($probId); + if (!$problem) { + throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); + } + + foreach ($problem->getContestProblems() as $contestProblem) { + /** @var ContestProblem $contestProblem */ + if ($contestProblem->getContest()->isLocked()) { + $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + } + } + + if ($editing) { + $testcaseGroup = $this->em->getRepository(TestcaseGroup::class)->find($testcaseGroupId); + if (!$testcaseGroup) { + throw new NotFoundHttpException(sprintf('Testcase group with ID %s not found in problem %s', $testcaseGroupId, $probId)); + } + } else { + $testcaseGroup = new TestcaseGroup(); + } + + $form = $this->createForm(TestcaseGroupType::class, $testcaseGroup); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $this->em->persist($testcaseGroup); + $this->saveEntity($this->em, $this->eventLogService, $this->dj, $testcaseGroup, null, true); + return $this->redirect($this->generateUrl( + 'jury_problem_testcase_groups', + ['probId' => $problem->getProbid()] + )); + } + + return $this->render('jury/problem_testcase_group_add.html.twig', [ + 'editing' => $editing, + 'form' => $form->createView(), + ]); + } +} diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 49852b97b2..b3dfeb7147 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -414,6 +414,16 @@ public function viewAction( } } + $testcaseGroupsRuns = []; + foreach ($runs as $run) { + $group = $run->getTestcaseGroup(); + if (!isset($testcaseGroupsRuns[$group->getTestcasegroupid()])) { + $testcaseGroupsRuns[$group->getTestcasegroupid()] = [$group, [$run]]; + } else { + $testcaseGroupsRuns[$group->getTestcasegroupid()][1][] = $run; + } + } + if ($submission->getOriginalSubmission()) { $lastSubmission = $submission->getOriginalSubmission(); } else { @@ -516,6 +526,7 @@ public function viewAction( 'selectedJudging' => $selectedJudging, 'lastJudging' => $lastJudging, 'runs' => $runs, + 'testcaseGroupsRuns' => $testcaseGroupsRuns, 'runsOutstanding' => $runsOutstanding, 'judgehosts' => $judgehosts, 'sameTestcaseIds' => $sameTestcaseIds, diff --git a/webapp/src/Controller/PublicController.php b/webapp/src/Controller/PublicController.php index eb9b279adc..603db1f2d5 100644 --- a/webapp/src/Controller/PublicController.php +++ b/webapp/src/Controller/PublicController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Entity\BlogPost; use App\Entity\Contest; use App\Entity\ContestProblem; use App\Entity\Team; @@ -10,8 +11,10 @@ use App\Service\DOMJudgeService; use App\Service\ScoreboardService; use App\Service\StatisticsService; +use DateTime; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; +use GuzzleHttp\Psr7\Uri; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -34,8 +37,29 @@ public function __construct( protected readonly EntityManagerInterface $em ) {} - #[Route(path: '', name: 'public_index')] - #[Route(path: '/scoreboard')] + #[Route(path: '')] + public function homepageAction(): Response { + /** @var BlogPost[] $blogPosts */ + $blogPosts = $this->em->getRepository(BlogPost::class) + ->createQueryBuilder('bp') + ->where('bp.publishtime <= :now') + ->orderBy('bp.publishtime', 'DESC') + ->setMaxResults($this->config->get('homepage_blog_post_count')) + ->getQuery() + ->setParameter('now', new DateTime()) + ->getResult(); + + return $this->render('public/homepage.html.twig', [ + 'blogPosts' => $blogPosts, + ]); + } + + #[Route(path: '/authors', name: 'public_authors')] + public function authorsAction(): Response { + return $this->render('public/authors.html.twig'); + } + + #[Route(path: '/scoreboard', name: 'public_scoreboard')] public function scoreboardAction( Request $request, #[MapQueryParameter(name: 'contest')] @@ -44,7 +68,7 @@ public function scoreboardAction( ?bool $static = false, ): Response { $response = new Response(); - $refreshUrl = $this->generateUrl('public_index'); + $refreshUrl = $this->generateUrl('public_scoreboard'); $contest = $this->dj->getCurrentContest(onlyPublic: true); $nonPublicContest = $this->dj->getCurrentContest(onlyPublic: false); if (!$contest && $nonPublicContest && $this->em->getRepository(TeamCategory::class)->count(['allow_self_registration' => 1])) { @@ -136,9 +160,10 @@ protected function getContestFromRequest(?string $contestId = null): ?Contest public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response { if ($this->isLocalReferer($router, $request)) { - $response = new RedirectResponse($request->headers->get('referer')); + $uri = new Uri($request->headers->get('referer')); + $response = new RedirectResponse((string)$uri->withQuery('')); } else { - $response = $this->redirectToRoute('public_index'); + $response = $this->redirectToRoute('public_scoreboard'); } return $this->dj->setCookie('domjudge_cid', (string)$contestId, 0, null, '', false, false, $response); diff --git a/webapp/src/Controller/RootController.php b/webapp/src/Controller/RootController.php index eb330a17f1..7d01c24ba1 100644 --- a/webapp/src/Controller/RootController.php +++ b/webapp/src/Controller/RootController.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -21,7 +22,8 @@ public function __construct(protected readonly DOMJudgeService $dj) } #[Route(path: '', name: 'root')] - public function redirectAction(AuthorizationCheckerInterface $authorizationChecker): RedirectResponse + #[Route(path: '', name: 'public_index')] + public function redirectAction(AuthorizationCheckerInterface $authorizationChecker): Response { if ($authorizationChecker->isGranted('IS_AUTHENTICATED_FULLY')) { if ($this->dj->checkrole('jury')) { @@ -37,7 +39,7 @@ public function redirectAction(AuthorizationCheckerInterface $authorizationCheck return $this->redirectToRoute('jury_clarifications'); } } - return $this->redirectToRoute('public_index'); + return $this->forward(PublicController::class . '::homepageAction'); } #[Route(path: '/markdown-preview', name: 'markdown_preview', methods: ['POST'])] diff --git a/webapp/src/Controller/SecurityController.php b/webapp/src/Controller/SecurityController.php index e0033e020e..d7825e3bd9 100644 --- a/webapp/src/Controller/SecurityController.php +++ b/webapp/src/Controller/SecurityController.php @@ -109,7 +109,11 @@ public function registerAction( $user->setName($user->getUsername()); } - $teamName = $registration_form->get('teamName')->getData(); + if ($this->config->get('allow_custom_team_name_registration')) { + $teamName = $registration_form->get('teamName')->getData(); + } else { + $teamName = $user->getUsername(); + } if ($selfRegistrationCategoriesCount === 1) { $teamCategory = $em->getRepository(TeamCategory::class)->findOneBy(['allow_self_registration' => 1]); diff --git a/webapp/src/Controller/Team/ClarificationController.php b/webapp/src/Controller/Team/ClarificationController.php index ab376d91b2..190140fb34 100644 --- a/webapp/src/Controller/Team/ClarificationController.php +++ b/webapp/src/Controller/Team/ClarificationController.php @@ -111,6 +111,9 @@ public function viewAction(Request $request, int $clarId): Response $data = [ 'clarification' => $clarification, + 'unreadClarifications' => $user->getTeam()->getUnreadClarifications()->filter( + fn(Clarification $c) => $c->getContest()->getCid() === $this->dj->getCurrentContest($user->getTeam()->getTeamid())->getCid() + ), 'team' => $team, 'categories' => $categories, 'form' => $form->createView(), diff --git a/webapp/src/Controller/Team/EditorController.php b/webapp/src/Controller/Team/EditorController.php new file mode 100644 index 0000000000..a41e273965 --- /dev/null +++ b/webapp/src/Controller/Team/EditorController.php @@ -0,0 +1,346 @@ +}/{langId}', name: 'team_editor')] + public function viewAction(Request $request, int $probId, string $langId): Response + { + /** @var Problem $problem */ + $problem = $this->em->getRepository(Problem::class)->find($probId); + if (!$problem) { + throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); + } + + /** @var Language $language */ + $language = $this->em->getRepository(Language::class)->find($langId); + if (!$language) { + throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); + } + + try { + /** @var Language $language */ + $language = $this->em->getRepository(Language::class)->createQueryBuilder('lang') + ->andWhere('lang.langid = :langId') + ->setParameter('langId', $langId) + ->andWhere('lang.allowSubmit = 1') + ->getQuery() + ->setMaxResults(1) + ->getOneOrNullResult(); + } catch (NonUniqueResultException $e) { + $this->logger->error($e->getMessage()); + throw new HttpException(500, 'Something went wrong getting allowed language'); + } + + if (!$language) { + throw new NotFoundHttpException(sprintf('Language with ID %s is not allowed', $langId)); + } + + $team = $this->dj->getUser()->getTeam(); + $contest = $this->dj->getCurrentContest($team->getTeamid()); + + if (!$contest->getProblems() + ->map(fn(ContestProblem $p) => $p->getProbid()) + ->contains($problem->getProbid()) + ) { + $this->addFlash('warning', 'This problem is not part of the current contest'); + return $this->redirectToRoute('team_problems', ['cid' => $contest->getCid()]); + } + + /** @var Submission|null $submission */ + $submission = $this->getLatestSubmission($team, $problem, $language, $contest); + + /** @var SubmissionFile[] $files */ + $files = []; + + if ($submission) { + $files = $submission->getFiles(); + } else { + $submission = (new Submission()) + ->setTeam($team) + ->setProblem($problem) + ->setLanguage($language) + ->setContest($contest) + ->setEntryPoint(null) + ->setOriginalSubmission(null); + + $attachments = $problem->getAttachments()->filter( + fn(ProblemAttachment $attachment) => $attachment->getType() === $language->getLangid() + ); + + foreach ($attachments as $rank => $attachment) { + $files[] = (new SubmissionFile()) + ->setFilename($attachment->getName()) + ->setRank($rank) + ->setSourcecode($attachment->getContent()->getContent()); + } + + if (empty($files)) { + $this->logger->error( + sprintf('Problem %s without pre-set attachment for %s', + $problem->getProbid(), + $language->getLangid() + ) + ); + $this->addFlash('warning', 'This problem is not setup for a web code editor using selected language'); + $route = $request->headers->get('referer'); + return $this->redirect($route); + } + } + + $data = [ + 'problem' => $submission->getProblem(), + 'language' => $submission->getLanguage(), + // FIXME: allow user to change entrypoint file + 'entry_point' => $submission->getEntryPoint(), + ]; + + foreach ($files as $file) { + $data['source' . $file->getRank()] = $file->getSourcecode(); + } + + $enabledSubmission = $contest->getAllowSubmit(); + + $formBuilder = $this->createFormBuilder($data) + ->add('entry_point', HiddenType::class) + ->add('save', SubmitType::class, [ + 'attr' => [ + 'class' => 'btn-secondary', + ], + 'disabled' => !$enabledSubmission + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'Save & Submit', + 'attr' => [ + 'class' => 'btn-success' + ], + 'disabled' => !$enabledSubmission + ]); + + foreach ($files as $file) { + $formBuilder->add('source' . $file->getRank(), TextareaType::class); + } + + $form = $formBuilder->getForm(); + + // Handle the form if it is submitted + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $submittedData = $form->getData(); + + /** @var UploadedFile[] $filesToSubmit */ + $filesToSubmit = []; + $tmpdir = $this->dj->getDomjudgeTmpDir(); + + // FIXME: this will not work with dynamic number of files in the future + // $files are loaded from pre-set templates or last submission (no new can be created without refactoring) + foreach ($files as $file) { + if (!($tmpfname = tempnam($tmpdir, "team_editor-"))) { + throw new ServiceUnavailableHttpException(null, "Could not create temporary file."); + } + file_put_contents($tmpfname, $submittedData['source' . $file->getRank()]); + $filesToSubmit[] = new UploadedFile($tmpfname, $file->getFilename(), null, null, true); + } + + $team = $this->dj->getUser()->getTeam(); + /** @var Language $language */ + $language = $submittedData['language']; + $entryPoint = $submittedData['entry_point']; + if ($language->getRequireEntryPoint() && $entryPoint === null) { + $entryPoint = '__auto__'; + } + + $ignoreSubmission = !$form->get('submit')->isClicked(); + + $submittedSubmission = $this->submissionService->submitSolution( + team: $team, + user: $this->dj->getUser(), + problem: $submittedData['problem'], + contest: $submission->getContest(), + language: $language, + files: $filesToSubmit, + source: 'team/editor', + entryPoint: $entryPoint, + message: $message, + ignoreSubmission: $ignoreSubmission + ); + + foreach ($filesToSubmit as $file) { + unlink($file->getRealPath()); + } + + if (!$submittedSubmission) { + $this->addFlash('danger', $message); + return $this->redirectToRoute('team_editor', [ + 'probId' => $problem->getProbid(), + 'langId' => $language->getLangid() + ]); + } + + return $this->redirectToRoute('team_editor', [ + 'probId' => $problem->getProbid(), + 'langId' => $language->getLangid() + ]); + } + + return $this->render('team/team_editor.html.twig', array_merge( + $this->getStatusData($request, $submission, $contest), + [ + 'language' => $language, + 'problem' => $problem, + 'submission' => $submission, + 'files' => $files, + 'form' => $form->createView(), + 'selected' => $request->query->get('ranknumber'), + 'static' => false + ] + )); + } + + #[Route('/status/{submitId<\d+>}', name: 'team_editor_status')] + public function statusAction(Request $request, int $submitId): Response + { + $submission = $this->getTeamSubmission($this->dj->getUser()->getTeam(), $submitId); + if (!$submission) { + throw new NotFoundHttpException(sprintf('Team submission with ID %s not found', $submitId)); + } + + $team = $this->dj->getUser()->getTeam(); + $contest = $this->dj->getCurrentContest($team->getTeamid()); + + $data = $this->getStatusData($request, $submission, $contest); + + if (!$request->isXmlHttpRequest()) { + $this->logger->error('Unintended status usage (not xhr request)'); + throw new HttpException(500, 'Something went wrong fetching last submission status'); + } + + return $this->render('team/partials/team_editor_status.html.twig', $data); + } + + public function getStatusData(Request $request, Submission $submission, Contest $contest): array + { + return ($this->em->contains($submission) && $submission->getValid()) ? array_merge( + $this->scoreboardService->getScoreboardTwigData( + request: $request, + response: null, + refreshUrl: '', + jury: true, + public: false, + static: true, + contest: $contest + ), + [ + 'showFlags' => $this->config->get('show_flags'), + 'refresh' => [ + 'after' => 5, + 'url' => $this->generateUrl('team_editor_status', ['submitId' => $submission->getSubmitid()]), + 'ajax' => true, + ], + 'maxWidth' => $this->config->get('team_column_width'), + 'limitToTeams' => [$this->dj->getUser()->getTeam()], + 'limitToProblems' => [$submission->getProblem()], + 'displayRank' => true + ], + $this->getSubmissionsData($submission) + ) : []; + } + + public function getSubmissionsData(Submission $submission): array + { + return [ + 'submissions' => [$submission], + 'allowDownload' => (bool)$this->config->get('allow_team_submission_download'), + 'verificationRequired' => (bool)$this->config->get('verification_required'), + 'showTooLateResult' => (bool)$this->config->get('show_too_late_result'), + ]; + } + + protected function getLatestSubmission(Team $team, Problem $problem, Language $language, Contest $contest): ?Submission + { + return $this->em->createQueryBuilder() + ->from(Submission::class, 's') + ->join('s.team', 't') + ->join('s.problem', 'p') + ->join('s.language', 'l') + ->join('s.contest', 'c') + ->join('s.files', 'f') + ->select('s') + ->andWhere('t.teamid = :teamid') + ->setParameter('teamid', $team->getTeamid()) + ->andWhere('p.probid = :probid') + ->setParameter('probid', $problem->getProbid()) + ->andWhere('l.langid = :langid') + ->setParameter('langid', $language->getLangid()) + ->andWhere('c.cid = :cid') + ->setParameter('cid', $contest->getCid()) + ->orderBy('s.submittime', 'DESC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + protected function getTeamSubmission(Team $team, int $submitId): ?Submission + { + return $this->em->createQueryBuilder() + ->from(Submission::class, 's') + ->join('s.team', 't') + ->select('s') + ->andWhere('s.submitid = :submitid') + ->setParameter('submitid', $submitId) + ->andWhere('t.teamid = :teamid') + ->setParameter('teamid', $team->getTeamid()) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } +} diff --git a/webapp/src/Controller/Team/MiscController.php b/webapp/src/Controller/Team/MiscController.php index 648b4bf727..43a1e88868 100644 --- a/webapp/src/Controller/Team/MiscController.php +++ b/webapp/src/Controller/Team/MiscController.php @@ -14,9 +14,11 @@ use App\Service\EventLogService; use App\Service\ScoreboardService; use App\Service\SubmissionService; +use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; +use GuzzleHttp\Psr7\Uri; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -72,6 +74,8 @@ public function homeAction(Request $request): Response 'maxWidth' => $this->config->get('team_column_width'), ]; if ($contest) { + $this->checkForSendingWelcomeMessage($contest->getCid()); + $scoreboard = $this->scoreboardService ->getTeamScoreboard($contest, $teamId, false); $data = array_merge( @@ -95,12 +99,10 @@ public function homeAction(Request $request): Response $clarifications = $this->em->createQueryBuilder() ->from(Clarification::class, 'c') ->leftJoin('c.problem', 'p') - ->leftJoin('p.contest_problems', 'cp') ->leftJoin('c.sender', 's') ->leftJoin('c.recipient', 'r') - ->select('c', 'cp', 'p') + ->select('c', 'p') ->andWhere('c.contest = :contest') - ->andWhere('cp.contest = :contest') ->andWhere('c.sender IS NULL') ->andWhere('c.recipient = :team OR c.recipient IS NULL') ->setParameter('contest', $contest) @@ -114,12 +116,10 @@ public function homeAction(Request $request): Response $clarificationRequests = $this->em->createQueryBuilder() ->from(Clarification::class, 'c') ->leftJoin('c.problem', 'p') - ->leftJoin('p.contest_problems', 'cp') ->leftJoin('c.sender', 's') ->leftJoin('c.recipient', 'r') - ->select('c', 'cp', 'p') + ->select('c', 'p') ->andWhere('c.contest = :contest') - ->andWhere('cp.contest = :contest') ->andWhere('c.sender = :team') ->setParameter('contest', $contest) ->setParameter('team', $team) @@ -130,6 +130,9 @@ public function homeAction(Request $request): Response $data['clarifications'] = $clarifications; $data['clarificationRequests'] = $clarificationRequests; + $data['unreadClarifications'] = $team->getUnreadClarifications()->filter( + fn(Clarification $c) => $c->getContest()->getCid() === $contest->getCid() + ); $data['categories'] = $this->config->get('clar_categories'); $data['allowDownload'] = (bool)$this->config->get('allow_team_submission_download'); $data['showTooLateResult'] = $this->config->get('show_too_late_result'); @@ -152,8 +155,13 @@ public function updatesAction(): JsonResponse #[Route(path: '/change-contest/{contestId<-?\d+>}', name: 'team_change_contest')] public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response { + if ($contestId != -1) { + $this->checkForSendingWelcomeMessage($contestId); + } + if ($this->isLocalReferer($router, $request)) { - $response = new RedirectResponse($request->headers->get('referer')); + $uri = new Uri($request->headers->get('referer')); + $response = new RedirectResponse((string)$uri->withQuery('')); } else { $response = $this->redirectToRoute('team_index'); } @@ -231,4 +239,35 @@ public function contestProblemsetAction(): StreamedResponse } return $contest->getContestProblemsetStreamedResponse(); } + + private function checkForSendingWelcomeMessage(int $contestId) + { + $team = $this->dj->getUser()->getTeam(); + $contest = $this->dj->getContest($contestId); + + if ($team->getReceivedClarifications()->exists( + fn(int $key, Clarification $c) => $c->getContest()->getCid() === $contestId + )) { + return; + } + + $clarification = new Clarification(); + + $clarification->setContest($contest); + + // to be changed + $clarification->setRecipient($team); + $clarification->setAnswered(true); + $clarification->setBody($this->config->get('welcome_message_body')); + $clarification->setSubmittime(Utils::now()); + + $team->addUnreadClarification($clarification); + + $this->em->persist($clarification); + $this->em->flush(); + + $clarId = $clarification->getClarId(); + $this->dj->auditlog('clarification', $clarId, 'added', null, null, $contest->getCid()); + $this->eventLogService->log('clarification', $clarId, 'create', $contest->getCid()); + } } diff --git a/webapp/src/Controller/Team/ProblemController.php b/webapp/src/Controller/Team/ProblemController.php index 2e99d6bfc7..f52bb50783 100644 --- a/webapp/src/Controller/Team/ProblemController.php +++ b/webapp/src/Controller/Team/ProblemController.php @@ -3,10 +3,12 @@ namespace App\Controller\Team; use App\Controller\BaseController; +use App\Entity\Clarification; use App\Entity\Contest; use App\Entity\ContestProblem; use App\Service\ConfigurationService; use App\Service\DOMJudgeService; +use App\Service\ScoreboardService; use App\Service\StatisticsService; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; @@ -30,7 +32,8 @@ public function __construct( protected readonly DOMJudgeService $dj, protected readonly ConfigurationService $config, protected readonly StatisticsService $stats, - protected readonly EntityManagerInterface $em + protected readonly EntityManagerInterface $em, + protected readonly ScoreboardService $scoreboardService ) {} /** @@ -39,9 +42,19 @@ public function __construct( #[Route(path: '/problems', name: 'team_problems')] public function problemsAction(): Response { - $teamId = $this->dj->getUser()->getTeam()->getTeamid(); - return $this->render('team/problems.html.twig', - $this->dj->getTwigDataForProblemsAction($this->stats, $teamId)); + $team = $this->dj->getUser()->getTeam(); + $teamId = $team->getTeamid(); + + $data = $this->dj->getTwigDataForProblemsAction($this->stats, $teamId); + if ($contest = $this->dj->getCurrentContest($team->getTeamid())) { + $data['unreadClarifications'] = $team->getUnreadClarifications()->filter( + fn(Clarification $c) => $c->getContest()->getCid() === $contest->getCid() + ); + + $data['teamScoreboard'] = $this->scoreboardService->getTeamScoreboard($contest, $team->getTeamid()); + } + + return $this->render('team/problems.html.twig', $data); } diff --git a/webapp/src/Controller/Team/ScoreboardController.php b/webapp/src/Controller/Team/ScoreboardController.php index e1feefbe69..b68a32a6ca 100644 --- a/webapp/src/Controller/Team/ScoreboardController.php +++ b/webapp/src/Controller/Team/ScoreboardController.php @@ -3,6 +3,7 @@ namespace App\Controller\Team; use App\Controller\BaseController; +use App\Entity\Clarification; use App\Entity\Team; use App\Service\ConfigurationService; use App\Service\DOMJudgeService; @@ -46,6 +47,12 @@ public function scoreboardAction(Request $request): Response ); $data['myTeamId'] = $user->getTeam()->getTeamid(); + if ($contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid())) { + $data['unreadClarifications'] = $user->getTeam()->getUnreadClarifications()->filter( + fn(Clarification $c) => $c->getContest()->getCid() === $contest->getCid() + ); + } + if ($request->isXmlHttpRequest()) { $data['current_contest'] = $contest; return $this->render('partials/scoreboard.html.twig', $data, $response); diff --git a/webapp/src/Entity/BlogPost.php b/webapp/src/Entity/BlogPost.php new file mode 100644 index 0000000000..afcce5419f --- /dev/null +++ b/webapp/src/Entity/BlogPost.php @@ -0,0 +1,154 @@ + 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', 'comment' => 'Public blog posts sent by the jury'], + indexes: [new ORM\Index(name: 'slug', columns: ['slug'])], + uniqueConstraints: [new ORM\UniqueConstraint(name: 'slug', columns: ['slug'])] +)] +#[UniqueEntity('slug')] +class BlogPost extends BaseApiEntity +{ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + #[ORM\Column(type: 'integer', length: 4, name: 'blogpostid', options: ['comment' => 'Blog post ID', 'unsigned' => true], nullable: false)] + #[Serializer\SerializedName('id')] + #[Serializer\Type('string')] + protected int $blogpostid; + + #[ORM\Column(type: 'string', name: 'slug', length: 255, options: ['comment' => 'Unique slug'], nullable: false)] + #[Serializer\Exclude] + private string $slug; + + #[ORM\Column(type: 'datetime', name: 'publishtime', options: ['comment' => 'Time sent', 'unsigned' => true], nullable: false)] + #[Serializer\Exclude] + private DateTime $publishtime; + + #[ORM\Column(type: 'string', name: 'author', length: 255, options: ['comment' => 'Name of the post author'], nullable: true)] + #[Serializer\Exclude] + private ?string $author; + + #[ORM\Column(type: 'string', name: 'title', length: 511, options: ['comment' => 'Blog post title'], nullable: false)] + #[Serializer\Exclude] + private string $title; + + #[ORM\Column(type: 'text', length: 4294967295, name: 'subtitle', options: ['comment' => 'Blog post subtitle'], nullable: false)] + #[Serializer\SerializedName('subtitle')] + private string $subtitle; + + #[ORM\Column(type: 'string', name: 'thumbnail_file_name', length: 255, options: ['comment' => 'Thumbnail file name'], nullable: false)] + #[Serializer\Exclude] + private string $thumbnail_file_name; + + #[ORM\Column(type: 'text', length: 4294967295, name: 'body', options: ['comment' => 'Blog post text'], nullable: false)] + #[Serializer\SerializedName('text')] + private string $body; + + public function __construct() + { + $this->publishtime = new DateTime(); + } + + public function getSlug(): string + { + return $this->slug; + } + + public function setSlug(?string $slug): void + { + $this->slug = $slug; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function setBlogpostid(int $blogpostid): BlogPost + { + $this->blogpostid = $blogpostid; + return $this; + } + + public function getBlogpostid(): int + { + return $this->blogpostid; + } + + public function getPublishtime(): DateTime + { + return $this->publishtime; + } + + public function setPublishtime(DateTime $publishtime): void + { + $this->publishtime = $publishtime; + } + + public function isPublished(): bool + { + return $this->publishtime < new DateTime(); + } + + public function getSubtitle(): string + { + return $this->subtitle; + } + + public function setSubtitle(string $subtitle): void + { + $this->subtitle = $subtitle; + } + + public function getThumbnailFileName(): string + { + return $this->thumbnail_file_name; + } + + public function setThumbnailFileName(string $thumbnailFileName): void + { + $this->thumbnail_file_name = $thumbnailFileName; + } + + public function setAuthor(?string $juryMember): BlogPost + { + $this->author = $juryMember; + return $this; + } + + public function getAuthor(): ?string + { + return $this->author; + } + + public function setBody(string $body): BlogPost + { + $this->body = $body; + return $this; + } + + public function getBody(): string + { + return $this->body; + } + + public function getShortDescription(): string + { + return $this->title; + } +} diff --git a/webapp/src/Entity/Contest.php b/webapp/src/Entity/Contest.php index 0b88932cd6..b0235d4527 100644 --- a/webapp/src/Entity/Contest.php +++ b/webapp/src/Entity/Contest.php @@ -37,6 +37,7 @@ #[ORM\Index(columns: ['cid', 'enabled'], name: 'cid')] #[ORM\UniqueConstraint(name: 'externalid', columns: ['externalid'], options: ['lengths' => [190]])] #[ORM\UniqueConstraint(name: 'shortname', columns: ['shortname'], options: ['lengths' => [190]])] +#[ORM\UniqueConstraint(name: 'ranknumber_unique', columns: ['ranknumber'])] #[ORM\HasLifecycleCallbacks] #[Serializer\VirtualProperty( name: 'formalName', @@ -331,6 +332,17 @@ class Contest extends BaseApiEntity implements AssetEntityInterface #[Serializer\Exclude] private ?string $contestProblemsetType = null; + #[ORM\Column( + type: 'integer', + name: '`ranknumber`', + options: [ + 'comment' => 'Determines order of the contests', + 'unsigned' => true + ], + nullable: false + )] + private int $ranknumber; + /** * @var Collection */ @@ -875,6 +887,17 @@ public function setIsLocked(bool $isLocked): Contest return $this; } + public function setRank(int $rank): Contest + { + $this->ranknumber = $rank; + return $this; + } + + public function getRank(): int + { + return $this->ranknumber; + } + public function addTeam(Team $team): Contest { $this->teams[] = $team; diff --git a/webapp/src/Entity/ContestProblem.php b/webapp/src/Entity/ContestProblem.php index 8d966ce35b..9615c5c63d 100644 --- a/webapp/src/Entity/ContestProblem.php +++ b/webapp/src/Entity/ContestProblem.php @@ -78,6 +78,17 @@ class ContestProblem extends BaseApiEntity #[Serializer\Exclude] private int $lazyEvalResults = 0; + #[ORM\Column( + name: 'partial_points_scoring', + type: 'boolean', + nullable: true, + options: [ + 'comment' => 'Whether to score this problem partially; if set this overrides the global configuration setting', + 'unsigned' => true + ] + )] + private ?bool $partialPointsScoring = true; + #[ORM\Id] #[ORM\ManyToOne(inversedBy: 'problems')] #[ORM\JoinColumn(name: 'cid', referencedColumnName: 'cid', onDelete: 'CASCADE')] @@ -215,6 +226,16 @@ public function determineOnDemand(int $config_lazy_eval_results): bool { return false; } + public function setPartialPointsScoring(?bool $partialPointsScoring): void + { + $this->partialPointsScoring = $partialPointsScoring; + } + + public function getPartialPointsScoring(): ?bool + { + return $this->partialPointsScoring; + } + public function setContest(?Contest $contest = null): ContestProblem { $this->contest = $contest; diff --git a/webapp/src/Entity/Judging.php b/webapp/src/Entity/Judging.php index 19b81e29ce..1843b39feb 100644 --- a/webapp/src/Entity/Judging.php +++ b/webapp/src/Entity/Judging.php @@ -64,6 +64,19 @@ class Judging extends BaseApiEntity implements ExternalRelationshipEntityInterfa #[Serializer\Exclude] private ?string $result = null; + #[ORM\Column( + name: 'points_scored', + type: 'float', + nullable: false, + options: [ + 'comment' => 'Points scored in this judging', + 'default' => 0, + 'unsigned' => true + ] + )] + #[Serializer\Exclude] + private ?float $points_scored = 0; + #[ORM\Column(options: ['comment' => 'Result verified by jury member?', 'default' => 0])] #[Serializer\Exclude] private bool $verified = false; @@ -261,6 +274,26 @@ public function getResult(): ?string return $this->result; } + public function getPointsScored(): ?float + { + return $this->points_scored; + } + + public function setPointsScored(?float $points_scored): void + { + $this->points_scored = $points_scored; + } + + public function getOutputCompileAsString(): ?string + { + return $this->output_compile_as_string; + } + + public function setOutputCompileAsString(?string $output_compile_as_string): void + { + $this->output_compile_as_string = $output_compile_as_string; + } + public function setVerified(bool $verified): Judging { $this->verified = $verified; diff --git a/webapp/src/Entity/Language.php b/webapp/src/Entity/Language.php index 02ec574835..420c891de1 100644 --- a/webapp/src/Entity/Language.php +++ b/webapp/src/Entity/Language.php @@ -414,6 +414,7 @@ public function getAceLanguage(): string 'py2', 'py3' => 'python', 'rb' => 'ruby', 'rs' => 'rust', + 'csharp-dotnet' => 'csharp', default => $this->getLangid(), }; } diff --git a/webapp/src/Entity/Problem.php b/webapp/src/Entity/Problem.php index 5dfe76351d..af725e30ce 100644 --- a/webapp/src/Entity/Problem.php +++ b/webapp/src/Entity/Problem.php @@ -145,6 +145,13 @@ class Problem extends BaseApiEntity #[Serializer\Exclude] private Collection $testcases; + #[ORM\OneToMany( + mappedBy: 'problem', + targetEntity: TestcaseGroup::class + )] + #[Serializer\Exclude] + private Collection $testcase_groups; + /** * @var Collection * @@ -499,4 +506,24 @@ public function getStatementForApi(): array { return array_filter([$this->statementForApi]); } + + /** + * @return TestcaseGroup[]|Collection + */ + public function getTestcaseGroups(): Collection + { + $testcaseGroups = new ArrayCollection(); + + foreach ($this->testcases as $testcase) { + $group = $testcase->getTestcaseGroup(); + + if ($group === null || $testcaseGroups->contains($group)) { + continue; + } + + $testcaseGroups->add($group); + } + + return $testcaseGroups; + } } diff --git a/webapp/src/Entity/Testcase.php b/webapp/src/Entity/Testcase.php index 6bf6d35d7d..c5651b2580 100644 --- a/webapp/src/Entity/Testcase.php +++ b/webapp/src/Entity/Testcase.php @@ -116,6 +116,11 @@ class Testcase #[Serializer\Exclude] private ?Problem $problem = null; + #[ORM\ManyToOne(targetEntity: TestcaseGroup::class, inversedBy: 'testcases')] + #[ORM\JoinColumn(name: 'testcasegroupid', referencedColumnName: 'testcasegroupid')] + #[Serializer\Exclude] + private ?TestcaseGroup $testcase_group; + public function __construct() { $this->judging_runs = new ArrayCollection(); @@ -309,4 +314,15 @@ public function getDownloadName(): string return sprintf('p%d.t%d', $this->getProblem()->getProbid(), $this->getRank()); } + + public function getTestcaseGroup(): ?TestcaseGroup + { + return $this->testcase_group; + } + + public function setTestcaseGroup(?TestcaseGroup $testcase_group): Testcase + { + $this->testcase_group = $testcase_group; + return $this; + } } diff --git a/webapp/src/Entity/TestcaseGroup.php b/webapp/src/Entity/TestcaseGroup.php new file mode 100644 index 0000000000..8eaea4170e --- /dev/null +++ b/webapp/src/Entity/TestcaseGroup.php @@ -0,0 +1,81 @@ + "utf8mb4_unicode_ci", "charset" => "utf8mb4", "comment" => "Stores testcase groups per problem."] +)] +class TestcaseGroup +{ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: "AUTO")] + #[ORM\Column(name: "testcasegroupid", type: "integer", nullable: false, options: ["comment" => "Testcase group ID", "unsigned" => true])] + private int $testcasegroupid; + + #[ORM\Column(name: "points_percentage", type: "float", nullable: false, options: ["comment" => "Percentage of problem points this group is worth", "default" => 0, "unsigned" => true])] + #[Serializer\Exclude] + private float $points_percentage; + + #[ORM\Column(name: "name", type: "string", length: 255, nullable: true, options: ["comment" => "Which part of the problem this group tests.", "default" => null])] + private string $name; + + #[ORM\OneToMany(mappedBy: "testcase_group", targetEntity: Testcase::class)] + #[ORM\OrderBy(["ranknumber" => "ASC"])] + #[Serializer\Exclude] + private Collection $testcases; + + public function getTestcasegroupid(): int + { + return $this->testcasegroupid; + } + + public function setTestcasegroupid(int $testcasegroupid): void + { + $this->testcasegroupid = $testcasegroupid; + } + + public function getPointsPercentage(): float + { + return $this->points_percentage; + } + + public function setPointsPercentage(float $points_percentage): void + { + $this->points_percentage = $points_percentage; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getShortDescription(): string + { + return $this->getName(); + } + + public function getTestcases(): Collection + { + return $this->testcases; + } + + public function setTestcases(Collection $testcases): void + { + $this->testcases = $testcases; + } +} diff --git a/webapp/src/EventListener/AddContentSecurityPolicyListener.php b/webapp/src/EventListener/AddContentSecurityPolicyListener.php index cd8fa8dad9..43009fc251 100644 --- a/webapp/src/EventListener/AddContentSecurityPolicyListener.php +++ b/webapp/src/EventListener/AddContentSecurityPolicyListener.php @@ -9,15 +9,63 @@ #[AsEventListener] class AddContentSecurityPolicyListener { - public function __construct(protected readonly ?Profiler $profiler) {} + public function __construct( + protected readonly ?Profiler $profiler, + protected readonly array $cspConfig + ) {} public function __invoke(ResponseEvent $event): void { - // Set the correct CSP based on whether the profiler is enabled, since - // the profiler requires 'unsafe-eval' for script-src 'self'. $response = $event->getResponse(); - $cspExtra = $this->profiler ? "'unsafe-eval'" : ""; - $csp = "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' $cspExtra; img-src 'self' data:"; + + $csp = implode('; ', [ + $this->getDefaultSrcCsp(), + $this->getStyleSrcCsp(), + $this->getScriptSrcCsp(), + $this->getImageSrcCsp(), + $this->getConnectSrcCsp(), + $this->getFontSrcScp(), + $this->getFrameAncestorsCsp() + ]); + $response->headers->set('Content-Security-Policy', $csp); } + + private function getDefaultSrcCsp(): string + { + return "default-src " . $this->cspConfig['defaultSrc']; + } + + private function getStyleSrcCsp(): string + { + return "style-src " . $this->cspConfig['styleSrc']; + } + + private function getScriptSrcCsp(): string + { + // Set the correct CSP based on whether the profiler is enabled, since + // the profiler requires 'unsafe-eval' for script-src 'self'. + $unsafeEvalCsp = $this->profiler ? " 'unsafe-eval'" : ""; + return "script-src " . $this->cspConfig['scriptSrc'] . $unsafeEvalCsp; + } + + private function getImageSrcCsp(): string + { + return "img-src " . $this->cspConfig['imgSrc']; + } + + private function getConnectSrcCsp(): string + { + return "connect-src " . $this->cspConfig['connectSrc']; + } + + private function getFontSrcScp(): string + { + return "font-src " . $this->cspConfig['fontSrc']; + } + + private function getFrameAncestorsCsp(): string + { + return "frame-ancestors " . $this->cspConfig['frameAncestors']; + } } diff --git a/webapp/src/EventSubscriber/ContestIdSubscriber.php b/webapp/src/EventSubscriber/ContestIdSubscriber.php new file mode 100644 index 0000000000..147a38c484 --- /dev/null +++ b/webapp/src/EventSubscriber/ContestIdSubscriber.php @@ -0,0 +1,165 @@ +dj = $dj; + $this->contestIdURLsPrefixes = $contestIdURLsPrefixes; + } + + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + $requestUri = new Uri($request->getRequestUri()); + + // if the request path does not start with any of the contestIdURLsPrefixes return null + if (!array_reduce( + $this->contestIdURLsPrefixes, + fn(bool $carry, string $prefix): bool => $carry || $this->strStartsWith($requestUri->getPath(), $prefix), + false + )) { + return; + } + + $teamId = $this->dj->getUser()?->getTeamId(); + $currentContest = $this->dj->getCurrentContest($teamId); + + if (!$cid = (int)$request->query->get('cid')) { + // "No cid found. Setting cid to current contest." + $response = new RedirectResponse( + $this->addQueryParameter( + $request->getUri(), + 'cid', + (string)$currentContest->getCid() + ) + ); + $response->headers->setCookie( + new Cookie('domjudge_cid', (string)$currentContest->getCid()) + ); + $event->setResponse($response); + return; + } + + if ($cid === $currentContest->getCid()) { + // "Contest equal to current contest." + return; + } + + $currentContests = $this->dj->getCurrentContests($teamId); + if (!$this->dj->getContest($cid) || !in_array($cid, array_keys($currentContests))) { + // "Contest not found. Setting cid to current contest." + $response = new RedirectResponse( + $this->addQueryParameter( + $request->getUri(), + 'cid', + (string)$currentContest->getCid() + ) + ); + $response->headers->setCookie( + new Cookie('domjudge_cid', (string)$currentContest->getCid()) + ); + + $event->setResponse($response); + return; + } + + // "Contest not equal to current contest. Setting cid to required cid." + $response = new RedirectResponse( + $this->addQueryParameter( + $request->getUri(), + 'cid', + (string)$cid + ) + ); + $response->headers->setCookie( + new Cookie('domjudge_cid', (string)$cid) + ); + + $event->setResponse($response); + } + + public function onKernelResponse(ResponseEvent $event) + { + $response = $event->getResponse(); + + // if not instance of RedirectResponse return + if (!$response instanceof RedirectResponse) { + return; + } + + $responseTargetUri = new Uri($response->getTargetUrl()); + + // if the response uri does not start with any of the contestIdURLsPrefixes return + if (!array_reduce( + $this->contestIdURLsPrefixes, + fn(bool $carry, string $prefix): bool => $carry || $this->strStartsWith($responseTargetUri->getPath(), $prefix), + false + )) { + return; + } + + // if the uri already has a cid query parameter return + if (strpos($response->getTargetUrl(), 'cid=') !== false) { + return; + } + + $cid = 0; + foreach ($response->headers->getCookies() as $cookie) { + if ($cookie->getName() === 'domjudge_cid') { + $cid = (int)$cookie->getValue(); + break; + } + } + + // if no cid cookie is found this was not a redirect from the contest switcher + if (!$cid) { + return; + } + + $response->setTargetUrl( + $this->addQueryParameter( + $response->getTargetUrl(), + 'cid', + (string)$cid + ) + ); + + $event->setResponse($response); + } + + // TODO: on >=php8.0, use str_starts_with() + private function strStartsWith(string $haystack, string $needle): bool + { + return substr($haystack, 0, strlen($needle)) === $needle; + } + + private function addQueryParameter(string $uri, string $param, string $value): string + { + return Uri::withQueryValue(new Uri($uri), $param, $value)->__toString(); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => 'onKernelRequest', + KernelEvents::RESPONSE => 'onKernelResponse', + ]; + } +} diff --git a/webapp/src/Form/Type/BlogPostType.php b/webapp/src/Form/Type/BlogPostType.php new file mode 100644 index 0000000000..beb0cb5563 --- /dev/null +++ b/webapp/src/Form/Type/BlogPostType.php @@ -0,0 +1,52 @@ +add('title', TextType::class); + $builder->add('subtitle', TextType::class); + $builder->add('author', TextType::class, [ + 'required' => false + ]); + $builder->add('thumbnail', FileType::class, [ + 'label' => 'Thumbnail', + 'mapped' => false, + 'required' => $options['thumbnail_required']]); + $builder->add('body', HiddenType::class); + $builder->add('publishtime', DateTimeType::class, ['label' => 'Publish time']); + + $builder->add('send', SubmitType::class); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults(['data_class' => BlogPost::class, 'thumbnail_required' => false]); + + $resolver->setAllowedTypes('thumbnail_required', 'bool'); + } +} diff --git a/webapp/src/Form/Type/ContestProblemType.php b/webapp/src/Form/Type/ContestProblemType.php index e3c89cdd1d..8ccac98345 100644 --- a/webapp/src/Form/Type/ContestProblemType.php +++ b/webapp/src/Form/Type/ContestProblemType.php @@ -64,6 +64,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'On demand' => DJS::EVAL_DEMAND, ], ]); + $builder->add('partialPointsScoring', ChoiceType::class, [ + 'label' => 'Partial points scoring', + 'choices' => [ + 'Default' => null, + 'Yes' => true, + 'No' => false, + ], + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/webapp/src/Form/Type/ProblemAttachmentType.php b/webapp/src/Form/Type/ProblemAttachmentType.php index ccdd70eea0..7e23a15183 100644 --- a/webapp/src/Form/Type/ProblemAttachmentType.php +++ b/webapp/src/Form/Type/ProblemAttachmentType.php @@ -2,6 +2,9 @@ namespace App\Form\Type; +use App\Entity\Language; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -14,6 +17,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('content', FileType::class, [ 'required' => true, ]); + $builder->add('language', EntityType::class, [ + 'class' => Language::class, + 'required' => false, + 'query_builder' => fn(EntityRepository $er) => $er + ->createQueryBuilder('l') + ->andWhere('l.allowSubmit = 1'), + 'choice_label' => 'name', + 'placeholder' => 'Select a language (optional)', + ]); $builder->add('add', SubmitType::class, [ 'attr' => [ 'class' => 'btn-sm btn-primary', diff --git a/webapp/src/Form/Type/TestcaseGroupType.php b/webapp/src/Form/Type/TestcaseGroupType.php new file mode 100644 index 0000000000..b9a7459474 --- /dev/null +++ b/webapp/src/Form/Type/TestcaseGroupType.php @@ -0,0 +1,38 @@ +add('name', TextType::class); + $builder->add('points_percentage', NumberType::class, ['help' => 'between 0 and 1', 'constraints' => [ + new Range(['min' => 0, 'max' => 1]), + ]]); + + $builder->add('save', SubmitType::class); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults(['data_class' => TestcaseGroup::class]); + } +} diff --git a/webapp/src/Form/Type/UserRegistrationType.php b/webapp/src/Form/Type/UserRegistrationType.php index 66ac1b91e3..0697c2f83a 100644 --- a/webapp/src/Form/Type/UserRegistrationType.php +++ b/webapp/src/Form/Type/UserRegistrationType.php @@ -11,10 +11,12 @@ use App\Service\DOMJudgeService; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Ramsey\Uuid\Uuid; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -50,38 +52,40 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]) ->add('name', TextType::class, [ 'label' => false, - 'required' => false, 'attr' => [ - 'placeholder' => 'Full name (optional)', + 'placeholder' => 'Full name', 'autocomplete' => 'name', ], ]) ->add('email', EmailType::class, [ 'label' => false, - 'required' => false, 'attr' => [ - 'placeholder' => 'Email address (optional)', + 'placeholder' => 'Email address', 'autocomplete' => 'email', ], 'constraints' => new Email(), - ]) - ->add('teamName', TextType::class, [ - 'label' => false, - 'attr' => [ - 'placeholder' => 'Team name', - ], - 'constraints' => [ - new NotBlank(), - new Callback(function ($teamName, ExecutionContext $context) { - if ($this->em->getRepository(Team::class)->findOneBy(['name' => $teamName])) { - $context->buildViolation('This team name is already in use.') - ->addViolation(); - } - }), - ], - 'mapped' => false, ]); + if ($this->config->get('allow_custom_team_name_registration')) { + $builder + ->add('teamName', TextType::class, [ + 'label' => false, + 'attr' => [ + 'placeholder' => 'Team name', + ], + 'constraints' => [ + new NotBlank(), + new Callback(function ($teamName, ExecutionContext $context) { + if ($this->em->getRepository(Team::class)->findOneBy(['name' => $teamName])) { + $context->buildViolation('This team name is already in use.') + ->addViolation(); + } + }), + ], + 'mapped' => false, + ]); + } + $selfRegistrationCategoriesCount = $this->em->getRepository(TeamCategory::class)->count(['allow_self_registration' => 1]); if ($selfRegistrationCategoriesCount > 1) { $builder @@ -111,13 +115,21 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $countries["$name ($alpha3)"] = $alpha3; } + $affiliationChoices = [ + 'Use existing school' => 'existing' + ]; + + if ($this->config->get('allow_create_affiliation_during_registration')) { + $affiliationChoices['Add new school'] = 'new'; + } + + if ($this->config->get('allow_registration_without_affiliation')) { + $affiliationChoices['No school'] = 'none'; + } + $builder ->add('affiliation', ChoiceType::class, [ - 'choices' => [ - 'Use existing affiliation' => 'existing', - 'Add new affiliation' => 'new', - 'No affiliation' => 'none', - ], + 'choices' => $affiliationChoices, 'expanded' => true, 'mapped' => false, 'label' => false, @@ -126,17 +138,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => false, 'required' => false, 'attr' => [ - 'placeholder' => 'Affiliation name', + 'placeholder' => 'School name', ], 'mapped' => false, ]) - ->add('affiliationShortName', TextType::class, [ + ->add('affiliationShortName', HiddenType::class, [ 'label' => false, 'required' => false, - 'attr' => [ - 'placeholder' => 'Affiliation shortname', - 'maxlength' => '32', - ], + 'empty_data' => Uuid::uuid4()->toString(), 'mapped' => false, ]); if ($this->config->get('show_flags')) { @@ -154,12 +163,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'mapped' => false, 'choice_label' => 'name', - 'placeholder' => '-- Select affiliation --', + 'placeholder' => '-- Select school --', 'query_builder' => fn(EntityRepository $er) => $er ->createQueryBuilder('a') ->orderBy('a.name'), 'attr' => [ - 'placeholder' => 'Affiliation', + 'placeholder' => 'School', ], ]); } diff --git a/webapp/src/Service/ConfigurationService.php b/webapp/src/Service/ConfigurationService.php index 6fe72c340f..731432e555 100644 --- a/webapp/src/Service/ConfigurationService.php +++ b/webapp/src/Service/ConfigurationService.php @@ -238,6 +238,7 @@ public function saveChanges( case 'string': case 'enum': + case 'textarea': $optionToSet->setValue($val); break; diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 207655aef3..87a9e1e0df 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -149,7 +149,8 @@ public function getCurrentContests( $qb->andWhere('c.enabled = 1') ->andWhere('c.deactivatetime IS NULL OR c.deactivatetime > :now') ->setParameter('now', $now) - ->orderBy('c.activatetime'); + ->orderBy('c.ranknumber') + ->addOrderBy('c.activatetime'); if (!$alsofuture) { $qb->andWhere('c.activatetime <= :now'); diff --git a/webapp/src/Service/ImportExportService.php b/webapp/src/Service/ImportExportService.php index 314f79ef3d..134ece44ee 100644 --- a/webapp/src/Service/ImportExportService.php +++ b/webapp/src/Service/ImportExportService.php @@ -226,6 +226,9 @@ public function importContestData(mixed $data, ?string &$errorMessage = null, st $contest->setDeactivatetimeString(date_format($deactivateTime, 'Y-m-d H:i:s e')); } + $contestsCount = $this->em->getRepository(Contest::class)->count([]); + $contest->setRank($contestsCount + 1); + // Get all visible categories. For now, we assume these are the ones getting awards $visibleCategories = $this->em->getRepository(TeamCategory::class)->findBy(['visible' => true]); diff --git a/webapp/src/Service/PointsScoreService.php b/webapp/src/Service/PointsScoreService.php new file mode 100644 index 0000000000..f31d253f21 --- /dev/null +++ b/webapp/src/Service/PointsScoreService.php @@ -0,0 +1,97 @@ +em = $em; + $this->logger = $logger; + $this->dj = $dj; + $this->config = $config; + $this->eventLogService = $eventLogService; + $this->scoreboardService = $scoreboardService; + } + + /** + * @param JudgingRun[] $judgingRuns + */ + public function getScoredPoints(Judging $judging, + array $judgingRuns): float + { + $contestProblem = $judging->getSubmission()->getContestProblem(); + + if ($judging->getResult() === 'correct') { + return $contestProblem->getPoints(); + } + + $lazyEval = $this->config->get('lazy_eval_results'); + $problemLazy = $judging->getSubmission()->getContestProblem()->getLazyEvalResults(); + if (isset($problemLazy)) { + $lazyEval = $problemLazy; + } + + $partialScoring = $this->config->get('partial_points_scoring'); + $problemPartialScoring = $judging->getSubmission()->getContestProblem()->getPartialPointsScoring(); + if (isset($problemPartialScoring)) { + $partialScoring = $problemPartialScoring; + } + + if (!$partialScoring || $lazyEval !== DOMJudgeService::EVAL_FULL) { + return 0; + } + + $groupRuns = []; + + foreach ($judgingRuns as $judgingRun) { + $group = $judgingRun->getTestcase()->getTestcaseGroup(); + if (!isset($groupRuns[$group->getTestcasegroupid()])) { + $groupRuns[$group->getTestcasegroupid()] = [$judgingRun]; + } else { + $groupRuns[$group->getTestcasegroupid()][] = $judgingRun; + } + } + + $pointsScored = 0; + + foreach ($groupRuns as $runs) { + /** @var JudgingRun[] $runs */ + $group = $runs[0]->getTestcase()->getTestcaseGroup(); + + $correctRuns = array_reduce($runs, + fn($carry, $run) => $run->getRunresult() == 'correct' ? $carry + 1 : $carry, 0); + if ($correctRuns < count($group->getTestcases())) { + continue; + } + + $pointsScored += $group->getPointsPercentage() * $contestProblem->getPoints(); + } + + return $pointsScored; + } +} diff --git a/webapp/src/Service/SubmissionService.php b/webapp/src/Service/SubmissionService.php index 1d01c96096..0305b9e461 100644 --- a/webapp/src/Service/SubmissionService.php +++ b/webapp/src/Service/SubmissionService.php @@ -356,7 +356,8 @@ public function submitSolution( ?string $externalId = null, ?float $submitTime = null, ?string &$message = null, - bool $forceImportInvalid = false + bool $forceImportInvalid = false, + ?bool $ignoreSubmission = false ): ?Submission { if (!$team instanceof Team) { $team = $this->em->getRepository(Team::class)->find($team); @@ -591,7 +592,8 @@ public function submitSolution( ->setOriginalSubmission($originalSubmission) ->setEntryPoint($entryPoint) ->setExternalid($externalId) - ->setImportError($importError); + ->setImportError($importError) + ->setValid(!$ignoreSubmission); // Add expected results from source. We only do this for jury submissions // to prevent accidental auto-verification of team submissions. diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 7238f43682..3775affe21 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -114,6 +114,7 @@ public function getFilters(): array new TwigFilter('entityIdBadge', $this->entityIdBadge(...), ['is_safe' => ['html']]), new TwigFilter('medalType', $this->awards->medalType(...)), new TwigFilter('numTableActions', $this->numTableActions(...)), + new TwigFilter('printPartialPointsScoringMode', $this->printPartialPointsScoringMode(...)), ]; } @@ -128,6 +129,7 @@ public function getGlobals(): array $selfRegistrationCategoriesCount = $this->em->getRepository(TeamCategory::class)->count(['allow_self_registration' => 1]); // These variables mostly exist for the header template. return [ + 'current_team' => $team, 'current_contest_id' => $this->dj->getCurrentContestCookie(), 'current_contest' => $this->dj->getCurrentContest(), 'current_contests' => $this->dj->getCurrentContests(), @@ -157,6 +159,13 @@ public function getGlobals(): array 'doc_links' => $this->dj->getDocLinks(), 'allow_registration' => $selfRegistrationCategoriesCount !== 0, 'enable_ranking' => $this->config->get('enable_ranking'), + 'google_analytics_tracking_id' => + $this->tokenStorage->getToken() && $this->authorizationChecker->isGranted('ROLE_ADMIN') ? + '' : $this->config->get('google_analytics_tracking_id'), + 'hotjar_tracking_id' => + $this->tokenStorage->getToken() && $this->authorizationChecker->isGranted('ROLE_ADMIN') ? + '' : $this->config->get('hotjar_tracking_id'), + 'discord_invite_url' => $this->config->get('discord_invite_url'), ]; } @@ -272,6 +281,16 @@ public static function printLazyMode(?int $val): string } } + public static function printPartialPointsScoringMode(?bool $val): string + { + if ($val === null){ + return "-"; + } else if ($val) { + return "Yes"; + } + return "No"; + } + public static function printYesNo(bool $val): string { return $val ? 'Yes' : 'No'; @@ -840,11 +859,11 @@ public function codeEditor(
%s
HTML; @@ -1085,10 +1104,8 @@ public function problemBadge(ContestProblem $problem): string // Pick the foreground text color based on the background color. $foreground = ($background[0] + $background[1] + $background[2] > 450) ? '#000000' : '#ffffff'; return sprintf( - '%s', - $rgb, + '
%s
', $border, - $foreground, $problem->getShortname() ); } diff --git a/webapp/templates/base.html.twig b/webapp/templates/base.html.twig index 895750ede8..24fc8311f8 100644 --- a/webapp/templates/base.html.twig +++ b/webapp/templates/base.html.twig @@ -1,5 +1,5 @@ - + @@ -9,9 +9,11 @@ + + {% for file in customAssetFiles('js') %} @@ -25,6 +27,32 @@ {% for file in customAssetFiles('css') %} {% endfor %} + + {% if google_analytics_tracking_id is not empty %} + + + + {% endif %} + + {% if hotjar_tracking_id is not empty %} + + + {% endif %} {% block menu %}{% endblock %} @@ -61,7 +89,9 @@ initializeAjaxModals(); }); -{% block footer %}{% endblock %} +{% block footer %} + {% include 'partials/footer.html.twig' %} +{% endblock %} {% block extrafooter %}{% endblock %} diff --git a/webapp/templates/jury/blog.html.twig b/webapp/templates/jury/blog.html.twig new file mode 100644 index 0000000000..05382c6ec9 --- /dev/null +++ b/webapp/templates/jury/blog.html.twig @@ -0,0 +1,21 @@ +{% extends "jury/base.html.twig" %} +{% import "jury/jury_macros.twig" as macros %} + +{% block title %}Blog posts - {{ parent() }}{% endblock %} + +{% block extrahead %} + {{ parent() }} + {{ macros.table_extrahead() }} +{% endblock %} + +{% block content %} +

Blog posts

+ + {{ macros.table(blog_posts, table_fields, num_actions) }} + + {% if is_granted('ROLE_ADMIN') %} +

+ {{ button(path('jury_blog_post_send'), 'Send a new blog post', 'primary', 'pencil') }} +

+ {% endif %} +{% endblock %} diff --git a/webapp/templates/jury/blog_post_send.html.twig b/webapp/templates/jury/blog_post_send.html.twig new file mode 100644 index 0000000000..7f67345867 --- /dev/null +++ b/webapp/templates/jury/blog_post_send.html.twig @@ -0,0 +1,137 @@ +{% extends "jury/base.html.twig" %} + +{% block title %}{{ action|capitalize }} a blog post - {{ parent() }}{% endblock %} + +{% block extrahead %} + {{ parent() }} +{% endblock %} + +{% block content %} + +
+

{{ action|capitalize }} a blog post

+ + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.subtitle) }} + +
+
+ {{ form_row(form.thumbnail) }} +
+ +
+ + + + {{ form_row(form.author) }} + + +
+
+
+ +
+
+
+ + {{ form_row(form.publishtime) }} + + {{ form_row(form.send) }} + + {{ form_end(form) }} +
+ + {% if data is defined %} + {{ data|raw }} + {% endif %} + + + + + + + + + + + +{% endblock %} diff --git a/webapp/templates/jury/config.html.twig b/webapp/templates/jury/config.html.twig index d9f41c8357..8c291c910f 100644 --- a/webapp/templates/jury/config.html.twig +++ b/webapp/templates/jury/config.html.twig @@ -100,6 +100,9 @@ name="config_{{ option.name }}" id="config_{{ option.name }}" value="{{ option.value }}"> {% endif %} + {% elseif option.type == 'textarea' %} + {% elseif option.type == 'array_keyval' %}
diff --git a/webapp/templates/jury/config_check.html.twig b/webapp/templates/jury/config_check.html.twig index d301e01f22..d868278f97 100644 --- a/webapp/templates/jury/config_check.html.twig +++ b/webapp/templates/jury/config_check.html.twig @@ -80,7 +80,7 @@ {% for test,testresult in groupresults %}