From 88ad942e108ebe0aed088d1219a6d5e7b999c360 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sat, 31 Dec 2022 10:34:43 +0100 Subject: [PATCH 1/4] composer update + gen-major (4.7) --- app/Metadata/Extractor.php | 13 +- composer.json | 2 +- composer.lock | 720 +++++++++--------- .../2022_12_31_103416_bump_version040700.php | 26 + version.md | 2 +- 5 files changed, 408 insertions(+), 355 deletions(-) create mode 100644 database/migrations/2022_12_31_103416_bump_version040700.php diff --git a/app/Metadata/Extractor.php b/app/Metadata/Extractor.php index af66202549f..bce4972d9fb 100644 --- a/app/Metadata/Extractor.php +++ b/app/Metadata/Extractor.php @@ -13,6 +13,7 @@ use Carbon\Exceptions\InvalidTimeZoneException; use Illuminate\Support\Carbon; use PHPExif\Adapter\NoAdapterException; +use PHPExif\Enum\ReaderType; use PHPExif\Exif; use PHPExif\Reader\Reader; use Safe\Exceptions\StringsException; @@ -77,10 +78,10 @@ public static function createFromFile(NativeLocalFile $file): self // 3. Imagick (not for videos, i.e. for supported photos and accepted raw files only) // 4. Native PHP exif reader (last resort) $reader = match (true) { - (Configs::hasFFmpeg() && $isSupportedVideo) => Reader::factory(Reader::TYPE_FFPROBE), - Configs::hasExiftool() => Reader::factory(Reader::TYPE_EXIFTOOL), - (Configs::hasImagick() && !$isSupportedVideo) => Reader::factory(Reader::TYPE_IMAGICK), - default => Reader::factory(Reader::TYPE_NATIVE), + (Configs::hasFFmpeg() && $isSupportedVideo) => Reader::factory(ReaderType::FFPROBE), + Configs::hasExiftool() => Reader::factory(ReaderType::EXIFTOOL), + (Configs::hasImagick() && !$isSupportedVideo) => Reader::factory(ReaderType::IMAGICK), + default => Reader::factory(ReaderType::NATIVE), }; // this can throw an exception in the case of Exiftool adapter! @@ -108,7 +109,7 @@ public static function createFromFile(NativeLocalFile $file): self try { Logs::notice(__METHOD__, __LINE__, 'Falling back to native adapter.'); // Use Php native tools - $reader = Reader::factory(Reader::TYPE_NATIVE); + $reader = Reader::factory(ReaderType::NATIVE); $exif = $reader->read($file->getRealPath()); } catch (\InvalidArgumentException|NoAdapterException $e) { throw new ExternalComponentMissingException('The configured EXIF adapter is not available', $e); @@ -132,7 +133,7 @@ public static function createFromFile(NativeLocalFile $file): self if (Configs::hasExiftool() && $sidecarFile->exists()) { try { // Don't use the same reader as the file in case it's a video - $sidecarReader = Reader::factory(Reader::TYPE_EXIFTOOL); + $sidecarReader = Reader::factory(ReaderType::EXIFTOOL); $sideCarExifData = $sidecarReader->read($sidecarFile->getRealPath()); if (!$sideCarExifData instanceof Exif) { throw new MediaFileOperationException('Could not even extract EXIF data with the exiftool adapter'); diff --git a/composer.json b/composer.json index c76b34b31c8..f2f0e379b39 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "laravel/framework": "^9.2", "livewire/livewire": "^2.7", "lychee-org/nestedset": "^6", - "lychee-org/php-exif": "^0.7.14", + "lychee-org/php-exif": "dev-readme", "maennchen/zipstream-php": "^2.1", "php-ffmpeg/php-ffmpeg": "^1.0", "php-http/guzzle7-adapter": "^1.0", diff --git a/composer.lock b/composer.lock index b71c0a84b10..a3dcfbe5d57 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": "37a26b01edd840015153b3ba14d34221", + "content-hash": "d2e44bde6559e481d7d4dcd90f7b7be9", "packages": [ { "name": "bepsvpt/secure-headers", @@ -376,16 +376,16 @@ }, { "name": "doctrine/dbal", - "version": "3.5.1", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5" + "reference": "63e513cebbbaf96a6795e5c5ee34d205831bfc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", - "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/63e513cebbbaf96a6795e5c5ee34d205831bfc85", + "reference": "63e513cebbbaf96a6795e5c5ee34d205831bfc85", "shasum": "" }, "require": { @@ -398,16 +398,16 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "10.0.0", - "jetbrains/phpstorm-stubs": "2022.2", - "phpstan/phpstan": "1.8.10", + "doctrine/coding-standard": "11.0.0", + "jetbrains/phpstorm-stubs": "2022.3", + "phpstan/phpstan": "1.9.2", "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "9.5.25", - "psalm/plugin-phpunit": "0.17.0", + "phpunit/phpunit": "9.5.27", + "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.29.0" + "vimeo/psalm": "4.30.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -467,7 +467,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.5.1" + "source": "https://github.com/doctrine/dbal/tree/3.5.2" }, "funding": [ { @@ -483,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-10-24T07:26:18+00:00" + "time": "2022-12-19T08:17:34+00:00" }, { "name": "doctrine/deprecations", @@ -712,31 +712,33 @@ }, { "name": "doctrine/lexer", - "version": "1.2.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", + "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -768,7 +770,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "source": "https://github.com/doctrine/lexer/tree/2.1.0" }, "funding": [ { @@ -784,7 +786,7 @@ "type": "tidelift" } ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2022-12-14T08:49:07+00:00" }, { "name": "dragonmantank/cron-expression", @@ -849,20 +851,20 @@ }, { "name": "egulias/email-validator", - "version": "3.2.1", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715" + "reference": "5f35e41eba05fdfbabd95d72f83795c835fb7ed2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715", - "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/5f35e41eba05fdfbabd95d72f83795c835fb7ed2", + "reference": "5f35e41eba05fdfbabd95d72f83795c835fb7ed2", "shasum": "" }, "require": { - "doctrine/lexer": "^1.2", + "doctrine/lexer": "^1.2|^2", "php": ">=7.2", "symfony/polyfill-intl-idn": "^1.15" }, @@ -905,7 +907,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.1" + "source": "https://github.com/egulias/EmailValidator/tree/3.2.4" }, "funding": [ { @@ -913,7 +915,7 @@ "type": "github" } ], - "time": "2022-06-18T20:57:19+00:00" + "time": "2022-12-30T14:09:25+00:00" }, { "name": "evenement/evenement", @@ -1033,6 +1035,56 @@ ], "time": "2022-02-20T15:07:15+00:00" }, + { + "name": "fylax/forceutf8", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/Fylax/forceutf8.git", + "reference": "9372e4488c5b6d5a671fd4d3244eef2268a4d1e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Fylax/forceutf8/zipball/9372e4488c5b6d5a671fd4d3244eef2268a4d1e4", + "reference": "9372e4488c5b6d5a671fd4d3244eef2268a4d1e4", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "suggest": { + "ext-iconv": "*" + }, + "type": "library", + "extra": { + "thanks": { + "name": "neitanod/forceutf8", + "url": "https://github.com/neitanod/forceutf8" + } + }, + "autoload": { + "psr-0": { + "ForceUTF8\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nico Caprioli", + "email": "nico.caprioli@gmail.com", + "role": "Developer" + } + ], + "description": "PHP Class Encoding featuring popular Encoding::toUTF8() function --formerly known as forceUTF8()-- that fixes mixed encoded strings.", + "homepage": "https://github.com/Fylax/forceutf8", + "support": { + "source": "https://github.com/Fylax/forceutf8/tree/v3.0.1" + }, + "time": "2022-12-29T13:20:55+00:00" + }, { "name": "geocoder-php/cache-provider", "version": "4.4.0", @@ -1718,16 +1770,16 @@ }, { "name": "laravel/framework", - "version": "v9.43.0", + "version": "v9.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b" + "reference": "faeb20d3fc61b69790068161ab42bcf2d5faccbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", - "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", + "url": "https://api.github.com/repos/laravel/framework/zipball/faeb20d3fc61b69790068161ab42bcf2d5faccbc", + "reference": "faeb20d3fc61b69790068161ab42bcf2d5faccbc", "shasum": "" }, "require": { @@ -1738,7 +1790,7 @@ "ext-openssl": "*", "fruitcake/php-cors": "^1.2", "laravel/serializable-closure": "^1.2.2", - "league/commonmark": "^2.2", + "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^2.0", "nesbot/carbon": "^2.62.1", @@ -1747,7 +1799,7 @@ "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "ramsey/uuid": "^4.2.2", + "ramsey/uuid": "^4.7", "symfony/console": "^6.0.9", "symfony/error-handler": "^6.0", "symfony/finder": "^6.0", @@ -1808,7 +1860,7 @@ "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", "doctrine/dbal": "^2.13.3|^3.1.4", - "fakerphp/faker": "^1.9.2", + "fakerphp/faker": "^1.21", "guzzlehttp/guzzle": "^7.5", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", @@ -1816,7 +1868,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.11", + "orchestra/testbench-core": "^7.16", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^9.5.8", @@ -1900,7 +1952,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-12-06T14:26:07+00:00" + "time": "2022-12-21T19:37:46+00:00" }, { "name": "laravel/serializable-closure", @@ -1964,16 +2016,16 @@ }, { "name": "league/commonmark", - "version": "2.3.7", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf" + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c493585c130544c4e91d2e0e131e6d35cb0cbc47", + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47", "shasum": "" }, "require": { @@ -2001,7 +2053,7 @@ "symfony/finder": "^5.3 | ^6.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -2066,20 +2118,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T17:29:46+00:00" + "time": "2022-12-10T16:02:17+00:00" }, { "name": "league/config", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { @@ -2088,7 +2140,7 @@ "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", "unleashedtech/php-coding-standard": "^3.1", @@ -2148,20 +2200,20 @@ "type": "github" } ], - "time": "2021-08-14T12:15:32+00:00" + "time": "2022-12-11T20:36:23+00:00" }, { "name": "league/flysystem", - "version": "3.11.0", + "version": "3.12.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "7e423e5dd240a60adfab9bde058d7668863b7731" + "reference": "2aef65a47e44f2d6f9938f720f6dd697e7ba7b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7e423e5dd240a60adfab9bde058d7668863b7731", - "reference": "7e423e5dd240a60adfab9bde058d7668863b7731", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2aef65a47e44f2d6f9938f720f6dd697e7ba7b76", + "reference": "2aef65a47e44f2d6f9938f720f6dd697e7ba7b76", "shasum": "" }, "require": { @@ -2223,7 +2275,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.11.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.12.0" }, "funding": [ { @@ -2239,7 +2291,7 @@ "type": "tidelift" } ], - "time": "2022-12-02T14:39:57+00:00" + "time": "2022-12-20T20:21:10+00:00" }, { "name": "league/mime-type-detection", @@ -2299,16 +2351,16 @@ }, { "name": "livewire/livewire", - "version": "v2.10.7", + "version": "v2.10.8", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "fa0441bf82f1674beecb3a8ad8a4ae428736ed18" + "reference": "4cc5dedaab1e9512efb4d528fde67df98e9b465a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/fa0441bf82f1674beecb3a8ad8a4ae428736ed18", - "reference": "fa0441bf82f1674beecb3a8ad8a4ae428736ed18", + "url": "https://api.github.com/repos/livewire/livewire/zipball/4cc5dedaab1e9512efb4d528fde67df98e9b465a", + "reference": "4cc5dedaab1e9512efb4d528fde67df98e9b465a", "shasum": "" }, "require": { @@ -2360,7 +2412,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.10.7" + "source": "https://github.com/livewire/livewire/tree/v2.10.8" }, "funding": [ { @@ -2368,7 +2420,7 @@ "type": "github" } ], - "time": "2022-08-08T13:52:53+00:00" + "time": "2022-12-21T22:28:25+00:00" }, { "name": "lychee-org/nestedset", @@ -2435,22 +2487,22 @@ }, { "name": "lychee-org/php-exif", - "version": "v0.7.14", + "version": "dev-readme", "source": { "type": "git", "url": "https://github.com/LycheeOrg/php-exif.git", - "reference": "c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c" + "reference": "49c4e13808d82cb95fee6e01cc661369c80be5db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c", - "reference": "c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c", + "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/49c4e13808d82cb95fee6e01cc661369c80be5db", + "reference": "49c4e13808d82cb95fee6e01cc661369c80be5db", "shasum": "" }, "require": { "ext-fileinfo": "*", - "neitanod/forceutf8": "^2.0.4", - "php": "^8.0", + "fylax/forceutf8": "^3.0.1", + "php": "^8.1", "php-ffmpeg/php-ffmpeg": "^1.0", "thecodingmachine/safe": "^2.2" }, @@ -2490,6 +2542,9 @@ ], "phpstan": [ "vendor/bin/phpstan analyze" + ], + "fix-code-style": [ + "vendor/bin/phpcbf --standard=PSR2 ./lib/" ] }, "license": [ @@ -2515,30 +2570,30 @@ "tiff" ], "support": { - "source": "https://github.com/LycheeOrg/php-exif/tree/v0.7.14", + "source": "https://github.com/LycheeOrg/php-exif/tree/readme", "issues": "https://github.com/LycheeOrg/php-exif/issues" }, - "time": "2022-10-28T15:10:47+00:00" + "time": "2022-12-31T09:33:07+00:00" }, { "name": "maennchen/zipstream-php", - "version": "2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "8df0a40fff7b5cbf86cf9a6d7d8d15b9bc03bc98" + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/8df0a40fff7b5cbf86cf9a6d7d8d15b9bc03bc98", - "reference": "8df0a40fff7b5cbf86cf9a6d7d8d15b9bc03bc98", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", "shasum": "" }, "require": { + "ext-mbstring": "*", "myclabs/php-enum": "^1.5", "php": "^8.0", - "psr/http-message": "^1.0", - "symfony/polyfill-mbstring": "^1.0" + "psr/http-message": "^1.0" }, "require-dev": { "ext-zip": "*", @@ -2547,7 +2602,7 @@ "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.4", "phpunit/phpunit": "^8.5.8 || ^9.4.2", - "vimeo/psalm": "^4.1" + "vimeo/psalm": "^5.0" }, "type": "library", "autoload": { @@ -2584,7 +2639,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.3.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/v2.4.0" }, "funding": [ { @@ -2596,7 +2651,7 @@ "type": "open_collective" } ], - "time": "2022-11-28T12:13:34+00:00" + "time": "2022-12-08T12:29:14+00:00" }, { "name": "monolog/monolog", @@ -2763,47 +2818,6 @@ ], "time": "2022-08-04T09:53:51+00:00" }, - { - "name": "neitanod/forceutf8", - "version": "v2.0.4", - "source": { - "type": "git", - "url": "https://github.com/neitanod/forceutf8.git", - "reference": "c1fbe70bfb5ad41b8ec5785056b0e308b40d4831" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/neitanod/forceutf8/zipball/c1fbe70bfb5ad41b8ec5785056b0e308b40d4831", - "reference": "c1fbe70bfb5ad41b8ec5785056b0e308b40d4831", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "ForceUTF8\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastián Grignoli", - "email": "grignoli@gmail.com" - } - ], - "description": "PHP Class Encoding featuring popular Encoding::toUTF8() function --formerly known as forceUTF8()-- that fixes mixed encoded strings.", - "homepage": "https://github.com/neitanod/forceutf8", - "support": { - "issues": "https://github.com/neitanod/forceutf8/issues", - "source": "https://github.com/neitanod/forceutf8/tree/master" - }, - "time": "2019-12-10T14:09:14+00:00" - }, { "name": "nesbot/carbon", "version": "2.64.0", @@ -3055,16 +3069,16 @@ }, { "name": "nunomaduro/termwind", - "version": "v1.14.2", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "9a8218511eb1a0965629ff820dda25985440aefc" + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/9a8218511eb1a0965629ff820dda25985440aefc", - "reference": "9a8218511eb1a0965629ff820dda25985440aefc", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/594ab862396c16ead000de0c3c38f4a5cbe1938d", + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d", "shasum": "" }, "require": { @@ -3121,7 +3135,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.14.2" + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.0" }, "funding": [ { @@ -3137,25 +3151,25 @@ "type": "github" } ], - "time": "2022-10-28T22:51:32+00:00" + "time": "2022-12-20T19:00:15+00:00" }, { "name": "php-ffmpeg/php-ffmpeg", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", - "reference": "bda300b69acecf791d2934cd5ed43a8ba24febf6" + "reference": "eace6f174ff6d206ba648483ebe59760f7f6a0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/bda300b69acecf791d2934cd5ed43a8ba24febf6", - "reference": "bda300b69acecf791d2934cd5ed43a8ba24febf6", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/eace6f174ff6d206ba648483ebe59760f7f6a0e1", + "reference": "eace6f174ff6d206ba648483ebe59760f7f6a0e1", "shasum": "" }, "require": { "evenement/evenement": "^3.0", - "php": "^8.0 || ^8.1", + "php": "^8.0 || ^8.1 || ^8.2", "psr/log": "^1.0 || ^2.0 || ^3.0", "spatie/temporary-directory": "^2.0", "symfony/cache": "^5.4 || ^6.0", @@ -3224,9 +3238,9 @@ ], "support": { "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", - "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.0.1" + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.1.0" }, - "time": "2022-02-22T15:54:06+00:00" + "time": "2022-12-09T13:57:05+00:00" }, { "name": "php-http/discovery", @@ -4138,42 +4152,53 @@ }, { "name": "ramsey/collection", - "version": "1.2.2", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4", + "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4", "shasum": "" }, "require": { - "php": "^7.3 || ^8", + "php": "^7.4 || ^8.0", "symfony/polyfill-php81": "^1.23" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" }, "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, "autoload": { "psr-4": { "Ramsey\\Collection\\": "src/" @@ -4201,7 +4226,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.2.2" + "source": "https://github.com/ramsey/collection/tree/1.3.0" }, "funding": [ { @@ -4213,27 +4238,27 @@ "type": "tidelift" } ], - "time": "2021-10-10T03:01:02+00:00" + "time": "2022-12-27T19:12:24+00:00" }, { "name": "ramsey/uuid", - "version": "4.6.0", + "version": "4.7.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f" + "reference": "5ed9ad582647bbc3864ef78db34bdc1afdcf9b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/5ed9ad582647bbc3864ef78db34bdc1afdcf9b49", + "reference": "5ed9ad582647bbc3864ef78db34bdc1afdcf9b49", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10", "ext-json": "*", "php": "^8.0", - "ramsey/collection": "^1.0" + "ramsey/collection": "^1.2" }, "replace": { "rhumsaa/uuid": "self.version" @@ -4293,7 +4318,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.6.0" + "source": "https://github.com/ramsey/uuid/tree/4.7.0" }, "funding": [ { @@ -4305,7 +4330,7 @@ "type": "tidelift" } ], - "time": "2022-11-05T23:03:38+00:00" + "time": "2022-12-19T22:30:49+00:00" }, { "name": "spatie/guzzle-rate-limiter-middleware", @@ -4577,16 +4602,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.13.7", + "version": "1.13.8", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "4af8e608184471b5568af6265ebb0ca0025c131a" + "reference": "781a2f637237e69c277eb401063acf15e2b4156b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/4af8e608184471b5568af6265ebb0ca0025c131a", - "reference": "4af8e608184471b5568af6265ebb0ca0025c131a", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/781a2f637237e69c277eb401063acf15e2b4156b", + "reference": "781a2f637237e69c277eb401063acf15e2b4156b", "shasum": "" }, "require": { @@ -4625,7 +4650,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.7" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.8" }, "funding": [ { @@ -4633,7 +4658,7 @@ "type": "github" } ], - "time": "2022-11-15T09:10:09+00:00" + "time": "2022-12-20T14:09:05+00:00" }, { "name": "spatie/temporary-directory", @@ -4698,16 +4723,16 @@ }, { "name": "symfony/cache", - "version": "v6.2.0", + "version": "v6.2.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "64cb231dfb25677097d18503d1ad4d016b19f19c" + "reference": "ddd1a70bfdf4ed19facad0db689c7bca979d322e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/64cb231dfb25677097d18503d1ad4d016b19f19c", - "reference": "64cb231dfb25677097d18503d1ad4d016b19f19c", + "url": "https://api.github.com/repos/symfony/cache/zipball/ddd1a70bfdf4ed19facad0db689c7bca979d322e", + "reference": "ddd1a70bfdf4ed19facad0db689c7bca979d322e", "shasum": "" }, "require": { @@ -4774,7 +4799,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.2.0" + "source": "https://github.com/symfony/cache/tree/v6.2.4" }, "funding": [ { @@ -4790,7 +4815,7 @@ "type": "tidelift" } ], - "time": "2022-11-24T11:58:37+00:00" + "time": "2022-12-29T16:29:13+00:00" }, { "name": "symfony/cache-contracts", @@ -4873,16 +4898,16 @@ }, { "name": "symfony/console", - "version": "v6.2.1", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "58f6cef5dc5f641b7bbdbf8b32b44cc926c35f3f" + "reference": "0f579613e771dba2dbb8211c382342a641f5da06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/58f6cef5dc5f641b7bbdbf8b32b44cc926c35f3f", - "reference": "58f6cef5dc5f641b7bbdbf8b32b44cc926c35f3f", + "url": "https://api.github.com/repos/symfony/console/zipball/0f579613e771dba2dbb8211c382342a641f5da06", + "reference": "0f579613e771dba2dbb8211c382342a641f5da06", "shasum": "" }, "require": { @@ -4949,7 +4974,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.1" + "source": "https://github.com/symfony/console/tree/v6.2.3" }, "funding": [ { @@ -4965,20 +4990,20 @@ "type": "tidelift" } ], - "time": "2022-12-01T13:44:20+00:00" + "time": "2022-12-28T14:26:22+00:00" }, { "name": "symfony/css-selector", - "version": "v6.2.0", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "91c342ffc99283c43653ed8eb47bc2a94db7f398" + "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/91c342ffc99283c43653ed8eb47bc2a94db7f398", - "reference": "91c342ffc99283c43653ed8eb47bc2a94db7f398", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", + "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", "shasum": "" }, "require": { @@ -5014,7 +5039,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.0" + "source": "https://github.com/symfony/css-selector/tree/v6.2.3" }, "funding": [ { @@ -5030,7 +5055,7 @@ "type": "tidelift" } ], - "time": "2022-08-26T05:51:22+00:00" + "time": "2022-12-28T14:26:22+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5101,16 +5126,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.2.1", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "b4e41f62c1124378863ff2705158a60da3e4c6b9" + "reference": "0926124c95d220499e2baf0fb465772af3a4eddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/b4e41f62c1124378863ff2705158a60da3e4c6b9", - "reference": "b4e41f62c1124378863ff2705158a60da3e4c6b9", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0926124c95d220499e2baf0fb465772af3a4eddb", + "reference": "0926124c95d220499e2baf0fb465772af3a4eddb", "shasum": "" }, "require": { @@ -5152,7 +5177,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.1" + "source": "https://github.com/symfony/error-handler/tree/v6.2.3" }, "funding": [ { @@ -5168,20 +5193,20 @@ "type": "tidelift" } ], - "time": "2022-12-01T21:07:46+00:00" + "time": "2022-12-19T14:33:49+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.0", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9efb1618fabee89515fe031314e8ed5625f85a53" + "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9efb1618fabee89515fe031314e8ed5625f85a53", - "reference": "9efb1618fabee89515fe031314e8ed5625f85a53", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3ffeb31139b49bf6ef0bc09d1db95eac053388d1", + "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1", "shasum": "" }, "require": { @@ -5235,7 +5260,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.2" }, "funding": [ { @@ -5251,7 +5276,7 @@ "type": "tidelift" } ], - "time": "2022-11-02T09:08:04+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5334,16 +5359,16 @@ }, { "name": "symfony/finder", - "version": "v6.2.0", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570" + "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/eb2355f69519e4ef33f1835bca4c935f5d42e570", - "reference": "eb2355f69519e4ef33f1835bca4c935f5d42e570", + "url": "https://api.github.com/repos/symfony/finder/zipball/81eefbddfde282ee33b437ba5e13d7753211ae8e", + "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e", "shasum": "" }, "require": { @@ -5378,7 +5403,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.0" + "source": "https://github.com/symfony/finder/tree/v6.2.3" }, "funding": [ { @@ -5394,20 +5419,20 @@ "type": "tidelift" } ], - "time": "2022-10-09T08:55:40+00:00" + "time": "2022-12-22T17:55:15+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.2.1", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0bbd5a7e81b38f32504399b9199f265505b7bac" + "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0bbd5a7e81b38f32504399b9199f265505b7bac", - "reference": "d0bbd5a7e81b38f32504399b9199f265505b7bac", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ddf4dd35de1623e7c02013523e6c2137b67b636f", + "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f", "shasum": "" }, "require": { @@ -5456,7 +5481,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.2.1" + "source": "https://github.com/symfony/http-foundation/tree/v6.2.2" }, "funding": [ { @@ -5472,20 +5497,20 @@ "type": "tidelift" } ], - "time": "2022-12-04T18:26:13+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.1", + "version": "v6.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "bcbd2ea12fee651a4c8bff4f6f00cce2ac1f8404" + "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/bcbd2ea12fee651a4c8bff4f6f00cce2ac1f8404", - "reference": "bcbd2ea12fee651a4c8bff4f6f00cce2ac1f8404", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/74f2e638ec3fa0315443bd85fab7fc8066b77f83", + "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83", "shasum": "" }, "require": { @@ -5567,7 +5592,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.2.1" + "source": "https://github.com/symfony/http-kernel/tree/v6.2.4" }, "funding": [ { @@ -5583,20 +5608,20 @@ "type": "tidelift" } ], - "time": "2022-12-06T17:28:26+00:00" + "time": "2022-12-29T19:05:08+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.1", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a18c3dd41cfcf011e3866802e39b9ae9e541deaf" + "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a18c3dd41cfcf011e3866802e39b9ae9e541deaf", - "reference": "a18c3dd41cfcf011e3866802e39b9ae9e541deaf", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", + "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", "shasum": "" }, "require": { @@ -5646,7 +5671,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.1" + "source": "https://github.com/symfony/mailer/tree/v6.2.2" }, "funding": [ { @@ -5662,20 +5687,20 @@ "type": "tidelift" } ], - "time": "2022-12-06T16:54:23+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/mime", - "version": "v6.2.0", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "1e8005a7cbd79fb824ad81308ef2a76592a08bc0" + "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/1e8005a7cbd79fb824ad81308ef2a76592a08bc0", - "reference": "1e8005a7cbd79fb824ad81308ef2a76592a08bc0", + "url": "https://api.github.com/repos/symfony/mime/zipball/8c98bf40406e791043890a163f6f6599b9cfa1ed", + "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed", "shasum": "" }, "require": { @@ -5729,7 +5754,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.0" + "source": "https://github.com/symfony/mime/tree/v6.2.2" }, "funding": [ { @@ -5745,7 +5770,7 @@ "type": "tidelift" } ], - "time": "2022-11-28T12:28:19+00:00" + "time": "2022-12-14T16:38:10+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6547,16 +6572,16 @@ }, { "name": "symfony/routing", - "version": "v6.2.0", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "857ac6f4df371470fbdefa2f5967a2618dbf1852" + "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/857ac6f4df371470fbdefa2f5967a2618dbf1852", - "reference": "857ac6f4df371470fbdefa2f5967a2618dbf1852", + "url": "https://api.github.com/repos/symfony/routing/zipball/35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", + "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", "shasum": "" }, "require": { @@ -6569,7 +6594,7 @@ "symfony/yaml": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", "symfony/config": "^6.2", "symfony/dependency-injection": "^5.4|^6.0", @@ -6615,7 +6640,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.0" + "source": "https://github.com/symfony/routing/tree/v6.2.3" }, "funding": [ { @@ -6631,20 +6656,20 @@ "type": "tidelift" } ], - "time": "2022-11-09T13:28:29+00:00" + "time": "2022-12-20T16:41:15+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", "shasum": "" }, "require": { @@ -6660,7 +6685,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -6700,7 +6725,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" }, "funding": [ { @@ -6716,20 +6741,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:18:58+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/string", - "version": "v6.2.0", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "145702685e0d12f81d755c71127bfff7582fdd36" + "reference": "863219fd713fa41cbcd285a79723f94672faff4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/145702685e0d12f81d755c71127bfff7582fdd36", - "reference": "145702685e0d12f81d755c71127bfff7582fdd36", + "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", + "reference": "863219fd713fa41cbcd285a79723f94672faff4d", "shasum": "" }, "require": { @@ -6786,7 +6811,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.0" + "source": "https://github.com/symfony/string/tree/v6.2.2" }, "funding": [ { @@ -6802,20 +6827,20 @@ "type": "tidelift" } ], - "time": "2022-11-30T17:13:47+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/translation", - "version": "v6.2.0", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "c08de62caead8357244efcb809d0b1a2584f2198" + "reference": "a2a15404ef4c15d92c205718eb828b225a144379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/c08de62caead8357244efcb809d0b1a2584f2198", - "reference": "c08de62caead8357244efcb809d0b1a2584f2198", + "url": "https://api.github.com/repos/symfony/translation/zipball/a2a15404ef4c15d92c205718eb828b225a144379", + "reference": "a2a15404ef4c15d92c205718eb828b225a144379", "shasum": "" }, "require": { @@ -6884,7 +6909,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.0" + "source": "https://github.com/symfony/translation/tree/v6.2.3" }, "funding": [ { @@ -6900,7 +6925,7 @@ "type": "tidelift" } ], - "time": "2022-11-02T09:08:04+00:00" + "time": "2022-12-23T14:11:11+00:00" }, { "name": "symfony/translation-contracts", @@ -7059,16 +7084,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.2.1", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "1e7544c8698627b908657e5276854d52ab70087a" + "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1e7544c8698627b908657e5276854d52ab70087a", - "reference": "1e7544c8698627b908657e5276854d52ab70087a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", + "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", "shasum": "" }, "require": { @@ -7127,7 +7152,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.1" + "source": "https://github.com/symfony/var-dumper/tree/v6.2.3" }, "funding": [ { @@ -7143,20 +7168,20 @@ "type": "tidelift" } ], - "time": "2022-12-03T22:32:58+00:00" + "time": "2022-12-22T17:55:15+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.2.1", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "8a3f442d48567a5447e984ce9e86875ed768304a" + "reference": "d055d12b20b42e407e607460e7552a1fe6d27f08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8a3f442d48567a5447e984ce9e86875ed768304a", - "reference": "8a3f442d48567a5447e984ce9e86875ed768304a", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d055d12b20b42e407e607460e7552a1fe6d27f08", + "reference": "d055d12b20b42e407e607460e7552a1fe6d27f08", "shasum": "" }, "require": { @@ -7201,7 +7226,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.2.1" + "source": "https://github.com/symfony/var-exporter/tree/v6.2.3" }, "funding": [ { @@ -7217,7 +7242,7 @@ "type": "tidelift" } ], - "time": "2022-12-03T22:32:58+00:00" + "time": "2022-12-22T17:55:15+00:00" }, { "name": "thecodingmachine/safe", @@ -8141,32 +8166,35 @@ }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "1.14.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/ad785217c1e9555a7d6c6c8c9f406395a5e2882b", + "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^1 || ^2", "ext-tokenizer": "*", "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", "vimeo/psalm": "^4.10" }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", "autoload": { "psr-4": { @@ -8208,36 +8236,36 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "source": "https://github.com/doctrine/annotations/tree/1.14.2" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2022-12-15T06:48:22+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -8264,7 +8292,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -8280,7 +8308,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "ergebnis/phpstan-rules", @@ -8433,16 +8461,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.13.0", + "version": "v3.13.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a6232229a8309e8811dc751c28b91cb34b2943e1" + "reference": "78d2251dd86b49c609a0fd37c20dcf0a00aea5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a6232229a8309e8811dc751c28b91cb34b2943e1", - "reference": "a6232229a8309e8811dc751c28b91cb34b2943e1", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/78d2251dd86b49c609a0fd37c20dcf0a00aea5a7", + "reference": "78d2251dd86b49c609a0fd37c20dcf0a00aea5a7", "shasum": "" }, "require": { @@ -8510,7 +8538,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.1" }, "funding": [ { @@ -8518,7 +8546,7 @@ "type": "github" } ], - "time": "2022-10-31T19:28:50+00:00" + "time": "2022-12-18T00:47:22+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8573,16 +8601,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.11", + "version": "v5.1.12", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6" + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/a790200347f0c6d07e2fca252ccb446df87520c6", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", "shasum": "" }, "require": { @@ -8629,7 +8657,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.11" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.12" }, "funding": [ { @@ -8637,7 +8665,7 @@ "type": "github" } ], - "time": "2022-11-02T21:11:04+00:00" + "time": "2022-12-13T00:04:12+00:00" }, { "name": "lychee-org/phpstan-lychee", @@ -8945,16 +8973,16 @@ }, { "name": "nunomaduro/larastan", - "version": "2.2.9", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "333e7915b984ce6606175749430081a372ead37e" + "reference": "8558ec396e10ac060c3e9b9e2c365dc2ea741fa1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/333e7915b984ce6606175749430081a372ead37e", - "reference": "333e7915b984ce6606175749430081a372ead37e", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/8558ec396e10ac060c3e9b9e2c365dc2ea741fa1", + "reference": "8558ec396e10ac060c3e9b9e2c365dc2ea741fa1", "shasum": "" }, "require": { @@ -8969,7 +8997,7 @@ "mockery/mockery": "^1.4.4", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.5", - "phpstan/phpstan": "^1.9.0" + "phpstan/phpstan": "^1.9.4" }, "require-dev": { "nikic/php-parser": "^4.13.2", @@ -9018,7 +9046,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/2.2.9" + "source": "https://github.com/nunomaduro/larastan/tree/2.3.3" }, "funding": [ { @@ -9038,7 +9066,7 @@ "type": "patreon" } ], - "time": "2022-11-04T14:58:00+00:00" + "time": "2022-12-29T22:13:56+00:00" }, { "name": "phar-io/manifest", @@ -9391,16 +9419,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.15.0", + "version": "1.15.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ff970a7101acfe99b3048e4bbfbc094e55c5b04" + "reference": "61800f71a5526081d1b5633766aa88341f1ade76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ff970a7101acfe99b3048e4bbfbc094e55c5b04", - "reference": "6ff970a7101acfe99b3048e4bbfbc094e55c5b04", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/61800f71a5526081d1b5633766aa88341f1ade76", + "reference": "61800f71a5526081d1b5633766aa88341f1ade76", "shasum": "" }, "require": { @@ -9430,22 +9458,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.15.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.15.3" }, - "time": "2022-12-07T16:12:39+00:00" + "time": "2022-12-20T20:56:55+00:00" }, { "name": "phpstan/phpstan", - "version": "1.9.2", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa" + "reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03bccee595e2146b7c9d174486b84f4dc61b0f2", + "reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2", "shasum": "" }, "require": { @@ -9475,7 +9503,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.2" + "source": "https://github.com/phpstan/phpstan/tree/1.9.4" }, "funding": [ { @@ -9491,36 +9519,34 @@ "type": "tidelift" } ], - "time": "2022-11-10T09:56:11+00:00" + "time": "2022-12-17T13:33:52+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682" + "reference": "2c6792eda026d9c474c14aa018aed312686714db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682", - "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/2c6792eda026d9c474c14aa018aed312686714db", + "reference": "2c6792eda026d9c474c14aa018aed312686714db", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^1.0" + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.3" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, "phpstan": { "includes": [ "rules.neon" @@ -9539,9 +9565,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.0.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.1.1" }, - "time": "2021-09-23T11:02:21+00:00" + "time": "2022-12-13T14:26:20+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -9593,16 +9619,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.19", + "version": "9.2.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", "shasum": "" }, "require": { @@ -9658,7 +9684,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" }, "funding": [ { @@ -9666,7 +9692,7 @@ "type": "github" } ], - "time": "2022-11-18T07:47:47+00:00" + "time": "2022-12-28T12:41:10+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9911,16 +9937,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.26", + "version": "9.5.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", "shasum": "" }, "require": { @@ -9993,7 +10019,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" }, "funding": [ { @@ -10009,7 +10035,7 @@ "type": "tidelift" } ], - "time": "2022-10-28T06:00:21+00:00" + "time": "2022-12-09T07:31:23+00:00" }, { "name": "sebastian/cli-parser", @@ -11295,16 +11321,16 @@ }, { "name": "symplify/phpstan-rules", - "version": "11.1.17", + "version": "11.1.24", "source": { "type": "git", "url": "https://github.com/symplify/phpstan-rules.git", - "reference": "238f7e5505f9f9b42c800b3fbe0dd05f511882fe" + "reference": "f967770be67705c930691ed3de7bac9beadec9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/phpstan-rules/zipball/238f7e5505f9f9b42c800b3fbe0dd05f511882fe", - "reference": "238f7e5505f9f9b42c800b3fbe0dd05f511882fe", + "url": "https://api.github.com/repos/symplify/phpstan-rules/zipball/f967770be67705c930691ed3de7bac9beadec9c5", + "reference": "f967770be67705c930691ed3de7bac9beadec9c5", "shasum": "" }, "require": { @@ -11323,7 +11349,6 @@ "phpstan": { "includes": [ "config/services/services.neon", - "config/packages/cognitive-complexity/cognitive-complexity-services.neon", "config/packages/symfony/services.neon" ] } @@ -11342,7 +11367,7 @@ ], "description": "Set of Symplify rules for PHPStan", "support": { - "source": "https://github.com/symplify/phpstan-rules/tree/11.1.17" + "source": "https://github.com/symplify/phpstan-rules/tree/11.1.24" }, "funding": [ { @@ -11354,7 +11379,7 @@ "type": "github" } ], - "time": "2022-11-10T15:18:43+00:00" + "time": "2022-12-23T14:59:23+00:00" }, { "name": "thecodingmachine/phpstan-safe-rule", @@ -11468,6 +11493,7 @@ "minimum-stability": "dev", "stability-flags": { "laragear/webauthn": 20, + "lychee-org/php-exif": 20, "lychee-org/phpstan-lychee": 20 }, "prefer-stable": true, diff --git a/database/migrations/2022_12_31_103416_bump_version040700.php b/database/migrations/2022_12_31_103416_bump_version040700.php new file mode 100644 index 00000000000..32079a70e93 --- /dev/null +++ b/database/migrations/2022_12_31_103416_bump_version040700.php @@ -0,0 +1,26 @@ +where('key', 'version')->update(['value' => '040700']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + DB::table('configs')->where('key', 'version')->update(['value' => '040605']); + } +}; diff --git a/version.md b/version.md index d9167bb6916..1163055e28e 100644 --- a/version.md +++ b/version.md @@ -1 +1 @@ -4.6.5 \ No newline at end of file +4.7.0 \ No newline at end of file From b8df47f3043cd4ec5bb8cf5f86721e473749f717 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sat, 31 Dec 2022 11:45:01 +0100 Subject: [PATCH 2/4] fix composer --- composer.json | 2 +- composer.lock | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index f2f0e379b39..e777a7b69e0 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "laravel/framework": "^9.2", "livewire/livewire": "^2.7", "lychee-org/nestedset": "^6", - "lychee-org/php-exif": "dev-readme", + "lychee-org/php-exif": "^0.8", "maennchen/zipstream-php": "^2.1", "php-ffmpeg/php-ffmpeg": "^1.0", "php-http/guzzle7-adapter": "^1.0", diff --git a/composer.lock b/composer.lock index a3dcfbe5d57..be62eedd784 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": "d2e44bde6559e481d7d4dcd90f7b7be9", + "content-hash": "4d8d838732655171326c4f561c3ba65c", "packages": [ { "name": "bepsvpt/secure-headers", @@ -2487,16 +2487,16 @@ }, { "name": "lychee-org/php-exif", - "version": "dev-readme", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/LycheeOrg/php-exif.git", - "reference": "49c4e13808d82cb95fee6e01cc661369c80be5db" + "reference": "ed40375c77fc29269f65b94daf2eada09aa191b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/49c4e13808d82cb95fee6e01cc661369c80be5db", - "reference": "49c4e13808d82cb95fee6e01cc661369c80be5db", + "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/ed40375c77fc29269f65b94daf2eada09aa191b8", + "reference": "ed40375c77fc29269f65b94daf2eada09aa191b8", "shasum": "" }, "require": { @@ -2570,10 +2570,10 @@ "tiff" ], "support": { - "source": "https://github.com/LycheeOrg/php-exif/tree/readme", + "source": "https://github.com/LycheeOrg/php-exif/tree/v0.8.0", "issues": "https://github.com/LycheeOrg/php-exif/issues" }, - "time": "2022-12-31T09:33:07+00:00" + "time": "2022-12-31T10:38:46+00:00" }, { "name": "maennchen/zipstream-php", @@ -11493,7 +11493,6 @@ "minimum-stability": "dev", "stability-flags": { "laragear/webauthn": 20, - "lychee-org/php-exif": 20, "lychee-org/phpstan-lychee": 20 }, "prefer-stable": true, From 2e0a6fe0127bfae4af8b167c3320e23c6965346f Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 1 Jan 2023 22:57:17 +0100 Subject: [PATCH 3/4] sync front --- public/Lychee-front | 2 +- public/dist/frontend.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/Lychee-front b/public/Lychee-front index 5a7a50c19a5..54d264ef803 160000 --- a/public/Lychee-front +++ b/public/Lychee-front @@ -1 +1 @@ -Subproject commit 5a7a50c19a503c32496a9dbcd41cf666cfd9aeec +Subproject commit 54d264ef8039b7bf7246a617ba8e8e5f4bc46c2a diff --git a/public/dist/frontend.js b/public/dist/frontend.js index b621e736b3f..8fafd1f1195 100644 --- a/public/dist/frontend.js +++ b/public/dist/frontend.js @@ -6329,7 +6329,7 @@ lychee.localizeStaticGuiElements = function () { // Footer var footer = document.querySelector("#lychee_footer"); - footer.querySelector("p.home_copyright").textContent = lychee.footer_show_copyright ? sprintf(lychee.locale["FOOTER_COPYRIGHT"], lychee.site_owner, lychee.site_copyright_begin === lychee.site_copyright_end ? lychee.site_copyright_begin : lychee.site_copyright_begin + "–" + lychee.site_copyright_begin) : ""; + footer.querySelector("p.home_copyright").textContent = lychee.footer_show_copyright ? sprintf(lychee.locale["FOOTER_COPYRIGHT"], lychee.site_owner, lychee.site_copyright_begin === lychee.site_copyright_end ? lychee.site_copyright_begin : lychee.site_copyright_begin + "–" + lychee.site_copyright_end) : ""; footer.querySelector("p.personal_text").textContent = lychee.footer_additional_text; footer.querySelector("p.hosted_by a").textContent = lychee.locale["HOSTED_WITH_LYCHEE"]; /** @type {HTMLDivElement} */ From c62cceb6dd2a8cb4b646a750614ad3d4b444bbc1 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 2 Jan 2023 12:18:13 +0100 Subject: [PATCH 4/4] Sync frontend --- public/Lychee-front | 2 +- public/dist/frontend.js | 21741 ++++++++++++++++++-------------------- public/dist/landing.js | 364 +- 3 files changed, 10672 insertions(+), 11435 deletions(-) diff --git a/public/Lychee-front b/public/Lychee-front index 54d264ef803..895001b6cba 160000 --- a/public/Lychee-front +++ b/public/Lychee-front @@ -1 +1 @@ -Subproject commit 54d264ef8039b7bf7246a617ba8e8e5f4bc46c2a +Subproject commit 895001b6cba94c0f6d8232d94386d4bf802fba11 diff --git a/public/dist/frontend.js b/public/dist/frontend.js index 8fafd1f1195..3e715df4e81 100644 --- a/public/dist/frontend.js +++ b/public/dist/frontend.js @@ -975,66 +975,18 @@ if (L.MarkerClusterGroup) { !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).basicContext=t()}(function(){return function o(i,c,l){function r(n,t){if(!c[n]){if(!i[n]){var e="function"==typeof require&&require;if(!t&&e)return e(n,!0);if(a)return a(n,!0);throw(t=new Error("Cannot find module '"+n+"'")).code="MODULE_NOT_FOUND",t}e=c[n]={exports:{}},i[n][0].call(e.exports,function(t){return r(i[n][1][t]||t)},e,e.exports,o,i,c,l)}return c[n].exports}for(var a="function"==typeof require&&require,t=0;t")),t.type===i?e="\n\t\t\t\t \n\t\t\t\t\t\t ").concat(o).concat(t.title,"\n\t\t\t\t \n\t\t\t\t "):t.type===c&&(e="\n\t\t\t\t \n\t\t\t\t "),e)},v=function(){var t=0\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t",t.forEach(function(t,n){return i+=x(t,n)});var i,c,l,r,a,s,u,f,d=i+="\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t",d=(document.body.insertAdjacentHTML("beforeend",d),b()),p=(l=d,c=v(c=n),r=c.x,a=c.y,s=document.querySelector(".basicContextContainer"),p=s.offsetWidth,s=s.offsetHeight,u=l.offsetWidth,f=l.offsetHeight,p"], [""]), - _templateObject2 = _taggedTemplateLiteral(["

$", "

"], ["

$", "

"]), - _templateObject3 = _taggedTemplateLiteral(["
", "
"], ["
", "
"]), - _templateObject4 = _taggedTemplateLiteral(["
"], ["
"]), - _templateObject5 = _taggedTemplateLiteral(["", "$", ""], ["", "$", ""]), - _templateObject6 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t"], ["\n\t\t\t
\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t"]), - _templateObject7 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"], ["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"]), - _templateObject8 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t
"], ["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t
"]), - _templateObject9 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t"], ["\n\t\t\t
\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t"]), - _templateObject10 = _taggedTemplateLiteral(["", "", ""], ["", "", ""]), - _templateObject11 = _taggedTemplateLiteral(["", ""], ["", ""]), - _templateObject12 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"], ["\n\t\t\t\t
\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"]), - _templateObject13 = _taggedTemplateLiteral(["\n\t\t
\n\t\t

$", "

\n\t\t"], ["\n\t\t
\n\t\t

$", "

\n\t\t"]), - _templateObject14 = _taggedTemplateLiteral([""], [""]), - _templateObject15 = _taggedTemplateLiteral(["big"], ["big"]), - _templateObject16 = _taggedTemplateLiteral(["", ""], ["", ""]), - _templateObject17 = _taggedTemplateLiteral(["
", ""], ["
", ""]), - _templateObject18 = _taggedTemplateLiteral(["

", "

"], ["

", "

"]), - _templateObject19 = _taggedTemplateLiteral(["$", "", ""], ["$", "", ""]), - _templateObject20 = _taggedTemplateLiteral(["$", ""], ["$", ""]), - _templateObject21 = _taggedTemplateLiteral(["
", "
"], ["
", "
"]), - _templateObject22 = _taggedTemplateLiteral(["
\n\t\t\t

\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t

\n\t\t\t", "\n\t\t\t", "\n\t\t
\n\t\t"], ["
\n\t\t\t

\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t

\n\t\t\t", "\n\t\t\t", "\n\t\t
\n\t\t"]), - _templateObject23 = _taggedTemplateLiteral(["
\n\t\t\t

\n\t\t\t\n\t\t\t", "\n\t\t\t

\n\t\t\tDelete\n\t\t
\n\t\t"], ["
\n\t\t\t

\n\t\t\t\n\t\t\t", "\n\t\t\t

\n\t\t\tDelete\n\t\t
\n\t\t"]), - _templateObject24 = _taggedTemplateLiteral(["\n\t\t\t ", "\n\t\t\t \"thumbnail\"\n\t\t\t
$", "
\n\t\t\t "], ["\n\t\t\t ", "\n\t\t\t \"thumbnail\"\n\t\t\t
$", "
\n\t\t\t "]), - _templateObject25 = _taggedTemplateLiteral(["$", "", ""], ["$", "", ""]), - _templateObject26 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t"], ["\n\t\t", "", "\n\t"]), - _templateObject27 = _taggedTemplateLiteral(["\n\t\t", "\n\t\t"], ["\n\t\t", "\n\t\t"]), - _templateObject28 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"], ["\n\t\t", "", " \n\t\t"]), - _templateObject29 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"], ["\n\t\t", "", " \n\t\t"]), - _templateObject30 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"], ["\n\t\t", "", " \n\t\t"]), - _templateObject31 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"], ["\n\t\t", "", "\n\t\t"]), - _templateObject32 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"], ["\n\t\t", "", "\n\t\t"]), - _templateObject33 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"], ["\n\t\t", "", "\n\t\t"]), - _templateObject34 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t", "", ""], ["\n\t\t", "", "\n\t\t", "", ""]), - _templateObject35 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"], ["\n\t\t", "", "\n\t\t"]), - _templateObject36 = _taggedTemplateLiteral([""], [""]), - _templateObject37 = _taggedTemplateLiteral(["\n\t\t\t\t \n\t\t\t\t \n\t\t\t\t "], ["\n\t\t\t\t \n\t\t\t\t
\n\t\t\t\t "]), - _templateObject38 = _taggedTemplateLiteral([", "], [", "]), - _templateObject39 = _taggedTemplateLiteral(["$", ""], ["$", ""]), - _templateObject40 = _taggedTemplateLiteral(["$", ""], ["$", ""]), - _templateObject41 = _taggedTemplateLiteral([""], [""]), - _templateObject42 = _taggedTemplateLiteral(["\n\t\t\t\t \n\t\t\t\t
\n\t\t\t\t\t
", "
\n\t\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t "], ["\n\t\t\t\t \n\t\t\t\t
\n\t\t\t\t\t
", "
\n\t\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t "]), - _templateObject43 = _taggedTemplateLiteral(["url(\"", "\")"], ["url(\"", "\")"]), - _templateObject44 = _taggedTemplateLiteral(["linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url(\"", "\")"], ["linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url(\"", "\")"]), - _templateObject45 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t
\n\t\t\t

$", "\n\t\t\t\t \n\t\t\t

\n\t\t\t

$", "\n\t\t\t\t \n\t\t\t\t \n\t\t\t\t \n\t\t\t

\n\t\t\t
\n\t\t\t\t\n\t\t\t\t$", "\n\t\t\t\t$", "\n\t\t\t
\n\t\t\t\n\t\t\t
"], ["\n\t\t\t
\n\t\t\t
\n\t\t\t

$", "\n\t\t\t\t \n\t\t\t

\n\t\t\t

$", "\n\t\t\t\t \n\t\t\t\t \n\t\t\t\t \n\t\t\t

\n\t\t\t
\n\t\t\t\t\n\t\t\t\t$", "\n\t\t\t\t$", "\n\t\t\t
\n\t\t\t\n\t\t\t
"]), - _templateObject46 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t$", "\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t"], ["\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t$", "\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t"]), - _templateObject47 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t\t", "\n\t\t\t
\n\t\t\t"], ["\n\t\t\t
\n\t\t\t\t", "\n\t\t\t
\n\t\t\t"]), - _templateObject48 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t"], ["\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t"]), - _templateObject49 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t

$", "

\n\t\t\t\t\t\t\t\t
"], ["\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t

$", "

\n\t\t\t\t\t\t\t\t
"]), - _templateObject50 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t
"], ["\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t
"]), - _templateObject51 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t
"], ["\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t
"]), - _templateObject52 = _taggedTemplateLiteral([""], [""]), - _templateObject53 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t

"], ["\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t

"]), - _templateObject54 = _taggedTemplateLiteral(["\n\t\t\t"], ["\n\t\t\t"]), - _templateObject55 = _taggedTemplateLiteral(["\n\t\t\t
"], ["\n\t\t\t
"]);
-
-function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
-
+var _templateObject, _templateObject2, _templateObject3, _templateObject4, _templateObject5, _templateObject6, _templateObject7, _templateObject8, _templateObject9, _templateObject10, _templateObject11, _templateObject12, _templateObject13, _templateObject14, _templateObject15, _templateObject16, _templateObject17, _templateObject18, _templateObject19, _templateObject20, _templateObject21, _templateObject22, _templateObject23, _templateObject24, _templateObject25, _templateObject26, _templateObject27, _templateObject28, _templateObject29, _templateObject30, _templateObject31, _templateObject32, _templateObject33, _templateObject34, _templateObject35, _templateObject36, _templateObject37, _templateObject38, _templateObject39, _templateObject40, _templateObject41, _templateObject42, _templateObject43, _templateObject44, _templateObject45, _templateObject46, _templateObject47, _templateObject48, _templateObject49, _templateObject50, _templateObject51, _templateObject52, _templateObject53, _templateObject54, _templateObject55, _templateObject56, _templateObject57, _templateObject58, _templateObject59, _templateObject60, _templateObject61;
+function _wrapRegExp() { _wrapRegExp = function _wrapRegExp(re, groups) { return new BabelRegExp(re, void 0, groups); }; var _super = RegExp.prototype, _groups = new WeakMap(); function BabelRegExp(re, flags, groups) { var _this = new RegExp(re, flags); return _groups.set(_this, groups || _groups.get(re)), _setPrototypeOf(_this, BabelRegExp.prototype); } function buildGroups(result, re) { var g = _groups.get(re); return Object.keys(g).reduce(function (groups, name) { var i = g[name]; if ("number" == typeof i) groups[name] = result[i];else { for (var k = 0; void 0 === result[i[k]] && k + 1 < i.length;) k++; groups[name] = result[i[k]]; } return groups; }, Object.create(null)); } return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (str) { var result = _super.exec.call(this, str); if (result) { result.groups = buildGroups(result, this); var indices = result.indices; indices && (indices.groups = buildGroups(indices, this)); } return result; }, BabelRegExp.prototype[Symbol.replace] = function (str, substitution) { if ("string" == typeof substitution) { var groups = _groups.get(this); return _super[Symbol.replace].call(this, str, substitution.replace(/\$<([^>]+)>/g, function (_, name) { var group = groups[name]; return "$" + (Array.isArray(group) ? group.join("$") : group); })); } if ("function" == typeof substitution) { var _this = this; return _super[Symbol.replace].call(this, str, function () { var args = arguments; return "object" != _typeof(args[args.length - 1]) && (args = [].slice.call(args)).push(buildGroups(args, _this)), substitution.apply(this, args); }); } return _super[Symbol.replace].call(this, str, substitution); }, _wrapRegExp.apply(this, arguments); }
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
+function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
+function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
+function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
+function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
+function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
 /**
  * @description This module communicates with Lychee's API
  */
@@ -1066,12 +1018,12 @@ function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defi
  * The main API object
  */
 var api = {
-	/**
-  * Global, default error handler
-  *
-  * @type {?APIErrorCB}
-  */
-	onError: null
+  /**
+   * Global, default error handler
+   *
+   * @type {?APIErrorCB}
+   */
+  onError: null
 };
 
 /**
@@ -1102,7 +1054,7 @@ var api = {
  * @returns {boolean}
  */
 api.hasSessionExpired = function (jqXHR, lycheeException) {
-	return jqXHR.status === 419 && !!lycheeException && lycheeException.exception.endsWith("SessionExpiredException") || jqXHR.status === 401 && !!lycheeException && lycheeException.exception.endsWith("UnauthenticatedException");
+  return jqXHR.status === 419 && !!lycheeException && lycheeException.exception.endsWith("SessionExpiredException") || jqXHR.status === 401 && !!lycheeException && lycheeException.exception.endsWith("UnauthenticatedException");
 };
 
 /**
@@ -1124,62 +1076,57 @@ api.hasSessionExpired = function (jqXHR, lycheeException) {
  * @returns {void}
  */
 api.post = function (fn, params) {
-	var successCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
-	var responseProgressCB = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
-	var errorCallback = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
-
-	loadingBar.show();
-
-	/**
-  * The success handler
-  * @param {Object} data the decoded JSON object of the response
-  */
-	var successHandler = function successHandler(data) {
-		setTimeout(loadingBar.hide, 100);
-		if (successCallback) successCallback(data);
-	};
-
-	/**
-  * The error handler
-  * @param {XMLHttpRequest} jqXHR the jQuery XMLHttpRequest object, see {@link https://api.jquery.com/jQuery.ajax/#jqXHR}.
-  */
-	var errorHandler = function errorHandler(jqXHR) {
-		/**
-   * @type {?LycheeException}
-   */
-		var lycheeException = jqXHR.responseJSON;
-
-		if (errorCallback) {
-			var isHandled = errorCallback(jqXHR, params, lycheeException);
-			if (isHandled) {
-				setTimeout(loadingBar.hide, 100);
-				return;
-			}
-		}
-		// Call global error handler for unhandled errors
-		api.onError(jqXHR, params, lycheeException);
-	};
-
-	var ajaxParams = {
-		type: "POST",
-		url: "api/" + fn,
-		contentType: "application/json",
-		data: JSON.stringify(params),
-		dataType: "json",
-		headers: {
-			"X-XSRF-TOKEN": csrf.getCSRFCookieValue()
-		},
-		success: successHandler,
-		error: errorHandler
-	};
-
-	if (responseProgressCB !== null) {
-		ajaxParams.xhrFields = {
-			onprogress: responseProgressCB
-		};
-	}
-
-	$.ajax(ajaxParams);
+  var successCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+  var responseProgressCB = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+  var errorCallback = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
+  loadingBar.show();
+
+  /**
+   * The success handler
+   * @param {Object} data the decoded JSON object of the response
+   */
+  var successHandler = function successHandler(data) {
+    setTimeout(loadingBar.hide, 100);
+    if (successCallback) successCallback(data);
+  };
+
+  /**
+   * The error handler
+   * @param {XMLHttpRequest} jqXHR the jQuery XMLHttpRequest object, see {@link https://api.jquery.com/jQuery.ajax/#jqXHR}.
+   */
+  var errorHandler = function errorHandler(jqXHR) {
+    /**
+     * @type {?LycheeException}
+     */
+    var lycheeException = jqXHR.responseJSON;
+    if (errorCallback) {
+      var isHandled = errorCallback(jqXHR, params, lycheeException);
+      if (isHandled) {
+        setTimeout(loadingBar.hide, 100);
+        return;
+      }
+    }
+    // Call global error handler for unhandled errors
+    api.onError(jqXHR, params, lycheeException);
+  };
+  var ajaxParams = {
+    type: "POST",
+    url: "api/" + fn,
+    contentType: "application/json",
+    data: JSON.stringify(params),
+    dataType: "json",
+    headers: {
+      "X-XSRF-TOKEN": csrf.getCSRFCookieValue()
+    },
+    success: successHandler,
+    error: errorHandler
+  };
+  if (responseProgressCB !== null) {
+    ajaxParams.xhrFields = {
+      onprogress: responseProgressCB
+    };
+  }
+  $.ajax(ajaxParams);
 };
 
 /**
@@ -1189,39 +1136,36 @@ api.post = function (fn, params) {
  * @returns {void}
  */
 api.getCSS = function (url, callback) {
-	loadingBar.show();
-
-	/**
-  * The success handler
-  * @param {Object} data the decoded JSON object of the response
-  */
-	var successHandler = function successHandler(data) {
-		setTimeout(loadingBar.hide, 100);
-
-		callback(data);
-	};
-
-	/**
-  * The error handler
-  * @param {XMLHttpRequest} jqXHR the jQuery XMLHttpRequest object, see {@link https://api.jquery.com/jQuery.ajax/#jqXHR}.
-  */
-	var errorHandler = function errorHandler(jqXHR) {
-		api.onError(jqXHR, {}, null);
-	};
-
-	$.ajax({
-		type: "GET",
-		url: url,
-		data: {},
-		dataType: "text",
-		headers: {
-			"X-XSRF-TOKEN": csrf.getCSRFCookieValue()
-		},
-		success: successHandler,
-		error: errorHandler
-	});
-};
+  loadingBar.show();
+
+  /**
+   * The success handler
+   * @param {Object} data the decoded JSON object of the response
+   */
+  var successHandler = function successHandler(data) {
+    setTimeout(loadingBar.hide, 100);
+    callback(data);
+  };
 
+  /**
+   * The error handler
+   * @param {XMLHttpRequest} jqXHR the jQuery XMLHttpRequest object, see {@link https://api.jquery.com/jQuery.ajax/#jqXHR}.
+   */
+  var errorHandler = function errorHandler(jqXHR) {
+    api.onError(jqXHR, {}, null);
+  };
+  $.ajax({
+    type: "GET",
+    url: url,
+    data: {},
+    dataType: "text",
+    headers: {
+      "X-XSRF-TOKEN": csrf.getCSRFCookieValue()
+    },
+    success: successHandler,
+    error: errorHandler
+  });
+};
 var csrf = {};
 
 /**
@@ -1232,151 +1176,131 @@ var csrf = {};
  * @returns {?string}
  */
 csrf.getCSRFCookieValue = function () {
-	var cookie = document.cookie.split(";").find(function (row) {
-		return (/^\s*(X-)?[XC]SRF-TOKEN\s*=/.test(row)
-		);
-	});
-	// We must remove all '%3D' from the end of the string.
-	// Background:
-	// The actual binary value of the CSFR value is encoded in Base64.
-	// If the length of original, binary value is not a multiple of 3 bytes,
-	// the encoding gets padded with `=` on the right; i.e. there might be
-	// zero, one or two `=` at the end of the encoded value.
-	// If the value is sent from the server to the client as part of a cookie,
-	// the `=` character is URL-encoded as `%3D`, because `=` is already used
-	// to separate a cookie key from its value.
-	// When we send back the value to the server as part of an AJAX request,
-	// Laravel expects an unpadded value.
-	// Hence, we must remove the `%3D`.
-	return cookie ? cookie.split("=")[1].trim().replace(/%3D/g, "") : null;
+  var cookie = document.cookie.split(";").find(function (row) {
+    return /^\s*(X-)?[XC]SRF-TOKEN\s*=/.test(row);
+  });
+  // We must remove all '%3D' from the end of the string.
+  // Background:
+  // The actual binary value of the CSFR value is encoded in Base64.
+  // If the length of original, binary value is not a multiple of 3 bytes,
+  // the encoding gets padded with `=` on the right; i.e. there might be
+  // zero, one or two `=` at the end of the encoded value.
+  // If the value is sent from the server to the client as part of a cookie,
+  // the `=` character is URL-encoded as `%3D`, because `=` is already used
+  // to separate a cookie key from its value.
+  // When we send back the value to the server as part of an AJAX request,
+  // Laravel expects an unpadded value.
+  // Hence, we must remove the `%3D`.
+  return cookie ? cookie.split("=")[1].trim().replace(/%3D/g, "") : null;
 };
-
 (function ($) {
-	var Swipe = function Swipe(el) {
-		var self = this;
-
-		this.el = $(el);
-		this.pos = { start: { x: 0, y: 0 }, end: { x: 0, y: 0 } };
-		this.startTime = null;
-
-		el.on("touchstart", function (e) {
-			self.touchStart(e);
-		});
-		el.on("touchmove", function (e) {
-			self.touchMove(e);
-		});
-		el.on("touchend", function () {
-			self.swipeEnd();
-		});
-		el.on("mousedown", function (e) {
-			self.mouseDown(e);
-		});
-	};
-
-	Swipe.prototype = {
-		touchStart: function touchStart(e) {
-			var touch = e.originalEvent.touches[0];
-
-			this.swipeStart(e, touch.pageX, touch.pageY);
-		},
-
-		touchMove: function touchMove(e) {
-			var touch = e.originalEvent.touches[0];
-
-			this.swipeMove(e, touch.pageX, touch.pageY);
-		},
-
-		mouseDown: function mouseDown(e) {
-			var self = this;
-
-			this.swipeStart(e, e.pageX, e.pageY);
-
-			this.el.on("mousemove", function (_e) {
-				self.mouseMove(_e);
-			});
-			this.el.on("mouseup", function () {
-				self.mouseUp();
-			});
-		},
-
-		mouseMove: function mouseMove(e) {
-			this.swipeMove(e, e.pageX, e.pageY);
-		},
-
-		mouseUp: function mouseUp(e) {
-			this.swipeEnd(e);
-
-			this.el.off("mousemove");
-			this.el.off("mouseup");
-		},
-
-		swipeStart: function swipeStart(e, x, y) {
-			this.pos.start.x = x;
-			this.pos.start.y = y;
-			this.pos.end.x = x;
-			this.pos.end.y = y;
-
-			this.startTime = new Date().getTime();
-
-			this.trigger("swipeStart", e);
-		},
-
-		swipeMove: function swipeMove(e, x, y) {
-			this.pos.end.x = x;
-			this.pos.end.y = y;
-
-			this.trigger("swipeMove", e);
-		},
-
-		swipeEnd: function swipeEnd(e) {
-			this.trigger("swipeEnd", e);
-		},
-
-		trigger: function trigger(e, originalEvent) {
-			var self = this;
-
-			var event = $.Event(e),
-			    x = self.pos.start.x - self.pos.end.x,
-			    y = self.pos.end.y - self.pos.start.y,
-			    radians = Math.atan2(y, x),
-			    direction = "up",
-			    distance = Math.round(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))),
-			    angle = Math.round(radians * 180 / Math.PI),
-			    speed = Math.round(distance / (new Date().getTime() - self.startTime) * 1000);
-
-			if (angle < 0) {
-				angle = 360 - Math.abs(angle);
-			}
-
-			if (angle <= 45 && angle >= 0 || angle <= 360 && angle >= 315) {
-				direction = "left";
-			} else if (angle >= 135 && angle <= 225) {
-				direction = "right";
-			} else if (angle > 45 && angle < 135) {
-				direction = "down";
-			}
-
-			event.originalEvent = originalEvent;
-
-			event.swipe = {
-				x: x,
-				y: y,
-				direction: direction,
-				distance: distance,
-				angle: angle,
-				speed: speed
-			};
-
-			$(self.el).trigger(event);
-		}
-	};
-
-	$.fn.swipe = function () {
-		// let swipe = new Swipe(this);
-		new Swipe(this);
-
-		return this;
-	};
+  var Swipe = function Swipe(el) {
+    var self = this;
+    this.el = $(el);
+    this.pos = {
+      start: {
+        x: 0,
+        y: 0
+      },
+      end: {
+        x: 0,
+        y: 0
+      }
+    };
+    this.startTime = null;
+    el.on("touchstart", function (e) {
+      self.touchStart(e);
+    });
+    el.on("touchmove", function (e) {
+      self.touchMove(e);
+    });
+    el.on("touchend", function () {
+      self.swipeEnd();
+    });
+    el.on("mousedown", function (e) {
+      self.mouseDown(e);
+    });
+  };
+  Swipe.prototype = {
+    touchStart: function touchStart(e) {
+      var touch = e.originalEvent.touches[0];
+      this.swipeStart(e, touch.pageX, touch.pageY);
+    },
+    touchMove: function touchMove(e) {
+      var touch = e.originalEvent.touches[0];
+      this.swipeMove(e, touch.pageX, touch.pageY);
+    },
+    mouseDown: function mouseDown(e) {
+      var self = this;
+      this.swipeStart(e, e.pageX, e.pageY);
+      this.el.on("mousemove", function (_e) {
+        self.mouseMove(_e);
+      });
+      this.el.on("mouseup", function () {
+        self.mouseUp();
+      });
+    },
+    mouseMove: function mouseMove(e) {
+      this.swipeMove(e, e.pageX, e.pageY);
+    },
+    mouseUp: function mouseUp(e) {
+      this.swipeEnd(e);
+      this.el.off("mousemove");
+      this.el.off("mouseup");
+    },
+    swipeStart: function swipeStart(e, x, y) {
+      this.pos.start.x = x;
+      this.pos.start.y = y;
+      this.pos.end.x = x;
+      this.pos.end.y = y;
+      this.startTime = new Date().getTime();
+      this.trigger("swipeStart", e);
+    },
+    swipeMove: function swipeMove(e, x, y) {
+      this.pos.end.x = x;
+      this.pos.end.y = y;
+      this.trigger("swipeMove", e);
+    },
+    swipeEnd: function swipeEnd(e) {
+      this.trigger("swipeEnd", e);
+    },
+    trigger: function trigger(e, originalEvent) {
+      var self = this;
+      var event = $.Event(e),
+        x = self.pos.start.x - self.pos.end.x,
+        y = self.pos.end.y - self.pos.start.y,
+        radians = Math.atan2(y, x),
+        direction = "up",
+        distance = Math.round(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))),
+        angle = Math.round(radians * 180 / Math.PI),
+        speed = Math.round(distance / (new Date().getTime() - self.startTime) * 1000);
+      if (angle < 0) {
+        angle = 360 - Math.abs(angle);
+      }
+      if (angle <= 45 && angle >= 0 || angle <= 360 && angle >= 315) {
+        direction = "left";
+      } else if (angle >= 135 && angle <= 225) {
+        direction = "right";
+      } else if (angle > 45 && angle < 135) {
+        direction = "down";
+      }
+      event.originalEvent = originalEvent;
+      event.swipe = {
+        x: x,
+        y: y,
+        direction: direction,
+        distance: distance,
+        angle: angle,
+        speed: speed
+      };
+      $(self.el).trigger(event);
+    }
+  };
+  $.fn.swipe = function () {
+    // let swipe = new Swipe(this);
+    new Swipe(this);
+    return this;
+  };
 })(jQuery);
 
 /**
@@ -1384,8 +1308,8 @@ csrf.getCSRFCookieValue = function () {
  */
 
 var album = {
-	/** @type {(?Album|?TagAlbum|?SearchAlbum)} */
-	json: null
+  /** @type {(?Album|?TagAlbum|?SearchAlbum)} */
+  json: null
 };
 
 /**
@@ -1393,7 +1317,7 @@ var album = {
  * @returns {boolean}
  */
 album.isSmartID = function (id) {
-	return id === SmartAlbumID.UNSORTED || id === SmartAlbumID.STARRED || id === SmartAlbumID.PUBLIC || id === SmartAlbumID.RECENT || id === SmartAlbumID.ON_THIS_DAY;
+  return id === SmartAlbumID.UNSORTED || id === SmartAlbumID.STARRED || id === SmartAlbumID.PUBLIC || id === SmartAlbumID.RECENT || id === SmartAlbumID.ON_THIS_DAY;
 };
 
 /**
@@ -1401,7 +1325,7 @@ album.isSmartID = function (id) {
  * @returns {boolean}
  */
 album.isSearchID = function (id) {
-	return id !== null && (id === SearchAlbumIDPrefix || id.startsWith(SearchAlbumIDPrefix + "/"));
+  return id !== null && (id === SearchAlbumIDPrefix || id.startsWith(SearchAlbumIDPrefix + "/"));
 };
 
 /**
@@ -1409,54 +1333,51 @@ album.isSearchID = function (id) {
  * @returns {boolean}
  */
 album.isModelID = function (id) {
-	return typeof id === "string" && /^[-_0-9a-zA-Z]{24}$/.test(id);
+  return typeof id === "string" && /^[-_0-9a-zA-Z]{24}$/.test(id);
 };
 
 /**
  * @returns {?string}
  */
 album.getParentID = function () {
-	if (album.json === null || album.isSmartID(album.json.id) || album.isSearchID(album.json.id) || !album.json.parent_id) {
-		return null;
-	}
-	return album.json.parent_id;
+  if (album.json === null || album.isSmartID(album.json.id) || album.isSearchID(album.json.id) || !album.json.parent_id) {
+    return null;
+  }
+  return album.json.parent_id;
 };
 
 /**
  * @returns {?string} the album ID
  */
 album.getID = function () {
-	/** @type {?string} */
-	var id = null;
+  /** @type {?string} */
+  var id = null;
 
-	/** @param {?string} _id */
-	var isID = function isID(_id) {
-		return album.isSmartID(_id) || album.isSearchID(_id) || album.isModelID(_id);
-	};
-
-	if (_photo3.json) id = _photo3.json.album_id;else if (album.json) id = album.json.id;else if (mapview.albumID) id = mapview.albumID;
-
-	if (isID(id) === false) {
-		var active = $(".album:hover, .album.active");
-		if (active.length === 1) {
-			id = active.attr("data-id") || null;
-		}
-	}
-	if (isID(id) === false) {
-		var _active = $(".photo:hover, .photo.active");
-		if (_active.length === 1) {
-			id = _active.attr("data-album-id") || null;
-		}
-	}
-
-	if (isID(id) === true) return id;else return null;
+  /** @param {?string} _id */
+  var isID = function isID(_id) {
+    return album.isSmartID(_id) || album.isSearchID(_id) || album.isModelID(_id);
+  };
+  if (_photo3.json) id = _photo3.json.album_id;else if (album.json) id = album.json.id;else if (mapview.albumID) id = mapview.albumID;
+  if (isID(id) === false) {
+    var active = $(".album:hover, .album.active");
+    if (active.length === 1) {
+      id = active.attr("data-id") || null;
+    }
+  }
+  if (isID(id) === false) {
+    var _active = $(".photo:hover, .photo.active");
+    if (_active.length === 1) {
+      id = _active.attr("data-album-id") || null;
+    }
+  }
+  if (isID(id) === true) return id;else return null;
 };
 
 /**
  * @returns {boolean}
  */
 album.isTagAlbum = function () {
-	return album.json && album.json.is_tag_album && album.json.is_tag_album === true;
+  return album.json && album.json.is_tag_album && album.json.is_tag_album === true;
 };
 
 /**
@@ -1464,21 +1385,19 @@ album.isTagAlbum = function () {
  * @returns {?Photo} the photo model
  */
 album.getByID = function (photoID) {
-	if (photoID == null || !album.json || !album.json.photos) {
-		loadingBar.show("error", "Error: Album json not found !");
-		return null;
-	}
-
-	var i = 0;
-	while (i < album.json.photos.length) {
-		if (album.json.photos[i].id === photoID) {
-			return album.json.photos[i];
-		}
-		i++;
-	}
-
-	loadingBar.show("error", "Error: photo " + photoID + " not found !");
-	return null;
+  if (photoID == null || !album.json || !album.json.photos) {
+    loadingBar.show("error", "Error: Album json not found !");
+    return null;
+  }
+  var i = 0;
+  while (i < album.json.photos.length) {
+    if (album.json.photos[i].id === photoID) {
+      return album.json.photos[i];
+    }
+    i++;
+  }
+  loadingBar.show("error", "Error: photo " + photoID + " not found !");
+  return null;
 };
 
 /**
@@ -1491,28 +1410,25 @@ album.getByID = function (photoID) {
  * @returns {(?Album|?TagAlbum)} the sub-album model
  */
 album.getSubByID = function (albumID) {
-	// The special `SearchAlbum`  may also contain `TagAlbum` as sub-albums
-	if (albumID == null || !album.json || !album.json.albums && !album.json.tag_albums) {
-		loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
-		return null;
-	}
-
-	var subAlbum = album.json.albums ? album.json.albums.find(function (a) {
-		return a.id === albumID;
-	}) : null;
-	if (subAlbum) {
-		return subAlbum;
-	}
-
-	var subTagAlbum = album.json.tag_albums ? album.json.tag_albums.find(function (a) {
-		return a.id === albumID;
-	}) : null;
-	if (subTagAlbum) {
-		return subTagAlbum;
-	}
-
-	loadingBar.show("error", sprintf(lychee.locale["ERROR_ALBUM_NOT_FOUND"], albumID));
-	return null;
+  // The special `SearchAlbum`  may also contain `TagAlbum` as sub-albums
+  if (albumID == null || !album.json || !album.json.albums && !album.json.tag_albums) {
+    loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
+    return null;
+  }
+  var subAlbum = album.json.albums ? album.json.albums.find(function (a) {
+    return a.id === albumID;
+  }) : null;
+  if (subAlbum) {
+    return subAlbum;
+  }
+  var subTagAlbum = album.json.tag_albums ? album.json.tag_albums.find(function (a) {
+    return a.id === albumID;
+  }) : null;
+  if (subTagAlbum) {
+    return subTagAlbum;
+  }
+  loadingBar.show("error", sprintf(lychee.locale["ERROR_ALBUM_NOT_FOUND"], albumID));
+  return null;
 };
 
 /**
@@ -1520,17 +1436,16 @@ album.getSubByID = function (albumID) {
  * @returns {void}
  */
 album.deleteByID = function (photoID) {
-	if (photoID == null || !album.json || !album.json.photos) {
-		loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
-		return;
-	}
-
-	$.each(album.json.photos, function (i) {
-		if (album.json.photos[i].id === photoID) {
-			album.json.photos.splice(i, 1);
-			return false;
-		}
-	});
+  if (photoID == null || !album.json || !album.json.photos) {
+    loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
+    return;
+  }
+  $.each(album.json.photos, function (i) {
+    if (album.json.photos[i].id === photoID) {
+      album.json.photos.splice(i, 1);
+      return false;
+    }
+  });
 };
 
 /**
@@ -1538,22 +1453,19 @@ album.deleteByID = function (photoID) {
  * @returns {boolean}
  */
 album.deleteSubByID = function (albumID) {
-	if (albumID == null || !album.json || !album.json.albums) {
-		loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
-		return false;
-	}
-
-	var deleted = false;
-
-	$.each(album.json.albums, function (i) {
-		if (album.json.albums[i].id === albumID) {
-			album.json.albums.splice(i, 1);
-			deleted = true;
-			return false;
-		}
-	});
-
-	return deleted;
+  if (albumID == null || !album.json || !album.json.albums) {
+    loadingBar.show("error", lychee.locale["ERROR_ALBUM_JSON_NOT_FOUND"]);
+    return false;
+  }
+  var deleted = false;
+  $.each(album.json.albums, function (i) {
+    if (album.json.albums[i].id === albumID) {
+      album.json.albums.splice(i, 1);
+      deleted = true;
+      return false;
+    }
+  });
+  return deleted;
 };
 
 /**
@@ -1572,134 +1484,125 @@ album.deleteSubByID = function (albumID) {
  * @returns {void}
  */
 album.load = function (albumID) {
-	var albumLoadedCB = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-	var parentID = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
-
-	/**
-  * @param {Album} data
-  */
-	var processAlbum = function processAlbum(data) {
-		album.json = data;
-
-		if (parentID !== null) {
-			// Used with search so that the back button sends back to the
-			// search results.
-			album.json.original_parent_id = album.json.parent_id;
-			album.json.parent_id = parentID;
-		}
-
-		if (albumLoadedCB === null) {
-			lychee.animate(lychee.content, "contentZoomOut");
-		}
-		var waitTime = 300;
-
-		// Skip delay when we have a callback `albumLoadedCB`
-		// Skip delay when opening a blank Lychee
-		if (albumLoadedCB) waitTime = 0;
-		if (!visible.albums() && !visible.photo() && !visible.album()) waitTime = 0;
-
-		setTimeout(function () {
-			view.album.init();
-
-			if (albumLoadedCB === null) {
-				lychee.animate(lychee.content, "contentZoomIn");
-				header.setMode("album");
-			}
-
-			tabindex.makeFocusable(lychee.content);
-			if (lychee.active_focus_on_page_load) {
-				// Put focus on first element - either album or photo
-				var first_album = $(".album:first");
-				if (first_album.length !== 0) {
-					first_album.focus();
-				} else {
-					var first_photo = $(".photo:first");
-					if (first_photo.length !== 0) {
-						first_photo.focus();
-					}
-				}
-			}
-		}, waitTime);
-	};
-
-	/**
-  * @param {Album} data
-  */
-	var successHandler = function successHandler(data) {
-		processAlbum(data);
-
-		tabindex.makeFocusable(lychee.content);
-
-		if (lychee.active_focus_on_page_load) {
-			// Put focus on first element - either album or photo
-			var first_album = $(".album:first");
-			if (first_album.length !== 0) {
-				first_album.focus();
-			} else {
-				var first_photo = $(".photo:first");
-				if (first_photo.length !== 0) {
-					first_photo.focus();
-				}
-			}
-		}
-
-		if (albumLoadedCB) albumLoadedCB(true);
-	};
-
-	/**
-  * @param {XMLHttpRequest} jqXHR
-  * @param {Object} params the original JSON parameters of the request
-  * @param {?LycheeException} lycheeException the Lychee exception
-  * @returns {boolean}
-  */
-	var errorHandler = function errorHandler(jqXHR, params, lycheeException) {
-		if (jqXHR.status !== 401 && jqXHR.status !== 403) {
-			// Any other error then unauthenticated or unauthorized
-			// shall be handled by the global error handler.
-			return false;
-		}
-
-		if (lycheeException.exception.endsWith("PasswordRequiredException")) {
-			// If a password is required, then try to unlock the album
-			// and in case of success, try again to load album with same
-			// parameters
-			password.getDialog(albumID, function () {
-				albums.refresh();
-				album.load(albumID, albumLoadedCB);
-			});
-			return true;
-		} else if (lycheeException.exception.endsWith("UnauthenticatedException") && !albumLoadedCB) {
-			// If no password is required, but we still get a 401 error
-			// try to properly log in as a user
-			// We only try this, if `albumLoadedCB` is not set.
-			// This is not optimal, but the best we can do without too much
-			// refactoring for now.
-			// `albumLoadedCB` is set, if the user directly jumps to a photo
-			// in an album via a direct link.
-			// Even though the album might be private, the photo could still
-			// be visible.
-			// If we caught users for a direct link to a public photo
-			// within a private album, we would "trap" the users in a login
-			// dialog which they cannot pass by.
-			lychee.loginDialog();
-			return true;
-		} else if (albumLoadedCB) {
-			// In case we could not successfully load and unlock the album,
-			// but we have a callback, we call that and consider the error
-			// handled.
-			// Note: This case occurs for a single public photo on an
-			// otherwise non-public album.
-			album.json = null;
-			albumLoadedCB(false);
-			return true;
-		} else {
-			// In any other case, let the global error handler deal with the
-			// problem.
-			return false;
-		}
-	};
-
-	api.post("Album::get", { albumID: albumID }, successHandler, null, errorHandler);
+  var albumLoadedCB = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+  var parentID = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+  /**
+   * @param {Album} data
+   */
+  var processAlbum = function processAlbum(data) {
+    album.json = data;
+    if (parentID !== null) {
+      // Used with search so that the back button sends back to the
+      // search results.
+      album.json.original_parent_id = album.json.parent_id;
+      album.json.parent_id = parentID;
+    }
+    if (albumLoadedCB === null) {
+      lychee.animate(lychee.content, "contentZoomOut");
+    }
+    var waitTime = 300;
+
+    // Skip delay when we have a callback `albumLoadedCB`
+    // Skip delay when opening a blank Lychee
+    if (albumLoadedCB) waitTime = 0;
+    if (!visible.albums() && !visible.photo() && !visible.album()) waitTime = 0;
+    setTimeout(function () {
+      view.album.init();
+      if (albumLoadedCB === null) {
+        lychee.animate(lychee.content, "contentZoomIn");
+        header.setMode("album");
+      }
+      tabindex.makeFocusable(lychee.content);
+      if (lychee.active_focus_on_page_load) {
+        // Put focus on first element - either album or photo
+        var first_album = $(".album:first");
+        if (first_album.length !== 0) {
+          first_album.focus();
+        } else {
+          var first_photo = $(".photo:first");
+          if (first_photo.length !== 0) {
+            first_photo.focus();
+          }
+        }
+      }
+    }, waitTime);
+  };
+
+  /**
+   * @param {Album} data
+   */
+  var successHandler = function successHandler(data) {
+    processAlbum(data);
+    tabindex.makeFocusable(lychee.content);
+    if (lychee.active_focus_on_page_load) {
+      // Put focus on first element - either album or photo
+      var first_album = $(".album:first");
+      if (first_album.length !== 0) {
+        first_album.focus();
+      } else {
+        var first_photo = $(".photo:first");
+        if (first_photo.length !== 0) {
+          first_photo.focus();
+        }
+      }
+    }
+    if (albumLoadedCB) albumLoadedCB(true);
+  };
+
+  /**
+   * @param {XMLHttpRequest} jqXHR
+   * @param {Object} params the original JSON parameters of the request
+   * @param {?LycheeException} lycheeException the Lychee exception
+   * @returns {boolean}
+   */
+  var errorHandler = function errorHandler(jqXHR, params, lycheeException) {
+    if (jqXHR.status !== 401 && jqXHR.status !== 403) {
+      // Any other error then unauthenticated or unauthorized
+      // shall be handled by the global error handler.
+      return false;
+    }
+    if (lycheeException.exception.endsWith("PasswordRequiredException")) {
+      // If a password is required, then try to unlock the album
+      // and in case of success, try again to load album with same
+      // parameters
+      password.getDialog(albumID, function () {
+        albums.refresh();
+        album.load(albumID, albumLoadedCB);
+      });
+      return true;
+    } else if (lycheeException.exception.endsWith("UnauthenticatedException") && !albumLoadedCB) {
+      // If no password is required, but we still get a 401 error
+      // try to properly log in as a user
+      // We only try this, if `albumLoadedCB` is not set.
+      // This is not optimal, but the best we can do without too much
+      // refactoring for now.
+      // `albumLoadedCB` is set, if the user directly jumps to a photo
+      // in an album via a direct link.
+      // Even though the album might be private, the photo could still
+      // be visible.
+      // If we caught users for a direct link to a public photo
+      // within a private album, we would "trap" the users in a login
+      // dialog which they cannot pass by.
+      lychee.loginDialog();
+      return true;
+    } else if (albumLoadedCB) {
+      // In case we could not successfully load and unlock the album,
+      // but we have a callback, we call that and consider the error
+      // handled.
+      // Note: This case occurs for a single public photo on an
+      // otherwise non-public album.
+      album.json = null;
+      albumLoadedCB(false);
+      return true;
+    } else {
+      // In any other case, let the global error handler deal with the
+      // problem.
+      return false;
+    }
+  };
+  api.post("Album::get", {
+    albumID: albumID
+  }, successHandler, null, errorHandler);
 };
 
 /**
@@ -1721,131 +1624,118 @@ album.load = function (albumID) {
  * @returns {void}
  */
 album.add = function () {
-	var IDs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-	var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-
-	/**
-  * @param {{title: string}} data
-  * @returns {void}
-  */
-	var action = function action(data) {
-		if (!data.title.trim()) {
-			basicModal.focusError("title");
-			return;
-		}
-
-		basicModal.close();
-
-		var params = {
-			title: data.title,
-			parent_id: null
-		};
-
-		if (visible.albums() || album.isSmartID(album.json.id) || album.isSearchID(album.json.id)) {
-			params.parent_id = null;
-		} else if (visible.album()) {
-			params.parent_id = album.json.id;
-		} else if (visible.photo()) {
-			params.parent_id = _photo3.json.album_id;
-		}
-
-		api.post("Album::add", params,
-		/** @param {Album} _data */
-		function (_data) {
-			if (IDs != null && callback != null) {
-				callback(IDs, _data.id, false); // we do not confirm
-			} else {
-				albums.refresh();
-				lychee.goto(_data.id);
-			}
-		});
-	};
-
-	/**
-  * @param {ModalDialogFormElements} formElements
-  * @param {HTMLDivElement} dialog
-  * @returns {void}
-  */
-	var initAddAlbumDialog = function initAddAlbumDialog(formElements, dialog) {
-		dialog.querySelector("p").textContent = lychee.locale["TITLE_NEW_ALBUM"];
-		formElements.title.placeholder = "Title";
-		formElements.title.value = lychee.locale["UNTITLED"];
-	};
-
-	var addAlbumDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t\n\t"; - - basicModal.show({ - body: addAlbumDialogBody, - readyCB: initAddAlbumDialog, - buttons: { - action: { - title: lychee.locale["CREATE_ALBUM"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + var IDs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + /** + * @param {{title: string}} data + * @returns {void} + */ + var action = function action(data) { + if (!data.title.trim()) { + basicModal.focusError("title"); + return; + } + basicModal.close(); + var params = { + title: data.title, + parent_id: null + }; + if (visible.albums() || album.isSmartID(album.json.id) || album.isSearchID(album.json.id)) { + params.parent_id = null; + } else if (visible.album()) { + params.parent_id = album.json.id; + } else if (visible.photo()) { + params.parent_id = _photo3.json.album_id; + } + api.post("Album::add", params, /** @param {Album} _data */ + function (_data) { + if (IDs != null && callback != null) { + callback(IDs, _data.id, false); // we do not confirm + } else { + albums.refresh(); + lychee["goto"](_data.id); + } + }); + }; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initAddAlbumDialog = function initAddAlbumDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["TITLE_NEW_ALBUM"]; + formElements.title.placeholder = "Title"; + formElements.title.value = lychee.locale["UNTITLED"]; + }; + var addAlbumDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t\n\t"; + basicModal.show({ + body: addAlbumDialogBody, + readyCB: initAddAlbumDialog, + buttons: { + action: { + title: lychee.locale["CREATE_ALBUM"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** * @returns {void} */ album.addByTags = function () { - /** @param {{title: string, tags: string}} data */ - var action = function action(data) { - if (!data.title.trim()) { - basicModal.focusError("title"); - return; - } - if (!data.tags.trim()) { - basicModal.focusError("tags"); - return; - } - - basicModal.close(); - - api.post("Album::addByTags", { - title: data.title, - tags: data.tags.split(",") - }, - /** @param {TagAlbum} _data */ - function (_data) { - albums.refresh(); - lychee.goto(_data.id); - }); - }; - - var addTagAlbumDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initAddTagAlbumDialog = function initAddTagAlbumDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["TITLE_NEW_ALBUM"]; - formElements.title.placeholder = "Title"; - formElements.title.value = lychee.locale["UNTITLED"]; - formElements.tags.placeholder = "Tags"; - }; - - basicModal.show({ - body: addTagAlbumDialogBody, - readyCB: initAddTagAlbumDialog, - buttons: { - action: { - title: lychee.locale["CREATE_TAG_ALBUM"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @param {{title: string, tags: string}} data */ + var action = function action(data) { + if (!data.title.trim()) { + basicModal.focusError("title"); + return; + } + if (!data.tags.trim()) { + basicModal.focusError("tags"); + return; + } + basicModal.close(); + api.post("Album::addByTags", { + title: data.title, + tags: data.tags.split(",") + }, /** @param {TagAlbum} _data */ + function (_data) { + albums.refresh(); + lychee["goto"](_data.id); + }); + }; + var addTagAlbumDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initAddTagAlbumDialog = function initAddTagAlbumDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["TITLE_NEW_ALBUM"]; + formElements.title.placeholder = "Title"; + formElements.title.value = lychee.locale["UNTITLED"]; + formElements.tags.placeholder = "Tags"; + }; + basicModal.show({ + body: addTagAlbumDialogBody, + readyCB: initAddTagAlbumDialog, + buttons: { + action: { + title: lychee.locale["CREATE_TAG_ALBUM"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -1853,60 +1743,55 @@ album.addByTags = function () { * @returns {void} */ album.setShowTags = function (albumID) { - /** @param {{show_tags: string}} data */ - var action = function action(data) { - if (!data.show_tags.trim()) { - basicModal.focusError("show_tags"); - return; - } - var new_show_tags = data.show_tags.split(",").map(function (tag) { - return tag.trim(); - }).filter(function (tag) { - return tag !== "" && tag.indexOf(",") === -1; - }).sort(); - - basicModal.close(); - - if (visible.album()) { - album.json.show_tags = new_show_tags; - view.album.show_tags(); - } - - api.post("Album::setShowTags", { - albumID: albumID, - show_tags: new_show_tags - }, function () { - return album.reload(); - }); - }; - - var setShowTagDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initShowTagAlbumDialog = function initShowTagAlbumDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["ALBUM_NEW_SHOWTAGS"]; - formElements.show_tags.placeholder = "Tags"; - formElements.show_tags.value = album.json.show_tags.sort().join(", "); - }; - - basicModal.show({ - body: setShowTagDialogBody, - readyCB: initShowTagAlbumDialog, - buttons: { - action: { - title: lychee.locale["ALBUM_SET_SHOWTAGS"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @param {{show_tags: string}} data */ + var action = function action(data) { + if (!data.show_tags.trim()) { + basicModal.focusError("show_tags"); + return; + } + var new_show_tags = data.show_tags.split(",").map(function (tag) { + return tag.trim(); + }).filter(function (tag) { + return tag !== "" && tag.indexOf(",") === -1; + }).sort(); + basicModal.close(); + if (visible.album()) { + album.json.show_tags = new_show_tags; + view.album.show_tags(); + } + api.post("Album::setShowTags", { + albumID: albumID, + show_tags: new_show_tags + }, function () { + return album.reload(); + }); + }; + var setShowTagDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initShowTagAlbumDialog = function initShowTagAlbumDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["ALBUM_NEW_SHOWTAGS"]; + formElements.show_tags.placeholder = "Tags"; + formElements.show_tags.value = album.json.show_tags.sort().join(", "); + }; + basicModal.show({ + body: setShowTagDialogBody, + readyCB: initShowTagAlbumDialog, + buttons: { + action: { + title: lychee.locale["ALBUM_SET_SHOWTAGS"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -1915,93 +1800,84 @@ album.setShowTags = function (albumID) { * @returns {boolean} */ album.setTitle = function (albumIDs) { - var oldTitle = ""; - - if (albumIDs.length === 1) { - // Get old title if only one album is selected - if (album.json) { - if (album.getID() === albumIDs[0]) { - oldTitle = album.json.title; - } else oldTitle = album.getSubByID(albumIDs[0]).title; - } - if (!oldTitle) { - var a = albums.getByID(albumIDs[0]); - if (a) oldTitle = a.title; - } - } - - /** @param {{title: string}} data */ - var action = function action(data) { - if (!data.title.trim()) { - basicModal.focusError("title"); - return; - } - - basicModal.close(); - - var newTitle = data.title; - - if (visible.album()) { - if (albumIDs.length === 1 && album.getID() === albumIDs[0]) { - // Rename only one album - - album.json.title = newTitle; - view.album.title(); - - var _a = albums.getByID(albumIDs[0]); - if (_a) _a.title = newTitle; - } else { - albumIDs.forEach(function (id) { - album.getSubByID(id).title = newTitle; - view.album.content.titleSub(id); - - var a = albums.getByID(id); - if (a) a.title = newTitle; - }); - } - } else if (visible.albums()) { - // Rename all albums - - albumIDs.forEach(function (id) { - var a = albums.getByID(id); - if (a) a.title = newTitle; - view.albums.content.title(id); - }); - } - - api.post("Album::setTitle", { - albumIDs: albumIDs, - title: newTitle - }); - }; - - var setAlbumTitleDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetAlbumTitleDialog = function initSetAlbumTitleDialog(formElements, dialog) { - dialog.querySelector("p").textContent = albumIDs.length === 1 ? lychee.locale["ALBUM_NEW_TITLE"] : sprintf(lychee.locale["ALBUMS_NEW_TITLE"], albumIDs.length); - formElements.title.placeholder = lychee.locale["ALBUM_TITLE"]; - formElements.title.value = oldTitle; - }; - - basicModal.show({ - body: setAlbumTitleDialogBody, - readyCB: initSetAlbumTitleDialog, - buttons: { - action: { - title: lychee.locale["ALBUM_SET_TITLE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + var oldTitle = ""; + if (albumIDs.length === 1) { + // Get old title if only one album is selected + if (album.json) { + if (album.getID() === albumIDs[0]) { + oldTitle = album.json.title; + } else oldTitle = album.getSubByID(albumIDs[0]).title; + } + if (!oldTitle) { + var a = albums.getByID(albumIDs[0]); + if (a) oldTitle = a.title; + } + } + + /** @param {{title: string}} data */ + var action = function action(data) { + if (!data.title.trim()) { + basicModal.focusError("title"); + return; + } + basicModal.close(); + var newTitle = data.title; + if (visible.album()) { + if (albumIDs.length === 1 && album.getID() === albumIDs[0]) { + // Rename only one album + + album.json.title = newTitle; + view.album.title(); + var _a = albums.getByID(albumIDs[0]); + if (_a) _a.title = newTitle; + } else { + albumIDs.forEach(function (id) { + album.getSubByID(id).title = newTitle; + view.album.content.titleSub(id); + var a = albums.getByID(id); + if (a) a.title = newTitle; + }); + } + } else if (visible.albums()) { + // Rename all albums + + albumIDs.forEach(function (id) { + var a = albums.getByID(id); + if (a) a.title = newTitle; + view.albums.content.title(id); + }); + } + api.post("Album::setTitle", { + albumIDs: albumIDs, + title: newTitle + }); + }; + var setAlbumTitleDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetAlbumTitleDialog = function initSetAlbumTitleDialog(formElements, dialog) { + dialog.querySelector("p").textContent = albumIDs.length === 1 ? lychee.locale["ALBUM_NEW_TITLE"] : sprintf(lychee.locale["ALBUMS_NEW_TITLE"], albumIDs.length); + formElements.title.placeholder = lychee.locale["ALBUM_TITLE"]; + formElements.title.value = oldTitle; + }; + basicModal.show({ + body: setAlbumTitleDialogBody, + readyCB: initSetAlbumTitleDialog, + buttons: { + action: { + title: lychee.locale["ALBUM_SET_TITLE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -2009,50 +1885,45 @@ album.setTitle = function (albumIDs) { * @returns {void} */ album.setDescription = function (albumID) { - /** @param {{description: string}} data */ - var action = function action(data) { - var description = data.description ? data.description : null; - - basicModal.close(); - - if (visible.album()) { - album.json.description = description; - view.album.description(); - } - - api.post("Album::setDescription", { - albumID: albumID, - description: description - }); - }; - - var setAlbumDescriptionDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetAlbumDescriptionDialog = function initSetAlbumDescriptionDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["ALBUM_NEW_DESCRIPTION"]; - formElements.description.placeholder = lychee.locale["ALBUM_DESCRIPTION"]; - formElements.description.value = album.json.description ? album.json.description : ""; - }; - - basicModal.show({ - body: setAlbumDescriptionDialogBody, - readyCB: initSetAlbumDescriptionDialog, - buttons: { - action: { - title: lychee.locale["ALBUM_SET_DESCRIPTION"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @param {{description: string}} data */ + var action = function action(data) { + var description = data.description ? data.description : null; + basicModal.close(); + if (visible.album()) { + album.json.description = description; + view.album.description(); + } + api.post("Album::setDescription", { + albumID: albumID, + description: description + }); + }; + var setAlbumDescriptionDialogBody = "\n\t\t

\n\t\t\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetAlbumDescriptionDialog = function initSetAlbumDescriptionDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["ALBUM_NEW_DESCRIPTION"]; + formElements.description.placeholder = lychee.locale["ALBUM_DESCRIPTION"]; + formElements.description.value = album.json.description ? album.json.description : ""; + }; + basicModal.show({ + body: setAlbumDescriptionDialogBody, + readyCB: initSetAlbumDescriptionDialog, + buttons: { + action: { + title: lychee.locale["ALBUM_SET_DESCRIPTION"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -2060,19 +1931,17 @@ album.setDescription = function (albumID) { * @returns {void} */ album.toggleCover = function (photoID) { - album.json.cover_id = album.json.cover_id === photoID ? null : photoID; - - var params = { - albumID: album.json.id, - photoID: album.json.cover_id - }; - - api.post("Album::setCover", params, function () { - view.album.content.cover(photoID); - if (!album.getParentID()) { - albums.refresh(); - } - }); + album.json.cover_id = album.json.cover_id === photoID ? null : photoID; + var params = { + albumID: album.json.id, + photoID: album.json.cover_id + }; + api.post("Album::setCover", params, function () { + view.album.content.cover(photoID); + if (!album.getParentID()) { + albums.refresh(); + } + }); }; /** @@ -2080,50 +1949,47 @@ album.toggleCover = function (photoID) { * @returns {void} */ album.setLicense = function (albumID) { - /** @param {{license: string}} data */ - var action = function action(data) { - basicModal.close(); - - api.post("Album::setLicense", { - albumID: albumID, - license: data.license - }, function () { - if (visible.album()) { - album.json.license = data.license; - view.album.license(); - } - }); - }; - - var setAlbumLicenseDialogBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t

\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetAlbumLicenseDialog = function initSetAlbumLicenseDialog(formElements, dialog) { - formElements.license.labels[0].textContent = lychee.locale["ALBUM_LICENSE"]; - formElements.license.item(0).textContent = lychee.locale["ALBUM_LICENSE_NONE"]; - formElements.license.item(1).textContent = lychee.locale["ALBUM_RESERVED"]; - formElements.license.value = album.json.license === "" ? "none" : album.json.license; - dialog.querySelector("p a").textContent = lychee.locale["ALBUM_LICENSE_HELP"]; - }; - - basicModal.show({ - body: setAlbumLicenseDialogBody, - readyCB: initSetAlbumLicenseDialog, - buttons: { - action: { - title: lychee.locale["ALBUM_SET_LICENSE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @param {{license: string}} data */ + var action = function action(data) { + basicModal.close(); + api.post("Album::setLicense", { + albumID: albumID, + license: data.license + }, function () { + if (visible.album()) { + album.json.license = data.license; + view.album.license(); + } + }); + }; + var setAlbumLicenseDialogBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t

\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetAlbumLicenseDialog = function initSetAlbumLicenseDialog(formElements, dialog) { + formElements.license.labels[0].textContent = lychee.locale["ALBUM_LICENSE"]; + formElements.license.item(0).textContent = lychee.locale["ALBUM_LICENSE_NONE"]; + formElements.license.item(1).textContent = lychee.locale["ALBUM_RESERVED"]; + formElements.license.value = album.json.license === "" ? "none" : album.json.license; + dialog.querySelector("p a").textContent = lychee.locale["ALBUM_LICENSE_HELP"]; + }; + basicModal.show({ + body: setAlbumLicenseDialogBody, + readyCB: initSetAlbumLicenseDialog, + buttons: { + action: { + title: lychee.locale["ALBUM_SET_LICENSE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -2131,65 +1997,60 @@ album.setLicense = function (albumID) { * @returns {void} */ album.setSorting = function (albumID) { - /** @param {{sorting_col: string, sorting_order: string}} data */ - var action = function action(data) { - basicModal.close(); - - api.post("Album::setSorting", { - albumID: albumID, - sorting_column: data.sorting_col, - sorting_order: data.sorting_order - }, function () { - if (visible.album()) { - album.reload(); - } - }); - }; - - var setAlbumSortingDialogBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetAlbumSortingDialog = function initSetAlbumSortingDialog(formElements, dialog) { - formElements.sorting_col.parentElement.previousElementSibling.textContent = lychee.locale["SORT_DIALOG_ATTRIBUTE_LABEL"]; - formElements.sorting_col.item(1).textContent = lychee.locale["SORT_PHOTO_SELECT_1"]; - formElements.sorting_col.item(2).textContent = lychee.locale["SORT_PHOTO_SELECT_2"]; - formElements.sorting_col.item(3).textContent = lychee.locale["SORT_PHOTO_SELECT_3"]; - formElements.sorting_col.item(4).textContent = lychee.locale["SORT_PHOTO_SELECT_4"]; - formElements.sorting_col.item(5).textContent = lychee.locale["SORT_PHOTO_SELECT_5"]; - formElements.sorting_col.item(6).textContent = lychee.locale["SORT_PHOTO_SELECT_6"]; - formElements.sorting_col.item(7).textContent = lychee.locale["SORT_PHOTO_SELECT_7"]; - - formElements.sorting_order.parentElement.previousElementSibling.textContent = lychee.locale["SORT_DIALOG_ORDER_LABEL"]; - formElements.sorting_order.item(1).textContent = lychee.locale["SORT_ASCENDING"]; - formElements.sorting_order.item(2).textContent = lychee.locale["SORT_DESCENDING"]; - - if (album.json.sorting) { - formElements.sorting_col.value = album.json.sorting.column; - formElements.sorting_order.value = album.json.sorting.order; - } else { - formElements.sorting_col.value = ""; - formElements.sorting_order.value = ""; - } - }; - - basicModal.show({ - body: setAlbumSortingDialogBody, - readyCB: initSetAlbumSortingDialog, - buttons: { - action: { - title: lychee.locale["ALBUM_SET_ORDER"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @param {{sorting_col: string, sorting_order: string}} data */ + var action = function action(data) { + basicModal.close(); + api.post("Album::setSorting", { + albumID: albumID, + sorting_column: data.sorting_col, + sorting_order: data.sorting_order + }, function () { + if (visible.album()) { + album.reload(); + } + }); + }; + var setAlbumSortingDialogBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetAlbumSortingDialog = function initSetAlbumSortingDialog(formElements, dialog) { + formElements.sorting_col.parentElement.previousElementSibling.textContent = lychee.locale["SORT_DIALOG_ATTRIBUTE_LABEL"]; + formElements.sorting_col.item(1).textContent = lychee.locale["SORT_PHOTO_SELECT_1"]; + formElements.sorting_col.item(2).textContent = lychee.locale["SORT_PHOTO_SELECT_2"]; + formElements.sorting_col.item(3).textContent = lychee.locale["SORT_PHOTO_SELECT_3"]; + formElements.sorting_col.item(4).textContent = lychee.locale["SORT_PHOTO_SELECT_4"]; + formElements.sorting_col.item(5).textContent = lychee.locale["SORT_PHOTO_SELECT_5"]; + formElements.sorting_col.item(6).textContent = lychee.locale["SORT_PHOTO_SELECT_6"]; + formElements.sorting_col.item(7).textContent = lychee.locale["SORT_PHOTO_SELECT_7"]; + formElements.sorting_order.parentElement.previousElementSibling.textContent = lychee.locale["SORT_DIALOG_ORDER_LABEL"]; + formElements.sorting_order.item(1).textContent = lychee.locale["SORT_ASCENDING"]; + formElements.sorting_order.item(2).textContent = lychee.locale["SORT_DESCENDING"]; + if (album.json.sorting) { + formElements.sorting_col.value = album.json.sorting.column; + formElements.sorting_order.value = album.json.sorting.order; + } else { + formElements.sorting_col.value = ""; + formElements.sorting_order.value = ""; + } + }; + basicModal.show({ + body: setAlbumSortingDialogBody, + readyCB: initSetAlbumSortingDialog, + buttons: { + action: { + title: lychee.locale["ALBUM_SET_ORDER"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -2199,152 +2060,143 @@ album.setSorting = function (albumID) { * @returns {void} */ album.setProtectionPolicy = function (albumID) { - /** - * @param {ModalDialogResult} data - */ - var action = function action(data) { - basicModal.close(); - albums.refresh(); - - album.json.policy.is_nsfw = data.is_nsfw; - album.json.policy.is_public = data.is_public; - album.json.policy.grants_full_photo_access = data.grants_full_photo_access; - album.json.policy.is_link_required = data.is_link_required; - album.json.policy.grants_download = data.grants_download; - album.json.policy.is_password_required = data.is_password_required; - - // Set data and refresh view - if (visible.album()) { - view.album.nsfw(); - view.album.public(); - view.album.requiresLink(); - view.album.downloadable(); - view.album.password(); - } - - var params = { - albumID: albumID, - grants_full_photo_access: album.json.policy.grants_full_photo_access, - is_public: album.json.policy.is_public, - is_nsfw: album.json.policy.is_nsfw, - is_link_required: album.json.policy.is_link_required, - grants_download: album.json.policy.grants_download - }; - if (album.json.policy.is_password_required) { - if (data.password) { - // We send the password only if there's been a change; that way the - // server will keep the current password if it wasn't changed. - params.password = data.password; - } - } else { - params.password = null; - } - - api.post("Album::setProtectionPolicy", params); - }; - - var setAlbumProtectionPolicyBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\n\t\t
\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; - - /** - * @typedef ProtectionPolicyDialogFormElements - * @property {HTMLInputElement} is_public - * @property {HTMLInputElement} grants_full_photo_access - * @property {HTMLInputElement} is_link_required - * @property {HTMLInputElement} grants_download - * @property {HTMLInputElement} is_password_required - * @property {HTMLInputElement} password - * @property {HTMLInputElement} is_nsfw - */ - - /** - * @param {ProtectionPolicyDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initAlbumProtectionPolicyDialog = function initAlbumProtectionPolicyDialog(formElements, dialog) { - formElements.is_public.previousElementSibling.textContent = lychee.locale["ALBUM_PUBLIC"]; - formElements.is_public.nextElementSibling.textContent = lychee.locale["ALBUM_PUBLIC_EXPL"]; - formElements.grants_full_photo_access.previousElementSibling.textContent = lychee.locale["ALBUM_FULL"]; - formElements.grants_full_photo_access.nextElementSibling.textContent = lychee.locale["ALBUM_FULL_EXPL"]; - formElements.is_link_required.previousElementSibling.textContent = lychee.locale["ALBUM_HIDDEN"]; - formElements.is_link_required.nextElementSibling.textContent = lychee.locale["ALBUM_HIDDEN_EXPL"]; - formElements.grants_download.previousElementSibling.textContent = lychee.locale["ALBUM_DOWNLOADABLE"]; - formElements.grants_download.nextElementSibling.textContent = lychee.locale["ALBUM_DOWNLOADABLE_EXPL"]; - formElements.is_password_required.previousElementSibling.textContent = lychee.locale["ALBUM_PASSWORD_PROT"]; - formElements.is_password_required.nextElementSibling.textContent = lychee.locale["ALBUM_PASSWORD_PROT_EXPL"]; - formElements.password.placeholder = lychee.locale["PASSWORD"]; - formElements.is_nsfw.previousElementSibling.textContent = lychee.locale["ALBUM_NSFW"]; - formElements.is_nsfw.nextElementSibling.textContent = lychee.locale["ALBUM_NSFW_EXPL"]; - - formElements.is_public.checked = album.json.is_public; - formElements.is_nsfw.checked = album.json.is_nsfw; - - /** - * Array of checkboxes which are enable/disabled wrt. the state of `is_public` - * @type {HTMLInputElement[]} - */ - var tristateCheckboxes = [formElements.grants_full_photo_access, formElements.is_link_required, formElements.grants_download, formElements.is_password_required]; - - formElements.is_public.checked = album.json.policy.is_public; - if (album.json.policy.is_public) { - tristateCheckboxes.forEach(function (checkbox) { - checkbox.parentElement.classList.remove("disabled"); - checkbox.disabled = false; - }); - // Initialize options based on album settings. - formElements.grants_full_photo_access.checked = album.json.policy.grants_full_photo_access; - formElements.is_link_required.checked = album.json.policy.is_link_required; - formElements.grants_download.checked = album.json.policy.grants_download; - formElements.is_password_required.checked = album.json.policy.is_password_required; - if (album.json.policy.is_password_required) { - formElements.password.parentElement.classList.remove("hidden"); - } else { - formElements.password.parentElement.classList.add("hidden"); - } - } else { - tristateCheckboxes.forEach(function (checkbox) { - checkbox.parentElement.classList.add("disabled"); - checkbox.disabled = true; - }); - // Initialize options based on global settings. - formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; - formElements.is_link_required.checked = false; - formElements.grants_download.checked = lychee.grants_download; - formElements.is_password_required.checked = false; - formElements.password.parentElement.classList.add("hidden"); - } - - formElements.is_public.addEventListener("change", function () { - tristateCheckboxes.forEach(function (checkbox) { - checkbox.parentElement.classList.toggle("disabled"); - checkbox.disabled = !formElements.is_public.checked; - }); - }); - - formElements.is_password_required.addEventListener("change", function () { - if (formElements.is_password_required.checked) { - formElements.password.parentElement.classList.remove("hidden"); - formElements.password.focus(); - } else { - formElements.password.parentElement.classList.add("hidden"); - } - }); - }; - - basicModal.show({ - body: setAlbumProtectionPolicyBody, - readyCB: initAlbumProtectionPolicyDialog, - buttons: { - action: { - title: lychee.locale["SAVE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {ModalDialogResult} data + */ + var action = function action(data) { + basicModal.close(); + albums.refresh(); + album.json.policy.is_nsfw = data.is_nsfw; + album.json.policy.is_public = data.is_public; + album.json.policy.grants_full_photo_access = data.grants_full_photo_access; + album.json.policy.is_link_required = data.is_link_required; + album.json.policy.grants_download = data.grants_download; + album.json.policy.is_password_required = data.is_password_required; + + // Set data and refresh view + if (visible.album()) { + view.album.nsfw(); + view.album["public"](); + view.album.requiresLink(); + view.album.downloadable(); + view.album.password(); + } + var params = { + albumID: albumID, + grants_full_photo_access: album.json.policy.grants_full_photo_access, + is_public: album.json.policy.is_public, + is_nsfw: album.json.policy.is_nsfw, + is_link_required: album.json.policy.is_link_required, + grants_download: album.json.policy.grants_download + }; + if (album.json.policy.is_password_required) { + if (data.password) { + // We send the password only if there's been a change; that way the + // server will keep the current password if it wasn't changed. + params.password = data.password; + } + } else { + params.password = null; + } + api.post("Album::setProtectionPolicy", params); + }; + var setAlbumProtectionPolicyBody = "\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\n\t\t
\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; + + /** + * @typedef ProtectionPolicyDialogFormElements + * @property {HTMLInputElement} is_public + * @property {HTMLInputElement} grants_full_photo_access + * @property {HTMLInputElement} is_link_required + * @property {HTMLInputElement} grants_download + * @property {HTMLInputElement} is_password_required + * @property {HTMLInputElement} password + * @property {HTMLInputElement} is_nsfw + */ + + /** + * @param {ProtectionPolicyDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initAlbumProtectionPolicyDialog = function initAlbumProtectionPolicyDialog(formElements, dialog) { + formElements.is_public.previousElementSibling.textContent = lychee.locale["ALBUM_PUBLIC"]; + formElements.is_public.nextElementSibling.textContent = lychee.locale["ALBUM_PUBLIC_EXPL"]; + formElements.grants_full_photo_access.previousElementSibling.textContent = lychee.locale["ALBUM_FULL"]; + formElements.grants_full_photo_access.nextElementSibling.textContent = lychee.locale["ALBUM_FULL_EXPL"]; + formElements.is_link_required.previousElementSibling.textContent = lychee.locale["ALBUM_HIDDEN"]; + formElements.is_link_required.nextElementSibling.textContent = lychee.locale["ALBUM_HIDDEN_EXPL"]; + formElements.grants_download.previousElementSibling.textContent = lychee.locale["ALBUM_DOWNLOADABLE"]; + formElements.grants_download.nextElementSibling.textContent = lychee.locale["ALBUM_DOWNLOADABLE_EXPL"]; + formElements.is_password_required.previousElementSibling.textContent = lychee.locale["ALBUM_PASSWORD_PROT"]; + formElements.is_password_required.nextElementSibling.textContent = lychee.locale["ALBUM_PASSWORD_PROT_EXPL"]; + formElements.password.placeholder = lychee.locale["PASSWORD"]; + formElements.is_nsfw.previousElementSibling.textContent = lychee.locale["ALBUM_NSFW"]; + formElements.is_nsfw.nextElementSibling.textContent = lychee.locale["ALBUM_NSFW_EXPL"]; + formElements.is_public.checked = album.json.is_public; + formElements.is_nsfw.checked = album.json.is_nsfw; + + /** + * Array of checkboxes which are enable/disabled wrt. the state of `is_public` + * @type {HTMLInputElement[]} + */ + var tristateCheckboxes = [formElements.grants_full_photo_access, formElements.is_link_required, formElements.grants_download, formElements.is_password_required]; + formElements.is_public.checked = album.json.policy.is_public; + if (album.json.policy.is_public) { + tristateCheckboxes.forEach(function (checkbox) { + checkbox.parentElement.classList.remove("disabled"); + checkbox.disabled = false; + }); + // Initialize options based on album settings. + formElements.grants_full_photo_access.checked = album.json.policy.grants_full_photo_access; + formElements.is_link_required.checked = album.json.policy.is_link_required; + formElements.grants_download.checked = album.json.policy.grants_download; + formElements.is_password_required.checked = album.json.policy.is_password_required; + if (album.json.policy.is_password_required) { + formElements.password.parentElement.classList.remove("hidden"); + } else { + formElements.password.parentElement.classList.add("hidden"); + } + } else { + tristateCheckboxes.forEach(function (checkbox) { + checkbox.parentElement.classList.add("disabled"); + checkbox.disabled = true; + }); + // Initialize options based on global settings. + formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; + formElements.is_link_required.checked = false; + formElements.grants_download.checked = lychee.grants_download; + formElements.is_password_required.checked = false; + formElements.password.parentElement.classList.add("hidden"); + } + formElements.is_public.addEventListener("change", function () { + tristateCheckboxes.forEach(function (checkbox) { + checkbox.parentElement.classList.toggle("disabled"); + checkbox.disabled = !formElements.is_public.checked; + }); + }); + formElements.is_password_required.addEventListener("change", function () { + if (formElements.is_password_required.checked) { + formElements.password.parentElement.classList.remove("hidden"); + formElements.password.focus(); + } else { + formElements.password.parentElement.classList.add("hidden"); + } + }); + }; + basicModal.show({ + body: setAlbumProtectionPolicyBody, + readyCB: initAlbumProtectionPolicyDialog, + buttons: { + action: { + title: lychee.locale["SAVE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -2354,113 +2206,106 @@ album.setProtectionPolicy = function (albumID) { * @returns {void} */ album.shareUsers = function (albumID) { - /** - * @param {ModalDialogResult} data - */ - var action = function action(data) { - basicModal.close(); - - /** @type {number[]} */ - var selectedUserIds = Object.entries(data).filter(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - userId = _ref2[0], - isChecked = _ref2[1]; - - return isChecked; - }).map(function (_ref3) { - var _ref4 = _slicedToArray(_ref3, 2), - userId = _ref4[0], - isChecked = _ref4[1]; - - return parseInt(userId, 10); - }); - - api.post("Sharing::setByAlbum", { - albumID: albumID, - userIDs: selectedUserIds - }); - }; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSharingDialog = function initSharingDialog(formElements, dialog) { - /** @type {HTMLParagraphElement} */ - var p = dialog.querySelector("p"); - p.textContent = lychee.locale["WAIT_FETCH_DATA"]; - - /** @param {SharingInfo} data */ - var successCallback = function successCallback(data) { - if (data.users.length === 0) { - p.textContent = lychee.locale["SHARING_ALBUM_USERS_NO_USERS"]; - return; - } - - p.textContent = lychee.locale["SHARING_ALBUM_USERS_LONG_MESSAGE"]; - - /** @type {HTMLFormElement} */ - var form = document.createElement("form"); - - var existingShares = new Set(data.shared.map(function (value) { - return value.user_id; - })); - - // Create a list with one checkbox per user - data.users.forEach(function (user) { - var div = form.appendChild(document.createElement("div")); - div.classList.add("input-group", "compact-inverse"); - var label = div.appendChild(document.createElement("label")); - label.htmlFor = "share_dialog_user_" + user.id; - label.textContent = user.username; - var input = div.appendChild(document.createElement("input")); - input.type = "checkbox"; - input.id = label.htmlFor; - input.name = user.id.toString(); - input.checked = existingShares.has(user.id); - }); - - // Append the pre-constructed form to the dialog after the paragraph - dialog.appendChild(form); - basicModal.cacheFormElements(); - }; - - api.post("Sharing::list", { albumID: albumID }, successCallback); - }; - - basicModal.show({ - body: "

", - readyCB: initSharingDialog, - buttons: { - action: { - title: lychee.locale["SAVE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); -}; + /** + * @param {ModalDialogResult} data + */ + var action = function action(data) { + basicModal.close(); + + /** @type {number[]} */ + var selectedUserIds = Object.entries(data).filter(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + userId = _ref2[0], + isChecked = _ref2[1]; + return isChecked; + }).map(function (_ref3) { + var _ref4 = _slicedToArray(_ref3, 2), + userId = _ref4[0], + isChecked = _ref4[1]; + return parseInt(userId, 10); + }); + api.post("Sharing::setByAlbum", { + albumID: albumID, + userIDs: selectedUserIds + }); + }; -/** - * Toggles the NSFW attribute of the currently loaded album. + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSharingDialog = function initSharingDialog(formElements, dialog) { + /** @type {HTMLParagraphElement} */ + var p = dialog.querySelector("p"); + p.textContent = lychee.locale["WAIT_FETCH_DATA"]; + + /** @param {SharingInfo} data */ + var successCallback = function successCallback(data) { + if (data.users.length === 0) { + p.textContent = lychee.locale["SHARING_ALBUM_USERS_NO_USERS"]; + return; + } + p.textContent = lychee.locale["SHARING_ALBUM_USERS_LONG_MESSAGE"]; + + /** @type {HTMLFormElement} */ + var form = document.createElement("form"); + var existingShares = new Set(data.shared.map(function (value) { + return value.user_id; + })); + + // Create a list with one checkbox per user + data.users.forEach(function (user) { + var div = form.appendChild(document.createElement("div")); + div.classList.add("input-group", "compact-inverse"); + var label = div.appendChild(document.createElement("label")); + label.htmlFor = "share_dialog_user_" + user.id; + label.textContent = user.username; + var input = div.appendChild(document.createElement("input")); + input.type = "checkbox"; + input.id = label.htmlFor; + input.name = user.id.toString(); + input.checked = existingShares.has(user.id); + }); + + // Append the pre-constructed form to the dialog after the paragraph + dialog.appendChild(form); + basicModal.cacheFormElements(); + }; + api.post("Sharing::list", { + albumID: albumID + }, successCallback); + }; + basicModal.show({ + body: "

", + readyCB: initSharingDialog, + buttons: { + action: { + title: lychee.locale["SAVE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); +}; + +/** + * Toggles the NSFW attribute of the currently loaded album. * * @returns {void} */ album.toggleNSFW = function () { - album.json.policy.is_nsfw = !album.json.policy.is_nsfw; - - view.album.nsfw(); - - api.post("Album::setNSFW", { - albumID: album.json.id, - is_nsfw: album.json.policy.is_nsfw - }, function () { - return albums.refresh(); - }); + album.json.policy.is_nsfw = !album.json.policy.is_nsfw; + view.album.nsfw(); + api.post("Album::setNSFW", { + albumID: album.json.id, + is_nsfw: album.json.policy.is_nsfw + }, function () { + return albums.refresh(); + }); }; /** @@ -2468,75 +2313,71 @@ album.toggleNSFW = function () { * @returns {void} */ album.share = function (service) { - if (!lychee.share_button_visible) { - return; - } - - var url = location.href; - - switch (service) { - case "twitter": - window.open("https://twitter.com/share?url=" + encodeURI(url)); - break; - case "facebook": - window.open("https://www.facebook.com/sharer.php?u=" + encodeURI(url) + "&t=" + encodeURI(album.json.title)); - break; - case "mail": - location.href = "mailto:?subject=" + encodeURI(album.json.title) + "&body=" + encodeURI(url); - break; - } + if (!lychee.share_button_visible) { + return; + } + var url = location.href; + switch (service) { + case "twitter": + window.open("https://twitter.com/share?url=".concat(encodeURI(url))); + break; + case "facebook": + window.open("https://www.facebook.com/sharer.php?u=".concat(encodeURI(url), "&t=").concat(encodeURI(album.json.title))); + break; + case "mail": + location.href = "mailto:?subject=".concat(encodeURI(album.json.title), "&body=").concat(encodeURI(url)); + break; + } }; /** * @returns {void} */ album.qrCode = function () { - if (!lychee.share_button_visible) { - return; - } - - // We need this indirection based on a resize observer, because the ready - // callback of the dialog is invoked _before_ the dialog is made visible - // in order to allow the ready callback to make initializations of - // form elements without causing flicker. - // However, for invisible elements `.clientWidth` returns zero, hence - // we cannot paint the QR code onto the canvas before it becomes visible. - var qrCodeCanvasObserver = function () { - var width = 0; - return new ResizeObserver(function (entries, observer) { - var qrCodeCanvas = entries[0].target; - // Avoid infinite resize events due to clearing and repainting - // the same QR code on the canvas. - if (width === qrCodeCanvas.clientWidth) { - return; - } - width = qrCodeCanvas.clientWidth; - - QrCreator.render({ - text: location.href, - radius: 0.0, - ecLevel: "H", - fill: "#000000", - background: "#FFFFFF", - size: width - }, qrCodeCanvas); - }); - }(); - - basicModal.show({ - body: "", - classList: ["qr-code"], - readyCB: function readyCB(formElements, dialog) { - var qrCodeCanvas = dialog.querySelector("canvas"); - qrCodeCanvasObserver.observe(qrCodeCanvas); - }, - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); + if (!lychee.share_button_visible) { + return; + } + + // We need this indirection based on a resize observer, because the ready + // callback of the dialog is invoked _before_ the dialog is made visible + // in order to allow the ready callback to make initializations of + // form elements without causing flicker. + // However, for invisible elements `.clientWidth` returns zero, hence + // we cannot paint the QR code onto the canvas before it becomes visible. + var qrCodeCanvasObserver = function () { + var width = 0; + return new ResizeObserver(function (entries, observer) { + var qrCodeCanvas = entries[0].target; + // Avoid infinite resize events due to clearing and repainting + // the same QR code on the canvas. + if (width === qrCodeCanvas.clientWidth) { + return; + } + width = qrCodeCanvas.clientWidth; + QrCreator.render({ + text: location.href, + radius: 0.0, + ecLevel: "H", + fill: "#000000", + background: "#FFFFFF", + size: width + }, qrCodeCanvas); + }); + }(); + basicModal.show({ + body: "", + classList: ["qr-code"], + readyCB: function readyCB(formElements, dialog) { + var qrCodeCanvas = dialog.querySelector("canvas"); + qrCodeCanvasObserver.observe(qrCodeCanvas); + }, + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); }; /** @@ -2544,7 +2385,7 @@ album.qrCode = function () { * @returns {void} */ album.getArchive = function (albumIDs) { - location.href = "api/Album::getArchive?albumIDs=" + albumIDs.join(); + location.href = "api/Album::getArchive?albumIDs=" + albumIDs.join(); }; /** @@ -2555,117 +2396,110 @@ album.getArchive = function (albumIDs) { * @returns {string} the message */ album.buildMessage = function (albumIDs, albumID, op1, ops) { - var targetTitle = lychee.locale["UNTITLED"]; - var sourceTitle = lychee.locale["UNTITLED"]; - var msg = ""; - - // Get title of target album - if (albumID === null) { - targetTitle = lychee.locale["ROOT"]; - } else { - var targetAlbum = albums.getByID(albumID) || album.getSubByID(albumID); - if (targetAlbum) { - targetTitle = targetAlbum.title; - } - } - - if (albumIDs.length === 1) { - // Get title of the unique source album - var sourceAlbum = albums.getByID(albumIDs[0]) || album.getSubByID(albumIDs[0]); - if (sourceAlbum) { - sourceTitle = sourceAlbum.title; - } - - msg = sprintf(lychee.locale[op1], sourceTitle, targetTitle); - } else { - msg = sprintf(lychee.locale[ops], targetTitle); - } - - return msg; + var targetTitle = lychee.locale["UNTITLED"]; + var sourceTitle = lychee.locale["UNTITLED"]; + var msg = ""; + + // Get title of target album + if (albumID === null) { + targetTitle = lychee.locale["ROOT"]; + } else { + var targetAlbum = albums.getByID(albumID) || album.getSubByID(albumID); + if (targetAlbum) { + targetTitle = targetAlbum.title; + } + } + if (albumIDs.length === 1) { + // Get title of the unique source album + var sourceAlbum = albums.getByID(albumIDs[0]) || album.getSubByID(albumIDs[0]); + if (sourceAlbum) { + sourceTitle = sourceAlbum.title; + } + msg = sprintf(lychee.locale[op1], sourceTitle, targetTitle); + } else { + msg = sprintf(lychee.locale[ops], targetTitle); + } + return msg; }; /** * @param {string[]} albumIDs * @returns {void} */ -album.delete = function (albumIDs) { - var isTagAlbum = albumIDs.length === 1 && albums.isTagAlbum(albumIDs[0]); - - var handleSuccessfulDeletion = function handleSuccessfulDeletion() { - if (visible.albums()) { - albumIDs.forEach(function (id) { - view.albums.content.delete(id); - albums.deleteByID(id); - }); - } else if (visible.album()) { - albums.refresh(); - if (albumIDs.length === 1 && album.getID() === albumIDs[0]) { - lychee.goto(album.getParentID()); - } else { - albumIDs.forEach(function (id) { - album.deleteSubByID(id); - view.album.content.deleteSub(id); - }); - } - } - }; - - var action = function action() { - basicModal.close(); - api.post("Album::delete", { albumIDs: albumIDs }, handleSuccessfulDeletion); - }; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - */ - var initConfirmDeletionDialog = function initConfirmDeletionDialog(formElements, dialog) { - /** @type {HTMLParagraphElement} */ - var p = dialog.querySelector("p"); - if (albumIDs.length === 1 && albumIDs[0] === SmartAlbumID.UNSORTED) { - p.textContent = lychee.locale["DELETE_UNSORTED_CONFIRM"]; - } else if (albumIDs.length === 1) { - var albumTitle = ""; - - // Get title - if (album.json) { - if (album.getID() === albumIDs[0]) { - albumTitle = album.json.title; - } else albumTitle = album.getSubByID(albumIDs[0]).title; - } - if (!albumTitle) { - var a = albums.getByID(albumIDs[0]); - if (a) albumTitle = a.title; - } - - // Fallback for album without a title - if (!albumTitle) albumTitle = lychee.locale["UNTITLED"]; - - p.textContent = isTagAlbum ? sprintf(lychee.locale["DELETE_TAG_ALBUM_CONFIRMATION"], albumTitle) : sprintf(lychee.locale["DELETE_ALBUM_CONFIRMATION"], albumTitle); - } else { - p.textContent = sprintf(lychee.locale["DELETE_ALBUMS_CONFIRMATION"], albumIDs.length); - } - }; - - var actionButtonLabel = albumIDs.length === 1 ? albumIDs[0] === SmartAlbumID.UNSORTED ? lychee.locale["CLEAR_UNSORTED"] : isTagAlbum ? lychee.locale["DELETE_TAG_ALBUM_QUESTION"] : lychee.locale["DELETE_ALBUM_QUESTION"] : lychee.locale["DELETE_ALBUMS_QUESTION"]; - - var cancelButtonLabel = albumIDs.length === 1 ? albumIDs[0] === SmartAlbumID.UNSORTED ? lychee.locale["KEEP_UNSORTED"] : lychee.locale["KEEP_ALBUM"] : lychee.locale["KEEP_ALBUMS"]; - - basicModal.show({ - body: "

", - readyCB: initConfirmDeletionDialog, - buttons: { - action: { - title: actionButtonLabel, - fn: action, - classList: ["red"] - }, - cancel: { - title: cancelButtonLabel, - fn: basicModal.close - } - } - }); +album["delete"] = function (albumIDs) { + var isTagAlbum = albumIDs.length === 1 && albums.isTagAlbum(albumIDs[0]); + var handleSuccessfulDeletion = function handleSuccessfulDeletion() { + if (visible.albums()) { + albumIDs.forEach(function (id) { + view.albums.content["delete"](id); + albums.deleteByID(id); + }); + } else if (visible.album()) { + albums.refresh(); + if (albumIDs.length === 1 && album.getID() === albumIDs[0]) { + lychee["goto"](album.getParentID()); + } else { + albumIDs.forEach(function (id) { + album.deleteSubByID(id); + view.album.content.deleteSub(id); + }); + } + } + }; + var action = function action() { + basicModal.close(); + api.post("Album::delete", { + albumIDs: albumIDs + }, handleSuccessfulDeletion); + }; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + */ + var initConfirmDeletionDialog = function initConfirmDeletionDialog(formElements, dialog) { + /** @type {HTMLParagraphElement} */ + var p = dialog.querySelector("p"); + if (albumIDs.length === 1 && albumIDs[0] === SmartAlbumID.UNSORTED) { + p.textContent = lychee.locale["DELETE_UNSORTED_CONFIRM"]; + } else if (albumIDs.length === 1) { + var albumTitle = ""; + + // Get title + if (album.json) { + if (album.getID() === albumIDs[0]) { + albumTitle = album.json.title; + } else albumTitle = album.getSubByID(albumIDs[0]).title; + } + if (!albumTitle) { + var a = albums.getByID(albumIDs[0]); + if (a) albumTitle = a.title; + } + + // Fallback for album without a title + if (!albumTitle) albumTitle = lychee.locale["UNTITLED"]; + p.textContent = isTagAlbum ? sprintf(lychee.locale["DELETE_TAG_ALBUM_CONFIRMATION"], albumTitle) : sprintf(lychee.locale["DELETE_ALBUM_CONFIRMATION"], albumTitle); + } else { + p.textContent = sprintf(lychee.locale["DELETE_ALBUMS_CONFIRMATION"], albumIDs.length); + } + }; + var actionButtonLabel = albumIDs.length === 1 ? albumIDs[0] === SmartAlbumID.UNSORTED ? lychee.locale["CLEAR_UNSORTED"] : isTagAlbum ? lychee.locale["DELETE_TAG_ALBUM_QUESTION"] : lychee.locale["DELETE_ALBUM_QUESTION"] : lychee.locale["DELETE_ALBUMS_QUESTION"]; + var cancelButtonLabel = albumIDs.length === 1 ? albumIDs[0] === SmartAlbumID.UNSORTED ? lychee.locale["KEEP_UNSORTED"] : lychee.locale["KEEP_ALBUM"] : lychee.locale["KEEP_ALBUMS"]; + basicModal.show({ + body: "

", + readyCB: initConfirmDeletionDialog, + buttons: { + action: { + title: actionButtonLabel, + fn: action, + classList: ["red"] + }, + cancel: { + title: cancelButtonLabel, + fn: basicModal.close + } + } + }); }; /** @@ -2674,40 +2508,37 @@ album.delete = function (albumIDs) { * @param {boolean} confirm */ album.merge = function (albumIDs, albumID) { - var confirm = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - - var action = function action() { - basicModal.close(); - - api.post("Album::merge", { - albumID: albumID, - albumIDs: albumIDs - }, function () { - return album.reload(); - }); - }; - - if (confirm) { - basicModal.show({ - body: "

", - readyCB: function readyCB(formElements, dialog) { - return dialog.querySelector("p").textContent = album.buildMessage(albumIDs, albumID, "ALBUM_MERGE", "ALBUMS_MERGE"); - }, - buttons: { - action: { - title: lychee.locale["MERGE_ALBUM"], - fn: action, - classList: ["red"] - }, - cancel: { - title: lychee.locale["DONT_MERGE"], - fn: basicModal.close - } - } - }); - } else { - action(); - } + var confirm = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + var action = function action() { + basicModal.close(); + api.post("Album::merge", { + albumID: albumID, + albumIDs: albumIDs + }, function () { + return album.reload(); + }); + }; + if (confirm) { + basicModal.show({ + body: "

", + readyCB: function readyCB(formElements, dialog) { + return dialog.querySelector("p").textContent = album.buildMessage(albumIDs, albumID, "ALBUM_MERGE", "ALBUMS_MERGE"); + }, + buttons: { + action: { + title: lychee.locale["MERGE_ALBUM"], + fn: action, + classList: ["red"] + }, + cancel: { + title: lychee.locale["DONT_MERGE"], + fn: basicModal.close + } + } + }); + } else { + action(); + } }; /** @@ -2716,51 +2547,48 @@ album.merge = function (albumIDs, albumID) { * @param {boolean} confirm show confirmation dialog? */ album.setAlbum = function (albumIDs, albumID) { - var confirm = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - - var action = function action() { - basicModal.close(); - - api.post("Album::move", { - albumID: albumID, - albumIDs: albumIDs - }, function () { - return album.reload(); - }); - }; - - if (confirm) { - basicModal.show({ - body: "

", - readyCB: function readyCB(formElements, dialog) { - return dialog.querySelector("p").textContent = album.buildMessage(albumIDs, albumID, "ALBUM_MOVE", "ALBUMS_MOVE"); - }, - buttons: { - action: { - title: lychee.locale["MOVE_ALBUMS"], - fn: action, - classList: ["red"] - }, - cancel: { - title: lychee.locale["NOT_MOVE_ALBUMS"], - fn: basicModal.close - } - } - }); - } else { - action(); - } + var confirm = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + var action = function action() { + basicModal.close(); + api.post("Album::move", { + albumID: albumID, + albumIDs: albumIDs + }, function () { + return album.reload(); + }); + }; + if (confirm) { + basicModal.show({ + body: "

", + readyCB: function readyCB(formElements, dialog) { + return dialog.querySelector("p").textContent = album.buildMessage(albumIDs, albumID, "ALBUM_MOVE", "ALBUMS_MOVE"); + }, + buttons: { + action: { + title: lychee.locale["MOVE_ALBUMS"], + fn: action, + classList: ["red"] + }, + cancel: { + title: lychee.locale["NOT_MOVE_ALBUMS"], + fn: basicModal.close + } + } + }); + } else { + action(); + } }; /** * @returns {void} */ album.apply_nsfw_filter = function () { - if (lychee.nsfw_visible) { - $('.album[data-nsfw="1"]').show(); - } else { - $('.album[data-nsfw="1"]').hide(); - } + if (lychee.nsfw_visible) { + $('.album[data-nsfw="1"]').show(); + } else { + $('.album[data-nsfw="1"]').hide(); + } }; /** @@ -2792,84 +2620,78 @@ album.apply_nsfw_filter = function () { * @returns {boolean} */ album.isUploadable = function () { - if (album.json !== null && album.json.rights.can_upload) { - return true; - } - - if (album.json === null && lychee.rights.root_album.can_upload) { - return true; - } - - return false; + if (album.json !== null && album.json.rights.can_upload) { + return true; + } + if (album.json === null && lychee.rights.root_album.can_upload) { + return true; + } + return false; }; /** * @param {Photo} data */ album.updatePhoto = function (data) { - /** - * @param {?SizeVariant} src - * @returns {?SizeVariant} - */ - var deepCopySizeVariant = function deepCopySizeVariant(src) { - if (src === undefined || src === null) return null; - return { - type: src.type, - url: src.url, - width: src.width, - height: src.height, - filesize: src.filesize - }; - }; - - if (album.json && album.json.photos) { - var _photo2 = album.json.photos.find(function (p) { - return p.id === data.id; - }); - - // Deep copy size variants - _photo2.size_variants = { - thumb: deepCopySizeVariant(data.size_variants.thumb), - thumb2x: deepCopySizeVariant(data.size_variants.thumb2x), - small: deepCopySizeVariant(data.size_variants.small), - small2x: deepCopySizeVariant(data.size_variants.small2x), - medium: deepCopySizeVariant(data.size_variants.medium), - medium2x: deepCopySizeVariant(data.size_variants.medium2x), - original: deepCopySizeVariant(data.size_variants.original) - }; - view.album.content.updatePhoto(_photo2); - albums.refresh(); - } + /** + * @param {?SizeVariant} src + * @returns {?SizeVariant} + */ + var deepCopySizeVariant = function deepCopySizeVariant(src) { + if (src === undefined || src === null) return null; + return { + type: src.type, + url: src.url, + width: src.width, + height: src.height, + filesize: src.filesize + }; + }; + if (album.json && album.json.photos) { + var _photo2 = album.json.photos.find(function (p) { + return p.id === data.id; + }); + + // Deep copy size variants + _photo2.size_variants = { + thumb: deepCopySizeVariant(data.size_variants.thumb), + thumb2x: deepCopySizeVariant(data.size_variants.thumb2x), + small: deepCopySizeVariant(data.size_variants.small), + small2x: deepCopySizeVariant(data.size_variants.small2x), + medium: deepCopySizeVariant(data.size_variants.medium), + medium2x: deepCopySizeVariant(data.size_variants.medium2x), + original: deepCopySizeVariant(data.size_variants.original) + }; + view.album.content.updatePhoto(_photo2); + albums.refresh(); + } }; /** * @returns {void} */ album.reload = function () { - var albumID = album.getID(); - - album.refresh(); - albums.refresh(); - - if (visible.album()) lychee.goto(albumID);else lychee.goto(); + var albumID = album.getID(); + album.refresh(); + albums.refresh(); + if (visible.album()) lychee["goto"](albumID);else lychee["goto"](); }; /** * @returns {void} */ album.refresh = function () { - album.json = null; + album.json = null; }; /** * @returns {void} */ album.deleteTrack = function () { - album.json.track_url = null; - - api.post("Album::deleteTrack", { - albumID: album.json.id - }); + album.json.track_url = null; + api.post("Album::deleteTrack", { + albumID: album.json.id + }); }; /** @@ -2877,93 +2699,86 @@ album.deleteTrack = function () { */ var albums = { - /** @type {?Albums} */ - json: null + /** @type {?Albums} */ + json: null }; /** * @returns {void} */ albums.load = function () { - var showRootAlbum = function showRootAlbum() { - // DO NOT change the order of `header.setMode` and `view.albums.init`. - // The latter relies on the header being set correctly. - // - // `view.albums.init` builds the HTML of the albums view (note the - // plural-s). - // Internally, this exploits code for regular albums which in - // turn calls `album.isUploadabe` (note the missing plural-s) to - // check whether the current album supports drag-&-drop. - // In order to return the correct value `album.isUploadabe` resorts - // to a hack: if no (regular) album is loaded `album.isUploadabe` - // normally returns `false` except the root album is visible. - // In that case `album.isUploadabe` returns a "fake" `true`. - // However, in order to do so `album.isUploadabe` needs to check - // whether the root album is visible which is determined by the - // visibility of the corresponding header. - // That is why the header needs to be set first. - // - // However, the actual bug is to call `album.isUploadable` for the - // root view. - // TODO: Fix the bug described above. - header.setMode("albums"); - view.albums.init(); - lychee.animate(lychee.content, "contentZoomIn"); - - tabindex.makeFocusable(lychee.content); - - if (lychee.active_focus_on_page_load) { - // Put focus on first element - either album or photo - var first_album = $(".album:first"); - if (first_album.length !== 0) { - first_album.focus(); - } else { - var first_photo = $(".photo:first"); - if (first_photo.length !== 0) { - first_photo.focus(); - } - } - } - - setTimeout(function () { - lychee.footer_show(); - }, 300); - - // If no user is authenticated and there is nothing to see in the - // root album, we automatically show the login dialog - if (lychee.publicMode === true && lychee.viewMode === false && albums.isEmpty()) { - lychee.loginDialog(); - } - }; - - var startTime = new Date().getTime(); - - lychee.animate(lychee.content, "contentZoomOut"); - - /** - * @param {Albums} data - */ - var successCallback = function successCallback(data) { - albums.json = data; - - // Skip delay when opening a blank Lychee - var skipDelay = !visible.albums() && !visible.photo() && !visible.album() || visible.album() && lychee.content.html() === ""; - // Calculate delay - var durationTime = new Date().getTime() - startTime; - var waitTime = durationTime > 300 || skipDelay ? 0 : 300 - durationTime; - - setTimeout(function () { - showRootAlbum(); - }, waitTime); - }; - - if (albums.json === null) { - api.post("Albums::get", {}, successCallback); - } else { - setTimeout(function () { - showRootAlbum(); - }, 300); - } + var showRootAlbum = function showRootAlbum() { + // DO NOT change the order of `header.setMode` and `view.albums.init`. + // The latter relies on the header being set correctly. + // + // `view.albums.init` builds the HTML of the albums view (note the + // plural-s). + // Internally, this exploits code for regular albums which in + // turn calls `album.isUploadabe` (note the missing plural-s) to + // check whether the current album supports drag-&-drop. + // In order to return the correct value `album.isUploadabe` resorts + // to a hack: if no (regular) album is loaded `album.isUploadabe` + // normally returns `false` except the root album is visible. + // In that case `album.isUploadabe` returns a "fake" `true`. + // However, in order to do so `album.isUploadabe` needs to check + // whether the root album is visible which is determined by the + // visibility of the corresponding header. + // That is why the header needs to be set first. + // + // However, the actual bug is to call `album.isUploadable` for the + // root view. + // TODO: Fix the bug described above. + header.setMode("albums"); + view.albums.init(); + lychee.animate(lychee.content, "contentZoomIn"); + tabindex.makeFocusable(lychee.content); + if (lychee.active_focus_on_page_load) { + // Put focus on first element - either album or photo + var first_album = $(".album:first"); + if (first_album.length !== 0) { + first_album.focus(); + } else { + var first_photo = $(".photo:first"); + if (first_photo.length !== 0) { + first_photo.focus(); + } + } + } + setTimeout(function () { + lychee.footer_show(); + }, 300); + + // If no user is authenticated and there is nothing to see in the + // root album, we automatically show the login dialog + if (lychee.publicMode === true && lychee.viewMode === false && albums.isEmpty()) { + lychee.loginDialog(); + } + }; + var startTime = new Date().getTime(); + lychee.animate(lychee.content, "contentZoomOut"); + + /** + * @param {Albums} data + */ + var successCallback = function successCallback(data) { + albums.json = data; + + // Skip delay when opening a blank Lychee + var skipDelay = !visible.albums() && !visible.photo() && !visible.album() || visible.album() && lychee.content.html() === ""; + // Calculate delay + var durationTime = new Date().getTime() - startTime; + var waitTime = durationTime > 300 || skipDelay ? 0 : 300 - durationTime; + setTimeout(function () { + showRootAlbum(); + }, waitTime); + }; + if (albums.json === null) { + api.post("Albums::get", {}, successCallback); + } else { + setTimeout(function () { + showRootAlbum(); + }, 300); + } }; /** @@ -2971,14 +2786,14 @@ albums.load = function () { * @returns {void} */ albums.parse = function (album) { - if (!album.thumb) { - album.thumb = { - id: "", - thumb: album.policy.is_password_required ? "img/password.svg" : "img/no_images.svg", - type: "image/svg+xml", - thumb2x: null - }; - } + if (!album.thumb) { + album.thumb = { + id: "", + thumb: album.policy.is_password_required ? "img/password.svg" : "img/no_images.svg", + type: "image/svg+xml", + thumb2x: null + }; + } }; /** @@ -2986,29 +2801,27 @@ albums.parse = function (album) { * @returns {boolean} */ albums.isShared = function (albumID) { - if (albumID == null) return false; - if (!albums.json) return false; - if (!albums.json.albums) return false; - - var found = false; - - /** - * @this {Album} - * @returns {boolean} - */ - var func = function func() { - if (this.id === albumID) { - found = true; - return false; // stop the loop - } - if (this.albums) { - $.each(this.albums, func); - } - }; - - if (albums.json.shared_albums !== null) $.each(albums.json.shared_albums, func); + if (albumID == null) return false; + if (!albums.json) return false; + if (!albums.json.albums) return false; + var found = false; + + /** + * @this {Album} + * @returns {boolean} + */ + var func = function func() { + if (this.id === albumID) { + found = true; + return false; // stop the loop + } - return found; + if (this.albums) { + $.each(this.albums, func); + } + }; + if (albums.json.shared_albums !== null) $.each(albums.json.shared_albums, func); + return found; }; /** @@ -3016,36 +2829,31 @@ albums.isShared = function (albumID) { * @returns {(null|Album|TagAlbum|SmartAlbum)} */ albums.getByID = function (albumID) { - if (albumID == null) return null; - if (!albums.json) return null; - if (!albums.json.albums) return null; - - if (albums.json.smart_albums.hasOwnProperty(albumID)) { - return albums.json.smart_albums[albumID]; - } - - var result = albums.json.tag_albums.find(function (tagAlbum) { - return tagAlbum.id === albumID; - }); - if (result) { - return result; - } - - result = albums.json.albums.find(function (album) { - return album.id === albumID; - }); - if (result) { - return result; - } - - result = albums.json.shared_albums.find(function (album) { - return album.id === albumID; - }); - if (result) { - return result; - } - - return null; + if (albumID == null) return null; + if (!albums.json) return null; + if (!albums.json.albums) return null; + if (albums.json.smart_albums.hasOwnProperty(albumID)) { + return albums.json.smart_albums[albumID]; + } + var result = albums.json.tag_albums.find(function (tagAlbum) { + return tagAlbum.id === albumID; + }); + if (result) { + return result; + } + result = albums.json.albums.find(function (album) { + return album.id === albumID; + }); + if (result) { + return result; + } + result = albums.json.shared_albums.find(function (album) { + return album.id === albumID; + }); + if (result) { + return result; + } + return null; }; /** @@ -3058,35 +2866,30 @@ albums.getByID = function (albumID) { * @returns {void} */ albums.deleteByID = function (albumID) { - if (albumID == null) return; - if (!albums.json) return; - if (!albums.json.albums) return; - - var idx = albums.json.albums.findIndex(function (a) { - return a.id === albumID; - }); - albums.json.albums.splice(idx, 1); - - if (idx !== -1) return; - - idx = albums.json.shared_albums.findIndex(function (a) { - return a.id === albumID; - }); - albums.json.shared_albums.splice(idx, 1); - - if (idx !== -1) return; - - idx = albums.json.tag_albums.findIndex(function (a) { - return a.id === albumID; - }); - albums.json.tag_albums.splice(idx, 1); + if (albumID == null) return; + if (!albums.json) return; + if (!albums.json.albums) return; + var idx = albums.json.albums.findIndex(function (a) { + return a.id === albumID; + }); + albums.json.albums.splice(idx, 1); + if (idx !== -1) return; + idx = albums.json.shared_albums.findIndex(function (a) { + return a.id === albumID; + }); + albums.json.shared_albums.splice(idx, 1); + if (idx !== -1) return; + idx = albums.json.tag_albums.findIndex(function (a) { + return a.id === albumID; + }); + albums.json.tag_albums.splice(idx, 1); }; /** * @returns {void} */ albums.refresh = function () { - albums.json = null; + albums.json = null; }; /** @@ -3094,9 +2897,9 @@ albums.refresh = function () { * @returns {boolean} */ albums.isTagAlbum = function (albumID) { - return albums.json && albums.json.tag_albums.find(function (tagAlbum) { - return tagAlbum.id === albumID; - }); + return albums.json && albums.json.tag_albums.find(function (tagAlbum) { + return tagAlbum.id === albumID; + }); }; /** @@ -3106,7 +2909,7 @@ albums.isTagAlbum = function (albumID) { * @returns {boolean} */ albums.isEmpty = function () { - return albums.json === null || albums.isSmartAlbumEmpty(albums.json.smart_albums.public) && albums.isSmartAlbumEmpty(albums.json.smart_albums.recent) && albums.isSmartAlbumEmpty(albums.json.smart_albums.starred) && albums.isSmartAlbumEmpty(albums.json.smart_albums.unsorted) && albums.isSmartAlbumEmpty(albums.json.smart_albums.on_this_day) && albums.json.albums.length === 0 && albums.json.shared_albums.length === 0 && albums.json.tag_albums.length === 0; + return albums.json === null || albums.isSmartAlbumEmpty(albums.json.smart_albums["public"]) && albums.isSmartAlbumEmpty(albums.json.smart_albums.recent) && albums.isSmartAlbumEmpty(albums.json.smart_albums.starred) && albums.isSmartAlbumEmpty(albums.json.smart_albums.unsorted) && albums.isSmartAlbumEmpty(albums.json.smart_albums.on_this_day) && albums.json.albums.length === 0 && albums.json.shared_albums.length === 0 && albums.json.tag_albums.length === 0; }; /** @@ -3114,7 +2917,7 @@ albums.isEmpty = function () { * @returns {boolean} */ albums.isSmartAlbumEmpty = function (smartAlbum) { - return !smartAlbum || !smartAlbum.photos || smartAlbum.photos.length === 0; + return !smartAlbum || !smartAlbum.photos || smartAlbum.photos.length === 0; }; //noinspection HtmlUnknownTarget @@ -3132,9 +2935,8 @@ var build = {}; * @returns {string} */ build.iconic = function (icon) { - var classes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; - - return lychee.html(_templateObject, classes, icon); + var classes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; + return lychee.html(_templateObject || (_templateObject = _taggedTemplateLiteral([""])), classes, icon); }; /** @@ -3142,7 +2944,7 @@ build.iconic = function (icon) { * @returns {string} */ build.divider = function (title) { - return lychee.html(_templateObject2, title); + return lychee.html(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["

$", "

"])), title); }; /** @@ -3150,7 +2952,7 @@ build.divider = function (title) { * @returns {string} */ build.editIcon = function (id) { - return lychee.html(_templateObject3, id, build.iconic("pencil")); + return lychee.html(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["
", "
"])), id, build.iconic("pencil")); }; /** @@ -3159,7 +2961,7 @@ build.editIcon = function (id) { * @returns {string} */ build.multiselect = function (top, left) { - return lychee.html(_templateObject4, top, left); + return lychee.html(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["
"])), top, left); }; /** @@ -3170,19 +2972,17 @@ build.multiselect = function (top, left) { * @returns {string} */ build.getAlbumThumb = function (data) { - var isVideo = data.thumb.type && data.thumb.type.indexOf("video") > -1; - var isRaw = data.thumb.type && data.thumb.type.indexOf("raw") > -1; - var thumb = data.thumb.thumb; - var thumb2x = data.thumb.thumb2x; - - if (thumb === "uploads/thumb/" && isVideo) { - return "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - } - if (thumb === "uploads/thumb/" && isRaw) { - return "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - } - - return "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; + var isVideo = data.thumb.type && data.thumb.type.indexOf("video") > -1; + var isRaw = data.thumb.type && data.thumb.type.indexOf("raw") > -1; + var thumb = data.thumb.thumb; + var thumb2x = data.thumb.thumb2x; + if (thumb === "uploads/thumb/" && isVideo) { + return "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + } + if (thumb === "uploads/thumb/" && isRaw) { + return "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + } + return "").concat(lychee.locale["PHOTO_THUMBNAIL"], ""); }; /** @@ -3192,93 +2992,87 @@ build.getAlbumThumb = function (data) { * @returns {string} HTML for the album */ build.album = function (data) { - var disabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - var formattedCreationTs = lychee.locale.printMonthYear(data.created_at); - var formattedMinTs = lychee.locale.printMonthYear(data.min_taken_at); - var formattedMaxTs = lychee.locale.printMonthYear(data.max_taken_at); - // The condition below is faulty wrt. to two issues: - // - // a) The condition only checks whether the owning/current album is - // uploadable (aka "editable"), but it does not check whether the - // album at hand whose icon is built is editable. - // But this is of similar importance. - // Currently, we only check whether the album at hand is a smart - // album or tag album which are always considered non-editable. - // But this is only half of the story. - // For example, a regular album might still be non-editable, if the - // current user is not the owner of that album. - // b) This method is not only called if the owning/current album is a - // proper album, but also for the root view. - // However, `album.isUploadable` should not be called for the root - // view. - // - // Moreover, we have to distinguish between "drag" and "drop". - // Doing so would also solve the problems above: - // - // - "Drag": If the current child album at hand can be dragged (away) - // is mostly determined by the user's rights on the parent album. - // Instead of (erroneously) using `album.isUploadable()` for that - // (even for the root view), the "right to drag" should be passed to - // this method as a parameter very much like `disabled` such that this - // method can be used for both regular albums and the root view. - // - "Drop": If something (e.g. a photo) can be dropped onto the child - // album at hand is independent of the user's rights on the containing - // album. - // Whether the child album supports the drop event depends on the type - // of the album (i.e. it must not be a smart or tag album), but also - // on the ownership of the album. - var disableDragDrop = !data.rights.can_edit || disabled || album.isSmartID(data.id) || data.is_tag_album; - var subtitle = formattedCreationTs; - - // check setting album_subtitle_type: - // takedate: date range (min/max_takedate from EXIF; if missing defaults to creation) - // creation: creation date of album - // description: album description - // default: any other type defaults to old style setting subtitles based of album sorting - switch (lychee.album_subtitle_type) { - case "description": - subtitle = data.description ? lychee.escapeHTML(data.description) : ""; - break; - case "takedate": - if (formattedMinTs !== "" || formattedMaxTs !== "") { - // either min_taken_at or max_taken_at is set - subtitle = formattedMinTs === formattedMaxTs ? formattedMaxTs : formattedMinTs + " - " + formattedMaxTs; - subtitle = lychee.html(_templateObject5, lychee.locale["CAMERA_DATE"], build.iconic("camera-slr"), subtitle); - break; - } - // fall through - case "creation": - break; - case "oldstyle": - default: - if (lychee.sorting_albums && data.min_taken_at && data.max_taken_at) { - if (lychee.sorting_albums.column === "max_taken_at" || lychee.sorting_albums.column === "min_taken_at") { - if (formattedMinTs !== "" && formattedMaxTs !== "") { - subtitle = formattedMinTs === formattedMaxTs ? formattedMaxTs : formattedMinTs + " - " + formattedMaxTs; - } else if (formattedMinTs !== "" && lychee.sorting_albums.column === "min_taken_at") { - subtitle = formattedMinTs; - } else if (formattedMaxTs !== "" && lychee.sorting_albums.column === "max_taken_at") { - subtitle = formattedMaxTs; - } - } - } - } - - var html = lychee.html(_templateObject6, disabled ? "disabled" : "", data.policy.is_nsfw && lychee.nsfw_blur ? "blurred" : "", data.id, data.policy.is_nsfw ? "1" : "0", tabindex.get_next_tab_index(), disableDragDrop ? "false" : "true", disableDragDrop ? "" : "ondragstart='lychee.startDrag(event)'\n\t\t\t\tondragover='lychee.overDrag(event)'\n\t\t\t\tondragleave='lychee.leaveDrag(event)'\n\t\t\t\tondragend='lychee.endDrag(event)'\n\t\t\t\tondrop='lychee.finishDrag(event)'", build.getAlbumThumb(data), build.getAlbumThumb(data), build.getAlbumThumb(data), data.title, data.title, subtitle); - - if (data.rights.can_edit && !disabled) { - var isCover = album.json && album.json.cover_id && data.thumb.id === album.json.cover_id; - html += lychee.html(_templateObject7, data.policy && data.policy.is_nsfw ? "badge--nsfw" : "", build.iconic("warning"), data.id === SmartAlbumID.STARRED ? "badge--star" : "", build.iconic("star"), data.id === SmartAlbumID.RECENT ? "badge--visible badge--list" : "", build.iconic("clock"), data.id === SmartAlbumID.ON_THIS_DAY ? "badge--tag badge--list" : "", build.iconic("calendar"), data.id === SmartAlbumID.PUBLIC || data.policy && data.policy.is_public ? "badge--visible" : "", data.policy && data.policy.is_link_required ? "badge--hidden" : "badge--not--hidden", build.iconic("eye"), data.id === SmartAlbumID.UNSORTED ? "badge--visible" : "", build.iconic("list"), data.policy && data.policy.is_password_required ? "badge--visible" : "", build.iconic("lock-unlocked"), data.is_tag_album ? "badge--tag" : "", build.iconic("tag"), isCover ? "badge--cover" : "", build.iconic("folder-cover")); - } - - if (data.albums && data.albums.length > 0 || data.has_albums) { - html += lychee.html(_templateObject8, build.iconic("layers")); - } - - html += ""; - - return html; + var disabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var formattedCreationTs = lychee.locale.printMonthYear(data.created_at); + var formattedMinTs = lychee.locale.printMonthYear(data.min_taken_at); + var formattedMaxTs = lychee.locale.printMonthYear(data.max_taken_at); + // The condition below is faulty wrt. to two issues: + // + // a) The condition only checks whether the owning/current album is + // uploadable (aka "editable"), but it does not check whether the + // album at hand whose icon is built is editable. + // But this is of similar importance. + // Currently, we only check whether the album at hand is a smart + // album or tag album which are always considered non-editable. + // But this is only half of the story. + // For example, a regular album might still be non-editable, if the + // current user is not the owner of that album. + // b) This method is not only called if the owning/current album is a + // proper album, but also for the root view. + // However, `album.isUploadable` should not be called for the root + // view. + // + // Moreover, we have to distinguish between "drag" and "drop". + // Doing so would also solve the problems above: + // + // - "Drag": If the current child album at hand can be dragged (away) + // is mostly determined by the user's rights on the parent album. + // Instead of (erroneously) using `album.isUploadable()` for that + // (even for the root view), the "right to drag" should be passed to + // this method as a parameter very much like `disabled` such that this + // method can be used for both regular albums and the root view. + // - "Drop": If something (e.g. a photo) can be dropped onto the child + // album at hand is independent of the user's rights on the containing + // album. + // Whether the child album supports the drop event depends on the type + // of the album (i.e. it must not be a smart or tag album), but also + // on the ownership of the album. + var disableDragDrop = !data.rights.can_edit || disabled || album.isSmartID(data.id) || data.is_tag_album; + var subtitle = formattedCreationTs; + + // check setting album_subtitle_type: + // takedate: date range (min/max_takedate from EXIF; if missing defaults to creation) + // creation: creation date of album + // description: album description + // default: any other type defaults to old style setting subtitles based of album sorting + switch (lychee.album_subtitle_type) { + case "description": + subtitle = data.description ? lychee.escapeHTML(data.description) : ""; + break; + case "takedate": + if (formattedMinTs !== "" || formattedMaxTs !== "") { + // either min_taken_at or max_taken_at is set + subtitle = formattedMinTs === formattedMaxTs ? formattedMaxTs : formattedMinTs + " - " + formattedMaxTs; + subtitle = lychee.html(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(["", "$", ""])), lychee.locale["CAMERA_DATE"], build.iconic("camera-slr"), subtitle); + break; + } + // fall through + case "creation": + break; + case "oldstyle": + default: + if (lychee.sorting_albums && data.min_taken_at && data.max_taken_at) { + if (lychee.sorting_albums.column === "max_taken_at" || lychee.sorting_albums.column === "min_taken_at") { + if (formattedMinTs !== "" && formattedMaxTs !== "") { + subtitle = formattedMinTs === formattedMaxTs ? formattedMaxTs : formattedMinTs + " - " + formattedMaxTs; + } else if (formattedMinTs !== "" && lychee.sorting_albums.column === "min_taken_at") { + subtitle = formattedMinTs; + } else if (formattedMaxTs !== "" && lychee.sorting_albums.column === "max_taken_at") { + subtitle = formattedMaxTs; + } + } + } + } + var html = lychee.html(_templateObject6 || (_templateObject6 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t"])), disabled ? "disabled" : "", data.policy.is_nsfw && lychee.nsfw_blur ? "blurred" : "", data.id, data.policy.is_nsfw ? "1" : "0", tabindex.get_next_tab_index(), disableDragDrop ? "false" : "true", disableDragDrop ? "" : "ondragstart='lychee.startDrag(event)'\n\t\t\t\tondragover='lychee.overDrag(event)'\n\t\t\t\tondragleave='lychee.leaveDrag(event)'\n\t\t\t\tondragend='lychee.endDrag(event)'\n\t\t\t\tondrop='lychee.finishDrag(event)'", build.getAlbumThumb(data), build.getAlbumThumb(data), build.getAlbumThumb(data), data.title, data.title, subtitle); + if (data.rights.can_edit && !disabled) { + var isCover = album.json && album.json.cover_id && data.thumb.id === album.json.cover_id; + html += lychee.html(_templateObject7 || (_templateObject7 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"])), data.policy && data.policy.is_nsfw ? "badge--nsfw" : "", build.iconic("warning"), data.id === SmartAlbumID.STARRED ? "badge--star" : "", build.iconic("star"), data.id === SmartAlbumID.RECENT ? "badge--visible badge--list" : "", build.iconic("clock"), data.id === SmartAlbumID.ON_THIS_DAY ? "badge--tag badge--list" : "", build.iconic("calendar"), data.id === SmartAlbumID.PUBLIC || data.policy && data.policy.is_public ? "badge--visible" : "", data.policy && data.policy.is_link_required ? "badge--hidden" : "badge--not--hidden", build.iconic("eye"), data.id === SmartAlbumID.UNSORTED ? "badge--visible" : "", build.iconic("list"), data.policy && data.policy.is_password_required ? "badge--visible" : "", build.iconic("lock-unlocked"), data.is_tag_album ? "badge--tag" : "", build.iconic("tag"), isCover ? "badge--cover" : "", build.iconic("folder-cover")); + } + if (data.albums && data.albums.length > 0 || data.has_albums) { + html += lychee.html(_templateObject8 || (_templateObject8 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t\t", "\n\t\t\t\t
"])), build.iconic("layers")); + } + html += "
"; + return html; }; /** @@ -3288,101 +3082,86 @@ build.album = function (data) { * @returns {string} HTML for the photo */ build.photo = function (data) { - var disabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - var html = ""; - var thumbnail = ""; - var thumb2x = ""; - // Note, album.json might not be loaded, if - // a) the photo is a single public photo in a private album - // b) the photo is part of a search result - var isCover = album.json && album.json.cover_id === data.id; - - var isVideo = data.type && data.type.indexOf("video") > -1; - var isRaw = data.type && data.type.indexOf("raw") > -1; - var isLivePhoto = data.live_photo_url !== "" && data.live_photo_url !== null; - - if (data.size_variants.thumb === null) { - if (isLivePhoto) { - thumbnail = "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - } - if (isVideo) { - thumbnail = "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - } else if (isRaw) { - thumbnail = "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - } - } else if (lychee.layout === 0) { - if (data.size_variants.thumb2x !== null) { - thumb2x = data.size_variants.thumb2x.url; - } - - if (thumb2x !== "") { - thumb2x = "data-srcset='" + thumb2x + " 2x'"; - } - - thumbnail = ""; - thumbnail += "" + lychee.locale["PHOTO_THUMBNAIL"] + ""); - thumbnail += ""; - } else { - if (data.size_variants.small !== null) { - if (data.size_variants.small2x !== null) { - thumb2x = "data-srcset='" + data.size_variants.small.url + " " + data.size_variants.small.width + "w, " + data.size_variants.small2x.url + " " + data.size_variants.small2x.width + "w'"; - } - - thumbnail = ""; - thumbnail += "" + lychee.locale["PHOTO_THUMBNAIL"] + ""); - thumbnail += ""; - } else if (data.size_variants.medium !== null) { - if (data.size_variants.medium2x !== null) { - thumb2x = "data-srcset='" + data.size_variants.medium.url + " " + data.size_variants.medium.width + "w, " + data.size_variants.medium2x.url + " " + data.size_variants.medium2x.width + "w'"; - } - - thumbnail = ""; - thumbnail += "" + lychee.locale["PHOTO_THUMBNAIL"] + ""); - thumbnail += ""; - } else if (!isVideo) { - // Fallback for images with no small or medium. - thumbnail = ""; - thumbnail += "" + lychee.locale["PHOTO_THUMBNAIL"] + ""; - thumbnail += ""; - } else { - // Fallback for videos with no small (the case of no thumb is - // handled at the top of this function). - - if (data.size_variants.thumb2x !== null) { - thumb2x = data.size_variants.thumb2x.url; - } - - if (thumb2x !== "") { - thumb2x = "data-srcset='" + data.size_variants.thumb.url + " " + data.size_variants.thumb.width + "w, " + thumb2x + " " + data.size_variants.thumb2x.width + "w'"; - } - - thumbnail = ""; - thumbnail += "" + lychee.locale["PHOTO_THUMBNAIL"] + ""); - thumbnail += ""; - } - } - - html += lychee.html(_templateObject9, disabled ? "disabled" : "", data.album_id, data.id, tabindex.get_next_tab_index(), !album.isUploadable() || disabled ? "false" : "true", thumbnail, data.title, data.title); - - if (data.taken_at !== null) html += lychee.html(_templateObject10, lychee.locale["CAMERA_DATE"], build.iconic("camera-slr"), lychee.locale.printDateTime(data.taken_at));else html += lychee.html(_templateObject11, lychee.locale.printDateTime(data.created_at)); - - html += ""; - - if (album.isUploadable()) { - // Note, `album.json` might be null, if the photo is displayed as - // part of a search result and therefore the actual parent album - // is not loaded. (The "parent" album is the virtual "search album" - // in this case). - // This also means that the displayed variant of the public badge of - // a photo depends on the availability of the parent album. - // This seems to be an undesired but unavoidable side effect. - html += lychee.html(_templateObject12, data.is_starred ? "badge--star" : "", build.iconic("star"), data.is_public && album.json && album.json.policy && !album.json.policy.is_public ? "badge--visible badge--hidden" : "", build.iconic("eye"), isCover ? "badge--cover" : "", build.iconic("folder-cover")); - } - - html += ""; - - return html; + var disabled = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var html = ""; + var thumbnail = ""; + var thumb2x = ""; + // Note, album.json might not be loaded, if + // a) the photo is a single public photo in a private album + // b) the photo is part of a search result + var isCover = album.json && album.json.cover_id === data.id; + var isVideo = data.type && data.type.indexOf("video") > -1; + var isRaw = data.type && data.type.indexOf("raw") > -1; + var isLivePhoto = data.live_photo_url !== "" && data.live_photo_url !== null; + if (data.size_variants.thumb === null) { + if (isLivePhoto) { + thumbnail = "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + } + if (isVideo) { + thumbnail = "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + } else if (isRaw) { + thumbnail = "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + } + } else if (lychee.layout === 0) { + if (data.size_variants.thumb2x !== null) { + thumb2x = data.size_variants.thumb2x.url; + } + if (thumb2x !== "") { + thumb2x = "data-srcset='".concat(thumb2x, " 2x'"); + } + thumbnail = ""); + thumbnail += "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + thumbnail += ""; + } else { + if (data.size_variants.small !== null) { + if (data.size_variants.small2x !== null) { + thumb2x = "data-srcset='".concat(data.size_variants.small.url, " ").concat(data.size_variants.small.width, "w, ").concat(data.size_variants.small2x.url, " ").concat(data.size_variants.small2x.width, "w'"); + } + thumbnail = ""); + thumbnail += "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + thumbnail += ""; + } else if (data.size_variants.medium !== null) { + if (data.size_variants.medium2x !== null) { + thumb2x = "data-srcset='".concat(data.size_variants.medium.url, " ").concat(data.size_variants.medium.width, "w, ").concat(data.size_variants.medium2x.url, " ").concat(data.size_variants.medium2x.width, "w'"); + } + thumbnail = ""); + thumbnail += "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + thumbnail += ""; + } else if (!isVideo) { + // Fallback for images with no small or medium. + thumbnail = ""); + thumbnail += "").concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + thumbnail += ""; + } else { + // Fallback for videos with no small (the case of no thumb is + // handled at the top of this function). + + if (data.size_variants.thumb2x !== null) { + thumb2x = data.size_variants.thumb2x.url; + } + if (thumb2x !== "") { + thumb2x = "data-srcset='".concat(data.size_variants.thumb.url, " ").concat(data.size_variants.thumb.width, "w, ").concat(thumb2x, " ").concat(data.size_variants.thumb2x.width, "w'"); + } + thumbnail = ""; + thumbnail += "".concat(lychee.locale["PHOTO_THUMBNAIL"], ""); + thumbnail += ""; + } + } + html += lychee.html(_templateObject9 || (_templateObject9 = _taggedTemplateLiteral(["\n\t\t\t
\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t\t

$", "

\n\t\t\t"])), disabled ? "disabled" : "", data.album_id, data.id, tabindex.get_next_tab_index(), !album.isUploadable() || disabled ? "false" : "true", thumbnail, data.title, data.title); + if (data.taken_at !== null) html += lychee.html(_templateObject10 || (_templateObject10 = _taggedTemplateLiteral(["", "", ""])), lychee.locale["CAMERA_DATE"], build.iconic("camera-slr"), lychee.locale.printDateTime(data.taken_at));else html += lychee.html(_templateObject11 || (_templateObject11 = _taggedTemplateLiteral(["", ""])), lychee.locale.printDateTime(data.created_at)); + html += "
"; + if (album.isUploadable()) { + // Note, `album.json` might be null, if the photo is displayed as + // part of a search result and therefore the actual parent album + // is not loaded. (The "parent" album is the virtual "search album" + // in this case). + // This also means that the displayed variant of the public badge of + // a photo depends on the availability of the parent album. + // This seems to be an undesired but unavoidable side effect. + html += lychee.html(_templateObject12 || (_templateObject12 = _taggedTemplateLiteral(["\n\t\t\t\t
\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t", "\n\t\t\t\t
\n\t\t\t\t"])), data.is_starred ? "badge--star" : "", build.iconic("star"), data.is_public && album.json && album.json.policy && !album.json.policy.is_public ? "badge--visible badge--hidden" : "", build.iconic("eye"), isCover ? "badge--cover" : "", build.iconic("folder-cover")); + } + html += "
"; + return html; }; /** @@ -3393,20 +3172,18 @@ build.photo = function (data) { * @returns {string} */ build.check_overlay_type = function (data, overlay_type) { - var next = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - - var types = ["desc", "date", "exif", "none"]; - var idx = types.indexOf(overlay_type); - if (idx < 0) return "none"; - if (next) idx++; - var exifHash = data.make + data.model + data.shutter + data.iso + (data.type.indexOf("video") !== 0 ? data.aperture + data.focal : ""); - - for (var i = 0; i < types.length; i++) { - var type = types[(idx + i) % types.length]; - if (type === "date" || type === "none") return type; - if (type === "desc" && data.description && data.description !== "") return type; - if (type === "exif" && exifHash !== "") return type; - } + var next = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + var types = ["desc", "date", "exif", "none"]; + var idx = types.indexOf(overlay_type); + if (idx < 0) return "none"; + if (next) idx++; + var exifHash = data.make + data.model + data.shutter + data.iso + (data.type.indexOf("video") !== 0 ? data.aperture + data.focal : ""); + for (var i = 0; i < types.length; i++) { + var type = types[(idx + i) % types.length]; + if (type === "date" || type === "none") return type; + if (type === "desc" && data.description && data.description !== "") return type; + if (type === "exif" && exifHash !== "") return type; + } }; /** @@ -3414,38 +3191,37 @@ build.check_overlay_type = function (data, overlay_type) { * @returns {string} */ build.overlay_image = function (data) { - var overlay = ""; - switch (build.check_overlay_type(data, lychee.image_overlay_type)) { - case "desc": - overlay = lychee.escapeHTML(data.description); - break; - case "date": - if (data.taken_at != null) overlay = "" + build.iconic("camera-slr") + "" + lychee.locale.printDateTime(data.taken_at) + "";else overlay = lychee.locale.printDateTime(data.created_at); - break; - case "exif": - var exifHash = data.make + data.model + data.shutter + data.aperture + data.focal + data.iso; - if (exifHash !== "") { - if (data.shutter && data.shutter !== "") overlay = data.shutter.replace("s", "sec"); - if (data.aperture && data.aperture !== "") { - if (overlay !== "") overlay += " at "; - overlay += data.aperture.replace("f/", "ƒ / "); - } - if (data.iso && data.iso !== "") { - if (overlay !== "") overlay += ", "; - overlay += sprintf(lychee.locale["PHOTO_ISO"], data.iso); - } - if (data.focal && data.focal !== "") { - if (overlay !== "") overlay += "
"; - overlay += data.focal + (data.lens && data.lens !== "" ? " (" + data.lens + ")" : ""); - } - } - break; - case "none": - default: - return ""; - } - - return lychee.html(_templateObject13, data.title ? data.title : lychee.locale["UNTITLED"]) + (overlay !== "" ? "

" + overlay + "

" : "") + "\n\t\t\n\t\t"; + var overlay = ""; + switch (build.check_overlay_type(data, lychee.image_overlay_type)) { + case "desc": + overlay = lychee.escapeHTML(data.description); + break; + case "date": + if (data.taken_at != null) overlay = "").concat(build.iconic("camera-slr"), "").concat(lychee.locale.printDateTime(data.taken_at), "");else overlay = lychee.locale.printDateTime(data.created_at); + break; + case "exif": + var exifHash = data.make + data.model + data.shutter + data.aperture + data.focal + data.iso; + if (exifHash !== "") { + if (data.shutter && data.shutter !== "") overlay = data.shutter.replace("s", "sec"); + if (data.aperture && data.aperture !== "") { + if (overlay !== "") overlay += " at "; + overlay += data.aperture.replace("f/", "ƒ / "); + } + if (data.iso && data.iso !== "") { + if (overlay !== "") overlay += ", "; + overlay += sprintf(lychee.locale["PHOTO_ISO"], data.iso); + } + if (data.focal && data.focal !== "") { + if (overlay !== "") overlay += "
"; + overlay += data.focal + (data.lens && data.lens !== "" ? " (" + data.lens + ")" : ""); + } + } + break; + case "none": + default: + return ""; + } + return lychee.html(_templateObject13 || (_templateObject13 = _taggedTemplateLiteral(["\n\t\t
\n\t\t

$", "

\n\t\t"])), data.title ? data.title : lychee.locale["UNTITLED"]) + (overlay !== "" ? "

".concat(overlay, "

") : "") + "\n\t\t
\n\t\t"; }; /** @@ -3455,58 +3231,54 @@ build.overlay_image = function (data) { * @returns {{thumb: string, html: string}} */ build.imageview = function (data, areControlsVisible, autoplay) { - var html = ""; - var thumb = ""; - - if (data.type.indexOf("video") > -1) { - html += lychee.html(_templateObject14, areControlsVisible ? "" : "full", autoplay ? "autoplay" : "", tabindex.get_next_tab_index(), data.size_variants.original.url); - } else if (data.type.indexOf("raw") > -1 && data.size_variants.medium === null) { - html += lychee.html(_templateObject15, areControlsVisible ? "" : "full", tabindex.get_next_tab_index()); - } else { - var img = ""; - - if (data.live_photo_url === "" || data.live_photo_url === null) { - // It's normal photo - - // See if we have the thumbnail loaded... - $(".photo").each(function () { - if ($(this).attr("data-id") && $(this).attr("data-id") === data.id) { - var thumbimg = $(this).find("img"); - if (thumbimg.length > 0) { - thumb = thumbimg[0].currentSrc ? thumbimg[0].currentSrc : thumbimg[0].src; - return false; - } - } - }); - - if (data.size_variants.medium !== null) { - var medium = ""; - - if (data.size_variants.medium2x !== null) { - medium = "srcset='" + data.size_variants.medium.url + " " + data.size_variants.medium.width + "w, " + data.size_variants.medium2x.url + " " + data.size_variants.medium2x.width + "w'"; - } - img = "medium"); - } else { - img = "big"; - } - } else { - if (data.size_variants.medium !== null) { - var medium_width = data.size_variants.medium.width; - var medium_height = data.size_variants.medium.height; - // It's a live photo - img = "
"; - } else { - // It's a live photo - img = "
"; - } - } - - html += lychee.html(_templateObject16, img); - } - - html += build.overlay_image(data) + ("\n\t\t\t\n\t\t\t\n\t\t\t"); - - return { html: html, thumb: thumb }; + var html = ""; + var thumb = ""; + if (data.type.indexOf("video") > -1) { + html += lychee.html(_templateObject14 || (_templateObject14 = _taggedTemplateLiteral([""])), areControlsVisible ? "" : "full", autoplay ? "autoplay" : "", tabindex.get_next_tab_index(), data.size_variants.original.url); + } else if (data.type.indexOf("raw") > -1 && data.size_variants.medium === null) { + html += lychee.html(_templateObject15 || (_templateObject15 = _taggedTemplateLiteral(["big"])), areControlsVisible ? "" : "full", tabindex.get_next_tab_index()); + } else { + var img = ""; + if (data.live_photo_url === "" || data.live_photo_url === null) { + // It's normal photo + + // See if we have the thumbnail loaded... + $(".photo").each(function () { + if ($(this).attr("data-id") && $(this).attr("data-id") === data.id) { + var thumbimg = $(this).find("img"); + if (thumbimg.length > 0) { + thumb = thumbimg[0].currentSrc ? thumbimg[0].currentSrc : thumbimg[0].src; + return false; + } + } + }); + if (data.size_variants.medium !== null) { + var medium = ""; + if (data.size_variants.medium2x !== null) { + medium = "srcset='".concat(data.size_variants.medium.url, " ").concat(data.size_variants.medium.width, "w, ").concat(data.size_variants.medium2x.url, " ").concat(data.size_variants.medium2x.width, "w'"); + } + img = "medium"); + } else { + img = "big"); + } + } else { + if (data.size_variants.medium !== null) { + var medium_width = data.size_variants.medium.width; + var medium_height = data.size_variants.medium.height; + // It's a live photo + img = "
"); + } else { + // It's a live photo + img = "
"); + } + } + html += lychee.html(_templateObject16 || (_templateObject16 = _taggedTemplateLiteral(["", ""])), img); + } + html += build.overlay_image(data) + "\n\t\t\t\n\t\t\t\n\t\t\t"); + return { + html: html, + thumb: thumb + }; }; /** @@ -3514,28 +3286,24 @@ build.imageview = function (data, areControlsVisible, autoplay) { * @returns {string} */ build.no_content = function (type) { - var html = ""; - - html += lychee.html(_templateObject17, build.iconic(type)); - - switch (type) { - case "magnifying-glass": - html += lychee.html(_templateObject18, lychee.locale["VIEW_NO_RESULT"]); - break; - case "eye": - html += lychee.html(_templateObject18, lychee.locale["VIEW_NO_PUBLIC_ALBUMS"]); - break; - case "cog": - html += lychee.html(_templateObject18, lychee.locale["VIEW_NO_CONFIGURATION"]); - break; - case "question-mark": - html += lychee.html(_templateObject18, lychee.locale["VIEW_PHOTO_NOT_FOUND"]); - break; - } - - html += ""; - - return html; + var html = ""; + html += lychee.html(_templateObject17 || (_templateObject17 = _taggedTemplateLiteral(["
", ""])), build.iconic(type)); + switch (type) { + case "magnifying-glass": + html += lychee.html(_templateObject18 || (_templateObject18 = _taggedTemplateLiteral(["

", "

"])), lychee.locale["VIEW_NO_RESULT"]); + break; + case "eye": + html += lychee.html(_templateObject19 || (_templateObject19 = _taggedTemplateLiteral(["

", "

"])), lychee.locale["VIEW_NO_PUBLIC_ALBUMS"]); + break; + case "cog": + html += lychee.html(_templateObject20 || (_templateObject20 = _taggedTemplateLiteral(["

", "

"])), lychee.locale["VIEW_NO_CONFIGURATION"]); + break; + case "question-mark": + html += lychee.html(_templateObject21 || (_templateObject21 = _taggedTemplateLiteral(["

", "

"])), lychee.locale["VIEW_PHOTO_NOT_FOUND"]); + break; + } + html += "
"; + return html; }; /** @@ -3543,28 +3311,26 @@ build.no_content = function (type) { * @returns {string} return safe HTMl code */ build.tags = function (tags) { - var html = ""; - var editable = album.isUploadable(); - - // Search is enabled if logged in (not publicMode) or public search is enabled - var searchable = !lychee.publicMode || lychee.public_search; - - // build class_string for tag - var a_class = searchable ? "tag search" : "tag"; - - if (tags.length !== 0) { - tags.forEach(function (tag, index) { - if (editable) { - html += lychee.html(_templateObject19, a_class, tag, index, build.iconic("x")); - } else { - html += lychee.html(_templateObject20, a_class, tag); - } - }); - } else { - html = lychee.html(_templateObject21, lychee.locale["NO_TAGS"]); - } - - return html; + var html = ""; + var editable = album.isUploadable(); + + // Search is enabled if logged in (not publicMode) or public search is enabled + var searchable = !lychee.publicMode || lychee.public_search; + + // build class_string for tag + var a_class = searchable ? "tag search" : "tag"; + if (tags.length !== 0) { + tags.forEach(function (tag, index) { + if (editable) { + html += lychee.html(_templateObject22 || (_templateObject22 = _taggedTemplateLiteral(["$", "", ""])), a_class, tag, index, build.iconic("x")); + } else { + html += lychee.html(_templateObject23 || (_templateObject23 = _taggedTemplateLiteral(["$", ""])), a_class, tag); + } + }); + } else { + html = lychee.html(_templateObject24 || (_templateObject24 = _taggedTemplateLiteral(["
", "
"])), lychee.locale["NO_TAGS"]); + } + return html; }; /** @@ -3572,7 +3338,7 @@ build.tags = function (tags) { * @returns {string} */ build.user = function (user) { - return lychee.html(_templateObject22, user.id, user.id, user.username, lychee.locale["USERNAME"], lychee.locale["NEW_PASSWORD"], lychee.locale["ALLOW_UPLOADS"], lychee.locale["ALLOW_USER_SELF_EDIT"], user.id, user.id !== lychee.user.id ? "" : "basicModal__button_OK_no_DEL", lychee.locale["SAVE"], user.id !== lychee.user.id ? "" + lychee.locale["DELETE"] + "" : ""); + return lychee.html(_templateObject25 || (_templateObject25 = _taggedTemplateLiteral(["
\n\t\t\t

\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t

\n\t\t\t", "\n\t\t\t", "\n\t\t
\n\t\t"])), user.id, user.id, user.username, lychee.locale["USERNAME"], lychee.locale["NEW_PASSWORD"], lychee.locale["ALLOW_UPLOADS"], lychee.locale["ALLOW_USER_SELF_EDIT"], user.id, user.id !== lychee.user.id ? "" : "basicModal__button_OK_no_DEL", lychee.locale["SAVE"], user.id !== lychee.user.id ? "").concat(lychee.locale["DELETE"], "") : ""); }; /** @@ -3580,7 +3346,7 @@ build.user = function (user) { * @returns {string} */ build.u2f = function (credential) { - return lychee.html(_templateObject23, credential.id, credential.id, credential.id.slice(0, 30), credential.id); + return lychee.html(_templateObject26 || (_templateObject26 = _taggedTemplateLiteral(["
\n\t\t\t

\n\t\t\t\n\t\t\t", "\n\t\t\t

\n\t\t\tDelete\n\t\t
\n\t\t"])), credential.id, credential.id, credential.id.slice(0, 30), credential.id); }; /** @@ -3593,104 +3359,123 @@ var contextMenu = {}; * @param {jQuery.Event} e */ contextMenu.add = function (e) { - var items = [{ title: build.iconic("image") + lychee.locale["UPLOAD_PHOTO"], fn: function fn() { - return $("#upload_files").click(); - } }, {}, { title: build.iconic("link-intact") + lychee.locale["IMPORT_LINK"], fn: function fn() { - return upload.start.url(); - } }, { title: build.iconic("dropbox", "ionicons") + lychee.locale["IMPORT_DROPBOX"], fn: function fn() { - return upload.start.dropbox(); - } }, { title: build.iconic("terminal") + lychee.locale["IMPORT_SERVER"], fn: function fn() { - return upload.start.server(); - } }, {}, { title: build.iconic("folder") + lychee.locale["NEW_ALBUM"], fn: function fn() { - return album.add(); - } }]; - - if (visible.albums()) { - items.push({ title: build.iconic("tags") + lychee.locale["NEW_TAG_ALBUM"], fn: function fn() { - return album.addByTags(); - } }); - } else if (album.isSmartID(album.getID()) || album.isSearchID(album.getID())) { - // remove Import and New album if smart album or search results - items.splice(1); - } - - if (!lychee.rights.settings.can_edit) { - // remove import from dropbox and server if not admin - items.splice(3, 2); - } else if (!lychee.dropboxKey || lychee.dropboxKey === "") { - // remove import from dropbox if dropboxKey not set - items.splice(3, 1); - } - - if (visible.album() && album.isUploadable()) { - // prepend further buttons if menu bar is reduced on small screens - var albumID = album.getID(); - if (album.isTagAlbum()) { - // For tag albums the context menu is normally not used. - items = []; - } - if (albumID.length === 24 || albumID === SmartAlbumID.UNSORTED) { - if (albumID !== SmartAlbumID.UNSORTED) { - var button_visibility_album = $("#button_visibility_album"); - if (button_visibility_album && button_visibility_album.css("display") === "none") { - items.unshift({ - title: build.iconic("eye") + lychee.locale["VISIBILITY_ALBUM"], - visible: lychee.enable_button_visibility, - fn: function fn() { - return album.setProtectionPolicy(albumID); - } - }); - } - } - var button_trash_album = $("#button_trash_album"); - if (button_trash_album && button_trash_album.css("display") === "none") { - items.unshift({ - title: build.iconic("trash") + lychee.locale["DELETE_ALBUM"], - visible: lychee.enable_button_trash, - fn: function fn() { - return album.delete([albumID]); - } - }); - } - if (albumID !== SmartAlbumID.UNSORTED) { - if (!album.isTagAlbum()) { - var button_move_album = $("#button_move_album"); - if (button_move_album && button_move_album.css("display") === "none") { - items.unshift({ - title: build.iconic("folder") + lychee.locale["MOVE_ALBUM"], - visible: lychee.enable_button_move, - fn: function fn(event) { - return contextMenu.move([albumID], event, album.setAlbum, "ROOT", album.getParentID() !== null); - } - }); - } - } - var button_nsfw_album = $("#button_nsfw_album"); - if (button_nsfw_album && button_nsfw_album.css("display") === "none") { - items.unshift({ - title: build.iconic("warning") + lychee.locale["ALBUM_MARK_NSFW"], - visible: true, - fn: function fn() { - return album.toggleNSFW(); - } - }); - } - } - if (!album.isSmartID(albumID) && lychee.map_display) { - // display track add button if it's a regular album - items.push({}, { title: build.iconic("location") + lychee.locale["UPLOAD_TRACK"], fn: function fn() { - return $("#upload_track_file").click(); - } }); - if (album.json.track_url) { - items.push({ title: build.iconic("trash") + lychee.locale["DELETE_TRACK"], fn: album.deleteTrack }); - } - } - } - } - - basicContext.show(items, e.originalEvent); - - upload.notify(); + var items = [{ + title: build.iconic("image") + lychee.locale["UPLOAD_PHOTO"], + fn: function fn() { + return $("#upload_files").click(); + } + }, {}, { + title: build.iconic("link-intact") + lychee.locale["IMPORT_LINK"], + fn: function fn() { + return upload.start.url(); + } + }, { + title: build.iconic("dropbox", "ionicons") + lychee.locale["IMPORT_DROPBOX"], + fn: function fn() { + return upload.start.dropbox(); + } + }, { + title: build.iconic("terminal") + lychee.locale["IMPORT_SERVER"], + fn: function fn() { + return upload.start.server(); + } + }, {}, { + title: build.iconic("folder") + lychee.locale["NEW_ALBUM"], + fn: function fn() { + return album.add(); + } + }]; + if (visible.albums()) { + items.push({ + title: build.iconic("tags") + lychee.locale["NEW_TAG_ALBUM"], + fn: function fn() { + return album.addByTags(); + } + }); + } else if (album.isSmartID(album.getID()) || album.isSearchID(album.getID())) { + // remove Import and New album if smart album or search results + items.splice(1); + } + if (!lychee.rights.settings.can_edit) { + // remove import from dropbox and server if not admin + items.splice(3, 2); + } else if (!lychee.dropboxKey || lychee.dropboxKey === "") { + // remove import from dropbox if dropboxKey not set + items.splice(3, 1); + } + if (visible.album() && album.isUploadable()) { + // prepend further buttons if menu bar is reduced on small screens + var albumID = album.getID(); + if (album.isTagAlbum()) { + // For tag albums the context menu is normally not used. + items = []; + } + if (albumID.length === 24 || albumID === SmartAlbumID.UNSORTED) { + if (albumID !== SmartAlbumID.UNSORTED) { + var button_visibility_album = $("#button_visibility_album"); + if (button_visibility_album && button_visibility_album.css("display") === "none") { + items.unshift({ + title: build.iconic("eye") + lychee.locale["VISIBILITY_ALBUM"], + visible: lychee.enable_button_visibility, + fn: function fn() { + return album.setProtectionPolicy(albumID); + } + }); + } + } + var button_trash_album = $("#button_trash_album"); + if (button_trash_album && button_trash_album.css("display") === "none") { + items.unshift({ + title: build.iconic("trash") + lychee.locale["DELETE_ALBUM"], + visible: lychee.enable_button_trash, + fn: function fn() { + return album["delete"]([albumID]); + } + }); + } + if (albumID !== SmartAlbumID.UNSORTED) { + if (!album.isTagAlbum()) { + var button_move_album = $("#button_move_album"); + if (button_move_album && button_move_album.css("display") === "none") { + items.unshift({ + title: build.iconic("folder") + lychee.locale["MOVE_ALBUM"], + visible: lychee.enable_button_move, + fn: function fn(event) { + return contextMenu.move([albumID], event, album.setAlbum, "ROOT", album.getParentID() !== null); + } + }); + } + } + var button_nsfw_album = $("#button_nsfw_album"); + if (button_nsfw_album && button_nsfw_album.css("display") === "none") { + items.unshift({ + title: build.iconic("warning") + lychee.locale["ALBUM_MARK_NSFW"], + visible: true, + fn: function fn() { + return album.toggleNSFW(); + } + }); + } + } + if (!album.isSmartID(albumID) && lychee.map_display) { + // display track add button if it's a regular album + items.push({}, { + title: build.iconic("location") + lychee.locale["UPLOAD_TRACK"], + fn: function fn() { + return $("#upload_track_file").click(); + } + }); + if (album.json.track_url) { + items.push({ + title: build.iconic("trash") + lychee.locale["DELETE_TRACK"], + fn: album.deleteTrack + }); + } + } + } + } + basicContext.show(items, e.originalEvent); + upload.notify(); }; /** @@ -3700,54 +3485,58 @@ contextMenu.add = function (e) { * @returns {void} */ contextMenu.album = function (albumID, e) { - // Notice for 'Merge': - // fn must call basicContext.close() first, - // in order to keep the selection - - if (album.isSmartID(albumID) || album.isSearchID(albumID)) return; - - var showMergeMove = !albums.isTagAlbum(albumID); - - var items = [{ title: build.iconic("pencil") + lychee.locale["RENAME"], fn: function fn() { - return album.setTitle([albumID]); - } }, { - title: build.iconic("collapse-left") + lychee.locale["MERGE"], - visible: showMergeMove, - fn: function fn() { - basicContext.close(); - contextMenu.move([albumID], e, album.merge, "ROOT", false); - } - }, { - title: build.iconic("folder") + lychee.locale["MOVE"], - visible: showMergeMove, - fn: function fn() { - basicContext.close(); - contextMenu.move([albumID], e, album.setAlbum, "ROOT"); - } - }, { title: build.iconic("trash") + lychee.locale["DELETE"], fn: function fn() { - return album.delete([albumID]); - } }, { title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], fn: function fn() { - return album.getArchive([albumID]); - } }]; - - if (visible.album()) { - // not top level - var myalbum = album.getSubByID(albumID); - if (myalbum.thumb.id) { - var coverActive = myalbum.thumb.id === album.json.cover_id; - // prepend context menu item - items.unshift({ - title: build.iconic("folder-cover", coverActive ? "active" : "") + lychee.locale[coverActive ? "REMOVE_COVER" : "SET_COVER"], - fn: function fn() { - return album.toggleCover(myalbum.thumb.id); - } - }); - } - } - - $('.album[data-id="' + albumID + '"]').addClass("active"); - - basicContext.show(items, e.originalEvent, contextMenu.close); + // Notice for 'Merge': + // fn must call basicContext.close() first, + // in order to keep the selection + + if (album.isSmartID(albumID) || album.isSearchID(albumID)) return; + var showMergeMove = !albums.isTagAlbum(albumID); + var items = [{ + title: build.iconic("pencil") + lychee.locale["RENAME"], + fn: function fn() { + return album.setTitle([albumID]); + } + }, { + title: build.iconic("collapse-left") + lychee.locale["MERGE"], + visible: showMergeMove, + fn: function fn() { + basicContext.close(); + contextMenu.move([albumID], e, album.merge, "ROOT", false); + } + }, { + title: build.iconic("folder") + lychee.locale["MOVE"], + visible: showMergeMove, + fn: function fn() { + basicContext.close(); + contextMenu.move([albumID], e, album.setAlbum, "ROOT"); + } + }, { + title: build.iconic("trash") + lychee.locale["DELETE"], + fn: function fn() { + return album["delete"]([albumID]); + } + }, { + title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], + fn: function fn() { + return album.getArchive([albumID]); + } + }]; + if (visible.album()) { + // not top level + var myalbum = album.getSubByID(albumID); + if (myalbum.thumb.id) { + var coverActive = myalbum.thumb.id === album.json.cover_id; + // prepend context menu item + items.unshift({ + title: build.iconic("folder-cover", coverActive ? "active" : "") + lychee.locale[coverActive ? "REMOVE_COVER" : "SET_COVER"], + fn: function fn() { + return album.toggleCover(myalbum.thumb.id); + } + }); + } + } + $('.album[data-id="' + albumID + '"]').addClass("active"); + basicContext.show(items, e.originalEvent, contextMenu.close); }; /** @@ -3760,21 +3549,20 @@ contextMenu.album = function (albumID, e) { * @returns {void} */ contextMenu.albumDrop = function (sourceAlbumID, targetAlbumID, e) { - var items = [{ - title: build.iconic("collapse-left") + lychee.locale["MERGE"], - fn: function fn() { - album.merge([sourceAlbumID], targetAlbumID); - } - }, { - title: build.iconic("folder") + lychee.locale["MOVE"], - visible: true, - fn: function fn() { - basicContext.close(); - album.setAlbum([sourceAlbumID], targetAlbumID); - } - }]; - - basicContext.show(items, e, contextMenu.close); + var items = [{ + title: build.iconic("collapse-left") + lychee.locale["MERGE"], + fn: function fn() { + album.merge([sourceAlbumID], targetAlbumID); + } + }, { + title: build.iconic("folder") + lychee.locale["MOVE"], + visible: true, + fn: function fn() { + basicContext.close(); + album.setAlbum([sourceAlbumID], targetAlbumID); + } + }]; + basicContext.show(items, e, contextMenu.close); }; /** @@ -3784,46 +3572,52 @@ contextMenu.albumDrop = function (sourceAlbumID, targetAlbumID, e) { * @returns {void} */ contextMenu.albumMulti = function (albumIDs, e) { - multiselect.stopResize(); - - // Automatically merge selected albums when albumIDs contains more than one album - // Show list of albums otherwise - var autoMerge = albumIDs.length > 1; - - var showMergeMove = albumIDs.every(function (albumID) { - return !albums.isTagAlbum(albumID); - }); - - var items = [{ title: build.iconic("pencil") + lychee.locale["RENAME_ALL"], fn: function fn() { - return album.setTitle(albumIDs); - } }, { - title: build.iconic("collapse-left") + lychee.locale["MERGE_ALL"], - visible: showMergeMove && autoMerge, - fn: function fn() { - var albumID = albumIDs.shift(); - album.merge(albumIDs, albumID); - } - }, { - title: build.iconic("collapse-left") + lychee.locale["MERGE"], - visible: showMergeMove && !autoMerge, - fn: function fn() { - basicContext.close(); - contextMenu.move(albumIDs, e, album.merge, "ROOT", false); - } - }, { - title: build.iconic("folder") + lychee.locale["MOVE_ALL"], - visible: showMergeMove, - fn: function fn() { - basicContext.close(); - contextMenu.move(albumIDs, e, album.setAlbum, "ROOT"); - } - }, { title: build.iconic("trash") + lychee.locale["DELETE_ALL"], fn: function fn() { - return album.delete(albumIDs); - } }, { title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD_ALL"], fn: function fn() { - return album.getArchive(albumIDs); - } }]; - - basicContext.show(items, e.originalEvent, contextMenu.close); + multiselect.stopResize(); + + // Automatically merge selected albums when albumIDs contains more than one album + // Show list of albums otherwise + var autoMerge = albumIDs.length > 1; + var showMergeMove = albumIDs.every(function (albumID) { + return !albums.isTagAlbum(albumID); + }); + var items = [{ + title: build.iconic("pencil") + lychee.locale["RENAME_ALL"], + fn: function fn() { + return album.setTitle(albumIDs); + } + }, { + title: build.iconic("collapse-left") + lychee.locale["MERGE_ALL"], + visible: showMergeMove && autoMerge, + fn: function fn() { + var albumID = albumIDs.shift(); + album.merge(albumIDs, albumID); + } + }, { + title: build.iconic("collapse-left") + lychee.locale["MERGE"], + visible: showMergeMove && !autoMerge, + fn: function fn() { + basicContext.close(); + contextMenu.move(albumIDs, e, album.merge, "ROOT", false); + } + }, { + title: build.iconic("folder") + lychee.locale["MOVE_ALL"], + visible: showMergeMove, + fn: function fn() { + basicContext.close(); + contextMenu.move(albumIDs, e, album.setAlbum, "ROOT"); + } + }, { + title: build.iconic("trash") + lychee.locale["DELETE_ALL"], + fn: function fn() { + return album["delete"](albumIDs); + } + }, { + title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD_ALL"], + fn: function fn() { + return album.getArchive(albumIDs); + } + }]; + basicContext.show(items, e.originalEvent, contextMenu.close); }; /** @@ -3849,59 +3643,50 @@ contextMenu.albumMulti = function (albumIDs, e) { * @returns {{title: string, disabled: boolean, fn: ContextMenuEventCB}[]} */ contextMenu.buildList = function (lists, exclude, action) { - var parentID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - var layer = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; - - var items = []; - - lists.forEach(function (item) { - if ((layer !== 0 || item.parent_id) && item.parent_id !== parentID) return; - - var thumb = "img/no_cover.svg"; - if (item.thumb && item.thumb.thumb) { - if (item.thumb.thumb === "uploads/thumb/") { - if (item.thumb.type && item.thumb.type.indexOf("video") > -1) { - thumb = "img/play-icon.png"; - } - } else { - thumb = item.thumb.thumb; - } - } else if (item.size_variants) { - if (item.size_variants.thumb === null) { - if (item.type && item.type.indexOf("video") > -1) { - thumb = "img/play-icon.png"; - } - } else { - thumb = item.size_variants.thumb.url; - } - } - - if (!item.title) item.title = lychee.locale["UNTITLED"]; - - var prefix = layer > 0 ? "  ".repeat(layer - 1) + "└ " : ""; - - var html = lychee.html(_templateObject24, prefix, thumb, item.title); - - items.push({ - title: html, - disabled: exclude.findIndex(function (id) { - return id === item.id; - }) !== -1, - fn: function fn() { - return action(item); - } - }); - - if (item.albums && item.albums.length > 0) { - items = items.concat(contextMenu.buildList(item.albums, exclude, action, item.id, layer + 1)); - } else { - // Fallback for flat tree representation. Should not be - // needed anymore but shouldn't hurt either. - items = items.concat(contextMenu.buildList(lists, exclude, action, item.id, layer + 1)); - } - }); - - return items; + var parentID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + var layer = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; + var items = []; + lists.forEach(function (item) { + if ((layer !== 0 || item.parent_id) && item.parent_id !== parentID) return; + var thumb = "img/no_cover.svg"; + if (item.thumb && item.thumb.thumb) { + if (item.thumb.thumb === "uploads/thumb/") { + if (item.thumb.type && item.thumb.type.indexOf("video") > -1) { + thumb = "img/play-icon.png"; + } + } else { + thumb = item.thumb.thumb; + } + } else if (item.size_variants) { + if (item.size_variants.thumb === null) { + if (item.type && item.type.indexOf("video") > -1) { + thumb = "img/play-icon.png"; + } + } else { + thumb = item.size_variants.thumb.url; + } + } + if (!item.title) item.title = lychee.locale["UNTITLED"]; + var prefix = layer > 0 ? "  ".repeat(layer - 1) + "└ " : ""; + var html = lychee.html(_templateObject27 || (_templateObject27 = _taggedTemplateLiteral(["\n\t\t\t ", "\n\t\t\t \"thumbnail\"\n\t\t\t
$", "
\n\t\t\t "])), prefix, thumb, item.title); + items.push({ + title: html, + disabled: exclude.findIndex(function (id) { + return id === item.id; + }) !== -1, + fn: function fn() { + return action(item); + } + }); + if (item.albums && item.albums.length > 0) { + items = items.concat(contextMenu.buildList(item.albums, exclude, action, item.id, layer + 1)); + } else { + // Fallback for flat tree representation. Should not be + // needed anymore but shouldn't hurt either. + items = items.concat(contextMenu.buildList(lists, exclude, action, item.id, layer + 1)); + } + }); + return items; }; /** @@ -3911,39 +3696,40 @@ contextMenu.buildList = function (lists, exclude, action) { * @returns {void} */ contextMenu.albumTitle = function (albumID, e) { - api.post("Albums::tree", {}, function (data) { - var items = []; - - items = items.concat({ title: lychee.locale["ROOT"], disabled: albumID === null, fn: function fn() { - return lychee.goto(); - } }); - - if (data.albums && data.albums.length > 0) { - items = items.concat({}); - items = items.concat(contextMenu.buildList(data.albums, albumID !== null ? [albumID] : [], function (a) { - return lychee.goto(a.id); - })); - } - - if (data.shared_albums && data.shared_albums.length > 0) { - items = items.concat({}); - items = items.concat(contextMenu.buildList(data.shared_albums, albumID !== null ? [albumID] : [], function (a) { - return lychee.goto(a.id); - })); - } - - if (albumID !== null && !album.isSmartID(albumID) && !album.isSearchID(albumID) && album.isUploadable()) { - if (items.length > 0) { - items.unshift({}); - } - - items.unshift({ title: build.iconic("pencil") + lychee.locale["RENAME"], fn: function fn() { - return album.setTitle([albumID]); - } }); - } - - basicContext.show(items, e.originalEvent, contextMenu.close); - }); + api.post("Albums::tree", {}, function (data) { + var items = []; + items = items.concat({ + title: lychee.locale["ROOT"], + disabled: albumID === null, + fn: function fn() { + return lychee["goto"](); + } + }); + if (data.albums && data.albums.length > 0) { + items = items.concat({}); + items = items.concat(contextMenu.buildList(data.albums, albumID !== null ? [albumID] : [], function (a) { + return lychee["goto"](a.id); + })); + } + if (data.shared_albums && data.shared_albums.length > 0) { + items = items.concat({}); + items = items.concat(contextMenu.buildList(data.shared_albums, albumID !== null ? [albumID] : [], function (a) { + return lychee["goto"](a.id); + })); + } + if (albumID !== null && !album.isSmartID(albumID) && !album.isSearchID(albumID) && album.isUploadable()) { + if (items.length > 0) { + items.unshift({}); + } + items.unshift({ + title: build.iconic("pencil") + lychee.locale["RENAME"], + fn: function fn() { + return album.setTitle([albumID]); + } + }); + } + basicContext.show(items, e.originalEvent, contextMenu.close); + }); }; /** @@ -3953,55 +3739,63 @@ contextMenu.albumTitle = function (albumID, e) { * @returns {void} */ contextMenu.photo = function (photoID, e) { - var coverActive = photoID === album.json.cover_id; - - var isPhotoStarred = album.getByID(photoID).is_starred; - - var items = [{ - title: build.iconic("star") + (isPhotoStarred ? lychee.locale["UNSTAR"] : lychee.locale["STAR"]), - fn: function fn() { - return _photo3.setStar([photoID], !isPhotoStarred); - } - }, { title: build.iconic("tag") + lychee.locale["TAG"], fn: function fn() { - return _photo3.editTags([photoID]); - } }, - // for future work, use a list of all the ancestors. - { - title: build.iconic("folder-cover", coverActive ? "active" : "") + lychee.locale[coverActive ? "REMOVE_COVER" : "SET_COVER"], - fn: function fn() { - return album.toggleCover(photoID); - } - }, {}, { title: build.iconic("pencil") + lychee.locale["RENAME"], fn: function fn() { - return _photo3.setTitle([photoID]); - } }, { - title: build.iconic("layers") + lychee.locale["COPY_TO"], - fn: function fn() { - basicContext.close(); - contextMenu.move([photoID], e, _photo3.copyTo); - } - }, - // Notice for 'Move': - // fn must call basicContext.close() first, - // in order to keep the selection - { - title: build.iconic("folder") + lychee.locale["MOVE"], - fn: function fn() { - basicContext.close(); - contextMenu.move([photoID], e, _photo3.setAlbum); - } - }, { title: build.iconic("trash") + lychee.locale["DELETE"], fn: function fn() { - return _photo3.delete([photoID]); - } }, { title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], fn: function fn() { - return _photo3.getArchive([photoID]); - } }]; - if (album.isSmartID(album.getID()) || album.isSearchID(album.getID()) || album.isTagAlbum()) { - // Cover setting not supported for smart or tag albums and search results. - items.splice(2, 1); - } - - $('.photo[data-id="' + photoID + '"]').addClass("active"); - - basicContext.show(items, e.originalEvent, contextMenu.close); + var coverActive = photoID === album.json.cover_id; + var isPhotoStarred = album.getByID(photoID).is_starred; + var items = [{ + title: build.iconic("star") + (isPhotoStarred ? lychee.locale["UNSTAR"] : lychee.locale["STAR"]), + fn: function fn() { + return _photo3.setStar([photoID], !isPhotoStarred); + } + }, { + title: build.iconic("tag") + lychee.locale["TAG"], + fn: function fn() { + return _photo3.editTags([photoID]); + } + }, + // for future work, use a list of all the ancestors. + { + title: build.iconic("folder-cover", coverActive ? "active" : "") + lychee.locale[coverActive ? "REMOVE_COVER" : "SET_COVER"], + fn: function fn() { + return album.toggleCover(photoID); + } + }, {}, { + title: build.iconic("pencil") + lychee.locale["RENAME"], + fn: function fn() { + return _photo3.setTitle([photoID]); + } + }, { + title: build.iconic("layers") + lychee.locale["COPY_TO"], + fn: function fn() { + basicContext.close(); + contextMenu.move([photoID], e, _photo3.copyTo); + } + }, + // Notice for 'Move': + // fn must call basicContext.close() first, + // in order to keep the selection + { + title: build.iconic("folder") + lychee.locale["MOVE"], + fn: function fn() { + basicContext.close(); + contextMenu.move([photoID], e, _photo3.setAlbum); + } + }, { + title: build.iconic("trash") + lychee.locale["DELETE"], + fn: function fn() { + return _photo3["delete"]([photoID]); + } + }, { + title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], + fn: function fn() { + return _photo3.getArchive([photoID]); + } + }]; + if (album.isSmartID(album.getID()) || album.isSearchID(album.getID()) || album.isTagAlbum()) { + // Cover setting not supported for smart or tag albums and search results. + items.splice(2, 1); + } + $('.photo[data-id="' + photoID + '"]').addClass("active"); + basicContext.show(items, e.originalEvent, contextMenu.close); }; /** @@ -4012,16 +3806,14 @@ contextMenu.photo = function (photoID, e) { * @returns {void} */ contextMenu.photoDrop = function (photoID, albumID, e) { - var items = [{ - title: build.iconic("folder") + lychee.locale["MOVE"], - fn: function fn() { - _photo3.setAlbum([photoID], albumID); - } - }]; - - $('.photo[data-id="' + photoID + '"]').addClass("active"); - - basicContext.show(items, e, contextMenu.close); + var items = [{ + title: build.iconic("folder") + lychee.locale["MOVE"], + fn: function fn() { + _photo3.setAlbum([photoID], albumID); + } + }]; + $('.photo[data-id="' + photoID + '"]').addClass("active"); + basicContext.show(items, e, contextMenu.close); }; /** @@ -4029,50 +3821,59 @@ contextMenu.photoDrop = function (photoID, albumID, e) { * @param {jQuery.Event} e */ contextMenu.photoMulti = function (photoIDs, e) { - multiselect.stopResize(); - - var arePhotosStarred = false; - var arePhotosNotStarred = false; - photoIDs.forEach(function (id) { - if (album.getByID(id).is_starred) { - arePhotosStarred = true; - } else { - arePhotosNotStarred = true; - } - }); - - var items = [ - // Only show the star/unstar menu item when the selected photos are - // consistently either all starred or all not starred. - { - title: build.iconic("star") + (arePhotosNotStarred ? lychee.locale["STAR_ALL"] : lychee.locale["UNSTAR_ALL"]), - visible: !(arePhotosStarred && arePhotosNotStarred), - fn: function fn() { - return _photo3.setStar(photoIDs, arePhotosNotStarred); - } - }, { title: build.iconic("tag") + lychee.locale["TAG_ALL"], fn: function fn() { - return _photo3.editTags(photoIDs); - } }, {}, { title: build.iconic("pencil") + lychee.locale["RENAME_ALL"], fn: function fn() { - return _photo3.setTitle(photoIDs); - } }, { - title: build.iconic("layers") + lychee.locale["COPY_ALL_TO"], - fn: function fn() { - basicContext.close(); - contextMenu.move(photoIDs, e, _photo3.copyTo); - } - }, { - title: build.iconic("folder") + lychee.locale["MOVE_ALL"], - fn: function fn() { - basicContext.close(); - contextMenu.move(photoIDs, e, _photo3.setAlbum); - } - }, { title: build.iconic("trash") + lychee.locale["DELETE_ALL"], fn: function fn() { - return _photo3.delete(photoIDs); - } }, { title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD_ALL"], fn: function fn() { - return _photo3.getArchive(photoIDs, "ORIGINAL"); - } }]; - - basicContext.show(items, e.originalEvent, contextMenu.close); + multiselect.stopResize(); + var arePhotosStarred = false; + var arePhotosNotStarred = false; + photoIDs.forEach(function (id) { + if (album.getByID(id).is_starred) { + arePhotosStarred = true; + } else { + arePhotosNotStarred = true; + } + }); + var items = [ + // Only show the star/unstar menu item when the selected photos are + // consistently either all starred or all not starred. + { + title: build.iconic("star") + (arePhotosNotStarred ? lychee.locale["STAR_ALL"] : lychee.locale["UNSTAR_ALL"]), + visible: !(arePhotosStarred && arePhotosNotStarred), + fn: function fn() { + return _photo3.setStar(photoIDs, arePhotosNotStarred); + } + }, { + title: build.iconic("tag") + lychee.locale["TAG_ALL"], + fn: function fn() { + return _photo3.editTags(photoIDs); + } + }, {}, { + title: build.iconic("pencil") + lychee.locale["RENAME_ALL"], + fn: function fn() { + return _photo3.setTitle(photoIDs); + } + }, { + title: build.iconic("layers") + lychee.locale["COPY_ALL_TO"], + fn: function fn() { + basicContext.close(); + contextMenu.move(photoIDs, e, _photo3.copyTo); + } + }, { + title: build.iconic("folder") + lychee.locale["MOVE_ALL"], + fn: function fn() { + basicContext.close(); + contextMenu.move(photoIDs, e, _photo3.setAlbum); + } + }, { + title: build.iconic("trash") + lychee.locale["DELETE_ALL"], + fn: function fn() { + return _photo3["delete"](photoIDs); + } + }, { + title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD_ALL"], + fn: function fn() { + return _photo3.getArchive(photoIDs, "ORIGINAL"); + } + }]; + basicContext.show(items, e.originalEvent, contextMenu.close); }; /** @@ -4081,28 +3882,27 @@ contextMenu.photoMulti = function (photoIDs, e) { * @param {jQuery.Event} e */ contextMenu.photoTitle = function (albumID, photoID, e) { - var items = [{ title: build.iconic("pencil") + lychee.locale["RENAME"], fn: function fn() { - return _photo3.setTitle([photoID]); - } }]; - - // Note: We can also have a photo without its parent album being loaded - // if the photo is a public photo within a private album - var photos = album.json ? album.json.photos : []; - - if (photos.length > 0) { - items.push({}); - - items = items.concat(contextMenu.buildList(photos, [photoID], function (a) { - return lychee.goto(albumID + "/" + a.id); - })); - } - - if (!album.isUploadable()) { - // Remove Rename and the spacer. - items.splice(0, 2); - } - - basicContext.show(items, e.originalEvent, contextMenu.close); + var items = [{ + title: build.iconic("pencil") + lychee.locale["RENAME"], + fn: function fn() { + return _photo3.setTitle([photoID]); + } + }]; + + // Note: We can also have a photo without its parent album being loaded + // if the photo is a public photo within a private album + var photos = album.json ? album.json.photos : []; + if (photos.length > 0) { + items.push({}); + items = items.concat(contextMenu.buildList(photos, [photoID], function (a) { + return lychee["goto"](albumID + "/" + a.id); + })); + } + if (!album.isUploadable()) { + // Remove Rename and the spacer. + items.splice(0, 2); + } + basicContext.show(items, e.originalEvent, contextMenu.close); }; /** @@ -4110,77 +3910,83 @@ contextMenu.photoTitle = function (albumID, photoID, e) { * @param {jQuery.Event} e */ contextMenu.photoMore = function (photoID, e) { - // Show download-item when - // a) We are allowed to upload to the album - // b) the photo is explicitly marked as downloadable (v4-only) - // c) or, the album is explicitly marked as downloadable - - var showDownload = album.isUploadable() || _photo3.json.grant_download; - var showFull = !!(_photo3.json.size_variants.original.url && _photo3.json.size_variants.original.url !== ""); - - var items = [{ title: build.iconic("fullscreen-enter") + lychee.locale["FULL_PHOTO"], visible: showFull, fn: function fn() { - return window.open(_photo3.getDirectLink()); - } }, { title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], visible: showDownload, fn: function fn() { - return _photo3.getArchive([photoID]); - } }]; - if (album.isUploadable()) { - // prepend further buttons if menu bar is reduced on small screens - var button_visibility = $("#button_visibility"); - if (button_visibility && button_visibility.css("display") === "none") { - items.unshift({ - title: build.iconic("eye") + lychee.locale["VISIBILITY_PHOTO"], - visible: lychee.enable_button_visibility, - fn: function fn() { - return _photo3.setProtectionPolicy(_photo3.getID()); - } - }); - } - var button_trash = $("#button_trash"); - if (button_trash && button_trash.css("display") === "none") { - items.unshift({ - title: build.iconic("trash") + lychee.locale["DELETE"], - visible: lychee.enable_button_trash, - fn: function fn() { - return _photo3.delete([_photo3.getID()]); - } - }); - } - var button_move = $("#button_move"); - if (button_move && button_move.css("display") === "none") { - items.unshift({ - title: build.iconic("folder") + lychee.locale["MOVE"], - visible: lychee.enable_button_move, - fn: function fn(event) { - return contextMenu.move([_photo3.getID()], event, _photo3.setAlbum); - } - }); - } - /* The condition below is copied from view.photo.header() */ - if (!(_photo3.json.type && (_photo3.json.type.indexOf("video") === 0 || _photo3.json.type === "raw") || _photo3.json.live_photo_url !== "" && _photo3.json.live_photo_url !== null)) { - var button_rotate_cwise = $("#button_rotate_cwise"); - if (button_rotate_cwise && button_rotate_cwise.css("display") === "none") { - items.unshift({ - title: build.iconic("clockwise") + lychee.locale["PHOTO_EDIT_ROTATECWISE"], - visible: lychee.enable_button_move, - fn: function fn() { - return photoeditor.rotate(_photo3.getID(), 1); - } - }); - } - var button_rotate_ccwise = $("#button_rotate_ccwise"); - if (button_rotate_ccwise && button_rotate_ccwise.css("display") === "none") { - items.unshift({ - title: build.iconic("counterclockwise") + lychee.locale["PHOTO_EDIT_ROTATECCWISE"], - visible: lychee.enable_button_move, - fn: function fn() { - return photoeditor.rotate(_photo3.getID(), -1); - } - }); - } - } - } - - basicContext.show(items, e.originalEvent); + // Show download-item when + // a) We are allowed to upload to the album + // b) the photo is explicitly marked as downloadable (v4-only) + // c) or, the album is explicitly marked as downloadable + + var showDownload = album.isUploadable() || _photo3.json.grant_download; + var showFull = !!(_photo3.json.size_variants.original.url && _photo3.json.size_variants.original.url !== ""); + var items = [{ + title: build.iconic("fullscreen-enter") + lychee.locale["FULL_PHOTO"], + visible: showFull, + fn: function fn() { + return window.open(_photo3.getDirectLink()); + } + }, { + title: build.iconic("cloud-download") + lychee.locale["DOWNLOAD"], + visible: showDownload, + fn: function fn() { + return _photo3.getArchive([photoID]); + } + }]; + if (album.isUploadable()) { + // prepend further buttons if menu bar is reduced on small screens + var button_visibility = $("#button_visibility"); + if (button_visibility && button_visibility.css("display") === "none") { + items.unshift({ + title: build.iconic("eye") + lychee.locale["VISIBILITY_PHOTO"], + visible: lychee.enable_button_visibility, + fn: function fn() { + return _photo3.setProtectionPolicy(_photo3.getID()); + } + }); + } + var button_trash = $("#button_trash"); + if (button_trash && button_trash.css("display") === "none") { + items.unshift({ + title: build.iconic("trash") + lychee.locale["DELETE"], + visible: lychee.enable_button_trash, + fn: function fn() { + return _photo3["delete"]([_photo3.getID()]); + } + }); + } + var button_move = $("#button_move"); + if (button_move && button_move.css("display") === "none") { + items.unshift({ + title: build.iconic("folder") + lychee.locale["MOVE"], + visible: lychee.enable_button_move, + fn: function fn(event) { + return contextMenu.move([_photo3.getID()], event, _photo3.setAlbum); + } + }); + } + /* The condition below is copied from view.photo.header() */ + if (!(_photo3.json.type && (_photo3.json.type.indexOf("video") === 0 || _photo3.json.type === "raw") || _photo3.json.live_photo_url !== "" && _photo3.json.live_photo_url !== null)) { + var button_rotate_cwise = $("#button_rotate_cwise"); + if (button_rotate_cwise && button_rotate_cwise.css("display") === "none") { + items.unshift({ + title: build.iconic("clockwise") + lychee.locale["PHOTO_EDIT_ROTATECWISE"], + visible: lychee.enable_button_move, + fn: function fn() { + return photoeditor.rotate(_photo3.getID(), 1); + } + }); + } + var button_rotate_ccwise = $("#button_rotate_ccwise"); + if (button_rotate_ccwise && button_rotate_ccwise.css("display") === "none") { + items.unshift({ + title: build.iconic("counterclockwise") + lychee.locale["PHOTO_EDIT_ROTATECCWISE"], + visible: lychee.enable_button_move, + fn: function fn() { + return photoeditor.rotate(_photo3.getID(), -1); + } + }); + } + } + } + basicContext.show(items, e.originalEvent); }; /** @@ -4190,19 +3996,16 @@ contextMenu.photoMore = function (photoID, e) { * @returns {string[]} */ contextMenu.getSubIDs = function (albums, albumID) { - var ids = [albumID]; - - albums.forEach(function (album) { - if (album.parent_id === albumID) { - ids = ids.concat(contextMenu.getSubIDs(albums, album.id)); - } - - if (album.albums && album.albums.length > 0) { - ids = ids.concat(contextMenu.getSubIDs(album.albums, albumID)); - } - }); - - return ids; + var ids = [albumID]; + albums.forEach(function (album) { + if (album.parent_id === albumID) { + ids = ids.concat(contextMenu.getSubIDs(albums, album.id)); + } + if (album.albums && album.albums.length > 0) { + ids = ids.concat(contextMenu.getSubIDs(album.albums, albumID)); + } + }); + return ids; }; /** @@ -4268,72 +4071,70 @@ contextMenu.getSubIDs = function (albums, albumID) { * @param {boolean} [display_root=true] - Whether the root (aka unsorted) album shall be shown */ contextMenu.move = function (IDs, e, callback) { - var kind = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "UNSORTED"; - var display_root = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; - - var items = []; - - api.post("Albums::tree", {}, function (data) { - var addItems = function addItems(albums) { - // Disable all children - // It's not possible to move us into them - var i = void 0, - s = void 0; - var exclude = []; - for (i = 0; i < IDs.length; i++) { - var sub = contextMenu.getSubIDs(albums, IDs[i]); - for (s = 0; s < sub.length; s++) { - exclude.push(sub[s]); - } - } - if (visible.album()) { - // For merging, don't exclude the parent. - // For photo copy, don't exclude the current album. - if (callback !== album.merge && callback !== _photo3.copyTo) { - exclude.push(album.getID()); - } - if (IDs.length === 1 && IDs[0] === album.getID() && album.getParentID() && callback === album.setAlbum) { - // If moving the current album, exclude its parent. - exclude.push(album.getParentID()); - } - } else if (visible.photo()) { - exclude.push(_photo3.json.album_id); - } - items = items.concat(contextMenu.buildList(albums, exclude.concat(IDs), function (a) { - return callback(IDs, a.id); - })); - }; - - if (data.albums && data.albums.length > 0) { - // items = items.concat(contextMenu.buildList(data.albums, [ album.getID() ], (a) => callback(IDs, a.id))); //photo.setAlbum - - addItems(data.albums); - } - - if (data.shared_albums && data.shared_albums.length > 0 && lychee.rights.settings.can_edit) { - items = items.concat({}); - addItems(data.shared_albums); - } - - // Show Unsorted when unsorted is not the current album - if (display_root && album.getID() !== "unsorted" && !visible.albums()) { - items.unshift({}); - items.unshift({ title: lychee.locale[kind], fn: function fn() { - return callback(IDs, null); - } }); - } - - // Don't allow to move the current album to a newly created subalbum - // (creating a cycle). - if (IDs.length !== 1 || IDs[0] !== (album.json ? album.json.id : null) || callback !== album.setAlbum) { - items.unshift({}); - items.unshift({ title: lychee.locale["NEW_ALBUM"], fn: function fn() { - return album.add(IDs, callback); - } }); - } - - basicContext.show(items, e.originalEvent, contextMenu.close); - }); + var kind = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "UNSORTED"; + var display_root = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; + var items = []; + api.post("Albums::tree", {}, function (data) { + var addItems = function addItems(albums) { + // Disable all children + // It's not possible to move us into them + var i, s; + var exclude = []; + for (i = 0; i < IDs.length; i++) { + var sub = contextMenu.getSubIDs(albums, IDs[i]); + for (s = 0; s < sub.length; s++) exclude.push(sub[s]); + } + if (visible.album()) { + // For merging, don't exclude the parent. + // For photo copy, don't exclude the current album. + if (callback !== album.merge && callback !== _photo3.copyTo) { + exclude.push(album.getID()); + } + if (IDs.length === 1 && IDs[0] === album.getID() && album.getParentID() && callback === album.setAlbum) { + // If moving the current album, exclude its parent. + exclude.push(album.getParentID()); + } + } else if (visible.photo()) { + exclude.push(_photo3.json.album_id); + } + items = items.concat(contextMenu.buildList(albums, exclude.concat(IDs), function (a) { + return callback(IDs, a.id); + })); + }; + if (data.albums && data.albums.length > 0) { + // items = items.concat(contextMenu.buildList(data.albums, [ album.getID() ], (a) => callback(IDs, a.id))); //photo.setAlbum + + addItems(data.albums); + } + if (data.shared_albums && data.shared_albums.length > 0 && lychee.rights.settings.can_edit) { + items = items.concat({}); + addItems(data.shared_albums); + } + + // Show Unsorted when unsorted is not the current album + if (display_root && album.getID() !== "unsorted" && !visible.albums()) { + items.unshift({}); + items.unshift({ + title: lychee.locale[kind], + fn: function fn() { + return callback(IDs, null); + } + }); + } + + // Don't allow to move the current album to a newly created subalbum + // (creating a cycle). + if (IDs.length !== 1 || IDs[0] !== (album.json ? album.json.id : null) || callback !== album.setAlbum) { + items.unshift({}); + items.unshift({ + title: lychee.locale["NEW_ALBUM"], + fn: function fn() { + return album.add(IDs, callback); + } + }); + } + basicContext.show(items, e.originalEvent, contextMenu.close); + }); }; /** @@ -4343,31 +4144,43 @@ contextMenu.move = function (IDs, e, callback) { * @returns {void} */ contextMenu.sharePhoto = function (photoID, e) { - if (!lychee.share_button_visible) { - return; - } - - var iconClass = "ionicons"; - - var items = [{ title: build.iconic("twitter", iconClass) + "Twitter", fn: function fn() { - return _photo3.share(photoID, "twitter"); - } }, { title: build.iconic("facebook", iconClass) + "Facebook", fn: function fn() { - return _photo3.share(photoID, "facebook"); - } }, { title: build.iconic("envelope-closed") + "Mail", fn: function fn() { - return _photo3.share(photoID, "mail"); - } }, { - title: build.iconic("dropbox", iconClass) + "Dropbox", - visible: lychee.rights.settings.can_edit === true, - fn: function fn() { - return _photo3.share(photoID, "dropbox"); - } - }, { title: build.iconic("link-intact") + lychee.locale["DIRECT_LINKS"], fn: function fn() { - return _photo3.showDirectLinks(photoID); - } }, { title: build.iconic("grid-two-up") + lychee.locale["QR_CODE"], fn: function fn() { - return _photo3.qrCode(photoID); - } }]; - - basicContext.show(items, e.originalEvent); + if (!lychee.share_button_visible) { + return; + } + var iconClass = "ionicons"; + var items = [{ + title: build.iconic("twitter", iconClass) + "Twitter", + fn: function fn() { + return _photo3.share(photoID, "twitter"); + } + }, { + title: build.iconic("facebook", iconClass) + "Facebook", + fn: function fn() { + return _photo3.share(photoID, "facebook"); + } + }, { + title: build.iconic("envelope-closed") + "Mail", + fn: function fn() { + return _photo3.share(photoID, "mail"); + } + }, { + title: build.iconic("dropbox", iconClass) + "Dropbox", + visible: lychee.rights.settings.can_edit === true, + fn: function fn() { + return _photo3.share(photoID, "dropbox"); + } + }, { + title: build.iconic("link-intact") + lychee.locale["DIRECT_LINKS"], + fn: function fn() { + return _photo3.showDirectLinks(photoID); + } + }, { + title: build.iconic("grid-two-up") + lychee.locale["QR_CODE"], + fn: function fn() { + return _photo3.qrCode(photoID); + } + }]; + basicContext.show(items, e.originalEvent); }; /** @@ -4377,81 +4190,86 @@ contextMenu.sharePhoto = function (photoID, e) { * @returns {void} */ contextMenu.shareAlbum = function (albumID, e) { - if (!lychee.share_button_visible) { - return; - } - - var iconClass = "ionicons"; - - var items = [{ title: build.iconic("twitter", iconClass) + "Twitter", fn: function fn() { - return album.share("twitter"); - } }, { title: build.iconic("facebook", iconClass) + "Facebook", fn: function fn() { - return album.share("facebook"); - } }, { title: build.iconic("envelope-closed") + "Mail", fn: function fn() { - return album.share("mail"); - } }, { - title: build.iconic("link-intact") + lychee.locale["DIRECT_LINK"], - fn: function fn() { - var url = lychee.getBaseUrl() + "r/" + albumID; - if (album.json.policy.is_password_required) { - // Copy the url with prefilled password param - url += "?password="; - } - navigator.clipboard.writeText(url).then(function () { - return loadingBar.show("success", lychee.locale["URL_COPIED_TO_CLIPBOARD"]); - }); - } - }, { title: build.iconic("grid-two-up") + lychee.locale["QR_CODE"], fn: function fn() { - return album.qrCode(); - } }]; - - basicContext.show(items, e.originalEvent); + if (!lychee.share_button_visible) { + return; + } + var iconClass = "ionicons"; + var items = [{ + title: build.iconic("twitter", iconClass) + "Twitter", + fn: function fn() { + return album.share("twitter"); + } + }, { + title: build.iconic("facebook", iconClass) + "Facebook", + fn: function fn() { + return album.share("facebook"); + } + }, { + title: build.iconic("envelope-closed") + "Mail", + fn: function fn() { + return album.share("mail"); + } + }, { + title: build.iconic("link-intact") + lychee.locale["DIRECT_LINK"], + fn: function fn() { + var url = lychee.getBaseUrl() + "r/" + albumID; + if (album.json.policy.is_password_required) { + // Copy the url with prefilled password param + url += "?password="; + } + navigator.clipboard.writeText(url).then(function () { + return loadingBar.show("success", lychee.locale["URL_COPIED_TO_CLIPBOARD"]); + }); + } + }, { + title: build.iconic("grid-two-up") + lychee.locale["QR_CODE"], + fn: function fn() { + return album.qrCode(); + } + }]; + basicContext.show(items, e.originalEvent); }; /** * @returns {void} */ contextMenu.close = function () { - if (!visible.contextMenu()) return; - - basicContext.close(); - - multiselect.clearSelection(); - if (visible.multiselect()) { - multiselect.close(); - } + if (!visible.contextMenu()) return; + basicContext.close(); + multiselect.clearSelection(); + if (visible.multiselect()) { + multiselect.close(); + } }; - var frame = { - /** @type {?Photo} */ - photo: null, - /** @type {Number} */ - nextTimeOutId: 0, - - _dom: { - /** - * Hidden image element with thumb variant of current image used - * as a source for blurring. - * @type {?HTMLImageElement} - */ - bgImage: null, - /** - * Canvas element which shows the blurry variant of `bgImage`. - * @type {?HTMLCanvasElement} - */ - canvas: null, - /** - * Image element which displays the full-size image - * @type {?HTMLImageElement} - */ - image: null, - /** - * Div element which works as a shutter to blend over between - * subsequent photos - * @type {?HTMLDivElement} - */ - shutter: null - } + /** @type {?Photo} */ + photo: null, + /** @type {Number} */ + nextTimeOutId: 0, + _dom: { + /** + * Hidden image element with thumb variant of current image used + * as a source for blurring. + * @type {?HTMLImageElement} + */ + bgImage: null, + /** + * Canvas element which shows the blurry variant of `bgImage`. + * @type {?HTMLCanvasElement} + */ + canvas: null, + /** + * Image element which displays the full-size image + * @type {?HTMLImageElement} + */ + image: null, + /** + * Div element which works as a shutter to blend over between + * subsequent photos + * @type {?HTMLDivElement} + */ + shutter: null + } }; /** @@ -4460,7 +4278,7 @@ var frame = { * @returns {boolean} */ frame.isRunning = function () { - return frame.nextTimeOutId !== 0; + return frame.nextTimeOutId !== 0; }; /** @@ -4468,11 +4286,11 @@ frame.isRunning = function () { * @returns {void} */ frame.stop = function () { - if (frame.nextTimeOutId !== 0) { - clearTimeout(frame.nextTimeOutId); - } - frame.photo = null; - frame.nextTimeOutId = 0; + if (frame.nextTimeOutId !== 0) { + clearTimeout(frame.nextTimeOutId); + } + frame.photo = null; + frame.nextTimeOutId = 0; }; /** @@ -4482,40 +4300,40 @@ frame.stop = function () { * @returns {void} */ frame.initAndStart = function () { - lychee.setMode("frame"); - if (frame._dom.bgImage === null) { - frame._dom.bgImage = document.getElementById("lychee_frame_bg_image"); - frame._dom.bgImage.addEventListener("load", function () { - // After a new background image has been loaded, draw a blurry - // version on the canvas. - StackBlur.image(frame._dom.bgImage, frame._dom.canvas, 20); - // We must reset the canvas to its originally defined dimensions - // as StackBlur resets it. - frame._dom.canvas.style.width = null; - frame._dom.canvas.style.height = null; - }); - } - if (frame._dom.canvas === null) { - frame._dom.canvas = document.getElementById("lychee_frame_bg_canvas"); - } - if (frame._dom.image === null) { - frame._dom.image = document.getElementById("lychee_frame_image"); - frame._dom.image.addEventListener("load", function () { - // After a new image has been loaded, open the shutter - frame._dom.shutter.classList.add("opened"); - }); - } - if (frame._dom.shutter === null) { - frame._dom.shutter = document.getElementById("lychee_frame_shutter"); - } - - // We also must call the very first invocation of `runPhotoLoop` - // asynchronously to ensure that `nextTimeOutId` is also set for the first - // call, otherwise `frame.isRunning` and `frame.stop` will report false - // results and not work during the first invocation. - frame.nextTimeOutId = setTimeout(function () { - return frame.runPhotoLoop(); - }, 0); + lychee.setMode("frame"); + if (frame._dom.bgImage === null) { + frame._dom.bgImage = document.getElementById("lychee_frame_bg_image"); + frame._dom.bgImage.addEventListener("load", function () { + // After a new background image has been loaded, draw a blurry + // version on the canvas. + StackBlur.image(frame._dom.bgImage, frame._dom.canvas, 20); + // We must reset the canvas to its originally defined dimensions + // as StackBlur resets it. + frame._dom.canvas.style.width = null; + frame._dom.canvas.style.height = null; + }); + } + if (frame._dom.canvas === null) { + frame._dom.canvas = document.getElementById("lychee_frame_bg_canvas"); + } + if (frame._dom.image === null) { + frame._dom.image = document.getElementById("lychee_frame_image"); + frame._dom.image.addEventListener("load", function () { + // After a new image has been loaded, open the shutter + frame._dom.shutter.classList.add("opened"); + }); + } + if (frame._dom.shutter === null) { + frame._dom.shutter = document.getElementById("lychee_frame_shutter"); + } + + // We also must call the very first invocation of `runPhotoLoop` + // asynchronously to ensure that `nextTimeOutId` is also set for the first + // call, otherwise `frame.isRunning` and `frame.stop` will report false + // results and not work during the first invocation. + frame.nextTimeOutId = setTimeout(function () { + return frame.runPhotoLoop(); + }, 0); }; /** @@ -4527,34 +4345,34 @@ frame.initAndStart = function () { * @returns {void} */ frame.runPhotoLoop = function () { - /** - * Forwards loaded photo to handler and recalls this method after the - * refresh timeout unless the loop hasn't been stopped in the meantime. - * - * @param {Photo} data - * @returns {void} - */ - var onSuccess = function onSuccess(data) { - frame.onRandomPhotoLoaded(data); - if (frame.nextTimeOutId !== 0) { - frame.nextTimeOutId = setTimeout(function () { - return frame.runPhotoLoop(); - }, 1000 * lychee.mod_frame_refresh); - } - }; - - // Closes the shutter and loads a new, random photo after that. - // The CSS defines that the shutter takes 1s to close; hence the - // 1s of timeout here and the duration of the animation as defined in the - // CSS must be aligned to for a pleasant visual experience. - frame._dom.shutter.classList.remove("opened"); - // Only set the timeout, if the loop hasn't been stopped in the - // meantime - if (frame.nextTimeOutId !== 0) { - frame.nextTimeOutId = setTimeout(function () { - return api.post("Photo::getRandom", {}, onSuccess); - }, 1000); - } + /** + * Forwards loaded photo to handler and recalls this method after the + * refresh timeout unless the loop hasn't been stopped in the meantime. + * + * @param {Photo} data + * @returns {void} + */ + var onSuccess = function onSuccess(data) { + frame.onRandomPhotoLoaded(data); + if (frame.nextTimeOutId !== 0) { + frame.nextTimeOutId = setTimeout(function () { + return frame.runPhotoLoop(); + }, 1000 * lychee.mod_frame_refresh); + } + }; + + // Closes the shutter and loads a new, random photo after that. + // The CSS defines that the shutter takes 1s to close; hence the + // 1s of timeout here and the duration of the animation as defined in the + // CSS must be aligned to for a pleasant visual experience. + frame._dom.shutter.classList.remove("opened"); + // Only set the timeout, if the loop hasn't been stopped in the + // meantime + if (frame.nextTimeOutId !== 0) { + frame.nextTimeOutId = setTimeout(function () { + return api.post("Photo::getRandom", {}, onSuccess); + }, 1000); + } }; /** @@ -4569,12 +4387,11 @@ frame.runPhotoLoop = function () { * @returns {void} */ frame.loadRandomPhoto = function (successCallback, errorCallback) { - api.post("Photo::getRandom", {}, - /** @param {Photo} data */ - function (data) { - frame.onRandomPhotoLoaded(data); - successCallback(data); - }, null, errorCallback); + api.post("Photo::getRandom", {}, /** @param {Photo} data */ + function (data) { + frame.onRandomPhotoLoaded(data); + successCallback(data); + }, null, errorCallback); }; /** @@ -4588,32 +4405,30 @@ frame.loadRandomPhoto = function (successCallback, errorCallback) { * @returns {void} */ frame.onRandomPhotoLoaded = function (photo) { - if (photo.size_variants.thumb) { - frame._dom.bgImage.src = photo.size_variants.thumb.url; - } else { - frame._dom.bgImage.src = ""; - console.log("Thumb not found"); - } - - frame.photo = photo; - frame._dom.image.src = photo.size_variants.medium !== null ? photo.size_variants.medium.url : photo.size_variants.original.url; - frame._dom.image.srcset = photo.size_variants.medium !== null && photo.size_variants.medium2x !== null ? photo.size_variants.medium.url + " " + photo.size_variants.medium.width + "w, " + photo.size_variants.medium2x.url + " " + photo.size_variants.medium2x.width + "w" : ""; - frame.resize(); + if (photo.size_variants.thumb) { + frame._dom.bgImage.src = photo.size_variants.thumb.url; + } else { + frame._dom.bgImage.src = ""; + console.log("Thumb not found"); + } + frame.photo = photo; + frame._dom.image.src = photo.size_variants.medium !== null ? photo.size_variants.medium.url : photo.size_variants.original.url; + frame._dom.image.srcset = photo.size_variants.medium !== null && photo.size_variants.medium2x !== null ? "".concat(photo.size_variants.medium.url, " ").concat(photo.size_variants.medium.width, "w, ").concat(photo.size_variants.medium2x.url, " ").concat(photo.size_variants.medium2x.width, "w") : ""; + frame.resize(); }; /** * @returns {void} */ frame.resize = function () { - if (frame.photo && frame._dom.image) { - var ratio = frame.photo.size_variants.original.height > 0 ? frame.photo.size_variants.original.width / frame.photo.size_variants.original.height : 1; - // Our math assumes that the image occupies the whole frame. That's - // not quite the case (the default css sets it to 95%) but it's close - // enough. - var width = window.innerWidth / ratio > window.innerHeight ? window.innerHeight * ratio : window.innerWidth; - - frame._dom.image.sizes = "" + width + "px"; - } + if (frame.photo && frame._dom.image) { + var ratio = frame.photo.size_variants.original.height > 0 ? frame.photo.size_variants.original.width / frame.photo.size_variants.original.height : 1; + // Our math assumes that the image occupies the whole frame. That's + // not quite the case (the default css sets it to 95%) but it's close + // enough. + var width = window.innerWidth / ratio > window.innerHeight ? window.innerHeight * ratio : window.innerWidth; + frame._dom.image.sizes = "" + width + "px"; + } }; /** @@ -4625,7 +4440,7 @@ frame.resize = function () { * @property {jQuery} _dom */ var header = { - _dom: $("#lychee_toolbar_container") + _dom: $("#lychee_toolbar_container") }; /** @@ -4633,176 +4448,164 @@ var header = { * @returns {jQuery} */ header.dom = function (selector) { - if (selector == null || selector === "") return header._dom; - return header._dom.find(selector); + if (selector == null || selector === "") return header._dom; + return header._dom.find(selector); }; /** * @returns {void} */ header.bind = function () { - // Event Name - var eventName = "click"; - - header.dom(".header__title").on(eventName, function (e) { - if ($(this).hasClass("header__title--editable") === false) return false; - - if (lychee.enable_contextmenu_header === false) return false; - - if (visible.photo()) contextMenu.photoTitle(album.getID(), _photo3.getID(), e);else contextMenu.albumTitle(album.getID(), e); - }); - - header.dom("#button_visibility").on(eventName, function () { - _photo3.setProtectionPolicy(_photo3.getID()); - }); - header.dom("#button_share").on(eventName, function (e) { - contextMenu.sharePhoto(_photo3.getID(), e); - }); - - header.dom("#button_visibility_album").on(eventName, function () { - album.setProtectionPolicy(album.getID()); - }); - - header.dom("#button_sharing_album_users").on(eventName, function () { - album.shareUsers(album.getID()); - }); - - header.dom("#button_share_album").on(eventName, function (e) { - contextMenu.shareAlbum(album.getID(), e); - }); - - header.dom("#button_signin").on(eventName, lychee.loginDialog); - header.dom("#button_settings").on(eventName, function (e) { - leftMenu.open(); - }); - header.dom("#button_close_config").on(eventName, function () { - tabindex.makeFocusable(header.dom()); - tabindex.makeFocusable(lychee.content); - tabindex.makeUnfocusable(leftMenu._dom); - multiselect.bind(); - lychee.load(); - }); - header.dom("#button_info_album").on(eventName, function () { - _sidebar.toggle(true); - }); - header.dom("#button_info").on(eventName, function () { - _sidebar.toggle(true); - }); - header.dom(".button--map-albums").on(eventName, function () { - lychee.gotoMap(); - }); - header.dom("#button_map_album").on(eventName, function () { - lychee.gotoMap(album.getID()); - }); - header.dom("#button_map").on(eventName, function () { - lychee.gotoMap(album.getID()); - }); - header.dom(".button_add").on(eventName, contextMenu.add); - header.dom("#button_more").on(eventName, function (e) { - contextMenu.photoMore(_photo3.getID(), e); - }); - header.dom("#button_move_album").on(eventName, function (e) { - contextMenu.move([album.getID()], e, album.setAlbum, "ROOT", album.getParentID() != null); - }); - header.dom("#button_nsfw_album").on(eventName, function () { - album.toggleNSFW(); - }); - header.dom("#button_move").on(eventName, function (e) { - contextMenu.move([_photo3.getID()], e, _photo3.setAlbum); - }); - header.dom(".header__hostedwith").on(eventName, function () { - window.open(lychee.website); - }); - header.dom("#button_trash_album").on(eventName, function () { - album.delete([album.getID()]); - }); - header.dom("#button_trash").on(eventName, function () { - _photo3.delete([_photo3.getID()]); - }); - header.dom("#button_archive").on(eventName, function () { - album.getArchive([album.getID()]); - }); - header.dom("#button_star").on(eventName, function () { - _photo3.toggleStar(); - }); - header.dom("#button_rotate_ccwise").on(eventName, function () { - photoeditor.rotate(_photo3.getID(), -1); - }); - header.dom("#button_rotate_cwise").on(eventName, function () { - photoeditor.rotate(_photo3.getID(), 1); - }); - header.dom("#button_back_home").on(eventName, function () { - if (!album.json.parent_id) { - lychee.goto(); - } else { - lychee.goto(album.getParentID()); - } - }); - header.dom("#button_back").on(eventName, function () { - lychee.goto(album.getID()); - }); - header.dom("#button_back_map").on(eventName, function () { - lychee.goto(album.getID()); - }); - header.dom("#button_fs_album_enter,#button_fs_enter").on(eventName, lychee.fullscreenEnter); - header.dom("#button_fs_album_exit,#button_fs_exit").on(eventName, lychee.fullscreenExit).hide(); - - header.dom(".header__search").on("keyup click", function () { - if ($(this).val().length > 0) { - lychee.goto(SearchAlbumIDPrefix + "/" + encodeURIComponent($(this).val())); - } else if (search.json !== null) { - search.reset(); - } - }); - header.dom(".header__clear").on(eventName, function () { - search.reset(); - }); - - header.bind_back(); + // Event Name + var eventName = "click"; + header.dom(".header__title").on(eventName, function (e) { + if ($(this).hasClass("header__title--editable") === false) return false; + if (lychee.enable_contextmenu_header === false) return false; + if (visible.photo()) contextMenu.photoTitle(album.getID(), _photo3.getID(), e);else contextMenu.albumTitle(album.getID(), e); + }); + header.dom("#button_visibility").on(eventName, function () { + _photo3.setProtectionPolicy(_photo3.getID()); + }); + header.dom("#button_share").on(eventName, function (e) { + contextMenu.sharePhoto(_photo3.getID(), e); + }); + header.dom("#button_visibility_album").on(eventName, function () { + album.setProtectionPolicy(album.getID()); + }); + header.dom("#button_sharing_album_users").on(eventName, function () { + album.shareUsers(album.getID()); + }); + header.dom("#button_share_album").on(eventName, function (e) { + contextMenu.shareAlbum(album.getID(), e); + }); + header.dom("#button_signin").on(eventName, lychee.loginDialog); + header.dom("#button_settings").on(eventName, function (e) { + leftMenu.open(); + }); + header.dom("#button_close_config").on(eventName, function () { + tabindex.makeFocusable(header.dom()); + tabindex.makeFocusable(lychee.content); + tabindex.makeUnfocusable(leftMenu._dom); + multiselect.bind(); + lychee.load(); + }); + header.dom("#button_info_album").on(eventName, function () { + _sidebar.toggle(true); + }); + header.dom("#button_info").on(eventName, function () { + _sidebar.toggle(true); + }); + header.dom(".button--map-albums").on(eventName, function () { + lychee.gotoMap(); + }); + header.dom("#button_map_album").on(eventName, function () { + lychee.gotoMap(album.getID()); + }); + header.dom("#button_map").on(eventName, function () { + lychee.gotoMap(album.getID()); + }); + header.dom(".button_add").on(eventName, contextMenu.add); + header.dom("#button_more").on(eventName, function (e) { + contextMenu.photoMore(_photo3.getID(), e); + }); + header.dom("#button_move_album").on(eventName, function (e) { + contextMenu.move([album.getID()], e, album.setAlbum, "ROOT", album.getParentID() != null); + }); + header.dom("#button_nsfw_album").on(eventName, function () { + album.toggleNSFW(); + }); + header.dom("#button_move").on(eventName, function (e) { + contextMenu.move([_photo3.getID()], e, _photo3.setAlbum); + }); + header.dom(".header__hostedwith").on(eventName, function () { + window.open(lychee.website); + }); + header.dom("#button_trash_album").on(eventName, function () { + album["delete"]([album.getID()]); + }); + header.dom("#button_trash").on(eventName, function () { + _photo3["delete"]([_photo3.getID()]); + }); + header.dom("#button_archive").on(eventName, function () { + album.getArchive([album.getID()]); + }); + header.dom("#button_star").on(eventName, function () { + _photo3.toggleStar(); + }); + header.dom("#button_rotate_ccwise").on(eventName, function () { + photoeditor.rotate(_photo3.getID(), -1); + }); + header.dom("#button_rotate_cwise").on(eventName, function () { + photoeditor.rotate(_photo3.getID(), 1); + }); + header.dom("#button_back_home").on(eventName, function () { + if (!album.json.parent_id) { + lychee["goto"](); + } else { + lychee["goto"](album.getParentID()); + } + }); + header.dom("#button_back").on(eventName, function () { + lychee["goto"](album.getID()); + }); + header.dom("#button_back_map").on(eventName, function () { + lychee["goto"](album.getID()); + }); + header.dom("#button_fs_album_enter,#button_fs_enter").on(eventName, lychee.fullscreenEnter); + header.dom("#button_fs_album_exit,#button_fs_exit").on(eventName, lychee.fullscreenExit).hide(); + header.dom(".header__search").on("keyup click", function () { + if ($(this).val().length > 0) { + lychee["goto"](SearchAlbumIDPrefix + "/" + encodeURIComponent($(this).val())); + } else if (search.json !== null) { + search.reset(); + } + }); + header.dom(".header__clear").on(eventName, function () { + search.reset(); + }); + header.bind_back(); }; /** * @returns {void} */ header.bind_back = function () { - header.dom(".header__title").on("click touchend", function () { - if (lychee.landing_page_enable && visible.albums()) { - window.location.href = "."; - } else { - return false; - } - }); + header.dom(".header__title").on("click touchend", function () { + if (lychee.landing_page_enable && visible.albums()) { + window.location.href = "."; + } else { + return false; + } + }); }; /** * @returns {void} */ header.show = function () { - lychee.imageview.removeClass("full"); - header.dom().removeClass("hidden"); - - tabindex.restoreSettings(header.dom()); + lychee.imageview.removeClass("full"); + header.dom().removeClass("hidden"); + tabindex.restoreSettings(header.dom()); }; /** * @returns {void} */ header.hideIfLivePhotoNotPlaying = function () { - // Hides the header, if current live photo is not playing - if (!_photo3.isLivePhotoPlaying()) header.hide(); + // Hides the header, if current live photo is not playing + if (!_photo3.isLivePhotoPlaying()) header.hide(); }; /** * @returns {void} */ header.hide = function () { - if (visible.photo() && !visible.sidebar() && !visible.contextMenu() && basicModal.isVisible() === false) { - tabindex.saveSettings(header.dom()); - tabindex.makeUnfocusable(header.dom()); - - lychee.imageview.addClass("full"); - header.dom().addClass("hidden"); - } + if (visible.photo() && !visible.sidebar() && !visible.contextMenu() && basicModal.isVisible() === false) { + tabindex.saveSettings(header.dom()); + tabindex.makeUnfocusable(header.dom()); + lychee.imageview.addClass("full"); + header.dom().addClass("hidden"); + } }; /** @@ -4810,10 +4613,9 @@ header.hide = function () { * @returns {void} */ header.setTitle = function (title) { - var $title = header.dom(".header__title"); - var html = lychee.html(_templateObject25, title, build.iconic("caret-bottom")); - - $title.html(html); + var $title = header.dom(".header__title"); + var html = lychee.html(_templateObject28 || (_templateObject28 = _taggedTemplateLiteral(["$", "", ""])), title, build.iconic("caret-bottom")); + $title.html(html); }; /** @@ -4835,337 +4637,320 @@ header.setTitle = function (title) { * @returns {void} */ header.setMode = function (mode) { - if (mode === "albums" && lychee.publicMode === true) mode = "public"; - - switch (mode) { - case "public": - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_public").addClass("visible"); - tabindex.makeUnfocusable(header.dom(".toolbar")); - tabindex.makeFocusable(header.dom("#lychee_toolbar_public")); - - if (lychee.public_search) { - var e = $(".header__search, .header__clear", "#lychee_toolbar_public"); - e.show(); - tabindex.makeFocusable(e); - } else { - var _e2 = $(".header__search, .header__clear", "#lychee_toolbar_public"); - _e2.hide(); - tabindex.makeUnfocusable(_e2); - } - - // Set icon in Public mode - if (lychee.map_display_public) { - var _e3 = $(".button--map-albums", "#lychee_toolbar_public"); - _e3.show(); - tabindex.makeFocusable(_e3); - } else { - var _e4 = $(".button--map-albums", "#lychee_toolbar_public"); - _e4.hide(); - tabindex.makeUnfocusable(_e4); - } - - // Set focus on login button - if (lychee.active_focus_on_page_load) { - $("#button_signin").focus(); - } - return; - - case "albums": - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_albums").addClass("visible"); - tabindex.makeUnfocusable(header.dom(".toolbar")); - tabindex.makeFocusable(header.dom("#lychee_toolbar_albums")); - - // If map is disabled, we should hide the icon - if (lychee.map_display) { - var _e5 = $(".button--map-albums", "#lychee_toolbar_albums"); - _e5.show(); - tabindex.makeFocusable(_e5); - } else { - var _e6 = $(".button--map-albums", "#lychee_toolbar_albums"); - _e6.hide(); - tabindex.makeUnfocusable(_e6); - } - - if (lychee.enable_button_add && lychee.rights.root_album.can_upload) { - var _e7 = $(".button_add", "#lychee_toolbar_albums"); - _e7.show(); - tabindex.makeFocusable(_e7); - } else { - var _e8 = $(".button_add", "#lychee_toolbar_albums"); - _e8.remove(); - } - - return; - - case "album": - var albumID = album.getID(); - - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_album").addClass("visible"); - tabindex.makeUnfocusable(header.dom(".toolbar")); - tabindex.makeFocusable(header.dom("#lychee_toolbar_album")); - - // Hide download button when album empty or we are not allowed to - // upload to it and it's not explicitly marked as downloadable. - if (!album.json || album.json.photos.length === 0 && album.json.albums && album.json.albums.length === 0 || !album.json.rights.can_download) { - var _e9 = $("#button_archive"); - _e9.hide(); - tabindex.makeUnfocusable(_e9); - } else { - var _e10 = $("#button_archive"); - _e10.show(); - tabindex.makeFocusable(_e10); - } - - if (!lychee.is_share_button_visible && ( - // The owner of an album (or the admin) shall always see - // the share button and be unaffected by the settings of - // the album - lychee.user === null || lychee.user.username !== album.json.owner_name) && !lychee.rights.is_admin) { - var _e11 = $("#button_share_album"); - _e11.hide(); - tabindex.makeUnfocusable(_e11); - } else { - var _e12 = $("#button_share_album"); - _e12.show(); - tabindex.makeFocusable(_e12); - } - - // If map is disabled, we should hide the icon - if (lychee.publicMode === true ? lychee.map_display_public : lychee.map_display) { - var _e13 = $("#button_map_album"); - _e13.show(); - tabindex.makeFocusable(_e13); - } else { - var _e14 = $("#button_map_album"); - _e14.hide(); - tabindex.makeUnfocusable(_e14); - } - - if (albumID === SmartAlbumID.STARRED || albumID === SmartAlbumID.PUBLIC || albumID === SmartAlbumID.RECENT || albumID === SmartAlbumID.ON_THIS_DAY) { - $("#button_nsfw_album, #button_info_album, #button_trash_album, #button_visibility_album, #button_sharing_album_users, #button_move_album").hide(); - if (album.isUploadable()) { - $(".button_add, .header__divider", "#lychee_toolbar_album").show(); - tabindex.makeFocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); - } else { - $(".button_add, .header__divider", "#lychee_toolbar_album").hide(); - tabindex.makeUnfocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); - } - tabindex.makeUnfocusable($("#button_nsfw_album, #button_info_album, #button_trash_album, #button_visibility_album, #button_sharing_album_users, #button_move_album")); - } else if (albumID === SmartAlbumID.UNSORTED) { - $("#button_nsfw_album, #button_info_album, #button_visibility_album, #button_sharing_album_users, #button_move_album").hide(); - $("#button_trash_album, .button_add, .header__divider", "#lychee_toolbar_album").show(); - tabindex.makeFocusable($("#button_trash_album, .button_add, .header__divider", "#lychee_toolbar_album")); - tabindex.makeUnfocusable($("#button_nsfw_album, #button_info_album, #button_visibility_album, #button_sharing_album_users, #button_move_album")); - } else if (album.isTagAlbum()) { - $("#button_info_album").show(); - if (_sidebar.keepSidebarVisible() && !visible.sidebar()) _sidebar.toggle(false); - $("#button_move_album").hide(); - $(".button_add, .header__divider", "#lychee_toolbar_album").hide(); - tabindex.makeFocusable($("#button_info_album")); - tabindex.makeUnfocusable($("#button_move_album")); - tabindex.makeUnfocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); - if (album.isUploadable()) { - $("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album").show(); - tabindex.makeFocusable($("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album")); - if ($("#button_visibility_album").is(":hidden")) { - // This can happen with narrow screens. In that - // case we re-enable the add button which will - // contain the overflow items. - $(".button_add, .header__divider", "#lychee_toolbar_album").show(); - tabindex.makeFocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); - } - } else { - $("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album").hide(); - tabindex.makeUnfocusable($("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album")); - } - } else { - $("#button_info_album").show(); - if (_sidebar.keepSidebarVisible() && !visible.sidebar()) _sidebar.toggle(false); - tabindex.makeFocusable($("#button_info_album")); - if (album.isUploadable()) { - $("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album").show(); - tabindex.makeFocusable($("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album")); - } else { - $("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album").hide(); - tabindex.makeUnfocusable($("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album")); - } - } - - // Remove buttons if needed - if (!lychee.enable_button_visibility) { - var _e15 = $("#button_visibility_album", "#button_sharing_album_users", "#lychee_toolbar_album"); - _e15.remove(); - } - if (!lychee.enable_button_share) { - var _e16 = $("#button_share_album", "#lychee_toolbar_album"); - _e16.remove(); - } - if (!lychee.enable_button_archive) { - var _e17 = $("#button_archive", "#lychee_toolbar_album"); - _e17.remove(); - } - if (!lychee.enable_button_move) { - var _e18 = $("#button_move_album", "#lychee_toolbar_album"); - _e18.remove(); - } - if (!lychee.enable_button_trash) { - var _e19 = $("#button_trash_album", "#lychee_toolbar_album"); - _e19.remove(); - } - if (!lychee.enable_button_fullscreen || !lychee.fullscreenAvailable()) { - var _e20 = $("#button_fs_album_enter", "#lychee_toolbar_album"); - _e20.remove(); - } - if (!lychee.enable_button_add) { - var _e21 = $(".button_add", "#lychee_toolbar_album"); - _e21.remove(); - } - - return; - - case "photo": - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_photo").addClass("visible"); - tabindex.makeUnfocusable(header.dom(".toolbar")); - tabindex.makeFocusable(header.dom("#lychee_toolbar_photo")); - // If map is disabled, we should hide the icon - if (lychee.publicMode === true ? lychee.map_display_public : lychee.map_display) { - var _e22 = $("#button_map"); - _e22.show(); - tabindex.makeFocusable(_e22); - } else { - var _e23 = $("#button_map"); - _e23.hide(); - tabindex.makeUnfocusable(_e23); - } - - if (lychee.enable_button_move && album.isUploadable()) { - var _e24 = $("#button_move"); - _e24.show(); - tabindex.makeFocusable(_e24); - } else { - var _e25 = $("#button_move"); - _e25.hide(); - tabindex.makeUnfocusable(_e25); - } - - if (!lychee.share_button_visible) { - var _e26 = $("#button_share"); - _e26.hide(); - tabindex.makeUnfocusable(_e26); - } else { - var _e27 = $("#button_share"); - _e27.show(); - tabindex.makeFocusable(_e27); - } - - if (lychee.enable_button_trash && album.isUploadable()) { - var _e28 = $("#button_trash"); - _e28.show(); - tabindex.makeFocusable(_e28); - } else { - var _e29 = $("#button_trash"); - _e29.hide(); - tabindex.makeUnfocusable(_e29); - } - - if (lychee.enable_button_fullscreen && lychee.fullscreenAvailable()) { - var _e30 = $("#button_fs_enter"); - _e30.show(); - } else { - var _e31 = $("#button_fs_enter"); - _e31.hide(); - } - - // Hide More menu if - // - empty (see contextMenu.photoMore) - // - not enabled - if (!lychee.enable_button_more || !( - // - album.isUploadable() || _photo3.json && !_photo3.json.rights.can_download && !_photo3.json.rights.can_access_full_photo && !(_photo3.json.size_variants.original.url && _photo3.json.size_variants.original.url !== ""))) { - var _e32 = $("#button_more"); - _e32.hide(); - tabindex.makeUnfocusable(_e32); - } else { - var _e33 = $("#button_more"); - _e33.show(); - tabindex.makeFocusable(_e33); - } - - // Hide buttons if needed - if (lychee.publicMode) { - var _e34 = $("#button_star", "#lychee_toolbar_photo"); - _e34.hide(); - tabindex.makeUnfocusable(_e34); - } else { - var _e35 = $("#button_star", "#lychee_toolbar_photo"); - _e35.show(); - } - - if (!lychee.enable_button_visibility || lychee.publicMode) { - var _e36 = $("#button_visibility", "#lychee_toolbar_photo"); - _e36.hide(); - } else { - var _e37 = $("#button_visibility", "#lychee_toolbar_photo"); - _e37.show(); - } - if (!lychee.enable_button_share) { - var _e38 = $("#button_share", "#lychee_toolbar_photo"); - _e38.hide(); - } else { - var _e39 = $("#button_share", "#lychee_toolbar_photo"); - _e39.show(); - } - if (!lychee.enable_button_move || lychee.publicMode) { - var _e40 = $("#button_move", "#lychee_toolbar_photo"); - _e40.hide(); - } else { - var _e41 = $("#button_move", "#lychee_toolbar_photo"); - _e41.show(); - } - if (!lychee.enable_button_trash || lychee.publicMode) { - var _e42 = $("#button_trash", "#lychee_toolbar_photo"); - _e42.hide(); - } else { - var _e43 = $("#button_trash", "#lychee_toolbar_photo"); - _e43.show(); - } - if (!lychee.enable_button_fullscreen || !lychee.fullscreenAvailable() || lychee.publicMode) { - var _e44 = $("#button_fs_enter", "#lychee_toolbar_photo"); - _e44.hide(); - } else { - var _e45 = $("#button_fs_enter", "#lychee_toolbar_photo"); - _e45.show(); - } - if (!lychee.enable_button_rotate || lychee.publicMode) { - var _e46 = $("#button_rotate_cwise", "#lychee_toolbar_photo"); - _e46.hide(); - - _e46 = $("#button_rotate_ccwise", "#lychee_toolbar_photo"); - _e46.hide(); - } else { - var _e47 = $("#button_rotate_cwise", "#lychee_toolbar_photo"); - _e47.show(); - - _e47 = $("#button_rotate_ccwise", "#lychee_toolbar_photo"); - _e47.show(); - tabindex.makeFocusable(_e47); - } - return; - case "map": - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_map").addClass("visible"); - tabindex.makeUnfocusable(header.dom(".toolbar")); - tabindex.makeFocusable(header.dom("#lychee_toolbar_map")); - return; - case "config": - header.dom(".toolbar").removeClass("visible"); - header.dom("#lychee_toolbar_config").addClass("visible"); - return; - } + if (mode === "albums" && lychee.publicMode === true) mode = "public"; + switch (mode) { + case "public": + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_public").addClass("visible"); + tabindex.makeUnfocusable(header.dom(".toolbar")); + tabindex.makeFocusable(header.dom("#lychee_toolbar_public")); + if (lychee.public_search) { + var e = $(".header__search, .header__clear", "#lychee_toolbar_public"); + e.show(); + tabindex.makeFocusable(e); + } else { + var _e2 = $(".header__search, .header__clear", "#lychee_toolbar_public"); + _e2.hide(); + tabindex.makeUnfocusable(_e2); + } + + // Set icon in Public mode + if (lychee.map_display_public) { + var _e3 = $(".button--map-albums", "#lychee_toolbar_public"); + _e3.show(); + tabindex.makeFocusable(_e3); + } else { + var _e4 = $(".button--map-albums", "#lychee_toolbar_public"); + _e4.hide(); + tabindex.makeUnfocusable(_e4); + } + + // Set focus on login button + if (lychee.active_focus_on_page_load) { + $("#button_signin").focus(); + } + return; + case "albums": + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_albums").addClass("visible"); + tabindex.makeUnfocusable(header.dom(".toolbar")); + tabindex.makeFocusable(header.dom("#lychee_toolbar_albums")); + + // If map is disabled, we should hide the icon + if (lychee.map_display) { + var _e5 = $(".button--map-albums", "#lychee_toolbar_albums"); + _e5.show(); + tabindex.makeFocusable(_e5); + } else { + var _e6 = $(".button--map-albums", "#lychee_toolbar_albums"); + _e6.hide(); + tabindex.makeUnfocusable(_e6); + } + if (lychee.enable_button_add && lychee.rights.root_album.can_upload) { + var _e7 = $(".button_add", "#lychee_toolbar_albums"); + _e7.show(); + tabindex.makeFocusable(_e7); + } else { + var _e8 = $(".button_add", "#lychee_toolbar_albums"); + _e8.remove(); + } + return; + case "album": + var albumID = album.getID(); + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_album").addClass("visible"); + tabindex.makeUnfocusable(header.dom(".toolbar")); + tabindex.makeFocusable(header.dom("#lychee_toolbar_album")); + + // Hide download button when album empty or we are not allowed to + // upload to it and it's not explicitly marked as downloadable. + if (!album.json || album.json.photos.length === 0 && album.json.albums && album.json.albums.length === 0 || !album.json.rights.can_download) { + var _e9 = $("#button_archive"); + _e9.hide(); + tabindex.makeUnfocusable(_e9); + } else { + var _e10 = $("#button_archive"); + _e10.show(); + tabindex.makeFocusable(_e10); + } + if (!lychee.is_share_button_visible && ( + // The owner of an album (or the admin) shall always see + // the share button and be unaffected by the settings of + // the album + lychee.user === null || lychee.user.username !== album.json.owner_name) && !lychee.rights.is_admin) { + var _e11 = $("#button_share_album"); + _e11.hide(); + tabindex.makeUnfocusable(_e11); + } else { + var _e12 = $("#button_share_album"); + _e12.show(); + tabindex.makeFocusable(_e12); + } + + // If map is disabled, we should hide the icon + if (lychee.publicMode === true ? lychee.map_display_public : lychee.map_display) { + var _e13 = $("#button_map_album"); + _e13.show(); + tabindex.makeFocusable(_e13); + } else { + var _e14 = $("#button_map_album"); + _e14.hide(); + tabindex.makeUnfocusable(_e14); + } + if (albumID === SmartAlbumID.STARRED || albumID === SmartAlbumID.PUBLIC || albumID === SmartAlbumID.RECENT || albumID === SmartAlbumID.ON_THIS_DAY) { + $("#button_nsfw_album, #button_info_album, #button_trash_album, #button_visibility_album, #button_sharing_album_users, #button_move_album").hide(); + if (album.isUploadable()) { + $(".button_add, .header__divider", "#lychee_toolbar_album").show(); + tabindex.makeFocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); + } else { + $(".button_add, .header__divider", "#lychee_toolbar_album").hide(); + tabindex.makeUnfocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); + } + tabindex.makeUnfocusable($("#button_nsfw_album, #button_info_album, #button_trash_album, #button_visibility_album, #button_sharing_album_users, #button_move_album")); + } else if (albumID === SmartAlbumID.UNSORTED) { + $("#button_nsfw_album, #button_info_album, #button_visibility_album, #button_sharing_album_users, #button_move_album").hide(); + $("#button_trash_album, .button_add, .header__divider", "#lychee_toolbar_album").show(); + tabindex.makeFocusable($("#button_trash_album, .button_add, .header__divider", "#lychee_toolbar_album")); + tabindex.makeUnfocusable($("#button_nsfw_album, #button_info_album, #button_visibility_album, #button_sharing_album_users, #button_move_album")); + } else if (album.isTagAlbum()) { + $("#button_info_album").show(); + if (_sidebar.keepSidebarVisible() && !visible.sidebar()) _sidebar.toggle(false); + $("#button_move_album").hide(); + $(".button_add, .header__divider", "#lychee_toolbar_album").hide(); + tabindex.makeFocusable($("#button_info_album")); + tabindex.makeUnfocusable($("#button_move_album")); + tabindex.makeUnfocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); + if (album.isUploadable()) { + $("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album").show(); + tabindex.makeFocusable($("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album")); + if ($("#button_visibility_album").is(":hidden")) { + // This can happen with narrow screens. In that + // case we re-enable the add button which will + // contain the overflow items. + $(".button_add, .header__divider", "#lychee_toolbar_album").show(); + tabindex.makeFocusable($(".button_add, .header__divider", "#lychee_toolbar_album")); + } + } else { + $("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album").hide(); + tabindex.makeUnfocusable($("#button_nsfw_album, #button_visibility_album, #button_sharing_album_users, #button_trash_album")); + } + } else { + $("#button_info_album").show(); + if (_sidebar.keepSidebarVisible() && !visible.sidebar()) _sidebar.toggle(false); + tabindex.makeFocusable($("#button_info_album")); + if (album.isUploadable()) { + $("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album").show(); + tabindex.makeFocusable($("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album")); + } else { + $("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album").hide(); + tabindex.makeUnfocusable($("#button_nsfw_album, #button_trash_album, #button_move_album, #button_visibility_album, #button_sharing_album_users, .button_add, .header__divider", "#lychee_toolbar_album")); + } + } + + // Remove buttons if needed + if (!lychee.enable_button_visibility) { + var _e15 = $("#button_visibility_album", "#button_sharing_album_users", "#lychee_toolbar_album"); + _e15.remove(); + } + if (!lychee.enable_button_share) { + var _e16 = $("#button_share_album", "#lychee_toolbar_album"); + _e16.remove(); + } + if (!lychee.enable_button_archive) { + var _e17 = $("#button_archive", "#lychee_toolbar_album"); + _e17.remove(); + } + if (!lychee.enable_button_move) { + var _e18 = $("#button_move_album", "#lychee_toolbar_album"); + _e18.remove(); + } + if (!lychee.enable_button_trash) { + var _e19 = $("#button_trash_album", "#lychee_toolbar_album"); + _e19.remove(); + } + if (!lychee.enable_button_fullscreen || !lychee.fullscreenAvailable()) { + var _e20 = $("#button_fs_album_enter", "#lychee_toolbar_album"); + _e20.remove(); + } + if (!lychee.enable_button_add) { + var _e21 = $(".button_add", "#lychee_toolbar_album"); + _e21.remove(); + } + return; + case "photo": + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_photo").addClass("visible"); + tabindex.makeUnfocusable(header.dom(".toolbar")); + tabindex.makeFocusable(header.dom("#lychee_toolbar_photo")); + // If map is disabled, we should hide the icon + if (lychee.publicMode === true ? lychee.map_display_public : lychee.map_display) { + var _e22 = $("#button_map"); + _e22.show(); + tabindex.makeFocusable(_e22); + } else { + var _e23 = $("#button_map"); + _e23.hide(); + tabindex.makeUnfocusable(_e23); + } + if (lychee.enable_button_move && album.isUploadable()) { + var _e24 = $("#button_move"); + _e24.show(); + tabindex.makeFocusable(_e24); + } else { + var _e25 = $("#button_move"); + _e25.hide(); + tabindex.makeUnfocusable(_e25); + } + if (!lychee.share_button_visible) { + var _e26 = $("#button_share"); + _e26.hide(); + tabindex.makeUnfocusable(_e26); + } else { + var _e27 = $("#button_share"); + _e27.show(); + tabindex.makeFocusable(_e27); + } + if (lychee.enable_button_trash && album.isUploadable()) { + var _e28 = $("#button_trash"); + _e28.show(); + tabindex.makeFocusable(_e28); + } else { + var _e29 = $("#button_trash"); + _e29.hide(); + tabindex.makeUnfocusable(_e29); + } + if (lychee.enable_button_fullscreen && lychee.fullscreenAvailable()) { + var _e30 = $("#button_fs_enter"); + _e30.show(); + } else { + var _e31 = $("#button_fs_enter"); + _e31.hide(); + } + + // Hide More menu if + // - empty (see contextMenu.photoMore) + // - not enabled + if (!lychee.enable_button_more || !( + // + + album.isUploadable() || _photo3.json && !_photo3.json.rights.can_download && !_photo3.json.rights.can_access_full_photo && !(_photo3.json.size_variants.original.url && _photo3.json.size_variants.original.url !== ""))) { + var _e32 = $("#button_more"); + _e32.hide(); + tabindex.makeUnfocusable(_e32); + } else { + var _e33 = $("#button_more"); + _e33.show(); + tabindex.makeFocusable(_e33); + } + + // Hide buttons if needed + if (lychee.publicMode) { + var _e34 = $("#button_star", "#lychee_toolbar_photo"); + _e34.hide(); + tabindex.makeUnfocusable(_e34); + } else { + var _e35 = $("#button_star", "#lychee_toolbar_photo"); + _e35.show(); + } + if (!lychee.enable_button_visibility || lychee.publicMode) { + var _e36 = $("#button_visibility", "#lychee_toolbar_photo"); + _e36.hide(); + } else { + var _e37 = $("#button_visibility", "#lychee_toolbar_photo"); + _e37.show(); + } + if (!lychee.enable_button_share) { + var _e38 = $("#button_share", "#lychee_toolbar_photo"); + _e38.hide(); + } else { + var _e39 = $("#button_share", "#lychee_toolbar_photo"); + _e39.show(); + } + if (!lychee.enable_button_move || lychee.publicMode) { + var _e40 = $("#button_move", "#lychee_toolbar_photo"); + _e40.hide(); + } else { + var _e41 = $("#button_move", "#lychee_toolbar_photo"); + _e41.show(); + } + if (!lychee.enable_button_trash || lychee.publicMode) { + var _e42 = $("#button_trash", "#lychee_toolbar_photo"); + _e42.hide(); + } else { + var _e43 = $("#button_trash", "#lychee_toolbar_photo"); + _e43.show(); + } + if (!lychee.enable_button_fullscreen || !lychee.fullscreenAvailable() || lychee.publicMode) { + var _e44 = $("#button_fs_enter", "#lychee_toolbar_photo"); + _e44.hide(); + } else { + var _e45 = $("#button_fs_enter", "#lychee_toolbar_photo"); + _e45.show(); + } + if (!lychee.enable_button_rotate || lychee.publicMode) { + var _e46 = $("#button_rotate_cwise", "#lychee_toolbar_photo"); + _e46.hide(); + _e46 = $("#button_rotate_ccwise", "#lychee_toolbar_photo"); + _e46.hide(); + } else { + var _e47 = $("#button_rotate_cwise", "#lychee_toolbar_photo"); + _e47.show(); + _e47 = $("#button_rotate_ccwise", "#lychee_toolbar_photo"); + _e47.show(); + tabindex.makeFocusable(_e47); + } + return; + case "map": + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_map").addClass("visible"); + tabindex.makeUnfocusable(header.dom(".toolbar")); + tabindex.makeFocusable(header.dom("#lychee_toolbar_map")); + return; + case "config": + header.dom(".toolbar").removeClass("visible"); + header.dom("#lychee_toolbar_config").addClass("visible"); + return; + } }; /** @@ -5177,9 +4962,8 @@ header.setMode = function (mode) { * @returns {void} */ header.setEditable = function (editable) { - var $title = header.dom(".header__title"); - - if (editable && !lychee.publicMode) $title.addClass("header__title--editable");else $title.removeClass("header__title--editable"); + var $title = header.dom(".header__title"); + if (editable && !lychee.publicMode) $title.addClass("header__title--editable");else $title.removeClass("header__title--editable"); }; /** @@ -5187,388 +4971,364 @@ header.setEditable = function (editable) { */ $(document).ready(function () { - // Event Name - var eventName = "click touchend"; - - // Set API error handler - api.onError = lychee.handleAPIError; - - // Make the application visible; initially the `` has an inline - // style `display: none` to avoid an ugly flash of massively over-sized - // icons from the header in case the HTML engine starts rendering before - // the (asynchronously loaded) CSS becomes available. - document.querySelector("body").style.display = null; - - // Multiselect - multiselect.bind(); - - // Header - header.bind(); - - // Image View - lychee.imageview.on(eventName, ".arrow_wrapper--previous", function () { - return _photo3.previous(false); - }).on(eventName, ".arrow_wrapper--next", function () { - return _photo3.next(false); - }).on(eventName, "img, #livephoto", function () { - return _photo3.cycle_display_overlay(); - }); - - // Keyboard - Mousetrap.addKeycodes({ - 18: "ContextMenu", - 179: "play_pause", - 227: "rewind", - 228: "forward" - }); - - Mousetrap.bind(["l"], function () { - lychee.loginDialog(); - return false; - }).bind(["k"], function () { - u2f.login(); - return false; - }).bind(["left"], function () { - if (visible.photo() && (!visible.header() || $("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { - $("#imageview a#previous").click(); - return false; - } - return true; - }).bind(["right"], function () { - if (visible.photo() && (!visible.header() || $("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { - $("#imageview a#next").click(); - return false; - } - return true; - }).bind(["u"], function () { - if (!visible.photo() && album.isUploadable() && !album.isTagAlbum()) { - $("#upload_files").click(); - return false; - } - }).bind(["n"], function () { - if (!visible.photo() && album.isUploadable()) { - album.add(); - return false; - } - }).bind(["s"], function () { - if (visible.photo() && album.isUploadable()) { - header.dom("#button_star").click(); - return false; - } else if (visible.albums()) { - header.dom(".header__search").focus(); - return false; - } - }).bind(["r"], function () { - if (album.isUploadable()) { - if (visible.album()) { - album.setTitle([album.getID()]); - return false; - } else if (visible.photo()) { - _photo3.setTitle([_photo3.getID()]); - return false; - } - } - }).bind(["h"], function () { - lychee.nsfw_visible = !lychee.nsfw_visible; - album.apply_nsfw_filter(); - return false; - }).bind(["d"], function () { - if (album.isUploadable()) { - if (visible.photo()) { - _photo3.setDescription(_photo3.getID()); - return false; - } else if (visible.album()) { - album.setDescription(album.getID()); - return false; - } - } - }).bind(["t"], function () { - if (visible.photo() && album.isUploadable()) { - _photo3.editTags([_photo3.getID()]); - return false; - } - }).bind(["i", "ContextMenu"], function () { - if (!visible.multiselect()) { - _sidebar.toggle(true); - return false; - } - }).bind(["command+backspace", "ctrl+backspace"], function () { - if (album.isUploadable()) { - if (visible.photo() && basicModal.isVisible() === false) { - _photo3.delete([_photo3.getID()]); - return false; - } else if (visible.album() && basicModal.isVisible() === false) { - album.delete([album.getID()]); - return false; - } - } - }).bind(["command+a", "ctrl+a"], function () { - if (visible.album() && basicModal.isVisible() === false) { - multiselect.selectAll(); - return false; - } else if (visible.albums() && basicModal.isVisible() === false) { - multiselect.selectAll(); - return false; - } - }).bind(["o"], function () { - if (visible.photo()) { - _photo3.cycle_display_overlay(); - return false; - } - }).bind(["f"], function () { - if (visible.album() || visible.photo()) { - lychee.fullscreenToggle(); - return false; - } - }); - - Mousetrap.bind(["play_pause"], function () { - // If it's a video, we toggle play/pause - var video = $("video"); - - if (video.length !== 0) { - if (video[0].paused) { - video[0].play(); - } else { - video[0].pause(); - } - } - }); - - Mousetrap.bindGlobal("enter", function () { - if (basicModal.isVisible() === true) { - // check if any of the input fields is focussed - // apply action, other do nothing - if ($(".basicModal__content input").is(":focus")) { - basicModal.action(); - return false; - } - } else if (visible.photo() && !lychee.header_auto_hide && ($("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { - if (visible.header()) { - header.hide(); - } else { - header.show(); - } - return false; - } - var clicked = false; - $(":focus").each(function () { - if (!$(this).is("input") && !$(this).is("textarea")) { - $(this).click(); - clicked = true; - } - }); - if (clicked) { - return false; - } - }); - - // Prevent 'esc keyup' event to trigger 'go back in history' - // and 'alt keyup' to show a webapp context menu for Fire TV - Mousetrap.bindGlobal(["esc", "ContextMenu"], function () { - return false; - }, "keyup"); - - Mousetrap.bindGlobal(["esc", "command+up"], function () { - if (basicModal.isVisible() === true) basicModal.cancel();else if (visible.config() || visible.leftMenu()) leftMenu.close();else if (visible.contextMenu()) contextMenu.close();else if (visible.photo()) lychee.goto(album.getID());else if (visible.album() && !album.json.parent_id) lychee.goto();else if (visible.album()) lychee.goto(album.getParentID());else if (visible.albums() && search.json !== null) search.reset();else if (visible.mapview()) mapview.close();else if (visible.albums() && lychee.enable_close_tab_on_esc) { - window.open("", "_self").close(); - } - return false; - }); - - $(document) - // Fullscreen on mobile - .on("touchend", "#imageview #image", function () { - // prevent triggering event 'mousemove' - // why? this also prevents 'click' from firing which results in unexpected behaviour - // unable to reproduce problems arising from 'mousemove' on iOS devices - // e.preventDefault(); - - if (typeof swipe.obj === null || Math.abs(swipe.offsetX) <= 5 && Math.abs(swipe.offsetY) <= 5) { - // Toggle header only if we're not moving to next/previous photo; - // In this case, swipe.preventNextHeaderToggle is set to true - if (!swipe.preventNextHeaderToggle) { - if (visible.header()) { - header.hide(); - } else { - header.show(); - } - } - - // For next 'touchend', behave again as normal and toggle header - swipe.preventNextHeaderToggle = false; - } - }); - $("#imageview") - // Swipe on mobile - .swipe().on("swipeStart", function () { - if (visible.photo()) swipe.start($("#imageview #image, #imageview #livephoto")); - }).swipe().on("swipeMove", - /** @param {jQuery.Event} e */function (e) { - if (visible.photo()) swipe.move(e.swipe); - }).swipe().on("swipeEnd", - /** @param {jQuery.Event} e */function (e) { - if (visible.photo()) swipe.stop(e.swipe, _photo3.previous, _photo3.next); - }); - - // Document - $(document) - // Navigation - .on("click", ".album", - /** @param {jQuery.Event} e */function (e) { - multiselect.albumClick(e, $(this)); - }).on("click", ".photo", - /** @param {jQuery.Event} e */function (e) { - multiselect.photoClick(e, $(this)); - }) - // Context Menu - .on("contextmenu", ".photo", - /** @param {jQuery.Event} e */function (e) { - multiselect.photoContextMenu(e, $(this)); - }).on("contextmenu", ".album", - /** @param {jQuery.Event} e */function (e) { - multiselect.albumContextMenu(e, $(this)); - }) - // Upload - .on("change", "#upload_files", function () { - var _this = this; - - basicModal.close(false, function () { - return upload.start.local(_this.files); - }); - }).on("change", "#upload_track_file", function () { - var _this2 = this; - - basicModal.close(false, function () { - return upload.uploadTrack(_this2.files); - }); - }) - // Drag and Drop upload - .on("dragover", function () { - return false; - }, false) - // In the long run, the "drop" event should not be defined on the - // global document element, but on the `DIV` which corresponds to the - // view onto which something is dropped. - // This would also avoid this highly fragile condition below. - // For example, in order to avoid that a photo unintentionally ends - // up in the root album when someone drops a photo while the - // setting screen is opened, we check for `!visible.config()`. - // This would simply not be necessary, if the drop event was directly - // defined on the albums view where it belongs. - // The conditions whether a user is allowed to upload to the root - // album (cp. `visible.albums()` below) or to a regular album - // (cp. `visible.album()` below) are slightly different. - // Nonetheless, we only have a single method `album.isUploadable` - // which tries to cover both cases and is prone to fail in certain - // corner cases. - // If the drop event was defined on the DIV for the root view and on - // the DIV for an album view, the whole problem would not exist. - // TODO: Fix that - .on("drop", - /** @param {jQuery.Event} e */function (e) { - if (album.isUploadable() && !visible.contextMenu() && !basicModal.isVisible() && !visible.leftMenu() && !visible.config() && (visible.album() || visible.albums())) { - // Detect if dropped item is a file or a link - if (e.originalEvent.dataTransfer.files.length > 0) { - upload.start.local(e.originalEvent.dataTransfer.files); - } else if (e.originalEvent.dataTransfer.getData("Text").length > 3 && !e.originalEvent.dataTransfer.getData("Text").startsWith("photo-") && // block drag and drop from albums/photos in web UI - !e.originalEvent.dataTransfer.getData("Text").startsWith("album-")) { - upload.start.url(e.originalEvent.dataTransfer.getData("Text")); - } - } - - return false; - }) - // click on thumbnail on map - .on("click", ".image-leaflet-popup", function () { - mapview.goto($(this)); - }) - // Paste upload - .on("paste", - /** @param {jQuery.Event} e */function (e) { - if (e.originalEvent.clipboardData.items) { - var items = e.originalEvent.clipboardData.items; - var filesToUpload = []; - - // Search clipboard items for an image - for (var i = 0; i < items.length; i++) { - if (items[i].type.indexOf("image") !== -1 || items[i].type.indexOf("video") !== -1) { - filesToUpload.push(items[i].getAsFile()); - } - } - - // We perform the check so deep because we don't want to - // prevent the paste from working in text input fields, etc. - if (filesToUpload.length > 0 && album.isUploadable() && !visible.contextMenu() && !basicModal.isVisible() && !visible.leftMenu() && !visible.config() && (visible.album() || visible.albums())) { - upload.start.local(filesToUpload); - - return false; - } else { - return true; - } - } - }); - - // Fullscreen - if (lychee.fullscreenAvailable()) $(document).on("fullscreenchange mozfullscreenchange webkitfullscreenchange msfullscreenchange", lychee.fullscreenUpdate); - - $("#sensitive_warning").on("click", view.album.nsfw_warning.next); - - /** - * @returns {void} - */ - var rememberScrollPage = function rememberScrollPage() { - if (visible.albums() && !visible.search() || visible.album()) { - var urls = JSON.parse(localStorage.getItem("scroll")); - if (urls == null || urls.length < 1) { - urls = {}; - } - - var urlWindow = window.location.href; - var urlScroll = $("#lychee_view_container").scrollTop(); - - urls[urlWindow] = urlScroll; - - if (urlScroll < 1) { - delete urls[urlWindow]; - } - - localStorage.setItem("scroll", JSON.stringify(urls)); - } - }; - - $(window).on("resize", function () { - if (visible.photo()) view.photo.onresize(); - frame.resize(); - }); - - $("#lychee_view_container").on("scroll", function () { - rememberScrollPage(); - }); - - // Init - lychee.init(); -}); - -/** - * @description This module is used for the context menu. - */ - -/** - * @namespace - * @property {jQuery} _dom - */ -var leftMenu = { - _dom: $("#lychee_left_menu_container") -}; + // Event Name + var eventName = "click touchend"; + + // Set API error handler + api.onError = lychee.handleAPIError; + + // Make the application visible; initially the `` has an inline + // style `display: none` to avoid an ugly flash of massively over-sized + // icons from the header in case the HTML engine starts rendering before + // the (asynchronously loaded) CSS becomes available. + document.querySelector("body").style.display = null; + + // Multiselect + multiselect.bind(); + + // Header + header.bind(); + + // Image View + lychee.imageview.on(eventName, ".arrow_wrapper--previous", function () { + return _photo3.previous(false); + }).on(eventName, ".arrow_wrapper--next", function () { + return _photo3.next(false); + }).on(eventName, "img, #livephoto", function () { + return _photo3.cycle_display_overlay(); + }); -/** - * @param {?string} [selector=null] - * @returns {jQuery} + // Keyboard + Mousetrap.addKeycodes({ + 18: "ContextMenu", + 179: "play_pause", + 227: "rewind", + 228: "forward" + }); + Mousetrap.bind(["l"], function () { + lychee.loginDialog(); + return false; + }).bind(["k"], function () { + u2f.login(); + return false; + }).bind(["left"], function () { + if (visible.photo() && (!visible.header() || $("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { + $("#imageview a#previous").click(); + return false; + } + return true; + }).bind(["right"], function () { + if (visible.photo() && (!visible.header() || $("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { + $("#imageview a#next").click(); + return false; + } + return true; + }).bind(["u"], function () { + if (!visible.photo() && album.isUploadable() && !album.isTagAlbum()) { + $("#upload_files").click(); + return false; + } + }).bind(["n"], function () { + if (!visible.photo() && album.isUploadable()) { + album.add(); + return false; + } + }).bind(["s"], function () { + if (visible.photo() && album.isUploadable()) { + header.dom("#button_star").click(); + return false; + } else if (visible.albums()) { + header.dom(".header__search").focus(); + return false; + } + }).bind(["r"], function () { + if (album.isUploadable()) { + if (visible.album()) { + album.setTitle([album.getID()]); + return false; + } else if (visible.photo()) { + _photo3.setTitle([_photo3.getID()]); + return false; + } + } + }).bind(["h"], function () { + lychee.nsfw_visible = !lychee.nsfw_visible; + album.apply_nsfw_filter(); + return false; + }).bind(["d"], function () { + if (album.isUploadable()) { + if (visible.photo()) { + _photo3.setDescription(_photo3.getID()); + return false; + } else if (visible.album()) { + album.setDescription(album.getID()); + return false; + } + } + }).bind(["t"], function () { + if (visible.photo() && album.isUploadable()) { + _photo3.editTags([_photo3.getID()]); + return false; + } + }).bind(["i", "ContextMenu"], function () { + if (!visible.multiselect()) { + _sidebar.toggle(true); + return false; + } + }).bind(["command+backspace", "ctrl+backspace"], function () { + if (album.isUploadable()) { + if (visible.photo() && basicModal.isVisible() === false) { + _photo3["delete"]([_photo3.getID()]); + return false; + } else if (visible.album() && basicModal.isVisible() === false) { + album["delete"]([album.getID()]); + return false; + } + } + }).bind(["command+a", "ctrl+a"], function () { + if (visible.album() && basicModal.isVisible() === false) { + multiselect.selectAll(); + return false; + } else if (visible.albums() && basicModal.isVisible() === false) { + multiselect.selectAll(); + return false; + } + }).bind(["o"], function () { + if (visible.photo()) { + _photo3.cycle_display_overlay(); + return false; + } + }).bind(["f"], function () { + if (visible.album() || visible.photo()) { + lychee.fullscreenToggle(); + return false; + } + }); + Mousetrap.bind(["play_pause"], function () { + // If it's a video, we toggle play/pause + var video = $("video"); + if (video.length !== 0) { + if (video[0].paused) { + video[0].play(); + } else { + video[0].pause(); + } + } + }); + Mousetrap.bindGlobal("enter", function () { + if (basicModal.isVisible() === true) { + // check if any of the input fields is focussed + // apply action, other do nothing + if ($(".basicModal__content input").is(":focus")) { + basicModal.action(); + return false; + } + } else if (visible.photo() && !lychee.header_auto_hide && ($("img#image").is(":focus") || $("img#livephoto").is(":focus") || $(":focus").length === 0)) { + if (visible.header()) { + header.hide(); + } else { + header.show(); + } + return false; + } + var clicked = false; + $(":focus").each(function () { + if (!$(this).is("input") && !$(this).is("textarea")) { + $(this).click(); + clicked = true; + } + }); + if (clicked) { + return false; + } + }); + + // Prevent 'esc keyup' event to trigger 'go back in history' + // and 'alt keyup' to show a webapp context menu for Fire TV + Mousetrap.bindGlobal(["esc", "ContextMenu"], function () { + return false; + }, "keyup"); + Mousetrap.bindGlobal(["esc", "command+up"], function () { + if (basicModal.isVisible() === true) basicModal.cancel();else if (visible.config() || visible.leftMenu()) leftMenu.close();else if (visible.contextMenu()) contextMenu.close();else if (visible.photo()) lychee["goto"](album.getID());else if (visible.album() && !album.json.parent_id) lychee["goto"]();else if (visible.album()) lychee["goto"](album.getParentID());else if (visible.albums() && search.json !== null) search.reset();else if (visible.mapview()) mapview.close();else if (visible.albums() && lychee.enable_close_tab_on_esc) { + window.open("", "_self").close(); + } + return false; + }); + $(document) + // Fullscreen on mobile + .on("touchend", "#imageview #image", function () { + // prevent triggering event 'mousemove' + // why? this also prevents 'click' from firing which results in unexpected behaviour + // unable to reproduce problems arising from 'mousemove' on iOS devices + // e.preventDefault(); + + if (_typeof(swipe.obj) === null || Math.abs(swipe.offsetX) <= 5 && Math.abs(swipe.offsetY) <= 5) { + // Toggle header only if we're not moving to next/previous photo; + // In this case, swipe.preventNextHeaderToggle is set to true + if (!swipe.preventNextHeaderToggle) { + if (visible.header()) { + header.hide(); + } else { + header.show(); + } + } + + // For next 'touchend', behave again as normal and toggle header + swipe.preventNextHeaderToggle = false; + } + }); + $("#imageview") + // Swipe on mobile + .swipe().on("swipeStart", function () { + if (visible.photo()) swipe.start($("#imageview #image, #imageview #livephoto")); + }).swipe().on("swipeMove", /** @param {jQuery.Event} e */function (e) { + if (visible.photo()) swipe.move(e.swipe); + }).swipe().on("swipeEnd", /** @param {jQuery.Event} e */function (e) { + if (visible.photo()) swipe.stop(e.swipe, _photo3.previous, _photo3.next); + }); + + // Document + $(document) + // Navigation + .on("click", ".album", /** @param {jQuery.Event} e */function (e) { + multiselect.albumClick(e, $(this)); + }).on("click", ".photo", /** @param {jQuery.Event} e */function (e) { + multiselect.photoClick(e, $(this)); + }) + // Context Menu + .on("contextmenu", ".photo", /** @param {jQuery.Event} e */function (e) { + multiselect.photoContextMenu(e, $(this)); + }).on("contextmenu", ".album", /** @param {jQuery.Event} e */function (e) { + multiselect.albumContextMenu(e, $(this)); + }) + // Upload + .on("change", "#upload_files", function () { + var _this = this; + basicModal.close(false, function () { + return upload.start.local(_this.files); + }); + }).on("change", "#upload_track_file", function () { + var _this2 = this; + basicModal.close(false, function () { + return upload.uploadTrack(_this2.files); + }); + }) + // Drag and Drop upload + .on("dragover", function () { + return false; + }, false) + // In the long run, the "drop" event should not be defined on the + // global document element, but on the `DIV` which corresponds to the + // view onto which something is dropped. + // This would also avoid this highly fragile condition below. + // For example, in order to avoid that a photo unintentionally ends + // up in the root album when someone drops a photo while the + // setting screen is opened, we check for `!visible.config()`. + // This would simply not be necessary, if the drop event was directly + // defined on the albums view where it belongs. + // The conditions whether a user is allowed to upload to the root + // album (cp. `visible.albums()` below) or to a regular album + // (cp. `visible.album()` below) are slightly different. + // Nonetheless, we only have a single method `album.isUploadable` + // which tries to cover both cases and is prone to fail in certain + // corner cases. + // If the drop event was defined on the DIV for the root view and on + // the DIV for an album view, the whole problem would not exist. + // TODO: Fix that + .on("drop", /** @param {jQuery.Event} e */function (e) { + if (album.isUploadable() && !visible.contextMenu() && !basicModal.isVisible() && !visible.leftMenu() && !visible.config() && (visible.album() || visible.albums())) { + // Detect if dropped item is a file or a link + if (e.originalEvent.dataTransfer.files.length > 0) { + upload.start.local(e.originalEvent.dataTransfer.files); + } else if (e.originalEvent.dataTransfer.getData("Text").length > 3 && !e.originalEvent.dataTransfer.getData("Text").startsWith("photo-") && + // block drag and drop from albums/photos in web UI + !e.originalEvent.dataTransfer.getData("Text").startsWith("album-")) { + upload.start.url(e.originalEvent.dataTransfer.getData("Text")); + } + } + return false; + }) + // click on thumbnail on map + .on("click", ".image-leaflet-popup", function () { + mapview["goto"]($(this)); + }) + // Paste upload + .on("paste", /** @param {jQuery.Event} e */function (e) { + if (e.originalEvent.clipboardData.items) { + var items = e.originalEvent.clipboardData.items; + var filesToUpload = []; + + // Search clipboard items for an image + for (var i = 0; i < items.length; i++) { + if (items[i].type.indexOf("image") !== -1 || items[i].type.indexOf("video") !== -1) { + filesToUpload.push(items[i].getAsFile()); + } + } + + // We perform the check so deep because we don't want to + // prevent the paste from working in text input fields, etc. + if (filesToUpload.length > 0 && album.isUploadable() && !visible.contextMenu() && !basicModal.isVisible() && !visible.leftMenu() && !visible.config() && (visible.album() || visible.albums())) { + upload.start.local(filesToUpload); + return false; + } else { + return true; + } + } + }); + + // Fullscreen + if (lychee.fullscreenAvailable()) $(document).on("fullscreenchange mozfullscreenchange webkitfullscreenchange msfullscreenchange", lychee.fullscreenUpdate); + $("#sensitive_warning").on("click", view.album.nsfw_warning.next); + + /** + * @returns {void} + */ + var rememberScrollPage = function rememberScrollPage() { + if (visible.albums() && !visible.search() || visible.album()) { + var urls = JSON.parse(localStorage.getItem("scroll")); + if (urls == null || urls.length < 1) { + urls = {}; + } + var urlWindow = window.location.href; + var urlScroll = $("#lychee_view_container").scrollTop(); + urls[urlWindow] = urlScroll; + if (urlScroll < 1) { + delete urls[urlWindow]; + } + localStorage.setItem("scroll", JSON.stringify(urls)); + } + }; + $(window).on("resize", function () { + if (visible.photo()) view.photo.onresize(); + frame.resize(); + }); + $("#lychee_view_container").on("scroll", function () { + rememberScrollPage(); + }); + + // Init + lychee.init(); +}); + +/** + * @description This module is used for the context menu. + */ + +/** + * @namespace + * @property {jQuery} _dom + */ +var leftMenu = { + _dom: $("#lychee_left_menu_container") +}; + +/** + * @param {?string} [selector=null] + * @returns {jQuery} */ leftMenu.dom = function (selector) { - if (selector == null || selector === "") return leftMenu._dom; - return leftMenu._dom.find(selector); + if (selector == null || selector === "") return leftMenu._dom; + return leftMenu._dom.find(selector); }; /** @@ -5576,34 +5336,33 @@ leftMenu.dom = function (selector) { * @returns {void} */ leftMenu.build = function () { - var html = lychee.html(_templateObject26, build.iconic("chevron-left"), lychee.locale["CLOSE"]); - - if (lychee.rights.settings.can_edit || lychee.rights.user.can_edit) { - html += lychee.html(_templateObject27, lychee.locale["SETTINGS"]); - } - if (lychee.new_photos_notification) { - html += lychee.html(_templateObject28, build.iconic("bell"), lychee.locale["NOTIFICATIONS"]); - } - if (lychee.rights.user_management.can_edit) { - html += lychee.html(_templateObject29, build.iconic("person"), lychee.locale["USERS"]); - } - if (lychee.rights.user.can_use_2fa) { - html += lychee.html(_templateObject30, build.iconic("key"), lychee.locale["U2F"]); - } - if (lychee.rights.root_album.can_upload) { - html += lychee.html(_templateObject31, build.iconic("cloud"), lychee.locale["SHARING"]); - } - if (lychee.rights.settings.can_see_logs) { - html += lychee.html(_templateObject32, build.iconic("align-left"), lychee.locale["LOGS"]); - } - if (lychee.rights.settings.can_see_diagnostics) { - html += lychee.html(_templateObject33, build.iconic("wrench"), lychee.locale["DIAGNOSTICS"]); - } - html += lychee.html(_templateObject34, build.iconic("info"), lychee.locale["ABOUT_LYCHEE"], build.iconic("account-logout"), lychee.locale["SIGN_OUT"]); - if (lychee.rights.settings.can_update && lychee.update_available) { - html += lychee.html(_templateObject35, build.iconic("timer"), lychee.locale["UPDATE_AVAILABLE"]); - } - leftMenu.dom("#lychee_left_menu").html(html); + var html = lychee.html(_templateObject29 || (_templateObject29 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t"])), build.iconic("chevron-left"), lychee.locale["CLOSE"]); + if (lychee.rights.settings.can_edit || lychee.rights.user.can_edit) { + html += lychee.html(_templateObject30 || (_templateObject30 = _taggedTemplateLiteral(["\n\t\t", "\n\t\t"])), lychee.locale["SETTINGS"]); + } + if (lychee.new_photos_notification) { + html += lychee.html(_templateObject31 || (_templateObject31 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"])), build.iconic("bell"), lychee.locale["NOTIFICATIONS"]); + } + if (lychee.rights.user_management.can_edit) { + html += lychee.html(_templateObject32 || (_templateObject32 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"])), build.iconic("person"), lychee.locale["USERS"]); + } + if (lychee.rights.user.can_use_2fa) { + html += lychee.html(_templateObject33 || (_templateObject33 = _taggedTemplateLiteral(["\n\t\t", "", " \n\t\t"])), build.iconic("key"), lychee.locale["U2F"]); + } + if (lychee.rights.root_album.can_upload) { + html += lychee.html(_templateObject34 || (_templateObject34 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"])), build.iconic("cloud"), lychee.locale["SHARING"]); + } + if (lychee.rights.settings.can_see_logs) { + html += lychee.html(_templateObject35 || (_templateObject35 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"])), build.iconic("align-left"), lychee.locale["LOGS"]); + } + if (lychee.rights.settings.can_see_diagnostics) { + html += lychee.html(_templateObject36 || (_templateObject36 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"])), build.iconic("wrench"), lychee.locale["DIAGNOSTICS"]); + } + html += lychee.html(_templateObject37 || (_templateObject37 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t", "", ""])), build.iconic("info"), lychee.locale["ABOUT_LYCHEE"], build.iconic("account-logout"), lychee.locale["SIGN_OUT"]); + if (lychee.rights.settings.can_update && lychee.update_available) { + html += lychee.html(_templateObject38 || (_templateObject38 = _taggedTemplateLiteral(["\n\t\t", "", "\n\t\t"])), build.iconic("timer"), lychee.locale["UPDATE_AVAILABLE"]); + } + leftMenu.dom("#lychee_left_menu").html(html); }; /** Set the width of the side navigation to 250px and the left margin of the page content to 250px @@ -5611,15 +5370,14 @@ leftMenu.build = function () { * @returns {void} */ leftMenu.open = function () { - leftMenu.dom().addClass("visible"); + leftMenu.dom().addClass("visible"); - // Make background unfocusable - tabindex.makeUnfocusable(header.dom()); - tabindex.makeUnfocusable(lychee.content); - tabindex.makeFocusable(leftMenu.dom()); - $("#button_signout").focus(); - - multiselect.unbind(); + // Make background unfocusable + tabindex.makeUnfocusable(header.dom()); + tabindex.makeUnfocusable(lychee.content); + tabindex.makeFocusable(leftMenu.dom()); + $("#button_signout").focus(); + multiselect.unbind(); }; /** @@ -5628,14 +5386,12 @@ leftMenu.open = function () { * @returns {void} */ leftMenu.close = function () { - leftMenu.dom().removeClass("visible"); - - tabindex.makeFocusable(header.dom()); - tabindex.makeFocusable(lychee.content); - tabindex.makeUnfocusable(leftMenu.dom()); - - multiselect.bind(); - lychee.load(); + leftMenu.dom().removeClass("visible"); + tabindex.makeFocusable(header.dom()); + tabindex.makeFocusable(lychee.content); + tabindex.makeUnfocusable(leftMenu.dom()); + multiselect.bind(); + lychee.load(); }; /** @@ -5644,92 +5400,90 @@ leftMenu.close = function () { * @returns {void} */ leftMenu.closeIfResponsive = function () { - if (window.matchMedia("only screen and (max-width: 567px), only screen and (max-width: 640px) and (orientation: portrait)").matches) { - leftMenu.dom().removeClass("visible"); - - tabindex.makeFocusable(header.dom()); - tabindex.makeFocusable(lychee.content); - tabindex.makeUnfocusable(leftMenu.dom()); - } + if (window.matchMedia("only screen and (max-width: 567px), only screen and (max-width: 640px) and (orientation: portrait)").matches) { + leftMenu.dom().removeClass("visible"); + tabindex.makeFocusable(header.dom()); + tabindex.makeFocusable(lychee.content); + tabindex.makeUnfocusable(leftMenu.dom()); + } }; /** * @returns {void} */ leftMenu.bind = function () { - // Event Name - var eventName = "click"; - - leftMenu.dom("#button_settings_close").on(eventName, leftMenu.close); - leftMenu.dom("#button_settings_open").on(eventName, function () { - leftMenu.closeIfResponsive(); - settings.open(); - }); - leftMenu.dom("#button_signout").on(eventName, lychee.logout); - leftMenu.dom("#button_logs").on(eventName, leftMenu.Logs); - leftMenu.dom("#button_diagnostics").on(eventName, leftMenu.Diagnostics); - leftMenu.dom("#button_about").on(eventName, lychee.aboutDialog); - leftMenu.dom("#button_notifications").on(eventName, leftMenu.Notifications); - leftMenu.dom("#button_users").on(eventName, leftMenu.Users); - leftMenu.dom("#button_u2f").on(eventName, leftMenu.u2f); - leftMenu.dom("#button_sharing").on(eventName, leftMenu.Sharing); - leftMenu.dom("#button_update").on(eventName, leftMenu.Update); + // Event Name + var eventName = "click"; + leftMenu.dom("#button_settings_close").on(eventName, leftMenu.close); + leftMenu.dom("#button_settings_open").on(eventName, function () { + leftMenu.closeIfResponsive(); + settings.open(); + }); + leftMenu.dom("#button_signout").on(eventName, lychee.logout); + leftMenu.dom("#button_logs").on(eventName, leftMenu.Logs); + leftMenu.dom("#button_diagnostics").on(eventName, leftMenu.Diagnostics); + leftMenu.dom("#button_about").on(eventName, lychee.aboutDialog); + leftMenu.dom("#button_notifications").on(eventName, leftMenu.Notifications); + leftMenu.dom("#button_users").on(eventName, leftMenu.Users); + leftMenu.dom("#button_u2f").on(eventName, leftMenu.u2f); + leftMenu.dom("#button_sharing").on(eventName, leftMenu.Sharing); + leftMenu.dom("#button_update").on(eventName, leftMenu.Update); }; /** * @returns {void} */ leftMenu.Logs = function () { - leftMenu.closeIfResponsive(); - view.logs.init(); + leftMenu.closeIfResponsive(); + view.logs.init(); }; /** * @returns {void} */ leftMenu.Diagnostics = function () { - leftMenu.closeIfResponsive(); - view.diagnostics.init(); + leftMenu.closeIfResponsive(); + view.diagnostics.init(); }; /** * @returns {void} */ leftMenu.Update = function () { - leftMenu.closeIfResponsive(); - view.update.init(); + leftMenu.closeIfResponsive(); + view.update.init(); }; /** * @returns {void} */ leftMenu.Notifications = function () { - leftMenu.closeIfResponsive(); - notifications.load(); + leftMenu.closeIfResponsive(); + notifications.load(); }; /** * @returns {void} */ leftMenu.Users = function () { - leftMenu.closeIfResponsive(); - users.list(); + leftMenu.closeIfResponsive(); + users.list(); }; /** * @returns {void} */ leftMenu.u2f = function () { - leftMenu.closeIfResponsive(); - u2f.list(); + leftMenu.closeIfResponsive(); + u2f.list(); }; /** * @returns {void} */ leftMenu.Sharing = function () { - leftMenu.closeIfResponsive(); - sharing.list(); + leftMenu.closeIfResponsive(); + sharing.list(); }; /** @@ -5737,10 +5491,10 @@ leftMenu.Sharing = function () { */ var loadingBar = { - /** @type {?string} */ - status: null, - /** @type {jQuery} */ - _dom: $("#lychee_loading") + /** @type {?string} */ + status: null, + /** @type {jQuery} */ + _dom: $("#lychee_loading") }; /** @@ -5748,8 +5502,8 @@ var loadingBar = { * @returns {jQuery} */ loadingBar.dom = function (selector) { - if (selector == null || selector === "") return loadingBar._dom; - return loadingBar._dom.find(selector); + if (selector == null || selector === "") return loadingBar._dom; + return loadingBar._dom.find(selector); }; /** @@ -5758,60 +5512,55 @@ loadingBar.dom = function (selector) { * @returns {void} */ loadingBar.show = function () { - var status = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - var errorText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - - if (status === "error") { - // Set status - loadingBar.status = "error"; - - // Parse text - if (errorText) errorText = errorText.replace("
", ""); - if (!errorText) errorText = lychee.locale["ERROR_TEXT"]; - - // Modify loading - loadingBar.dom().removeClass().html("

" + lychee.locale["ERROR"] + (": " + errorText + "

")).addClass(status); - - // Set timeout - clearTimeout(loadingBar._timeout); - loadingBar._timeout = setTimeout(function () { - return loadingBar.hide(true); - }, 3000); - - return; - } - - if (status === "success") { - // Set status - loadingBar.status = "success"; - - // Parse text - if (errorText) errorText = errorText.replace("
", ""); - if (!errorText) errorText = lychee.locale["ERROR_TEXT"]; - - // Modify loading - loadingBar.dom().removeClass().html("

" + lychee.locale["SUCCESS"] + (": " + errorText + "

")).addClass(status); - - // Set timeout - clearTimeout(loadingBar._timeout); - loadingBar._timeout = setTimeout(function () { - return loadingBar.hide(true); - }, 2000); - - return; - } - - if (loadingBar.status === null) { - // Set status - loadingBar.status = lychee.locale["LOADING"]; - - // Set timeout - clearTimeout(loadingBar._timeout); - loadingBar._timeout = setTimeout(function () { - // Modify loading - loadingBar.dom().removeClass().html("").addClass("loading"); - }, 1000); - } + var status = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var errorText = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + if (status === "error") { + // Set status + loadingBar.status = "error"; + + // Parse text + if (errorText) errorText = errorText.replace("
", ""); + if (!errorText) errorText = lychee.locale["ERROR_TEXT"]; + + // Modify loading + loadingBar.dom().removeClass().html("

" + lychee.locale["ERROR"] + ": ".concat(errorText, "

")).addClass(status); + + // Set timeout + clearTimeout(loadingBar._timeout); + loadingBar._timeout = setTimeout(function () { + return loadingBar.hide(true); + }, 3000); + return; + } + if (status === "success") { + // Set status + loadingBar.status = "success"; + + // Parse text + if (errorText) errorText = errorText.replace("
", ""); + if (!errorText) errorText = lychee.locale["ERROR_TEXT"]; + + // Modify loading + loadingBar.dom().removeClass().html("

" + lychee.locale["SUCCESS"] + ": ".concat(errorText, "

")).addClass(status); + + // Set timeout + clearTimeout(loadingBar._timeout); + loadingBar._timeout = setTimeout(function () { + return loadingBar.hide(true); + }, 2000); + return; + } + if (loadingBar.status === null) { + // Set status + loadingBar.status = lychee.locale["LOADING"]; + + // Set timeout + clearTimeout(loadingBar._timeout); + loadingBar._timeout = setTimeout(function () { + // Modify loading + loadingBar.dom().removeClass().html("").addClass("loading"); + }, 1000); + } }; /** @@ -5819,20 +5568,20 @@ loadingBar.show = function () { * @returns {void} */ loadingBar.hide = function (force) { - if (loadingBar.status !== "error" && loadingBar.status !== "success" && loadingBar.status != null || force) { - // Remove status - loadingBar.status = null; - - // Also move up the dark background - $(".basicModalContainer").removeClass("basicModalContainer--error"); - $(".basicModal").removeClass("basicModal--error"); - - // Set timeout - clearTimeout(loadingBar._timeout); - setTimeout(function () { - return loadingBar.dom().removeClass(); - }, 300); - } + if (loadingBar.status !== "error" && loadingBar.status !== "success" && loadingBar.status != null || force) { + // Remove status + loadingBar.status = null; + + // Also move up the dark background + $(".basicModalContainer").removeClass("basicModalContainer--error"); + $(".basicModal").removeClass("basicModal--error"); + + // Set timeout + clearTimeout(loadingBar._timeout); + setTimeout(function () { + return loadingBar.dom().removeClass(); + }, 300); + } }; /** @@ -5840,327 +5589,308 @@ loadingBar.hide = function (force) { */ var lychee = { - /** - * The version of the backend in human-readable - * @type {Version} - */ - version: null, - - updateGit: "https://github.com/LycheeOrg/Lychee", - updateURL: "https://github.com/LycheeOrg/Lychee/releases", - website: "https://LycheeOrg.github.io", - - publicMode: false, - viewMode: false, - grants_full_photo_access: true, - grants_download: false, - public_photos_hidden: true, - share_button_visible: false, - /** - * The authenticated user or `null` if unauthenticated - * @type {?User} - */ - user: null, - /** - * The rights granted by the backend - * @type {?GlobalRightsDTO} - */ - rights: null, - /** - * Values: - * - * - `0`: Use default, "square" layout. - * - `1`: Use Flickr-like "justified" layout. - * - `2`: Use Google-like "unjustified" layout - * - * @type {number} - */ - layout: 1, - /** - * Display search in public mode. - * @type {boolean} - */ - public_search: false, - /** - * Overlay display type - * @type {string} - */ - image_overlay_type: "exif", - /** - * Image overlay type default type - * @type {string} - */ - image_overlay_type_default: "exif", - /** - * Display photo coordinates on map - * @type {boolean} - */ - map_display: false, - /** - * Display photos of public album on map (user not logged in) - * @type {boolean} - */ - map_display_public: false, - /** - * Use the GPS direction data on displayed maps - * @type {boolean} - */ - map_display_direction: true, - /** - * Provider of OSM Tiles - * @type {string} - */ - map_provider: "Wikimedia", - /** - * Include photos of subalbums on map - * @type {boolean} - */ - map_include_subalbums: false, - /** - * Retrieve location name from GPS data - * @type {boolean} - */ - location_decoding: false, - /** - * Caching mode for GPS data decoding - * @type {string} - */ - location_decoding_caching_type: "Harddisk", - /** - * Show location name - * @type {boolean} - */ - location_show: false, - /** - * Show location name for public albums - * @type {boolean} - */ - location_show_public: false, - /** - * Tolerance for navigating when swiping images to the left and right on mobile - * @type {number} - */ - swipe_tolerance_x: 150, - /** - * Tolerance for navigating when swiping images up and down - * @type {number} - */ - swipe_tolerance_y: 250, - - /** - * Is landing page enabled? - * @type {boolean} - */ - landing_page_enabled: false, - delete_imported: false, - import_via_symlink: false, - skip_duplicates: false, - - nsfw_visible: true, - nsfw_visible_saved: true, - nsfw_blur: false, - nsfw_warning: false, - /** @type {string} */ - nsfw_banner_override: "", - - album_subtitle_type: "oldstyle", - - upload_processing_limit: 4, - - /** - * Allow users to change their username - * @type {boolean} - */ - allow_username_change: true, - - /** - * The URL to the Facebook page related to this site - * @type {string} - */ - sm_facebook_url: "", - /** - * The URL to the Flickr page related to this site - * @type {string} - */ - sm_flickr_url: "", - /** - * The URL to the Instagram page related to this site - * @type {string} - */ - sm_instagram_url: "", - /** - * The URL to the Twitter page related to this site - * @type {string} - */ - sm_twitter_url: "", - /** - * The URL to the YouTube channel related to this site - * @type {string} - */ - sm_youtube_url: "", - /** - * Indicates whether RSS feeds are enabled or not - * @type {boolean} - */ - rss_enable: false, - /** - * An array of RSS feeds provided by the site - * @type {Feed[]} - */ - rss_feeds: [], - /** - * The site title. - * @type {string} - */ - site_title: "", - /** - * The name of the site owner. - * @type {string} - */ - site_owner: "", - /** - * Begin of copyright. - * @type {string} - */ - site_copyright_begin: "", - /** - * End of copyright. - * @type {string} - */ - site_copyright_end: "", - - /** - * Determines if social media links are shown in footer. - * @type {boolean} - */ - footer_show_social_media: false, - /** - * Determines if copyright notice is shown in footer. - * @type {boolean} - */ - footer_show_copyright: false, - /** - * An optional line of text to be shown in the footer. - * @type {string} - */ - footer_additional_text: "", - - /** - * Determines whether frame mode is enabled or not - * @type {boolean} - */ - mod_frame_enabled: false, - /** - * Refresh rate in seconds for the frame mode. - * @type {number} - */ - mod_frame_refresh: 30, - - // this is device specific config, in this case default is Desktop. - header_auto_hide: true, - active_focus_on_page_load: false, - enable_button_visibility: true, - enable_button_share: true, - enable_button_archive: true, - enable_button_move: true, - enable_button_trash: true, - enable_button_fullscreen: true, - enable_button_download: true, - enable_button_add: true, - enable_button_more: true, - enable_button_rotate: true, - enable_close_tab_on_esc: false, - enable_tabindex: false, - enable_contextmenu_header: true, - hide_content_during_imgview: false, - - checkForUpdates: true, - update_json: false, - update_available: false, - new_photos_notification: false, - /** @type {?SortingCriterion} */ - sorting_photos: null, - /** @type {?SortingCriterion} */ - sorting_albums: null, - /** - * The absolute path of the server-side installation directory of Lychee, e.g. `/var/www/lychee` - * @type {string} - */ - location: "", - - lang: "", - /** @type {string[]} */ - lang_available: [], - - dropbox: false, - dropboxKey: "", - - content: $("#lychee_view_content"), - imageview: $("#imageview"), - footer: $("#lychee_footer"), - - /** @type {Locale} */ - locale: {}, - - nsfw_unlocked_albums: [] + /** + * The version of the backend in human-readable + * @type {Version} + */ + version: null, + updateGit: "https://github.com/LycheeOrg/Lychee", + updateURL: "https://github.com/LycheeOrg/Lychee/releases", + website: "https://LycheeOrg.github.io", + publicMode: false, + viewMode: false, + grants_full_photo_access: true, + grants_download: false, + public_photos_hidden: true, + share_button_visible: false, + /** + * The authenticated user or `null` if unauthenticated + * @type {?User} + */ + user: null, + /** + * The rights granted by the backend + * @type {?GlobalRightsDTO} + */ + rights: null, + /** + * Values: + * + * - `0`: Use default, "square" layout. + * - `1`: Use Flickr-like "justified" layout. + * - `2`: Use Google-like "unjustified" layout + * + * @type {number} + */ + layout: 1, + /** + * Display search in public mode. + * @type {boolean} + */ + public_search: false, + /** + * Overlay display type + * @type {string} + */ + image_overlay_type: "exif", + /** + * Image overlay type default type + * @type {string} + */ + image_overlay_type_default: "exif", + /** + * Display photo coordinates on map + * @type {boolean} + */ + map_display: false, + /** + * Display photos of public album on map (user not logged in) + * @type {boolean} + */ + map_display_public: false, + /** + * Use the GPS direction data on displayed maps + * @type {boolean} + */ + map_display_direction: true, + /** + * Provider of OSM Tiles + * @type {string} + */ + map_provider: "Wikimedia", + /** + * Include photos of subalbums on map + * @type {boolean} + */ + map_include_subalbums: false, + /** + * Retrieve location name from GPS data + * @type {boolean} + */ + location_decoding: false, + /** + * Caching mode for GPS data decoding + * @type {string} + */ + location_decoding_caching_type: "Harddisk", + /** + * Show location name + * @type {boolean} + */ + location_show: false, + /** + * Show location name for public albums + * @type {boolean} + */ + location_show_public: false, + /** + * Tolerance for navigating when swiping images to the left and right on mobile + * @type {number} + */ + swipe_tolerance_x: 150, + /** + * Tolerance for navigating when swiping images up and down + * @type {number} + */ + swipe_tolerance_y: 250, + /** + * Is landing page enabled? + * @type {boolean} + */ + landing_page_enabled: false, + delete_imported: false, + import_via_symlink: false, + skip_duplicates: false, + nsfw_visible: true, + nsfw_visible_saved: true, + nsfw_blur: false, + nsfw_warning: false, + /** @type {string} */ + nsfw_banner_override: "", + album_subtitle_type: "oldstyle", + upload_processing_limit: 4, + /** + * Allow users to change their username + * @type {boolean} + */ + allow_username_change: true, + /** + * The URL to the Facebook page related to this site + * @type {string} + */ + sm_facebook_url: "", + /** + * The URL to the Flickr page related to this site + * @type {string} + */ + sm_flickr_url: "", + /** + * The URL to the Instagram page related to this site + * @type {string} + */ + sm_instagram_url: "", + /** + * The URL to the Twitter page related to this site + * @type {string} + */ + sm_twitter_url: "", + /** + * The URL to the YouTube channel related to this site + * @type {string} + */ + sm_youtube_url: "", + /** + * Indicates whether RSS feeds are enabled or not + * @type {boolean} + */ + rss_enable: false, + /** + * An array of RSS feeds provided by the site + * @type {Feed[]} + */ + rss_feeds: [], + /** + * The site title. + * @type {string} + */ + site_title: "", + /** + * The name of the site owner. + * @type {string} + */ + site_owner: "", + /** + * Begin of copyright. + * @type {string} + */ + site_copyright_begin: "", + /** + * End of copyright. + * @type {string} + */ + site_copyright_end: "", + /** + * Determines if social media links are shown in footer. + * @type {boolean} + */ + footer_show_social_media: false, + /** + * Determines if copyright notice is shown in footer. + * @type {boolean} + */ + footer_show_copyright: false, + /** + * An optional line of text to be shown in the footer. + * @type {string} + */ + footer_additional_text: "", + /** + * Determines whether frame mode is enabled or not + * @type {boolean} + */ + mod_frame_enabled: false, + /** + * Refresh rate in seconds for the frame mode. + * @type {number} + */ + mod_frame_refresh: 30, + // this is device specific config, in this case default is Desktop. + header_auto_hide: true, + active_focus_on_page_load: false, + enable_button_visibility: true, + enable_button_share: true, + enable_button_archive: true, + enable_button_move: true, + enable_button_trash: true, + enable_button_fullscreen: true, + enable_button_download: true, + enable_button_add: true, + enable_button_more: true, + enable_button_rotate: true, + enable_close_tab_on_esc: false, + enable_tabindex: false, + enable_contextmenu_header: true, + hide_content_during_imgview: false, + checkForUpdates: true, + update_json: false, + update_available: false, + new_photos_notification: false, + /** @type {?SortingCriterion} */ + sorting_photos: null, + /** @type {?SortingCriterion} */ + sorting_albums: null, + /** + * The absolute path of the server-side installation directory of Lychee, e.g. `/var/www/lychee` + * @type {string} + */ + location: "", + lang: "", + /** @type {string[]} */ + lang_available: [], + dropbox: false, + dropboxKey: "", + content: $("#lychee_view_content"), + imageview: $("#imageview"), + footer: $("#lychee_footer"), + /** @type {Locale} */ + locale: {}, + nsfw_unlocked_albums: [] }; /** * @returns {string} */ lychee.diagnostics = function () { - return "/Diagnostics"; + return "/Diagnostics"; }; /** * @returns {string} */ lychee.logs = function () { - return "/Logs"; + return "/Logs"; }; /** * @returns {void} */ lychee.aboutDialog = function () { - var aboutDialogBody = "\n\t\t

Lychee

\n\t\t

\n\t\t

\n\t\t

\n\t\t

"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initAboutDialog = function initAboutDialog(formElements, dialog) { - dialog.querySelector("span.version-number").textContent = lychee.version.major + "." + lychee.version.minor + "." + lychee.version.patch; - // If Release is available : show release - // If Git is available : show git - if (lychee.update_available) { - dialog.querySelector("p.up-to-date-release a").textContent = lychee.locale["UPDATE_AVAILABLE"]; - dialog.querySelector("p.up-to-date-release").classList.remove("up-to-date-release"); - } else if (lychee.update_json) { - dialog.querySelector("p.up-to-date-git a").textContent = lychee.locale["UPDATE_AVAILABLE"]; - dialog.querySelector("p.up-to-date-git").classList.remove("up-to-date-git"); - } - - dialog.querySelector("h2").textContent = lychee.locale["ABOUT_SUBTITLE"]; - // We should not use `innerHTML`, but either hard-code HTML or build it - // programmatically. - // Also, localized strings should not contain HTML tags. - // TODO: Find a better solution for this. - dialog.querySelector("p.about-desc").innerHTML = sprintf(lychee.locale["ABOUT_DESCRIPTION"], lychee.website); - }; - - basicModal.show({ - body: aboutDialogBody, - readyCB: initAboutDialog, - classList: ["about-dialog"], - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); + var aboutDialogBody = "\n\t\t

Lychee

\n\t\t

\n\t\t

\n\t\t

\n\t\t

"); + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initAboutDialog = function initAboutDialog(formElements, dialog) { + dialog.querySelector("span.version-number").textContent = lychee.version.major + "." + lychee.version.minor + "." + lychee.version.patch; + // If Release is available : show release + // If Git is available : show git + if (lychee.update_available) { + dialog.querySelector("p.up-to-date-release a").textContent = lychee.locale["UPDATE_AVAILABLE"]; + dialog.querySelector("p.up-to-date-release").classList.remove("up-to-date-release"); + } else if (lychee.update_json) { + dialog.querySelector("p.up-to-date-git a").textContent = lychee.locale["UPDATE_AVAILABLE"]; + dialog.querySelector("p.up-to-date-git").classList.remove("up-to-date-git"); + } + dialog.querySelector("h2").textContent = lychee.locale["ABOUT_SUBTITLE"]; + // We should not use `innerHTML`, but either hard-code HTML or build it + // programmatically. + // Also, localized strings should not contain HTML tags. + // TODO: Find a better solution for this. + dialog.querySelector("p.about-desc").innerHTML = sprintf(lychee.locale["ABOUT_DESCRIPTION"], lychee.website); + }; + basicModal.show({ + body: aboutDialogBody, + readyCB: initAboutDialog, + classList: ["about-dialog"], + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); }; /** @@ -6171,30 +5901,26 @@ lychee.aboutDialog = function () { * @returns {void} */ lychee.init = function () { - var isFirstInitialization = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - api.post("Session::init", {}, - /** @param {InitializationData} data */ - function (data) { - lychee.parseInitializationData(data); - - if (data.user !== null || data.rights.settings.can_edit) { - // Authenticated or no admin is registered - leftMenu.build(); - leftMenu.bind(); - lychee.setMode("logged_in"); - } else { - lychee.setMode("public"); - } - - if (isFirstInitialization) { - $(window).on("popstate", function () { - var autoplay = history.state && history.state.hasOwnProperty("autoplay") ? history.state.autoplay : true; - lychee.load(autoplay); - }); - lychee.load(); - } - }); + var isFirstInitialization = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + api.post("Session::init", {}, /** @param {InitializationData} data */ + function (data) { + lychee.parseInitializationData(data); + if (data.user !== null || data.rights.settings.can_edit) { + // Authenticated or no admin is registered + leftMenu.build(); + leftMenu.bind(); + lychee.setMode("logged_in"); + } else { + lychee.setMode("public"); + } + if (isFirstInitialization) { + $(window).on("popstate", function () { + var autoplay = history.state && history.state.hasOwnProperty("autoplay") ? history.state.autoplay : true; + lychee.load(autoplay); + }); + lychee.load(); + } + }); }; /** @@ -6202,27 +5928,25 @@ lychee.init = function () { * @returns {void} */ lychee.parseInitializationData = function (data) { - lychee.user = data.user; - lychee.rights = data.rights; - lychee.update_json = data.update_json; - lychee.update_available = data.update_available; - lychee.version = data.config.version; - - // we copy the locale that exists only. - // This ensures forward and backward compatibility. - // e.g. if the front localization is unfinished in a language - // or if we need to change some locale string - for (var key in data.locale) { - lychee.locale[key] = data.locale[key]; - } - - lychee.parsePublicInitializationData(data); - if (lychee.user !== null || lychee.rights.settings.can_edit) { - lychee.parseProtectedInitializationData(data); - } - - lychee.initHtmlHeader(); - lychee.localizeStaticGuiElements(); + lychee.user = data.user; + lychee.rights = data.rights; + lychee.update_json = data.update_json; + lychee.update_available = data.update_available; + lychee.version = data.config.version; + + // we copy the locale that exists only. + // This ensures forward and backward compatibility. + // e.g. if the front localization is unfinished in a language + // or if we need to change some locale string + for (var key in data.locale) { + lychee.locale[key] = data.locale[key]; + } + lychee.parsePublicInitializationData(data); + if (lychee.user !== null || lychee.rights.settings.can_edit) { + lychee.parseProtectedInitializationData(data); + } + lychee.initHtmlHeader(); + lychee.localizeStaticGuiElements(); }; /** @@ -6234,21 +5958,21 @@ lychee.parseInitializationData = function (data) { * but is static for the entire site. */ lychee.initHtmlHeader = function () { - // General Meta Data - document.querySelector('meta[name="author"]').content = lychee.site_owner; - document.querySelector('meta[name="publisher"]').content = lychee.site_owner; - // RSS feeds - if (lychee.rss_enable) { - var head = document.querySelector("head"); - lychee.rss_feeds.forEach(function (feed) { - var link = document.createElement("link"); - link.rel = "alternate"; - link.type = feed.mimetype; - link.href = feed.url; - link.title = feed.title; - head.appendChild(link); - }); - } + // General Meta Data + document.querySelector('meta[name="author"]').content = lychee.site_owner; + document.querySelector('meta[name="publisher"]').content = lychee.site_owner; + // RSS feeds + if (lychee.rss_enable) { + var head = document.querySelector("head"); + lychee.rss_feeds.forEach(function (feed) { + var link = document.createElement("link"); + link.rel = "alternate"; + link.type = feed.mimetype; + link.href = feed.url; + link.title = feed.title; + head.appendChild(link); + }); + } }; /** @@ -6263,87 +5987,82 @@ lychee.initHtmlHeader = function () { * @return {void} */ lychee.localizeStaticGuiElements = function () { - // Toolbars in the header - var tbPublic = document.querySelector("div#lychee_toolbar_public"); - tbPublic.querySelector("a#button_signin").title = lychee.locale["SIGN_IN"]; - var tbPublicSearch = tbPublic.querySelector("input.header__search"); - if (tbPublicSearch instanceof HTMLInputElement) { - // See remark about `lychee.setMode` in the jsDoc comment of this method. - tbPublicSearch.placeholder = lychee.locale["SEARCH"]; - } - tbPublic.querySelector("a.button--map-albums").title = lychee.locale["DISPLAY_FULL_MAP"]; - - var tbAlbums = document.querySelector("div#lychee_toolbar_albums"); - tbAlbums.querySelector("a#button_settings").title = lychee.locale["SETTINGS"]; - var tbAlbumsSearch = tbAlbums.querySelector("input.header__search"); - if (tbAlbumsSearch instanceof HTMLInputElement) { - // See remark about `lychee.setMode` in the jsDoc comment of this method. - tbAlbumsSearch.placeholder = lychee.locale["SEARCH"]; - } - tbAlbums.querySelector("a.button--map-albums").title = lychee.locale["DISPLAY_FULL_MAP"]; - tbAlbums.querySelector("a.button_add").title = lychee.locale["ADD"]; - - var tbAlbum = document.querySelector("div#lychee_toolbar_album"); - tbAlbum.querySelector("a#button_back_home").title = lychee.locale["CLOSE_ALBUM"]; - tbAlbum.querySelector("a#button_visibility_album").title = lychee.locale["VISIBILITY_ALBUM"]; - tbAlbum.querySelector("a#button_sharing_album_users").title = lychee.locale["SHARING_ALBUM_USERS"]; - tbAlbum.querySelector("a#button_nsfw_album").title = lychee.locale["ALBUM_MARK_NSFW"]; - tbAlbum.querySelector("a#button_share_album").title = lychee.locale["SHARE_ALBUM"]; - tbAlbum.querySelector("a#button_archive").title = lychee.locale["DOWNLOAD_ALBUM"]; - tbAlbum.querySelector("a#button_info_album").title = lychee.locale["ABOUT_ALBUM"]; - tbAlbum.querySelector("a#button_map_album").title = lychee.locale["DISPLAY_FULL_MAP"]; - tbAlbum.querySelector("a#button_move_album").title = lychee.locale["MOVE_ALBUM"]; - tbAlbum.querySelector("a#button_trash_album").title = lychee.locale["DELETE_ALBUM"]; - tbAlbum.querySelector("a#button_fs_album_enter").title = lychee.locale["FULLSCREEN_ENTER"]; - tbAlbum.querySelector("a#button_fs_album_exit").title = lychee.locale["FULLSCREEN_EXIT"]; - tbAlbum.querySelector("a.button_add").title = lychee.locale["ADD"]; - - var tbPhoto = document.querySelector("div#lychee_toolbar_photo"); - tbPhoto.querySelector("a#button_back").title = lychee.locale["CLOSE_PHOTO"]; - tbPhoto.querySelector("a#button_star").title = lychee.locale["STAR_PHOTO"]; - tbPhoto.querySelector("a#button_visibility").title = lychee.locale["VISIBILITY_PHOTO"]; - tbPhoto.querySelector("a#button_rotate_ccwise").title = lychee.locale["PHOTO_EDIT_ROTATECCWISE"]; - tbPhoto.querySelector("a#button_rotate_cwise").title = lychee.locale["PHOTO_EDIT_ROTATECWISE"]; - tbPhoto.querySelector("a#button_share").title = lychee.locale["SHARE_PHOTO"]; - tbPhoto.querySelector("a#button_info").title = lychee.locale["ABOUT_PHOTO"]; - tbPhoto.querySelector("a#button_map").title = lychee.locale["DISPLAY_FULL_MAP"]; - tbPhoto.querySelector("a#button_move").title = lychee.locale["MOVE"]; - tbPhoto.querySelector("a#button_trash").title = lychee.locale["DELETE"]; - tbPhoto.querySelector("a#button_fs_enter").title = lychee.locale["FULLSCREEN_ENTER"]; - tbPhoto.querySelector("a#button_fs_exit").title = lychee.locale["FULLSCREEN_EXIT"]; - tbPhoto.querySelector("a#button_more").title = lychee.locale["MORE"]; - - var tbMap = document.querySelector("div#lychee_toolbar_map"); - tbMap.querySelector("a#button_back_map").title = lychee.locale["CLOSE_MAP"]; - - var tbConfig = document.querySelector("div#lychee_toolbar_config"); - tbConfig.querySelector("a#button_close_config").title = lychee.locale["CLOSE"]; - - // Sidebar - document.querySelector("#lychee_sidebar_header h1").textContent = lychee.locale["PHOTO_ABOUT"]; - - // NSFW Warning Banner - /** @type {HTMLDivElement} */ - var nsfwBanner = document.querySelector("#sensitive_warning"); - nsfwBanner.innerHTML = lychee.nsfw_banner_override ? lychee.nsfw_banner_override : lychee.locale["NSFW_BANNER"]; - - // Footer - var footer = document.querySelector("#lychee_footer"); - footer.querySelector("p.home_copyright").textContent = lychee.footer_show_copyright ? sprintf(lychee.locale["FOOTER_COPYRIGHT"], lychee.site_owner, lychee.site_copyright_begin === lychee.site_copyright_end ? lychee.site_copyright_begin : lychee.site_copyright_begin + "–" + lychee.site_copyright_end) : ""; - footer.querySelector("p.personal_text").textContent = lychee.footer_additional_text; - footer.querySelector("p.hosted_by a").textContent = lychee.locale["HOSTED_WITH_LYCHEE"]; - /** @type {HTMLDivElement} */ - var footerSocialMedia = footer.querySelector("div#home_socials"); - if (lychee.footer_show_social_media) { - footerSocialMedia.style.display = null; - footerSocialMedia.querySelector("a#facebook").href = lychee.sm_facebook_url; - footerSocialMedia.querySelector("a#flickr").href = lychee.sm_flickr_url; - footerSocialMedia.querySelector("a#instagram").href = lychee.sm_instagram_url; - footerSocialMedia.querySelector("a#twitter").href = lychee.sm_twitter_url; - footerSocialMedia.querySelector("a#youtube").href = lychee.sm_youtube_url; - } else { - footerSocialMedia.style.display = "none"; - } + // Toolbars in the header + var tbPublic = document.querySelector("div#lychee_toolbar_public"); + tbPublic.querySelector("a#button_signin").title = lychee.locale["SIGN_IN"]; + var tbPublicSearch = tbPublic.querySelector("input.header__search"); + if (tbPublicSearch instanceof HTMLInputElement) { + // See remark about `lychee.setMode` in the jsDoc comment of this method. + tbPublicSearch.placeholder = lychee.locale["SEARCH"]; + } + tbPublic.querySelector("a.button--map-albums").title = lychee.locale["DISPLAY_FULL_MAP"]; + var tbAlbums = document.querySelector("div#lychee_toolbar_albums"); + tbAlbums.querySelector("a#button_settings").title = lychee.locale["SETTINGS"]; + var tbAlbumsSearch = tbAlbums.querySelector("input.header__search"); + if (tbAlbumsSearch instanceof HTMLInputElement) { + // See remark about `lychee.setMode` in the jsDoc comment of this method. + tbAlbumsSearch.placeholder = lychee.locale["SEARCH"]; + } + tbAlbums.querySelector("a.button--map-albums").title = lychee.locale["DISPLAY_FULL_MAP"]; + tbAlbums.querySelector("a.button_add").title = lychee.locale["ADD"]; + var tbAlbum = document.querySelector("div#lychee_toolbar_album"); + tbAlbum.querySelector("a#button_back_home").title = lychee.locale["CLOSE_ALBUM"]; + tbAlbum.querySelector("a#button_visibility_album").title = lychee.locale["VISIBILITY_ALBUM"]; + tbAlbum.querySelector("a#button_sharing_album_users").title = lychee.locale["SHARING_ALBUM_USERS"]; + tbAlbum.querySelector("a#button_nsfw_album").title = lychee.locale["ALBUM_MARK_NSFW"]; + tbAlbum.querySelector("a#button_share_album").title = lychee.locale["SHARE_ALBUM"]; + tbAlbum.querySelector("a#button_archive").title = lychee.locale["DOWNLOAD_ALBUM"]; + tbAlbum.querySelector("a#button_info_album").title = lychee.locale["ABOUT_ALBUM"]; + tbAlbum.querySelector("a#button_map_album").title = lychee.locale["DISPLAY_FULL_MAP"]; + tbAlbum.querySelector("a#button_move_album").title = lychee.locale["MOVE_ALBUM"]; + tbAlbum.querySelector("a#button_trash_album").title = lychee.locale["DELETE_ALBUM"]; + tbAlbum.querySelector("a#button_fs_album_enter").title = lychee.locale["FULLSCREEN_ENTER"]; + tbAlbum.querySelector("a#button_fs_album_exit").title = lychee.locale["FULLSCREEN_EXIT"]; + tbAlbum.querySelector("a.button_add").title = lychee.locale["ADD"]; + var tbPhoto = document.querySelector("div#lychee_toolbar_photo"); + tbPhoto.querySelector("a#button_back").title = lychee.locale["CLOSE_PHOTO"]; + tbPhoto.querySelector("a#button_star").title = lychee.locale["STAR_PHOTO"]; + tbPhoto.querySelector("a#button_visibility").title = lychee.locale["VISIBILITY_PHOTO"]; + tbPhoto.querySelector("a#button_rotate_ccwise").title = lychee.locale["PHOTO_EDIT_ROTATECCWISE"]; + tbPhoto.querySelector("a#button_rotate_cwise").title = lychee.locale["PHOTO_EDIT_ROTATECWISE"]; + tbPhoto.querySelector("a#button_share").title = lychee.locale["SHARE_PHOTO"]; + tbPhoto.querySelector("a#button_info").title = lychee.locale["ABOUT_PHOTO"]; + tbPhoto.querySelector("a#button_map").title = lychee.locale["DISPLAY_FULL_MAP"]; + tbPhoto.querySelector("a#button_move").title = lychee.locale["MOVE"]; + tbPhoto.querySelector("a#button_trash").title = lychee.locale["DELETE"]; + tbPhoto.querySelector("a#button_fs_enter").title = lychee.locale["FULLSCREEN_ENTER"]; + tbPhoto.querySelector("a#button_fs_exit").title = lychee.locale["FULLSCREEN_EXIT"]; + tbPhoto.querySelector("a#button_more").title = lychee.locale["MORE"]; + var tbMap = document.querySelector("div#lychee_toolbar_map"); + tbMap.querySelector("a#button_back_map").title = lychee.locale["CLOSE_MAP"]; + var tbConfig = document.querySelector("div#lychee_toolbar_config"); + tbConfig.querySelector("a#button_close_config").title = lychee.locale["CLOSE"]; + + // Sidebar + document.querySelector("#lychee_sidebar_header h1").textContent = lychee.locale["PHOTO_ABOUT"]; + + // NSFW Warning Banner + /** @type {HTMLDivElement} */ + var nsfwBanner = document.querySelector("#sensitive_warning"); + nsfwBanner.innerHTML = lychee.nsfw_banner_override ? lychee.nsfw_banner_override : lychee.locale["NSFW_BANNER"]; + + // Footer + var footer = document.querySelector("#lychee_footer"); + footer.querySelector("p.home_copyright").textContent = lychee.footer_show_copyright ? sprintf(lychee.locale["FOOTER_COPYRIGHT"], lychee.site_owner, lychee.site_copyright_begin === lychee.site_copyright_end ? lychee.site_copyright_begin : lychee.site_copyright_begin + "–" + lychee.site_copyright_end) : ""; + footer.querySelector("p.personal_text").textContent = lychee.footer_additional_text; + footer.querySelector("p.hosted_by a").textContent = lychee.locale["HOSTED_WITH_LYCHEE"]; + /** @type {HTMLDivElement} */ + var footerSocialMedia = footer.querySelector("div#home_socials"); + if (lychee.footer_show_social_media) { + footerSocialMedia.style.display = null; + footerSocialMedia.querySelector("a#facebook").href = lychee.sm_facebook_url; + footerSocialMedia.querySelector("a#flickr").href = lychee.sm_flickr_url; + footerSocialMedia.querySelector("a#instagram").href = lychee.sm_instagram_url; + footerSocialMedia.querySelector("a#twitter").href = lychee.sm_twitter_url; + footerSocialMedia.querySelector("a#youtube").href = lychee.sm_youtube_url; + } else { + footerSocialMedia.style.display = "none"; + } }; /** @@ -6355,72 +6074,64 @@ lychee.localizeStaticGuiElements = function () { * @returns {void} */ lychee.parsePublicInitializationData = function (data) { - lychee.allow_username_change = data.config.allow_username_change === "1"; - lychee.sorting_photos = data.config.sorting_photos; - lychee.sorting_albums = data.config.sorting_albums; - lychee.album_subtitle_type = data.config.album_subtitle_type || "oldstyle"; - lychee.checkForUpdates = data.config.check_for_updates; - lychee.layout = Number.parseInt(data.config.layout, 10); - if (Number.isNaN(lychee.layout)) lychee.layout = 1; - lychee.landing_page_enable = data.config.landing_page_enable === "1"; - lychee.public_search = data.config.public_search === "1"; - lychee.image_overlay_type = data.config.image_overlay_type || "exif"; - lychee.image_overlay_type_default = lychee.image_overlay_type; - lychee.map_display = data.config.map_display === "1"; - lychee.map_display_public = data.config.map_display_public === "1"; - lychee.map_display_direction = data.config.map_display_direction === "1"; - lychee.map_provider = data.config.map_provider || "Wikimedia"; - lychee.map_include_subalbums = data.config.map_include_subalbums === "1"; - lychee.location_show = data.config.location_show === "1"; - lychee.location_show_public = data.config.location_show_public === "1"; - lychee.swipe_tolerance_x = Number.parseInt(data.config.swipe_tolerance_x, 10) || 150; - lychee.swipe_tolerance_y = Number.parseInt(data.config.swipe_tolerance_y, 10) || 250; - - lychee.nsfw_visible = data.config.nsfw_visible === "1"; - lychee.nsfw_visible_saved = lychee.nsfw_visible; - lychee.nsfw_blur = data.config.nsfw_blur === "1"; - lychee.nsfw_warning = data.config.nsfw_warning === "1"; - lychee.nsfw_banner_override = data.config.nsfw_banner_override || ""; - - lychee.sm_facebook_url = data.config.sm_facebook_url; - lychee.sm_flickr_url = data.config.sm_flickr_url; - lychee.sm_instagram_url = data.config.sm_instagram_url; - lychee.sm_twitter_url = data.config.sm_twitter_url; - lychee.sm_youtube_url = data.config.sm_youtube_url; - - lychee.rss_enable = data.config.rss_enable === "1"; - lychee.rss_feeds = data.config.rss_feeds; - - lychee.site_title = data.config.site_title; - lychee.site_owner = data.config.site_owner; - lychee.site_copyright_begin = data.config.site_copyright_begin; - lychee.site_copyright_end = data.config.site_copyright_end; - - lychee.footer_show_social_media = data.config.footer_show_social_media === "1"; - lychee.footer_show_copyright = data.config.footer_show_copyright === "1"; - lychee.footer_additional_text = data.config.footer_additional_text; - - lychee.mod_frame_enabled = data.config.mod_frame_enabled === "1"; - lychee.mod_frame_refresh = Number.parseInt(data.config.mod_frame_refresh, 10) || 30; - - var isTv = window.matchMedia("tv").matches; - - lychee.header_auto_hide = !isTv; - lychee.active_focus_on_page_load = isTv; - lychee.enable_button_visibility = !isTv; - lychee.enable_button_share = !isTv; - lychee.enable_button_archive = !isTv; - lychee.enable_button_move = !isTv; - lychee.enable_button_trash = !isTv; - lychee.enable_button_fullscreen = !isTv; - lychee.enable_button_download = !isTv; - lychee.enable_button_add = !isTv; - lychee.enable_button_more = !isTv; - lychee.enable_button_rotate = !isTv; - lychee.enable_close_tab_on_esc = isTv; - lychee.enable_tabindex = isTv; - lychee.enable_contextmenu_header = !isTv; - lychee.hide_content_during_imgview = isTv; + lychee.allow_username_change = data.config.allow_username_change === "1"; + lychee.sorting_photos = data.config.sorting_photos; + lychee.sorting_albums = data.config.sorting_albums; + lychee.album_subtitle_type = data.config.album_subtitle_type || "oldstyle"; + lychee.checkForUpdates = data.config.check_for_updates; + lychee.layout = Number.parseInt(data.config.layout, 10); + if (Number.isNaN(lychee.layout)) lychee.layout = 1; + lychee.landing_page_enable = data.config.landing_page_enable === "1"; + lychee.public_search = data.config.public_search === "1"; + lychee.image_overlay_type = data.config.image_overlay_type || "exif"; + lychee.image_overlay_type_default = lychee.image_overlay_type; + lychee.map_display = data.config.map_display === "1"; + lychee.map_display_public = data.config.map_display_public === "1"; + lychee.map_display_direction = data.config.map_display_direction === "1"; + lychee.map_provider = data.config.map_provider || "Wikimedia"; + lychee.map_include_subalbums = data.config.map_include_subalbums === "1"; + lychee.location_show = data.config.location_show === "1"; + lychee.location_show_public = data.config.location_show_public === "1"; + lychee.swipe_tolerance_x = Number.parseInt(data.config.swipe_tolerance_x, 10) || 150; + lychee.swipe_tolerance_y = Number.parseInt(data.config.swipe_tolerance_y, 10) || 250; + lychee.nsfw_visible = data.config.nsfw_visible === "1"; + lychee.nsfw_visible_saved = lychee.nsfw_visible; + lychee.nsfw_blur = data.config.nsfw_blur === "1"; + lychee.nsfw_warning = data.config.nsfw_warning === "1"; + lychee.nsfw_banner_override = data.config.nsfw_banner_override || ""; + lychee.sm_facebook_url = data.config.sm_facebook_url; + lychee.sm_flickr_url = data.config.sm_flickr_url; + lychee.sm_instagram_url = data.config.sm_instagram_url; + lychee.sm_twitter_url = data.config.sm_twitter_url; + lychee.sm_youtube_url = data.config.sm_youtube_url; + lychee.rss_enable = data.config.rss_enable === "1"; + lychee.rss_feeds = data.config.rss_feeds; + lychee.site_title = data.config.site_title; + lychee.site_owner = data.config.site_owner; + lychee.site_copyright_begin = data.config.site_copyright_begin; + lychee.site_copyright_end = data.config.site_copyright_end; + lychee.footer_show_social_media = data.config.footer_show_social_media === "1"; + lychee.footer_show_copyright = data.config.footer_show_copyright === "1"; + lychee.footer_additional_text = data.config.footer_additional_text; + lychee.mod_frame_enabled = data.config.mod_frame_enabled === "1"; + lychee.mod_frame_refresh = Number.parseInt(data.config.mod_frame_refresh, 10) || 30; + var isTv = window.matchMedia("tv").matches; + lychee.header_auto_hide = !isTv; + lychee.active_focus_on_page_load = isTv; + lychee.enable_button_visibility = !isTv; + lychee.enable_button_share = !isTv; + lychee.enable_button_archive = !isTv; + lychee.enable_button_move = !isTv; + lychee.enable_button_trash = !isTv; + lychee.enable_button_fullscreen = !isTv; + lychee.enable_button_download = !isTv; + lychee.enable_button_add = !isTv; + lychee.enable_button_more = !isTv; + lychee.enable_button_rotate = !isTv; + lychee.enable_close_tab_on_esc = isTv; + lychee.enable_tabindex = isTv; + lychee.enable_contextmenu_header = !isTv; + lychee.hide_content_during_imgview = isTv; }; /** @@ -6432,23 +6143,23 @@ lychee.parsePublicInitializationData = function (data) { * @returns {void} */ lychee.parseProtectedInitializationData = function (data) { - lychee.dropboxKey = data.config.dropbox_key || ""; - lychee.location = data.config.location || ""; - lychee.checkForUpdates = data.config.check_for_updates === "1"; - lychee.lang = data.config.lang || ""; - lychee.lang_available = data.config.lang_available || []; - lychee.location_decoding = data.config.location_decoding === "1"; - lychee.default_license = data.config.default_license || "none"; - lychee.css = data.config.css || ""; - lychee.grants_full_photo_access = data.config.grants_full_photo_access === "1"; - lychee.grants_download = data.config.grants_download === "1"; - lychee.public_photos_hidden = data.config.public_photos_hidden === "1"; - lychee.delete_imported = data.config.delete_imported === "1"; - lychee.import_via_symlink = data.config.import_via_symlink === "1"; - lychee.skip_duplicates = data.config.skip_duplicates === "1"; - lychee.editor_enabled = data.config.editor_enabled === "1"; - lychee.new_photos_notification = data.config.new_photos_notification === "1"; - lychee.upload_processing_limit = Number.parseInt(data.config.upload_processing_limit, 10) || 4; + lychee.dropboxKey = data.config.dropbox_key || ""; + lychee.location = data.config.location || ""; + lychee.checkForUpdates = data.config.check_for_updates === "1"; + lychee.lang = data.config.lang || ""; + lychee.lang_available = data.config.lang_available || []; + lychee.location_decoding = data.config.location_decoding === "1"; + lychee.default_license = data.config.default_license || "none"; + lychee.css = data.config.css || ""; + lychee.grants_full_photo_access = data.config.grants_full_photo_access === "1"; + lychee.grants_download = data.config.grants_download === "1"; + lychee.public_photos_hidden = data.config.public_photos_hidden === "1"; + lychee.delete_imported = data.config.delete_imported === "1"; + lychee.import_via_symlink = data.config.import_via_symlink === "1"; + lychee.skip_duplicates = data.config.skip_duplicates === "1"; + lychee.editor_enabled = data.config.editor_enabled === "1"; + lychee.new_photos_notification = data.config.new_photos_notification === "1"; + lychee.upload_processing_limit = Number.parseInt(data.config.upload_processing_limit, 10) || 4; }; /** @@ -6456,96 +6167,97 @@ lychee.parseProtectedInitializationData = function (data) { * @returns {void} */ lychee.login = function (data) { - if (!data.username.trim()) { - basicModal.focusError("username"); - return; - } - if (!data.password.trim()) { - basicModal.focusError("password"); - return; - } - - api.post("Session::login", data, function () { - return window.location.reload(); - }, null, function (jqXHR) { - if (jqXHR.status === 401) { - basicModal.focusError("password"); - return true; - } else { - return false; - } - }); + if (!data.username.trim()) { + basicModal.focusError("username"); + return; + } + if (!data.password.trim()) { + basicModal.focusError("password"); + return; + } + api.post("Session::login", data, function () { + return window.location.reload(); + }, null, function (jqXHR) { + if (jqXHR.status === 401) { + basicModal.focusError("password"); + return true; + } else { + return false; + } + }); }; /** * @returns {void} */ lychee.loginDialog = function () { - var loginDialogBody = "\n\t\t\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t

Lychee \n\t\t\t\n\t\t\t\n\t\t

\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initLoginDialog = function initLoginDialog(formElements, dialog) { - tabindex.makeUnfocusable(header.dom()); - tabindex.makeUnfocusable(lychee.content); - tabindex.makeUnfocusable(lychee.imageview); - tabindex.makeFocusable($(dialog)); - - formElements.username.placeholder = lychee.locale["USERNAME"]; - formElements.password.placeholder = lychee.locale["PASSWORD"]; - if (!!lychee.version) { - dialog.querySelector("span.version-number").textContent = lychee.version.major + "." + lychee.version.minor + "." + lychee.version.patch; - } else { - dialog.querySelector("span.version-number").textContent = ""; - } - // If Release is available : show release - // If Git is available : show git - if (lychee.update_available) { - dialog.querySelector("span.up-to-date-release a").textContent = lychee.locale["UPDATE_AVAILABLE"]; - dialog.querySelector("span.up-to-date-release").classList.remove("up-to-date-release"); - } else if (lychee.update_json) { - dialog.querySelector("span.up-to-date-git a").textContent = lychee.locale["UPDATE_AVAILABLE"]; - dialog.querySelector("span.up-to-date-git").classList.remove("up-to-date-git"); - } - - // This feels awkward, because this hooks into the modal dialog in some - // unpredictable way. - // It would be better to have a checkbox for password-less login in the - // dialog and then let the action handler of the modal dialog, i.e. - // `lychee.login` handle both cases. - // TODO: Refactor this. - dialog.querySelector("#signInKeyLess").addEventListener("click", u2f.login); - }; - - basicModal.show({ - body: loginDialogBody, - readyCB: initLoginDialog, - classList: ["login"], - buttons: { - action: { - title: lychee.locale["SIGN_IN"], - fn: lychee.login, - attributes: { "data-tabindex": tabindex.get_next_tab_index() } - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close, - attributes: { "data-tabindex": tabindex.get_next_tab_index() } - } - } - }); + var loginDialogBody = "\n\t\t\n\t\t\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t

Lychee \n\t\t\t\n\t\t\t\n\t\t

\n\t\t"); + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initLoginDialog = function initLoginDialog(formElements, dialog) { + tabindex.makeUnfocusable(header.dom()); + tabindex.makeUnfocusable(lychee.content); + tabindex.makeUnfocusable(lychee.imageview); + tabindex.makeFocusable($(dialog)); + formElements.username.placeholder = lychee.locale["USERNAME"]; + formElements.password.placeholder = lychee.locale["PASSWORD"]; + if (!!lychee.version) { + dialog.querySelector("span.version-number").textContent = lychee.version.major + "." + lychee.version.minor + "." + lychee.version.patch; + } else { + dialog.querySelector("span.version-number").textContent = ""; + } + // If Release is available : show release + // If Git is available : show git + if (lychee.update_available) { + dialog.querySelector("span.up-to-date-release a").textContent = lychee.locale["UPDATE_AVAILABLE"]; + dialog.querySelector("span.up-to-date-release").classList.remove("up-to-date-release"); + } else if (lychee.update_json) { + dialog.querySelector("span.up-to-date-git a").textContent = lychee.locale["UPDATE_AVAILABLE"]; + dialog.querySelector("span.up-to-date-git").classList.remove("up-to-date-git"); + } + + // This feels awkward, because this hooks into the modal dialog in some + // unpredictable way. + // It would be better to have a checkbox for password-less login in the + // dialog and then let the action handler of the modal dialog, i.e. + // `lychee.login` handle both cases. + // TODO: Refactor this. + dialog.querySelector("#signInKeyLess").addEventListener("click", u2f.login); + }; + basicModal.show({ + body: loginDialogBody, + readyCB: initLoginDialog, + classList: ["login"], + buttons: { + action: { + title: lychee.locale["SIGN_IN"], + fn: lychee.login, + attributes: { + "data-tabindex": tabindex.get_next_tab_index() + } + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close, + attributes: { + "data-tabindex": tabindex.get_next_tab_index() + } + } + } + }); }; /** * @returns {void} */ lychee.logout = function () { - api.post("Session::logout", {}, function () { - return window.location.reload(); - }); + api.post("Session::logout", {}, function () { + return window.location.reload(); + }); }; /** @@ -6554,13 +6266,14 @@ lychee.logout = function () { * * @returns {void} */ -lychee.goto = function () { - var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - var autoplay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - - url = "#" + (url !== null ? url : ""); - history.pushState({ autoplay: autoplay }, null, url); - lychee.load(autoplay); +lychee["goto"] = function () { + var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var autoplay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + url = "#" + (url !== null ? url : ""); + history.pushState({ + autoplay: autoplay + }, null, url); + lychee.load(autoplay); }; /** @@ -6570,15 +6283,14 @@ lychee.goto = function () { * @returns {void} */ lychee.gotoMap = function () { - var albumID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - var autoplay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - - // If map functionality is disabled -> go to album - if (!lychee.map_display) { - loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); - return; - } - lychee.goto("map/" + (albumID !== null ? albumID : ""), autoplay); + var albumID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var autoplay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + // If map functionality is disabled -> go to album + if (!lychee.map_display) { + loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); + return; + } + lychee["goto"]("map/" + (albumID !== null ? albumID : ""), autoplay); }; /** @@ -6602,49 +6314,47 @@ lychee.gotoMap = function () { * and an asynchronous reloading has been scheduled */ lychee.reloadIfLegacyIDs = function (albumID, photoID, autoplay) { - /** @param {?string} id the inspected ID */ - var isLegacyID = function isLegacyID(id) { - // The legacy IDs were pure numeric values. We exclude values which - // have 24 digits, because these could also be modern IDs. - // A modern IDs is a 24 character long, base64 encoded value and thus - // could also match 24 digits by accident. - return id && id.length !== 24 && parseInt(id, 10).toString() === id; - }; - - if (!isLegacyID(albumID) && !isLegacyID(photoID)) { - // this function is a no-op if neither ID is in legacy format - return false; - } - - /** - * Callback to be called asynchronously which executes the actual reloading. - * - * @param {?string} newAlbumID - * @param {?string} newPhotoID - * - * @returns {void} - */ - var reloadWithNewIDs = function reloadWithNewIDs(newAlbumID, newPhotoID) { - var newUrl = ""; - if (newAlbumID) { - newUrl += newAlbumID; - newUrl += newPhotoID ? "/" + newPhotoID : ""; - } - lychee.goto(newUrl, autoplay); - }; - - // We have to deal with three cases: - // 1. the album and photo ID need to be translated - // 2. only the album ID needs to be translated - // 3. only the photo ID needs to be translated - var params = {}; - if (isLegacyID(albumID)) params.albumID = parseInt(albumID, 10); - if (isLegacyID(photoID)) params.photoID = parseInt(photoID, 10); - api.post("Legacy::translateLegacyModelIDs", params, function (data) { - reloadWithNewIDs(data.hasOwnProperty("albumID") ? data.albumID : albumID, data.hasOwnProperty("photoID") ? data.photoID : photoID); - }); - - return true; + /** @param {?string} id the inspected ID */ + var isLegacyID = function isLegacyID(id) { + // The legacy IDs were pure numeric values. We exclude values which + // have 24 digits, because these could also be modern IDs. + // A modern IDs is a 24 character long, base64 encoded value and thus + // could also match 24 digits by accident. + return id && id.length !== 24 && parseInt(id, 10).toString() === id; + }; + if (!isLegacyID(albumID) && !isLegacyID(photoID)) { + // this function is a no-op if neither ID is in legacy format + return false; + } + + /** + * Callback to be called asynchronously which executes the actual reloading. + * + * @param {?string} newAlbumID + * @param {?string} newPhotoID + * + * @returns {void} + */ + var reloadWithNewIDs = function reloadWithNewIDs(newAlbumID, newPhotoID) { + var newUrl = ""; + if (newAlbumID) { + newUrl += newAlbumID; + newUrl += newPhotoID ? "/" + newPhotoID : ""; + } + lychee["goto"](newUrl, autoplay); + }; + + // We have to deal with three cases: + // 1. the album and photo ID need to be translated + // 2. only the album ID needs to be translated + // 3. only the photo ID needs to be translated + var params = {}; + if (isLegacyID(albumID)) params.albumID = parseInt(albumID, 10); + if (isLegacyID(photoID)) params.photoID = parseInt(photoID, 10); + api.post("Legacy::translateLegacyModelIDs", params, function (data) { + reloadWithNewIDs(data.hasOwnProperty("albumID") ? data.albumID : albumID, data.hasOwnProperty("photoID") ? data.photoID : photoID); + }); + return true; }; /** @@ -6684,239 +6394,231 @@ lychee.reloadIfLegacyIDs = function (albumID, photoID, autoplay) { * @returns {void} */ lychee.load = function () { - var autoplay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - var albumID = ""; - var photoID = ""; - - var viewMatch = document.location.href.match(/\/view(?:\/|(\?p=))(?[-_0-9A-Za-z]+)$/); - var hashMatch = document.location.hash.replace("#", "").split("/"); - - if (/\/frame\/?$/.test(document.location.href)) { - albumID = "frame"; - photoID = ""; - } else if (viewMatch !== null && viewMatch.groups.photoID) { - albumID = "view"; - photoID = viewMatch.groups.photoID; - } else { - albumID = hashMatch[0]; - if (albumID === SearchAlbumIDPrefix && hashMatch.length > 1) { - albumID += "/" + hashMatch[1]; - } - photoID = hashMatch[album.isSearchID(albumID) ? 2 : 1]; - } - - contextMenu.close(); - multiselect.close(); - tabindex.reset(); - - // If Lychee is currently in frame or view mode, we need to re-initialize. - // Note, this is a temporary nasty hack. - // In an optimal world, we would simply call `lychee.setMode` to leave - // view or frame mode and to enter gallery or public mode. - // However, `lychee.setMode` does not support that direction (see comment - // here). - // Hence, in order to get back to a "full" mode, we need to re-initialize - // completely. - var bodyClasses = document.querySelector("body").classList; - if (bodyClasses.contains("mode-frame") || bodyClasses.contains("mode-view")) { - lychee.init(false); - return; - } - - if (albumID && photoID) { - if (albumID === "map") { - // If map functionality is disabled -> do nothing - if (!lychee.map_display) { - loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); - return; - } - $(".no_content").remove(); - // show map - // albumID has been stored in photoID due to URL format #map/albumID - albumID = photoID; - photoID = null; - - // Trash data - _photo3.json = null; - - // Show Album -> it's below the map - if (visible.photo()) view.photo.hide(); - if (visible.sidebar()) _sidebar.toggle(false); - if (album.json && albumID === album.json.id) { - view.album.title(); - } - mapview.open(albumID); - lychee.footer_hide(); - } else { - if (lychee.reloadIfLegacyIDs(albumID, photoID, autoplay)) { - return; - } - - $(".no_content").remove(); - // Show photo - - // Trash data - _photo3.json = null; - - /** - * @param {boolean} isParentAlbumAccessible - * @returns {void} - */ - var loadPhoto = function loadPhoto(isParentAlbumAccessible) { - if (!isParentAlbumAccessible) { - lychee.setMode("view"); - } - _photo3.load(photoID, albumID, autoplay); - - // Make imageview focusable - tabindex.makeFocusable(lychee.imageview); - - // Make thumbnails unfocusable and store which element had focus - tabindex.makeUnfocusable(lychee.content, true); - - // hide contentview if requested - if (lychee.hide_content_during_imgview) lychee.content.hide(); - - lychee.footer_hide(); - }; - - // Load Photo - if (albumID === "view") { - // If the photo shall be displayed in "view" mode, delete - // any album which we possibly have and load the photo as - // if the parent album was inaccessible (even if a user is - // authenticated). - albumID = null; - album.refresh(); - lychee.content.empty(); - loadPhoto(false); - } else if (lychee.content.html() === "" || album.json === null || album.json.id !== albumID) { - // If we don't have an album or the wrong album load the album - // first and let the album loader load the photo afterwards or - // load the photo directly. - lychee.content.hide(); - album.load(albumID, loadPhoto); - } else { - loadPhoto(true); - } - } - } else if (albumID) { - if (albumID === "map") { - $(".no_content").remove(); - // Show map of all albums - // If map functionality is disabled -> do nothing - if (!lychee.map_display) { - loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); - return; - } - - // Trash data - _photo3.json = null; - - // Show Album -> it's below the map - if (visible.photo()) view.photo.hide(); - if (visible.sidebar()) _sidebar.toggle(false); - mapview.open(); - lychee.footer_hide(); - } else if (albumID === "frame") { - if (lychee.mod_frame_enabled) { - frame.initAndStart(); - } else { - loadingBar.show("error", "Frame mode disabled"); - } - } else { - if (lychee.reloadIfLegacyIDs(albumID, photoID, autoplay)) { - return; - } - - $(".no_content").remove(); - // Trash data - _photo3.json = null; - - // Show Album - if (visible.photo()) { - view.photo.hide(); - tabindex.makeUnfocusable(lychee.imageview); - } - if (visible.mapview()) mapview.close(); - if (visible.sidebar() && (album.isSmartID(albumID) || album.isSearchID(albumID))) _sidebar.toggle(false); - $("#sensitive_warning").removeClass("active"); - if (album.json && albumID === album.json.id) { - if (album.isSearchID(albumID)) { - // We are probably coming back to the search results from - // viewing an image. Because search results is not a - // regular album, it needs to be treated a little - // differently. - header.setMode("albums"); - lychee.setMetaData(lychee.locale["SEARCH_RESULTS"]); - } else { - view.album.title(); - } - lychee.content.show(); - tabindex.makeFocusable(lychee.content, true); - // If the album was loaded in the background (when content is - // hidden), scrolling may not have worked. - view.album.content.restoreScroll(); - } else if (album.isSearchID(albumID)) { - // Search has been triggered - var search_string = decodeURIComponent(hashMatch[1]).trim(); - - if (search_string === "") { - // do nothing on "only space" search strings - return; - } - // If public search is disabled -> do nothing - if (lychee.publicMode === true && !lychee.public_search) { - loadingBar.show("error", lychee.locale["ERROR_SEARCH_DEACTIVATED"]); - return; - } - - header.dom(".header__search").val(search_string); - search.find(search_string); - } else if (visible.search()) { - // Somebody clicked on an album in search results. We - // will alter the parent_id of that album once it's loaded - // so that the back button sends us back to the search - // results. - // Trash data so that it's reloaded if needed (just as we - // would for a regular parent album). - search.json = null; - album.load(albumID, null, album.getID()); - } else { - album.load(albumID); - } - lychee.footer_show(); - } - } else { - $(".no_content").remove(); - - // Trash data - search.json = null; - album.json = null; - _photo3.json = null; - - // Hide sidebar - if (visible.sidebar()) _sidebar.toggle(false); - - // Show Albums - if (visible.photo()) { - view.photo.hide(); - tabindex.makeUnfocusable(lychee.imageview); - } - if (visible.mapview()) mapview.close(); - $("#sensitive_warning").removeClass("active"); - lychee.content.show(); - lychee.footer_show(); - albums.load(); - } -}; + var autoplay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var albumID = ""; + var photoID = ""; + var viewMatch = document.location.href.match( /*#__PURE__*/_wrapRegExp(/\/view(?:\/|(\?p=))([-_0-9A-Za-z]+)$/, { + photoID: 2 + })); + var hashMatch = document.location.hash.replace("#", "").split("/"); + if (/\/frame\/?$/.test(document.location.href)) { + albumID = "frame"; + photoID = ""; + } else if (viewMatch !== null && viewMatch.groups.photoID) { + albumID = "view"; + photoID = viewMatch.groups.photoID; + } else { + albumID = hashMatch[0]; + if (albumID === SearchAlbumIDPrefix && hashMatch.length > 1) { + albumID += "/" + hashMatch[1]; + } + photoID = hashMatch[album.isSearchID(albumID) ? 2 : 1]; + } + contextMenu.close(); + multiselect.close(); + tabindex.reset(); + + // If Lychee is currently in frame or view mode, we need to re-initialize. + // Note, this is a temporary nasty hack. + // In an optimal world, we would simply call `lychee.setMode` to leave + // view or frame mode and to enter gallery or public mode. + // However, `lychee.setMode` does not support that direction (see comment + // here). + // Hence, in order to get back to a "full" mode, we need to re-initialize + // completely. + var bodyClasses = document.querySelector("body").classList; + if (bodyClasses.contains("mode-frame") || bodyClasses.contains("mode-view")) { + lychee.init(false); + return; + } + if (albumID && photoID) { + if (albumID === "map") { + // If map functionality is disabled -> do nothing + if (!lychee.map_display) { + loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); + return; + } + $(".no_content").remove(); + // show map + // albumID has been stored in photoID due to URL format #map/albumID + albumID = photoID; + photoID = null; + + // Trash data + _photo3.json = null; + + // Show Album -> it's below the map + if (visible.photo()) view.photo.hide(); + if (visible.sidebar()) _sidebar.toggle(false); + if (album.json && albumID === album.json.id) { + view.album.title(); + } + mapview.open(albumID); + lychee.footer_hide(); + } else { + if (lychee.reloadIfLegacyIDs(albumID, photoID, autoplay)) { + return; + } + $(".no_content").remove(); + // Show photo + + // Trash data + _photo3.json = null; + + /** + * @param {boolean} isParentAlbumAccessible + * @returns {void} + */ + var loadPhoto = function loadPhoto(isParentAlbumAccessible) { + if (!isParentAlbumAccessible) { + lychee.setMode("view"); + } + _photo3.load(photoID, albumID, autoplay); + + // Make imageview focusable + tabindex.makeFocusable(lychee.imageview); + + // Make thumbnails unfocusable and store which element had focus + tabindex.makeUnfocusable(lychee.content, true); + + // hide contentview if requested + if (lychee.hide_content_during_imgview) lychee.content.hide(); + lychee.footer_hide(); + }; + + // Load Photo + if (albumID === "view") { + // If the photo shall be displayed in "view" mode, delete + // any album which we possibly have and load the photo as + // if the parent album was inaccessible (even if a user is + // authenticated). + albumID = null; + album.refresh(); + lychee.content.empty(); + loadPhoto(false); + } else if (lychee.content.html() === "" || album.json === null || album.json.id !== albumID) { + // If we don't have an album or the wrong album load the album + // first and let the album loader load the photo afterwards or + // load the photo directly. + lychee.content.hide(); + album.load(albumID, loadPhoto); + } else { + loadPhoto(true); + } + } + } else if (albumID) { + if (albumID === "map") { + $(".no_content").remove(); + // Show map of all albums + // If map functionality is disabled -> do nothing + if (!lychee.map_display) { + loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); + return; + } -/** - * Sets the title and various other meta for the current page. - * - * The title is shown in the browser window and in the header bar. - * The window title is prefixed by the value of the configuration setting + // Trash data + _photo3.json = null; + + // Show Album -> it's below the map + if (visible.photo()) view.photo.hide(); + if (visible.sidebar()) _sidebar.toggle(false); + mapview.open(); + lychee.footer_hide(); + } else if (albumID === "frame") { + if (lychee.mod_frame_enabled) { + frame.initAndStart(); + } else { + loadingBar.show("error", "Frame mode disabled"); + } + } else { + if (lychee.reloadIfLegacyIDs(albumID, photoID, autoplay)) { + return; + } + $(".no_content").remove(); + // Trash data + _photo3.json = null; + + // Show Album + if (visible.photo()) { + view.photo.hide(); + tabindex.makeUnfocusable(lychee.imageview); + } + if (visible.mapview()) mapview.close(); + if (visible.sidebar() && (album.isSmartID(albumID) || album.isSearchID(albumID))) _sidebar.toggle(false); + $("#sensitive_warning").removeClass("active"); + if (album.json && albumID === album.json.id) { + if (album.isSearchID(albumID)) { + // We are probably coming back to the search results from + // viewing an image. Because search results is not a + // regular album, it needs to be treated a little + // differently. + header.setMode("albums"); + lychee.setMetaData(lychee.locale["SEARCH_RESULTS"]); + } else { + view.album.title(); + } + lychee.content.show(); + tabindex.makeFocusable(lychee.content, true); + // If the album was loaded in the background (when content is + // hidden), scrolling may not have worked. + view.album.content.restoreScroll(); + } else if (album.isSearchID(albumID)) { + // Search has been triggered + var search_string = decodeURIComponent(hashMatch[1]).trim(); + if (search_string === "") { + // do nothing on "only space" search strings + return; + } + // If public search is disabled -> do nothing + if (lychee.publicMode === true && !lychee.public_search) { + loadingBar.show("error", lychee.locale["ERROR_SEARCH_DEACTIVATED"]); + return; + } + header.dom(".header__search").val(search_string); + search.find(search_string); + } else if (visible.search()) { + // Somebody clicked on an album in search results. We + // will alter the parent_id of that album once it's loaded + // so that the back button sends us back to the search + // results. + // Trash data so that it's reloaded if needed (just as we + // would for a regular parent album). + search.json = null; + album.load(albumID, null, album.getID()); + } else { + album.load(albumID); + } + lychee.footer_show(); + } + } else { + $(".no_content").remove(); + + // Trash data + search.json = null; + album.json = null; + _photo3.json = null; + + // Hide sidebar + if (visible.sidebar()) _sidebar.toggle(false); + + // Show Albums + if (visible.photo()) { + view.photo.hide(); + tabindex.makeUnfocusable(lychee.imageview); + } + if (visible.mapview()) mapview.close(); + $("#sensitive_warning").removeClass("active"); + lychee.content.show(); + lychee.footer_show(); + albums.load(); + } +}; + +/** + * Sets the title and various other meta for the current page. + * + * The title is shown in the browser window and in the header bar. + * The window title is prefixed by the value of the configuration setting * `lychee.site_title`. * If both, the prefix `lychee.site_title` and the given title, are not empty, * they are seperated by an en-dash. @@ -6929,31 +6631,29 @@ lychee.load = function () { * @param {string=""} photoUrl */ lychee.setMetaData = function () { - var title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; - var isTitleEditable = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - var description = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; - var photoUrl = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ""; - - var pageTitle = lychee.site_title + (lychee.site_title && title ? " – " : "") + title; - var pageDescription = description ? description + " – via Lychee" : ""; + var title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; + var isTitleEditable = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var description = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; + var photoUrl = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ""; + var pageTitle = lychee.site_title + (lychee.site_title && title ? " – " : "") + title; + var pageDescription = description ? description + " – via Lychee" : ""; - // General Meta Data - document.title = pageTitle; - document.querySelector('meta[name="description"]').content = pageDescription; + // General Meta Data + document.title = pageTitle; + document.querySelector('meta[name="description"]').content = pageDescription; - // Twitter Meta Data - document.querySelector('meta[name="twitter:title"]').content = pageTitle; - document.querySelector('meta[name="twitter:description"]').content = pageDescription; - document.querySelector('meta[name="twitter:image"]').content = photoUrl; + // Twitter Meta Data + document.querySelector('meta[name="twitter:title"]').content = pageTitle; + document.querySelector('meta[name="twitter:description"]').content = pageDescription; + document.querySelector('meta[name="twitter:image"]').content = photoUrl; - // OpenGraph Meta Data (e.g. used by Facebook) - document.querySelector('meta[property="og:title"]').content = pageTitle; - document.querySelector('meta[property="og:description"]').content = pageDescription; - document.querySelector('meta[property="og:image"]').content = photoUrl; - document.querySelector('meta[property="og:url"]').content = window.location.href; - - header.setEditable(isTitleEditable); - header.setTitle(title); + // OpenGraph Meta Data (e.g. used by Facebook) + document.querySelector('meta[property="og:title"]').content = pageTitle; + document.querySelector('meta[property="og:description"]').content = pageDescription; + document.querySelector('meta[property="og:image"]').content = photoUrl; + document.querySelector('meta[property="og:url"]').content = window.location.href; + header.setEditable(isTitleEditable); + header.setTitle(title); }; /** @@ -6973,91 +6673,79 @@ lychee.setMetaData = function () { * @param {string} mode - one out of: `public`, `view`, `logged_in`, `frame` */ lychee.setMode = function (mode) { - if (!lychee.rights.settings.can_edit && !lychee.rights.user.can_edit || mode === "view" || mode === "frame") { - $("#button_settings_open").hide(); - } - - if (!lychee.rights.root_album.can_upload || mode === "view" || mode === "frame") { - $("#button_sharing").hide(); - - $(document).off("click", ".header__title--editable").off("touchend", ".header__title--editable").off("contextmenu", ".photo").off("contextmenu", ".album").off("drop"); - - Mousetrap.unbind(["u"]).unbind(["s"]).unbind(["n"]).unbind(["r"]).unbind(["d"]).unbind(["t"]).unbind(["command+backspace", "ctrl+backspace"]).unbind(["command+a", "ctrl+a"]); - } - if (!lychee.rights.user_management.can_list || mode === "view" || mode === "frame") { - $("#button_users").hide(); - } - if (!lychee.rights.settings.can_see_diagnostics || mode === "view" || mode === "frame") { - $("#button_diagnostics").hide(); - } - if (!lychee.rights.settings.can_see_logs || mode === "view" || mode === "frame") { - $("#button_logs").hide(); - } - - var bodyClasses = document.querySelector("body").classList; - - if (mode === "logged_in") { - if (!bodyClasses.contains("mode-gallery")) { - bodyClasses.replace("mode-none", "mode-gallery"); - bodyClasses.replace("mode-frame", "mode-gallery"); - bodyClasses.replace("mode-view", "mode-gallery"); - } - // After login the keyboard short-cuts to login by password (l) and - // by key (k) are not required anymore, so we unbind them. - Mousetrap.unbind(["l"]).unbind(["k"]); - - // The code searches by class, so remove the other instance. - $(".header__search, .header__clear", "#lychee_toolbar_public").hide(); - - if (!lychee.editor_enabled) { - $("#button_rotate_cwise").hide(); - $("#button_rotate_ccwise").hide(); - } - return; - } - $(".header__search, .header__clear", "#lychee_toolbar_albums").hide(); - $("#button_rotate_cwise").hide(); - $("#button_rotate_ccwise").hide(); - - $("#button_settings, .header__divider, #lychee_left_menu_container").hide(); - - if (mode === "public") { - if (!bodyClasses.contains("mode-gallery")) { - bodyClasses.replace("mode-none", "mode-gallery"); - bodyClasses.replace("mode-frame", "mode-gallery"); - bodyClasses.replace("mode-view", "mode-gallery"); - } - lychee.publicMode = true; - } else if (mode === "view") { - if (!bodyClasses.contains("mode-view")) { - bodyClasses.replace("mode-none", "mode-view"); - bodyClasses.replace("mode-frame", "mode-view"); - bodyClasses.replace("mode-gallery", "mode-view"); - } - Mousetrap.unbind(["esc", "command+up"]); - - $("#button_back, a#next, a#previous").hide(); - $(".no_content").hide(); - - lychee.publicMode = true; - lychee.viewMode = true; - } else if (mode === "frame") { - if (!bodyClasses.contains("mode-frame")) { - bodyClasses.replace("mode-none", "mode-frame"); - bodyClasses.replace("mode-view", "mode-frame"); - bodyClasses.replace("mode-gallery", "mode-frame"); - } - Mousetrap.unbind(["esc", "command+up"]); - - $("#button_back, a#next, a#previous").hide(); - $(".no_content").hide(); - - lychee.publicMode = true; - lychee.viewMode = true; - } - - // just mak - header.bind_back(); + if (!lychee.rights.settings.can_edit && !lychee.rights.user.can_edit || mode === "view" || mode === "frame") { + $("#button_settings_open").hide(); + } + if (!lychee.rights.root_album.can_upload || mode === "view" || mode === "frame") { + $("#button_sharing").hide(); + $(document).off("click", ".header__title--editable").off("touchend", ".header__title--editable").off("contextmenu", ".photo").off("contextmenu", ".album").off("drop"); + Mousetrap.unbind(["u"]).unbind(["s"]).unbind(["n"]).unbind(["r"]).unbind(["d"]).unbind(["t"]).unbind(["command+backspace", "ctrl+backspace"]).unbind(["command+a", "ctrl+a"]); + } + if (!lychee.rights.user_management.can_list || mode === "view" || mode === "frame") { + $("#button_users").hide(); + } + if (!lychee.rights.settings.can_see_diagnostics || mode === "view" || mode === "frame") { + $("#button_diagnostics").hide(); + } + if (!lychee.rights.settings.can_see_logs || mode === "view" || mode === "frame") { + $("#button_logs").hide(); + } + var bodyClasses = document.querySelector("body").classList; + if (mode === "logged_in") { + if (!bodyClasses.contains("mode-gallery")) { + bodyClasses.replace("mode-none", "mode-gallery"); + bodyClasses.replace("mode-frame", "mode-gallery"); + bodyClasses.replace("mode-view", "mode-gallery"); + } + // After login the keyboard short-cuts to login by password (l) and + // by key (k) are not required anymore, so we unbind them. + Mousetrap.unbind(["l"]).unbind(["k"]); + + // The code searches by class, so remove the other instance. + $(".header__search, .header__clear", "#lychee_toolbar_public").hide(); + if (!lychee.editor_enabled) { + $("#button_rotate_cwise").hide(); + $("#button_rotate_ccwise").hide(); + } + return; + } + $(".header__search, .header__clear", "#lychee_toolbar_albums").hide(); + $("#button_rotate_cwise").hide(); + $("#button_rotate_ccwise").hide(); + $("#button_settings, .header__divider, #lychee_left_menu_container").hide(); + if (mode === "public") { + if (!bodyClasses.contains("mode-gallery")) { + bodyClasses.replace("mode-none", "mode-gallery"); + bodyClasses.replace("mode-frame", "mode-gallery"); + bodyClasses.replace("mode-view", "mode-gallery"); + } + lychee.publicMode = true; + } else if (mode === "view") { + if (!bodyClasses.contains("mode-view")) { + bodyClasses.replace("mode-none", "mode-view"); + bodyClasses.replace("mode-frame", "mode-view"); + bodyClasses.replace("mode-gallery", "mode-view"); + } + Mousetrap.unbind(["esc", "command+up"]); + $("#button_back, a#next, a#previous").hide(); + $(".no_content").hide(); + lychee.publicMode = true; + lychee.viewMode = true; + } else if (mode === "frame") { + if (!bodyClasses.contains("mode-frame")) { + bodyClasses.replace("mode-none", "mode-frame"); + bodyClasses.replace("mode-view", "mode-frame"); + bodyClasses.replace("mode-gallery", "mode-frame"); + } + Mousetrap.unbind(["esc", "command+up"]); + $("#button_back, a#next, a#previous").hide(); + $(".no_content").hide(); + lychee.publicMode = true; + lychee.viewMode = true; + } + + // just mak + header.bind_back(); }; /** @@ -7067,16 +6755,15 @@ lychee.setMode = function (mode) { * @returns {void} */ lychee.animate = function (obj, animation) { - var animations = [["fadeIn", "fadeOut"], ["contentZoomIn", "contentZoomOut"]]; - - for (var i = 0; i < animations.length; i++) { - for (var x = 0; x < animations[i].length; x++) { - if (animations[i][x] === animation) { - obj.removeClass(animations[i][0] + " " + animations[i][1]).addClass(animation); - return; - } - } - } + var animations = [["fadeIn", "fadeOut"], ["contentZoomIn", "contentZoomOut"]]; + for (var i = 0; i < animations.length; i++) { + for (var x = 0; x < animations[i].length; x++) { + if (animations[i][x] === animation) { + obj.removeClass(animations[i][0] + " " + animations[i][1]).addClass(animation); + return; + } + } + } }; /** @@ -7093,36 +6780,34 @@ lychee.animate = function (obj, animation) { * @param {DropboxLoadedCB} callback */ lychee.loadDropbox = function (callback) { - if (!lychee.dropboxKey) { - loadingBar.show("error", lychee.locale["ERROR_DROPBOX_KEY"]); - return; - } - - // If the dropbox component has already been loaded, immediately call - // the callback; otherwise load the component first and call callback - // on success. - if (lychee.dropbox) { - callback(); - } else { - loadingBar.show(); - - var g = document.createElement("script"); - var s = document.getElementsByTagName("script")[0]; - - g.src = "https://www.dropbox.com/static/api/1/dropins.js"; - g.id = "dropboxjs"; - g.type = "text/javascript"; - g.async = true; - g.setAttribute("data-app-key", lychee.dropboxKey); - g.onload = g.onreadystatechange = function () { - var rs = this.readyState; - if (rs && rs !== "complete" && rs !== "loaded") return; - lychee.dropbox = true; - loadingBar.hide(); - callback(); - }; - s.parentNode.insertBefore(g, s); - } + if (!lychee.dropboxKey) { + loadingBar.show("error", lychee.locale["ERROR_DROPBOX_KEY"]); + return; + } + + // If the dropbox component has already been loaded, immediately call + // the callback; otherwise load the component first and call callback + // on success. + if (lychee.dropbox) { + callback(); + } else { + loadingBar.show(); + var g = document.createElement("script"); + var s = document.getElementsByTagName("script")[0]; + g.src = "https://www.dropbox.com/static/api/1/dropins.js"; + g.id = "dropboxjs"; + g.type = "text/javascript"; + g.async = true; + g.setAttribute("data-app-key", lychee.dropboxKey); + g.onload = g.onreadystatechange = function () { + var rs = this.readyState; + if (rs && rs !== "complete" && rs !== "loaded") return; + lychee.dropbox = true; + loadingBar.hide(); + callback(); + }; + s.parentNode.insertBefore(g, s); + } }; /** @@ -7138,15 +6823,13 @@ lychee.loadDropbox = function (callback) { * @returns {string} */ lychee.escapeHTML = function () { - var html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; - - // Ensure that html is a string - html += ""; + var html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; + // Ensure that html is a string + html += ""; - // Escape all critical characters - html = html.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, "`"); - - return html; + // Escape all critical characters + html = html.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, "`"); + return html; }; /** @@ -7164,37 +6847,33 @@ lychee.escapeHTML = function () { * @returns {string} */ lychee.html = function (literalSections) { - // Use raw literal sections: we don’t want - // backslashes (\n etc.) to be interpreted - var raw = literalSections.raw; - var result = ""; - - for (var _len = arguments.length, substs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - substs[_key - 1] = arguments[_key]; - } - - substs.forEach(function (subst, i) { - // Retrieve the literal section preceding - // the current substitution - var lit = raw[i]; - - // If the substitution is preceded by a dollar sign, - // we escape special characters in it - if (lit.slice(-1) === "$") { - subst = lychee.escapeHTML(subst); - lit = lit.slice(0, -1); - } - - result += lit; - result += subst; - }); - - // Take care of last literal section - // (Never fails, because an empty template string - // produces one literal section, an empty string) - result += raw[raw.length - 1]; + // Use raw literal sections: we don’t want + // backslashes (\n etc.) to be interpreted + var raw = literalSections.raw; + var result = ""; + for (var _len = arguments.length, substs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + substs[_key - 1] = arguments[_key]; + } + substs.forEach(function (subst, i) { + // Retrieve the literal section preceding + // the current substitution + var lit = raw[i]; + + // If the substitution is preceded by a dollar sign, + // we escape special characters in it + if (lit.slice(-1) === "$") { + subst = lychee.escapeHTML(subst); + lit = lit.slice(0, -1); + } + result += lit; + result += subst; + }); - return result; + // Take care of last literal section + // (Never fails, because an empty template string + // produces one literal section, an empty string) + result += raw[raw.length - 1]; + return result; }; /** @@ -7204,127 +6883,127 @@ lychee.html = function (literalSections) { * @returns {boolean} */ lychee.handleAPIError = function (jqXHR, params, lycheeException) { - if (api.hasSessionExpired(jqXHR, lycheeException)) { - loadingBar.show("error", lychee.locale["ERROR_SESSION"]); - setTimeout(function () { - lychee.goto(); - window.location.reload(); - }, 3000); - } else { - var msg = jqXHR.statusText + (lycheeException ? " - " + lycheeException.message : ""); - loadingBar.show("error", msg); - console.error("The server returned an error response", { - description: msg, - params: params, - response: lycheeException - }); - } - return true; + if (api.hasSessionExpired(jqXHR, lycheeException)) { + loadingBar.show("error", lychee.locale["ERROR_SESSION"]); + setTimeout(function () { + lychee["goto"](); + window.location.reload(); + }, 3000); + } else { + var msg = jqXHR.statusText + (lycheeException ? " - " + lycheeException.message : ""); + loadingBar.show("error", msg); + console.error("The server returned an error response", { + description: msg, + params: params, + response: lycheeException + }); + } + return true; }; /** * @returns {void} */ lychee.fullscreenEnter = function () { - var elem = document.documentElement; - if (elem.requestFullscreen) { - elem.requestFullscreen(); - } else if (elem.mozRequestFullScreen) { - /* Firefox */ - elem.mozRequestFullScreen(); - } else if (elem.webkitRequestFullscreen) { - /* Chrome, Safari and Opera */ - elem.webkitRequestFullscreen(); - } else if (elem.msRequestFullscreen) { - /* IE/Edge */ - elem.msRequestFullscreen(); - } + var elem = document.documentElement; + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.mozRequestFullScreen) { + /* Firefox */ + elem.mozRequestFullScreen(); + } else if (elem.webkitRequestFullscreen) { + /* Chrome, Safari and Opera */ + elem.webkitRequestFullscreen(); + } else if (elem.msRequestFullscreen) { + /* IE/Edge */ + elem.msRequestFullscreen(); + } }; /** * @returns {void} */ lychee.fullscreenExit = function () { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - /* Firefox */ - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - /* Chrome, Safari and Opera */ - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - /* IE/Edge */ - document.msExitFullscreen(); - } + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + /* Firefox */ + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + /* Chrome, Safari and Opera */ + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + /* IE/Edge */ + document.msExitFullscreen(); + } }; /** * @returns {void} */ lychee.fullscreenToggle = function () { - if (lychee.fullscreenStatus()) { - lychee.fullscreenExit(); - } else { - lychee.fullscreenEnter(); - } + if (lychee.fullscreenStatus()) { + lychee.fullscreenExit(); + } else { + lychee.fullscreenEnter(); + } }; /** * @returns {boolean} */ lychee.fullscreenStatus = function () { - var elem = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement; - return !!elem; + var elem = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement; + return !!elem; }; /** * @returns {boolean} */ lychee.fullscreenAvailable = function () { - return document.fullscreenEnabled || document.mozFullscreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled; + return document.fullscreenEnabled || document.mozFullscreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled; }; /** * @returns {void} */ lychee.fullscreenUpdate = function () { - if (lychee.fullscreenStatus()) { - $("#button_fs_album_enter,#button_fs_enter").hide(); - $("#button_fs_album_exit,#button_fs_exit").show(); - } else { - $("#button_fs_album_enter,#button_fs_enter").show(); - $("#button_fs_album_exit,#button_fs_exit").hide(); - } + if (lychee.fullscreenStatus()) { + $("#button_fs_album_enter,#button_fs_enter").hide(); + $("#button_fs_album_exit,#button_fs_exit").show(); + } else { + $("#button_fs_album_enter,#button_fs_enter").show(); + $("#button_fs_album_exit,#button_fs_exit").hide(); + } }; /** * @returns {void} */ lychee.footer_show = function () { - setTimeout(function () { - lychee.footer.removeClass("hide_footer"); - }, 200); + setTimeout(function () { + lychee.footer.removeClass("hide_footer"); + }, 200); }; /** * @returns {void} */ lychee.footer_hide = function () { - lychee.footer.addClass("hide_footer"); + lychee.footer.addClass("hide_footer"); }; /** * @returns {string} */ lychee.getBaseUrl = function () { - if (location.href.includes("index.html")) { - return location.href.replace("index.html" + location.hash, ""); - } else if (location.href.includes("gallery#")) { - return location.href.replace("gallery" + location.hash, ""); - } else { - return location.href.replace(location.hash, ""); - } + if (location.href.includes("index.html")) { + return location.href.replace("index.html" + location.hash, ""); + } else if (location.href.includes("gallery#")) { + return location.href.replace("gallery" + location.hash, ""); + } else { + return location.href.replace(location.hash, ""); + } }; /** @@ -7333,11 +7012,11 @@ lychee.getBaseUrl = function () { * @returns {void} */ lychee.startDrag = function (ev) { - /** @type ?HTMLDivElement */ - var div = ev.target.closest("div.album,div.photo"); - if (!div) return; - var type = div.classList.contains("album") ? "album" : "photo"; - ev.dataTransfer.setData("text/plain", type + "-" + div.dataset.id); + /** @type ?HTMLDivElement */ + var div = ev.target.closest("div.album,div.photo"); + if (!div) return; + var type = div.classList.contains("album") ? "album" : "photo"; + ev.dataTransfer.setData("text/plain", "".concat(type, "-").concat(div.dataset.id)); }; /** @@ -7346,21 +7025,20 @@ lychee.startDrag = function (ev) { * @returns {void} */ lychee.finishDrag = function (ev) { - ev.preventDefault(); - - /** @type string */ - var data = ev.dataTransfer.getData("text/plain"); - /** @type string */ - var targetId = ev.target.closest("div.album").dataset.id; - if (!targetId || data.substring(6) === targetId) return; - - if (data.startsWith("photo-")) { - // photo is dragged - contextMenu.photoDrop(data.substring(6), targetId, ev); - } else { - // album is dragged - contextMenu.albumDrop(data.substring(6), targetId, ev); - } + ev.preventDefault(); + + /** @type string */ + var data = ev.dataTransfer.getData("text/plain"); + /** @type string */ + var targetId = ev.target.closest("div.album").dataset.id; + if (!targetId || data.substring(6) === targetId) return; + if (data.startsWith("photo-")) { + // photo is dragged + contextMenu.photoDrop(data.substring(6), targetId, ev); + } else { + // album is dragged + contextMenu.albumDrop(data.substring(6), targetId, ev); + } }; /** @@ -7369,12 +7047,12 @@ lychee.finishDrag = function (ev) { * @returns {void} */ lychee.overDrag = function (ev) { - ev.preventDefault(); - /** @type ?HTMLDivElement */ - var div = ev.target.closest("div.album"); - if (div) { - div.classList.add("album__dragover"); - } + ev.preventDefault(); + /** @type ?HTMLDivElement */ + var div = ev.target.closest("div.album"); + if (div) { + div.classList.add("album__dragover"); + } }; /** @@ -7383,11 +7061,11 @@ lychee.overDrag = function (ev) { * @returns {void} */ lychee.leaveDrag = function (ev) { - /** @type ?HTMLDivElement */ - var div = ev.target.closest("div.album"); - if (div) { - div.classList.remove("album__dragover"); - } + /** @type ?HTMLDivElement */ + var div = ev.target.closest("div.album"); + if (div) { + div.classList.remove("album__dragover"); + } }; /** @@ -7396,7 +7074,7 @@ lychee.leaveDrag = function (ev) { * @returns {void} */ lychee.endDrag = function (ev) { - $("div.album").removeClass("album__dragover"); + $("div.album").removeClass("album__dragover"); }; /** @@ -7409,8 +7087,8 @@ lychee.endDrag = function (ev) { * @return {void} */ lychee.addClickOrTouchListener = function (eventTarget, listener, options) { - eventTarget.addEventListener("click", listener, options); - eventTarget.addEventListener("touchend", listener, options); + eventTarget.addEventListener("click", listener, options); + eventTarget.addEventListener("touchend", listener, options); }; /** @@ -7421,602 +7099,561 @@ lychee.addClickOrTouchListener = function (eventTarget, listener, options) { */ lychee.locale = { - USERNAME: "Username", - PASSWORD: "Password", - ENTER: "Enter", - CANCEL: "Cancel", - SIGN_IN: "Sign In", - CLOSE: "Close", - SETTINGS: "Settings", - SEARCH: "Search …", - MORE: "More", - DEFAULT: "Default", - GALLERY: "Gallery", - - USERS: "Users", - CREATE: "Create", - REMOVE: "Remove", - SHARE: "Share", - U2F: "U2F", - NOTIFICATIONS: "Notifications", - SHARING: "Sharing", - CHANGE_LOGIN: "Change Login", - CHANGE_SORTING: "Change Sorting", - SET_DROPBOX: "Set Dropbox", - ABOUT_LYCHEE: "About Lychee", - DIAGNOSTICS: "Diagnostics", - DIAGNOSTICS_GET_SIZE: "Request space usage", - LOGS: "Show Logs", - CLEAN_LOGS: "Clean Noise", - CLEAR: "Clear", - SIGN_OUT: "Sign Out", - UPDATE_AVAILABLE: "Update available!", - MIGRATION_AVAILABLE: "Migration available!", - CHECK_FOR_UPDATE: "Check for updates", - DEFAULT_LICENSE: "Default license for new uploads:", - SET_LICENSE: "Set License", - SET_OVERLAY_TYPE: "Set Overlay", - SET_MAP_PROVIDER: "Set OpenStreetMap tiles provider", - FULL_SETTINGS: "Full Settings", - UPDATE: "Update", - RESET: "Reset", - DISABLE_TOKEN_TOOLTIP: "Disable", - ENABLE_TOKEN: "Enable API token", - DISABLED_TOKEN_STATUS_MSG: "Disabled", - TOKEN_BUTTON: "API Token ...", - TOKEN_NOT_AVAILABLE: "You have already viewed this token.", - TOKEN_WAIT: "Wait ...", - - SMART_ALBUMS: "Smart albums", - SHARED_ALBUMS: "Shared albums", - ALBUMS: "Albums", - PHOTOS: "Pictures", - SEARCH_RESULTS: "Search results", - - RENAME: "Rename", - RENAME_ALL: "Rename Selected", - MERGE: "Merge", - MERGE_ALL: "Merge Selected", - MAKE_PUBLIC: "Make Public", - SHARE_ALBUM: "Share Album", - SHARE_PHOTO: "Share Photo", - VISIBILITY_ALBUM: "Album Visibility", - VISIBILITY_PHOTO: "Photo Visibility", - DOWNLOAD_ALBUM: "Download Album", - ABOUT_ALBUM: "About Album", - DELETE_ALBUM: "Delete Album", - MOVE_ALBUM: "Move Album", - FULLSCREEN_ENTER: "Enter Fullscreen", - FULLSCREEN_EXIT: "Exit Fullscreen", - - SHARING_ALBUM_USERS: "Share this album with users", - WAIT_FETCH_DATA: "Please wait while we get the data …", - SHARING_ALBUM_USERS_NO_USERS: "There are no users to share the album with", - SHARING_ALBUM_USERS_LONG_MESSAGE: "Select the users to share this album with", - - DELETE_ALBUM_QUESTION: "Delete Album and Photos", - KEEP_ALBUM: "Keep Album", - DELETE_ALBUM_CONFIRMATION: "Are you sure you want to delete the album “%s” and all of the photos it contains? This action can’t be undone!", - - DELETE_TAG_ALBUM_QUESTION: "Delete Album", - DELETE_TAG_ALBUM_CONFIRMATION: "Are you sure you want to delete the album “%s” (any photos inside will not be deleted)? This action can’t be undone!", - - DELETE_ALBUMS_QUESTION: "Delete Albums and Photos", - KEEP_ALBUMS: "Keep Albums", - DELETE_ALBUMS_CONFIRMATION: "Are you sure you want to delete all %d selected albums and all of the photos they contain? This action can’t be undone!", - - DELETE_UNSORTED_CONFIRM: "Are you sure you want to delete all photos from “Unsorted”? This action can’t be undone!", - CLEAR_UNSORTED: "Clear Unsorted", - KEEP_UNSORTED: "Keep Unsorted", - - EDIT_SHARING: "Edit Sharing", - MAKE_PRIVATE: "Make Private", - - CLOSE_ALBUM: "Close Album", - CLOSE_PHOTO: "Close Photo", - CLOSE_MAP: "Close Map", - - ADD: "Add", - MOVE: "Move", - MOVE_ALL: "Move Selected", - DUPLICATE: "Duplicate", - DUPLICATE_ALL: "Duplicate Selected", - COPY_TO: "Copy to …", - COPY_ALL_TO: "Copy Selected to …", - DELETE: "Delete", - SAVE: "Save", - DELETE_ALL: "Delete Selected", - DOWNLOAD: "Download", - DOWNLOAD_ALL: "Download Selected", - UPLOAD_PHOTO: "Upload Photo", - IMPORT_LINK: "Import from Link", - IMPORT_DROPBOX: "Import from Dropbox", - IMPORT_SERVER: "Import from Server", - NEW_ALBUM: "New Album", - NEW_TAG_ALBUM: "New Tag Album", - UPLOAD_TRACK: "Upload track", - DELETE_TRACK: "Delete track", - - TITLE_NEW_ALBUM: "Enter a title for the new album:", - UNTITLED: "Untitled", - UNSORTED: "Unsorted", - STARRED: "Starred", - RECENT: "Recent", - PUBLIC: "Public", - ON_THIS_DAY: "On This Day", - NUM_PHOTOS: "Photos", - - CREATE_ALBUM: "Create Album", - CREATE_TAG_ALBUM: "Create Tag Album", - - STAR_PHOTO: "Star Photo", - STAR: "Star", - UNSTAR: "Unstar", - STAR_ALL: "Star Selected", - UNSTAR_ALL: "Unstar Selected", - TAG: "Tag", - TAG_ALL: "Tag Selected", - UNSTAR_PHOTO: "Unstar Photo", - SET_COVER: "Set Album Cover", - REMOVE_COVER: "Remove Album Cover", - - FULL_PHOTO: "Open Original", - ABOUT_PHOTO: "About Photo", - DISPLAY_FULL_MAP: "Map", - DIRECT_LINK: "Direct Link", - DIRECT_LINKS: "Direct Links", - QR_CODE: "QR Code", - - ALBUM_ABOUT: "About", - ALBUM_BASICS: "Basics", - ALBUM_TITLE: "Title", - ALBUM_NEW_TITLE: "Enter a new title for this album:", - ALBUMS_NEW_TITLE: "Enter a title for all %d selected albums:", - ALBUM_SET_TITLE: "Set Title", - ALBUM_DESCRIPTION: "Description", - ALBUM_SHOW_TAGS: "Tags to show", - ALBUM_NEW_DESCRIPTION: "Enter a new description for this album:", - ALBUM_SET_DESCRIPTION: "Set Description", - ALBUM_NEW_SHOWTAGS: "Enter tags of photos that will be visible in this album:", - ALBUM_SET_SHOWTAGS: "Set tags to show", - ALBUM_ALBUM: "Album", - ALBUM_CREATED: "Created", - ALBUM_IMAGES: "Images", - ALBUM_VIDEOS: "Videos", - ALBUM_SUBALBUMS: "Subalbums", - ALBUM_SHARING: "Share", - ALBUM_SHR_YES: "YES", - ALBUM_SHR_NO: "No", - ALBUM_PUBLIC: "Public", - ALBUM_PUBLIC_EXPL: "Anonymous users can access this album, subject to the restrictions below.", - ALBUM_FULL: "Original", - ALBUM_FULL_EXPL: "Anonymous users can behold full-resolution photos.", - ALBUM_HIDDEN: "Hidden", - ALBUM_HIDDEN_EXPL: "Anonymous users need a direct link to access this album.", - ALBUM_MARK_NSFW: "Mark album as sensitive", - ALBUM_UNMARK_NSFW: "Unmark album as sensitive", - ALBUM_NSFW: "Sensitive", - ALBUM_NSFW_EXPL: "Album contains sensitive content.", - ALBUM_DOWNLOADABLE: "Downloadable", - ALBUM_DOWNLOADABLE_EXPL: "Anonymous users can download this album.", - ALBUM_SHARE_BUTTON_VISIBLE: "Share button is visible", - ALBUM_SHARE_BUTTON_VISIBLE_EXPL: "Anonymous users can see social media sharing links.", - ALBUM_PASSWORD: "Password", - ALBUM_PASSWORD_PROT: "Password protected", - ALBUM_PASSWORD_PROT_EXPL: "Anonymous users need a shared password to access this album.", - ALBUM_PASSWORD_REQUIRED: "This album is protected by a password. Enter the password below to view the photos of this album:", - ALBUM_MERGE: "Are you sure you want to merge the album “%1$s” into the album “%2$s”?", - ALBUMS_MERGE: "Are you sure you want to merge all selected albums into the album “%s”?", - MERGE_ALBUM: "Merge Albums", - DONT_MERGE: "Don’t Merge", - ALBUM_MOVE: "Are you sure you want to move the album “%1$s” into the album “%2$s”?", - ALBUMS_MOVE: "Are you sure you want to move all selected albums into the album “%s”?", - MOVE_ALBUMS: "Move Albums", - NOT_MOVE_ALBUMS: "Don’t Move", - ROOT: "Albums", - ALBUM_REUSE: "Reuse", - ALBUM_LICENSE: "License", - ALBUM_SET_LICENSE: "Set License", - ALBUM_LICENSE_HELP: "Need help choosing?", - ALBUM_LICENSE_NONE: "None", - ALBUM_RESERVED: "All Rights Reserved", - ALBUM_SET_ORDER: "Set Order", - ALBUM_ORDERING: "Order by", - ALBUM_OWNER: "Owner", - - PHOTO_ABOUT: "About", - PHOTO_BASICS: "Basics", - PHOTO_TITLE: "Title", - PHOTO_NEW_TITLE: "Enter a new title for this photo:", - PHOTO_SET_TITLE: "Set Title", - PHOTO_UPLOADED: "Uploaded", - PHOTO_DESCRIPTION: "Description", - PHOTO_NEW_DESCRIPTION: "Enter a new description for this photo:", - PHOTO_SET_DESCRIPTION: "Set Description", - PHOTO_NEW_LICENSE: "Add a License", - PHOTO_SET_LICENSE: "Set License", - PHOTO_LICENSE: "License", - PHOTO_LICENSE_HELP: "Need help choosing?", - PHOTO_REUSE: "Reuse", - PHOTO_LICENSE_NONE: "None", - PHOTO_RESERVED: "All Rights Reserved", - PHOTO_LATITUDE: "Latitude", - PHOTO_LONGITUDE: "Longitude", - PHOTO_ALTITUDE: "Altitude", - PHOTO_IMGDIRECTION: "Direction", - PHOTO_LOCATION: "Location", - PHOTO_IMAGE: "Image", - PHOTO_VIDEO: "Video", - PHOTO_SIZE: "Size", - PHOTO_FORMAT: "Format", - PHOTO_RESOLUTION: "Resolution", - PHOTO_DURATION: "Duration", - PHOTO_FPS: "Frame rate", - PHOTO_TAGS: "Tags", - PHOTO_NOTAGS: "No Tags", - PHOTO_NEW_TAGS: "Enter your tags for this photo. You can add multiple tags by separating them with a comma:", - PHOTOS_NEW_TAGS: "Enter your tags for all %d selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma:", - PHOTO_SET_TAGS: "Set Tags", - TAGS_OVERRIDE_INFO: "If this is unchecked, the tags will be added to the existing tags of the photo.", - PHOTO_CAMERA: "Camera", - PHOTO_CAPTURED: "Captured", - PHOTO_MAKE: "Make", - PHOTO_TYPE: "Type/Model", - PHOTO_LENS: "Lens", - PHOTO_SHUTTER: "Shutter Speed", - PHOTO_APERTURE: "Aperture", - PHOTO_FOCAL: "Focal Length", - PHOTO_ISO: "ISO %s", - PHOTO_SHARING: "Sharing", - PHOTO_SHR_PUBLIC: "Public", - PHOTO_SHR_ALB: "Yes (Album)", - PHOTO_SHR_PHT: "Yes (Photo)", - PHOTO_SHR_NO: "No", - PHOTO_DELETE: "Delete Photo", - PHOTO_KEEP: "Keep Photo", - PHOTO_DELETE_CONFIRMATION: "Are you sure you want to delete the photo “%s”? This action can’t be undone!", - PHOTO_DELETE_ALL: "Are you sure you want to delete all %d selected photo? This action can’t be undone!", - PHOTOS_NEW_TITLE: "Enter a title for all %d selected photos:", - PHOTO_MAKE_PRIVATE_ALBUM: "This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.", - PHOTO_SHOW_ALBUM: "Show Album", - PHOTO_PUBLIC: "Public", - PHOTO_PUBLIC_EXPL: "Anonymous users can view this photo, subject to the restrictions below.", - PHOTO_FULL: "Original", - PHOTO_FULL_EXPL: "Anonymous users can behold full-resolution photo.", - PHOTO_HIDDEN: "Hidden", - PHOTO_HIDDEN_EXPL: "Anonymous users need a direct link to view this photo.", - PHOTO_DOWNLOADABLE: "Downloadable", - PHOTO_DOWNLOADABLE_EXPL: "Anonymous users may download this photo.", - PHOTO_SHARE_BUTTON_VISIBLE: "Share button is visible", - PHOTO_SHARE_BUTTON_VISIBLE_EXPL: "Anonymous users can see social media sharing links.", - PHOTO_PASSWORD_PROT: "Password protected", - PHOTO_PASSWORD_PROT_EXPL: "Anonymous users need a shared password to view this photo.", - PHOTO_EDIT_SHARING_TEXT: "The sharing properties of this photo will be changed to the following:", - PHOTO_NO_EDIT_SHARING_TEXT: "Because this photo is located in a public album, it inherits that album’s visibility settings. Its current visibility is shown below for informational purposes only.", - PHOTO_EDIT_GLOBAL_SHARING_TEXT: "The visibility of this photo can be fine-tuned using global Lychee settings. Its current visibility is shown below for informational purposes only.", - PHOTO_NEW_CREATED_AT: "Enter the upload date for this photo. mm/dd/yyyy, hh:mm [am/pm]", - PHOTO_SET_CREATED_AT: "Set upload date", - - LOADING: "Loading", - ERROR: "Error", - ERROR_TEXT: "Whoops, it looks like something went wrong. Please reload the site and try again!", - ERROR_UNKNOWN: "Something unexpected happened. Please try again and check your installation and server. Take a look at the readme for more information.", - ERROR_MAP_DEACTIVATED: "Map functionality has been deactivated under settings.", - ERROR_SEARCH_DEACTIVATED: "Search functionality has been deactivated under settings.", - SUCCESS: "OK", - RETRY: "Retry", - OVERRIDE: "Override", - - SETTINGS_SUCCESS_LOGIN: "Login Info updated.", - SETTINGS_SUCCESS_SORT: "Sorting order updated.", - SETTINGS_SUCCESS_DROPBOX: "Dropbox Key updated.", - SETTINGS_SUCCESS_LANG: "Language updated", - SETTINGS_SUCCESS_LAYOUT: "Layout updated", - SETTINGS_SUCCESS_IMAGE_OVERLAY: "EXIF Overlay setting updated", - SETTINGS_SUCCESS_PUBLIC_SEARCH: "Public search updated", - SETTINGS_SUCCESS_LICENSE: "Default license updated", - SETTINGS_SUCCESS_MAP_DISPLAY: "Map display settings updated", - SETTINGS_SUCCESS_MAP_DISPLAY_PUBLIC: "Map display settings for public albums updated", - SETTINGS_SUCCESS_MAP_PROVIDER: "Map provider settings updated", - SETTINGS_SUCCESS_CSS: "CSS updated", - SETTINGS_SUCCESS_UPDATE: "Settings updated successfully", - SETTINGS_DROPBOX_KEY: "Dropbox API Key", - SETTINGS_ADVANCED_WARNING_EXPL: "Changing these advanced settings can be harmful to the stability, security and performance of this application. You should only modify them if you are sure of what you are doing.", - SETTINGS_ADVANCED_SAVE: "Save my modifications, I accept the risk!", - - U2F_NOT_SUPPORTED: "U2F not supported. Sorry.", - U2F_NOT_SECURE: "Environment not secured. U2F not available.", - U2F_REGISTER_KEY: "Register new device.", - U2F_REGISTRATION_SUCCESS: "Registration successful!", - U2F_AUTHENTIFICATION_SUCCESS: "Authentication successful!", - U2F_CREDENTIALS: "Credentials", - U2F_CREDENTIALS_DELETED: "Credentials deleted!", - - NEW_PHOTOS_NOTIFICATION: "Send new photos notification emails.", - SETTINGS_SUCCESS_NEW_PHOTOS_NOTIFICATION: "New photos notification updated", - USER_EMAIL_INSTRUCTION: "Add your email below to enable receiving email notifications. To stop receiving emails, simply remove your email below.", - - LOGIN_USERNAME: "New Username", - LOGIN_PASSWORD: "New Password", - LOGIN_PASSWORD_CONFIRM: "Confirm Password", - PASSWORD_TITLE: "Enter your current password:", - PASSWORD_CURRENT: "Current Password", - PASSWORD_TEXT: "Your credentials will be changed to the following:", - PASSWORD_CHANGE: "Change Login", - - EDIT_SHARING_TITLE: "Edit Sharing", - EDIT_SHARING_TEXT: "The sharing properties of this album will be changed to the following:", - SHARE_ALBUM_TEXT: "This album will be shared with the following properties:", - - SORT_DIALOG_ATTRIBUTE_LABEL: "Attribute", - SORT_DIALOG_ORDER_LABEL: "Order", - - SORT_ALBUM_BY: "Sort albums by %1$s in an %2$s order.", - - SORT_ALBUM_SELECT_1: "Creation Time", - SORT_ALBUM_SELECT_2: "Title", - SORT_ALBUM_SELECT_3: "Description", - SORT_ALBUM_SELECT_4: "Public", - SORT_ALBUM_SELECT_5: "Latest Take Date", - SORT_ALBUM_SELECT_6: "Oldest Take Date", - - SORT_PHOTO_BY: "Sort photos by %1$s in an %2$s order.", - - SORT_PHOTO_SELECT_1: "Upload Time", - SORT_PHOTO_SELECT_2: "Take Date", - SORT_PHOTO_SELECT_3: "Title", - SORT_PHOTO_SELECT_4: "Description", - SORT_PHOTO_SELECT_5: "Public", - SORT_PHOTO_SELECT_6: "Star", - SORT_PHOTO_SELECT_7: "Photo Format", - - SORT_ASCENDING: "Ascending", - SORT_DESCENDING: "Descending", - SORT_CHANGE: "Change Sorting", - - DROPBOX_TITLE: "Set Dropbox Key", - DROPBOX_TEXT: "In order to import photos from your Dropbox, you need a valid drop-ins app key from their website. Generate yourself a personal key and enter it below:", - - LANG_TEXT: "Change Lychee language for:", - LANG_TITLE: "Change Language", - - CSS_TEXT: "Personalize CSS:", - CSS_TITLE: "Change CSS", - PUBLIC_SEARCH_TEXT: "Public search allowed:", - OVERLAY_TYPE: "Photo overlay:", - OVERLAY_NONE: "None", - OVERLAY_EXIF: "EXIF data", - OVERLAY_DESCRIPTION: "Description", - OVERLAY_DATE: "Date taken", - MAP_DISPLAY_TEXT: "Enable maps (provided by OpenStreetMap):", - MAP_DISPLAY_PUBLIC_TEXT: "Enable maps for public albums (provided by OpenStreetMap):", - MAP_PROVIDER: "Provider of OpenStreetMap tiles:", - MAP_PROVIDER_WIKIMEDIA: "Wikimedia", - MAP_PROVIDER_OSM_ORG: "OpenStreetMap.org (no HiDPI)", - MAP_PROVIDER_OSM_DE: "OpenStreetMap.de (no HiDPI)", - MAP_PROVIDER_OSM_FR: "OpenStreetMap.fr (no HiDPI)", - MAP_PROVIDER_RRZE: "University of Erlangen, Germany (only HiDPI)", - MAP_INCLUDE_SUBALBUMS_TEXT: "Include photos of subalbums on map:", - LOCATION_DECODING: "Decode GPS data into location name", - LOCATION_SHOW: "Show location name", - LOCATION_SHOW_PUBLIC: "Show location name for public mode", - - LAYOUT_TYPE: "Layout of photos:", - LAYOUT_SQUARES: "Square thumbnails", - LAYOUT_JUSTIFIED: "With aspect, justified", - LAYOUT_UNJUSTIFIED: "With aspect, unjustified", - SET_LAYOUT: "Change layout", - - NSFW_VISIBLE_TEXT_1: "Make Sensitive albums visible by default.", - NSFW_VISIBLE_TEXT_2: "If the album is public, it is still accessible, just hidden from the view and can be revealed by pressing H.", - SETTINGS_SUCCESS_NSFW_VISIBLE: "Default sensitive album visibility updated with success.", - - NSFW_BANNER: "

Sensitive content

This album contains sensitive content which some people may find offensive or disturbing.

Tap to consent.

", - - VIEW_NO_RESULT: "No results", - VIEW_NO_PUBLIC_ALBUMS: "No public albums", - VIEW_NO_CONFIGURATION: "No configuration", - VIEW_PHOTO_NOT_FOUND: "Photo not found", - - NO_TAGS: "No Tags", - - UPLOAD_MANAGE_NEW_PHOTOS: "You can now manage your new photo(s).", - UPLOAD_COMPLETE: "Upload complete", - UPLOAD_COMPLETE_FAILED: "Failed to upload one or more photos.", - UPLOAD_IMPORTING: "Importing", - UPLOAD_IMPORTING_URL: "Importing URL", - UPLOAD_UPLOADING: "Uploading", - UPLOAD_FINISHED: "Finished", - UPLOAD_PROCESSING: "Processing", - UPLOAD_FAILED: "Failed", - UPLOAD_FAILED_ERROR: "Upload failed. The server returned an error!", - UPLOAD_FAILED_WARNING: "Upload failed. The server returned a warning!", - UPLOAD_CANCELLED: "Cancelled", - UPLOAD_SKIPPED: "Skipped", - UPLOAD_UPDATED: "Updated", - UPLOAD_GENERAL: "General", - UPLOAD_IMPORT_SKIPPED_DUPLICATE: "This photo has been skipped because it’s already in your library.", - UPLOAD_IMPORT_RESYNCED_DUPLICATE: "This photo has been skipped because it’s already in your library, but its metadata has been updated.", - UPLOAD_ERROR_CONSOLE: "Please take a look at the console of your browser for further details.", - UPLOAD_UNKNOWN: "Server returned an unknown response. Please take a look at the console of your browser for further details.", - UPLOAD_ERROR_UNKNOWN: "Upload failed. The server returned an unknown error!", - UPLOAD_ERROR_POSTSIZE: "Upload failed. The PHP post_max_size may be too small! Otherwise check the FAQ.", - UPLOAD_ERROR_FILESIZE: "Upload failed. The PHP upload_max_filesize may be too small! Otherwise check the FAQ.", - UPLOAD_IN_PROGRESS: "Lychee is currently uploading!", - UPLOAD_IMPORT_WARN_ERR: "The import has been finished, but returned warnings or errors. Please take a look at the log (Settings -> Show Log) for further details.", - UPLOAD_IMPORT_COMPLETE: "Import complete", - UPLOAD_IMPORT_INSTR: "Please enter the direct link to a photo to import it:", - UPLOAD_IMPORT: "Import", - UPLOAD_IMPORT_SERVER: "Importing from server", - UPLOAD_IMPORT_SERVER_FOLD: "Folder empty or no readable files to process. Please take a look at the log (Settings -> Show Log) for further details.", - UPLOAD_IMPORT_SERVER_INSTR: "Import all photos, folders, and sub-folders located in the folders with the following absolute paths (on the server). Paths are space-separated, use \\ to escape a space in a path.", - UPLOAD_ABSOLUTE_PATH: "Absolute path to directories, space separated", - UPLOAD_IMPORT_SERVER_EMPT: "Could not start import because the folder was empty!", - UPLOAD_IMPORT_DELETE_ORIGINALS: "Delete originals", - UPLOAD_IMPORT_DELETE_ORIGINALS_EXPL: "Original files will be deleted after the import when possible.", - UPLOAD_IMPORT_VIA_SYMLINK: "Symbolic links", - UPLOAD_IMPORT_VIA_SYMLINK_EXPL: "Import files using symbolic links to originals.", - UPLOAD_IMPORT_SKIP_DUPLICATES: "Skip duplicates", - UPLOAD_IMPORT_SKIP_DUPLICATES_EXPL: "Existing media files are skipped.", - UPLOAD_IMPORT_RESYNC_METADATA: "Re-sync metadata", - UPLOAD_IMPORT_RESYNC_METADATA_EXPL: "Update metadata of existing media files.", - UPLOAD_IMPORT_LOW_MEMORY_EXPL: "The import process on the server is approaching the memory limit and may end up being terminated prematurely.", - UPLOAD_WARNING: "Warning", - UPLOAD_IMPORT_NOT_A_DIRECTORY: "The given path is not a readable directory!", - UPLOAD_IMPORT_PATH_RESERVED: "The given path is a reserved path of Lychee!", - UPLOAD_IMPORT_FAILED: "Could not import the file!", - UPLOAD_IMPORT_UNSUPPORTED: "Unsupported file type!", - UPLOAD_IMPORT_CANCELLED: "Import cancelled", - - ABOUT_SUBTITLE: "Self-hosted photo-management done right", - ABOUT_DESCRIPTION: "Lychee is a free photo-management tool, which runs on your server or web-space. Installing is a matter of seconds. Upload, manage and share photos like from a native application. Lychee comes with everything you need and all your photos are stored securely.", - FOOTER_COPYRIGHT: "All images on this website are subject to copyright by %1$s © %2$s", - HOSTED_WITH_LYCHEE: "Hosted with Lychee", - - URL_COPY_TO_CLIPBOARD: "Copy to clipboard", - URL_COPIED_TO_CLIPBOARD: "Copied URL to clipboard!", - PHOTO_DIRECT_LINKS_TO_IMAGES: "Direct links to image files:", - PHOTO_ORIGINAL: "Original", - PHOTO_MEDIUM: "Medium", - PHOTO_MEDIUM_HIDPI: "Medium HiDPI", - PHOTO_SMALL: "Thumb", - PHOTO_SMALL_HIDPI: "Thumb HiDPI", - PHOTO_THUMB: "Square thumb", - PHOTO_THUMB_HIDPI: "Square thumb HiDPI", - PHOTO_THUMBNAIL: "Photo thumbnail", - PHOTO_LIVE_VIDEO: "Video part of live-photo", - PHOTO_VIEW: "Lychee Photo View:", - - PHOTO_EDIT_ROTATECWISE: "Rotate clockwise", - PHOTO_EDIT_ROTATECCWISE: "Rotate counter-clockwise", - - ERROR_GPX: "Error loading GPX file: ", - ERROR_EITHER_ALBUMS_OR_PHOTOS: "Please select either albums or photos!", - ERROR_COULD_NOT_FIND: "Could not find what you want.", - ERROR_INVALID_EMAIL: "Not a valid email address.", - EMAIL_SUCCESS: "Email updated!", - ERROR_PHOTO_NOT_FOUND: "Error: photo %s not found!", - ERROR_EMPTY_USERNAME: "new username cannot be empty.", - ERROR_PASSWORD_DOES_NOT_MATCH: "new password does not match.", - ERROR_EMPTY_PASSWORD: "new password cannot be empty.", - ERROR_SELECT_ALBUM: "Select an album to share!", - ERROR_SELECT_USER: "Select a user to share with!", - ERROR_SELECT_SHARING: "Select a sharing to remove!", - SHARING_SUCCESS: "Sharing updated!", - SHARING_REMOVED: "Sharing removed!", - USER_CREATED: "User created!", - USER_DELETED: "User deleted!", - USER_UPDATED: "User updated!", - ENTER_EMAIL: "Enter your email address:", - ERROR_ALBUM_JSON_NOT_FOUND: "Error: Album JSON not found!", - ERROR_ALBUM_NOT_FOUND: "Error: album %s not found", - ERROR_DROPBOX_KEY: "Error: Dropbox key not set", - ERROR_SESSION: "Session expired.", - CAMERA_DATE: "Camera date", - NEW_PASSWORD: "new password", - ALLOW_UPLOADS: "Allow uploads", - ALLOW_USER_SELF_EDIT: "Allow self-management of user account", - OSM_CONTRIBUTORS: "OpenStreetMap contributors", - - dateTimeFormatter: new Intl.DateTimeFormat("default", { dateStyle: "medium", timeStyle: "medium" }), - - /** - * Formats a number representing a filesize in bytes as a localized string - * @param {!number} filesize - * @returns {string} A formatted and localized string - */ - printFilesizeLocalized: function printFilesizeLocalized(filesize) { - var suffix = [" B", " kB", " MB", " GB"]; - var i = 0; - // Sic! We check if the number is larger than 1000 but divide by 1024 by intention - // We aim at a number which has at most 3 non-decimal digits, i.e. the result shall be in the interval - // [1000/1024, 1000) = [0.977, 1000) (lower bound included, upper bound excluded) - while (filesize >= 1000.0 && i < suffix.length) { - filesize = filesize / 1024.0; - i++; - } - - // The number of decimal digits is anti-proportional to the number of non-decimal digits - // In total, there shall always be three digits - if (filesize >= 100.0) { - filesize = Math.round(filesize); - } else if (filesize >= 10.0) { - filesize = Math.round(filesize * 10.0) / 10.0; - } else { - filesize = Math.round(filesize * 100.0) / 100.0; - } - - return Number(filesize).toLocaleString() + suffix[i]; - }, - - /** - * Converts a JSON encoded date/time into a localized string relative to - * the original timezone - * - * The localized string uses the JS "medium" verbosity. - * The precise definition of "medium verbosity" depends on the current locale, but for Western languages this - * means that the date portion is fully printed with digits (e.g. something like 03/30/2021 for English, - * 30/03/2021 for French and 30.03.2021 for German), and that the time portion is printed with a resolution of - * seconds with two digits for all parts either in 24h or 12h scheme (e.g. something like 02:24:13pm for English - * and 14:24:13 for French/German). - * - * @param {?string} jsonDateTime - * @returns {string} A formatted and localized time - */ - printDateTime: function printDateTime(jsonDateTime) { - if (typeof jsonDateTime !== "string" || jsonDateTime === "") return ""; - - // Unfortunately, the built-in JS Date object is rather dumb. - // It is only required to support the timezone of the runtime - // environment and UTC. - // Moreover, the method `toLocalString` may or may not convert - // the represented time to the timezone of the runtime environment - // before formatting it as a string. - // However, we want to keep the printed time in the original timezone, - // because this facilitates human interaction with a photo. - // To this end we apply a "dirty" trick here. - // We first cut off any explicit timezone indication from the JSON - // string and only pass a date/time of the form `YYYYMMDDThhmmss` to - // `Date`. - // `Date` is required to interpret those time values according to the - // local timezone (see [MDN Web Docs - Date Time String Format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#date_time_string_format)). - // Most likely, the resulting `Date` object will represent the - // wrong instant in time (given in seconds since epoch), but we only - // want to call `toLocalString` which is fine and don't do any time - // arithmetics. - // Then we add the original timezone to the string manually. - var splitDateTime = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([,.]\d{1,6})?)([-Z+])(\d{2}:\d{2})?$/.exec(jsonDateTime); - // The capturing groups are: - // - 0: the whole string - // - 1: the whole date/time segment incl. fractional seconds - // - 2: the fractional seconds (if present) - // - 3: the timezone separator, i.e. "Z", "-" or "+" (if present) - // - 4: the absolute timezone offset without the sign (if present) - console.assert(splitDateTime.length === 5, "'jsonDateTime' is not formatted acc. to ISO 8601; passed string was: " + jsonDateTime); - var result = lychee.locale.dateTimeFormatter.format(new Date(splitDateTime[1])); - if (splitDateTime[3] === "Z" || splitDateTime[4] === "00:00") { - result += " UTC"; - } else { - result += " UTC" + splitDateTime[3] + splitDateTime[4]; - } - return result; - }, - - /** - * Converts a JSON encoded date/time into a localized string which only displays month and year. - * - * The month is printed as a shortened word with 3/4 letters, the year is printed with 4 digits (e.g. something like - * "Aug 2020" in English or "Août 2020" in French). - * - * @param {?string} jsonDateTime - * @returns {string} A formatted and localized month and year - */ - printMonthYear: function printMonthYear(jsonDateTime) { - if (typeof jsonDateTime !== "string" || jsonDateTime === "") return ""; - var locale = "default"; // use the user's browser settings - var format = { month: "short", year: "numeric" }; - return new Date(jsonDateTime).toLocaleDateString(locale, format); - } + USERNAME: "Username", + PASSWORD: "Password", + ENTER: "Enter", + CANCEL: "Cancel", + SIGN_IN: "Sign In", + CLOSE: "Close", + SETTINGS: "Settings", + SEARCH: "Search …", + MORE: "More", + DEFAULT: "Default", + GALLERY: "Gallery", + USERS: "Users", + CREATE: "Create", + REMOVE: "Remove", + SHARE: "Share", + U2F: "U2F", + NOTIFICATIONS: "Notifications", + SHARING: "Sharing", + CHANGE_LOGIN: "Change Login", + CHANGE_SORTING: "Change Sorting", + SET_DROPBOX: "Set Dropbox", + ABOUT_LYCHEE: "About Lychee", + DIAGNOSTICS: "Diagnostics", + DIAGNOSTICS_GET_SIZE: "Request space usage", + LOGS: "Show Logs", + CLEAN_LOGS: "Clean Noise", + CLEAR: "Clear", + SIGN_OUT: "Sign Out", + UPDATE_AVAILABLE: "Update available!", + MIGRATION_AVAILABLE: "Migration available!", + CHECK_FOR_UPDATE: "Check for updates", + DEFAULT_LICENSE: "Default license for new uploads:", + SET_LICENSE: "Set License", + SET_OVERLAY_TYPE: "Set Overlay", + SET_MAP_PROVIDER: "Set OpenStreetMap tiles provider", + FULL_SETTINGS: "Full Settings", + UPDATE: "Update", + RESET: "Reset", + DISABLE_TOKEN_TOOLTIP: "Disable", + ENABLE_TOKEN: "Enable API token", + DISABLED_TOKEN_STATUS_MSG: "Disabled", + TOKEN_BUTTON: "API Token ...", + TOKEN_NOT_AVAILABLE: "You have already viewed this token.", + TOKEN_WAIT: "Wait ...", + SMART_ALBUMS: "Smart albums", + SHARED_ALBUMS: "Shared albums", + ALBUMS: "Albums", + PHOTOS: "Pictures", + SEARCH_RESULTS: "Search results", + RENAME: "Rename", + RENAME_ALL: "Rename Selected", + MERGE: "Merge", + MERGE_ALL: "Merge Selected", + MAKE_PUBLIC: "Make Public", + SHARE_ALBUM: "Share Album", + SHARE_PHOTO: "Share Photo", + VISIBILITY_ALBUM: "Album Visibility", + VISIBILITY_PHOTO: "Photo Visibility", + DOWNLOAD_ALBUM: "Download Album", + ABOUT_ALBUM: "About Album", + DELETE_ALBUM: "Delete Album", + MOVE_ALBUM: "Move Album", + FULLSCREEN_ENTER: "Enter Fullscreen", + FULLSCREEN_EXIT: "Exit Fullscreen", + SHARING_ALBUM_USERS: "Share this album with users", + WAIT_FETCH_DATA: "Please wait while we get the data …", + SHARING_ALBUM_USERS_NO_USERS: "There are no users to share the album with", + SHARING_ALBUM_USERS_LONG_MESSAGE: "Select the users to share this album with", + DELETE_ALBUM_QUESTION: "Delete Album and Photos", + KEEP_ALBUM: "Keep Album", + DELETE_ALBUM_CONFIRMATION: "Are you sure you want to delete the album “%s” and all of the photos it contains? This action can’t be undone!", + DELETE_TAG_ALBUM_QUESTION: "Delete Album", + DELETE_TAG_ALBUM_CONFIRMATION: "Are you sure you want to delete the album “%s” (any photos inside will not be deleted)? This action can’t be undone!", + DELETE_ALBUMS_QUESTION: "Delete Albums and Photos", + KEEP_ALBUMS: "Keep Albums", + DELETE_ALBUMS_CONFIRMATION: "Are you sure you want to delete all %d selected albums and all of the photos they contain? This action can’t be undone!", + DELETE_UNSORTED_CONFIRM: "Are you sure you want to delete all photos from “Unsorted”? This action can’t be undone!", + CLEAR_UNSORTED: "Clear Unsorted", + KEEP_UNSORTED: "Keep Unsorted", + EDIT_SHARING: "Edit Sharing", + MAKE_PRIVATE: "Make Private", + CLOSE_ALBUM: "Close Album", + CLOSE_PHOTO: "Close Photo", + CLOSE_MAP: "Close Map", + ADD: "Add", + MOVE: "Move", + MOVE_ALL: "Move Selected", + DUPLICATE: "Duplicate", + DUPLICATE_ALL: "Duplicate Selected", + COPY_TO: "Copy to …", + COPY_ALL_TO: "Copy Selected to …", + DELETE: "Delete", + SAVE: "Save", + DELETE_ALL: "Delete Selected", + DOWNLOAD: "Download", + DOWNLOAD_ALL: "Download Selected", + UPLOAD_PHOTO: "Upload Photo", + IMPORT_LINK: "Import from Link", + IMPORT_DROPBOX: "Import from Dropbox", + IMPORT_SERVER: "Import from Server", + NEW_ALBUM: "New Album", + NEW_TAG_ALBUM: "New Tag Album", + UPLOAD_TRACK: "Upload track", + DELETE_TRACK: "Delete track", + TITLE_NEW_ALBUM: "Enter a title for the new album:", + UNTITLED: "Untitled", + UNSORTED: "Unsorted", + STARRED: "Starred", + RECENT: "Recent", + PUBLIC: "Public", + ON_THIS_DAY: "On This Day", + NUM_PHOTOS: "Photos", + CREATE_ALBUM: "Create Album", + CREATE_TAG_ALBUM: "Create Tag Album", + STAR_PHOTO: "Star Photo", + STAR: "Star", + UNSTAR: "Unstar", + STAR_ALL: "Star Selected", + UNSTAR_ALL: "Unstar Selected", + TAG: "Tag", + TAG_ALL: "Tag Selected", + UNSTAR_PHOTO: "Unstar Photo", + SET_COVER: "Set Album Cover", + REMOVE_COVER: "Remove Album Cover", + FULL_PHOTO: "Open Original", + ABOUT_PHOTO: "About Photo", + DISPLAY_FULL_MAP: "Map", + DIRECT_LINK: "Direct Link", + DIRECT_LINKS: "Direct Links", + QR_CODE: "QR Code", + ALBUM_ABOUT: "About", + ALBUM_BASICS: "Basics", + ALBUM_TITLE: "Title", + ALBUM_NEW_TITLE: "Enter a new title for this album:", + ALBUMS_NEW_TITLE: "Enter a title for all %d selected albums:", + ALBUM_SET_TITLE: "Set Title", + ALBUM_DESCRIPTION: "Description", + ALBUM_SHOW_TAGS: "Tags to show", + ALBUM_NEW_DESCRIPTION: "Enter a new description for this album:", + ALBUM_SET_DESCRIPTION: "Set Description", + ALBUM_NEW_SHOWTAGS: "Enter tags of photos that will be visible in this album:", + ALBUM_SET_SHOWTAGS: "Set tags to show", + ALBUM_ALBUM: "Album", + ALBUM_CREATED: "Created", + ALBUM_IMAGES: "Images", + ALBUM_VIDEOS: "Videos", + ALBUM_SUBALBUMS: "Subalbums", + ALBUM_SHARING: "Share", + ALBUM_SHR_YES: "YES", + ALBUM_SHR_NO: "No", + ALBUM_PUBLIC: "Public", + ALBUM_PUBLIC_EXPL: "Anonymous users can access this album, subject to the restrictions below.", + ALBUM_FULL: "Original", + ALBUM_FULL_EXPL: "Anonymous users can behold full-resolution photos.", + ALBUM_HIDDEN: "Hidden", + ALBUM_HIDDEN_EXPL: "Anonymous users need a direct link to access this album.", + ALBUM_MARK_NSFW: "Mark album as sensitive", + ALBUM_UNMARK_NSFW: "Unmark album as sensitive", + ALBUM_NSFW: "Sensitive", + ALBUM_NSFW_EXPL: "Album contains sensitive content.", + ALBUM_DOWNLOADABLE: "Downloadable", + ALBUM_DOWNLOADABLE_EXPL: "Anonymous users can download this album.", + ALBUM_SHARE_BUTTON_VISIBLE: "Share button is visible", + ALBUM_SHARE_BUTTON_VISIBLE_EXPL: "Anonymous users can see social media sharing links.", + ALBUM_PASSWORD: "Password", + ALBUM_PASSWORD_PROT: "Password protected", + ALBUM_PASSWORD_PROT_EXPL: "Anonymous users need a shared password to access this album.", + ALBUM_PASSWORD_REQUIRED: "This album is protected by a password. Enter the password below to view the photos of this album:", + ALBUM_MERGE: "Are you sure you want to merge the album “%1$s” into the album “%2$s”?", + ALBUMS_MERGE: "Are you sure you want to merge all selected albums into the album “%s”?", + MERGE_ALBUM: "Merge Albums", + DONT_MERGE: "Don’t Merge", + ALBUM_MOVE: "Are you sure you want to move the album “%1$s” into the album “%2$s”?", + ALBUMS_MOVE: "Are you sure you want to move all selected albums into the album “%s”?", + MOVE_ALBUMS: "Move Albums", + NOT_MOVE_ALBUMS: "Don’t Move", + ROOT: "Albums", + ALBUM_REUSE: "Reuse", + ALBUM_LICENSE: "License", + ALBUM_SET_LICENSE: "Set License", + ALBUM_LICENSE_HELP: "Need help choosing?", + ALBUM_LICENSE_NONE: "None", + ALBUM_RESERVED: "All Rights Reserved", + ALBUM_SET_ORDER: "Set Order", + ALBUM_ORDERING: "Order by", + ALBUM_OWNER: "Owner", + PHOTO_ABOUT: "About", + PHOTO_BASICS: "Basics", + PHOTO_TITLE: "Title", + PHOTO_NEW_TITLE: "Enter a new title for this photo:", + PHOTO_SET_TITLE: "Set Title", + PHOTO_UPLOADED: "Uploaded", + PHOTO_DESCRIPTION: "Description", + PHOTO_NEW_DESCRIPTION: "Enter a new description for this photo:", + PHOTO_SET_DESCRIPTION: "Set Description", + PHOTO_NEW_LICENSE: "Add a License", + PHOTO_SET_LICENSE: "Set License", + PHOTO_LICENSE: "License", + PHOTO_LICENSE_HELP: "Need help choosing?", + PHOTO_REUSE: "Reuse", + PHOTO_LICENSE_NONE: "None", + PHOTO_RESERVED: "All Rights Reserved", + PHOTO_LATITUDE: "Latitude", + PHOTO_LONGITUDE: "Longitude", + PHOTO_ALTITUDE: "Altitude", + PHOTO_IMGDIRECTION: "Direction", + PHOTO_LOCATION: "Location", + PHOTO_IMAGE: "Image", + PHOTO_VIDEO: "Video", + PHOTO_SIZE: "Size", + PHOTO_FORMAT: "Format", + PHOTO_RESOLUTION: "Resolution", + PHOTO_DURATION: "Duration", + PHOTO_FPS: "Frame rate", + PHOTO_TAGS: "Tags", + PHOTO_NOTAGS: "No Tags", + PHOTO_NEW_TAGS: "Enter your tags for this photo. You can add multiple tags by separating them with a comma:", + PHOTOS_NEW_TAGS: "Enter your tags for all %d selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma:", + PHOTO_SET_TAGS: "Set Tags", + TAGS_OVERRIDE_INFO: "If this is unchecked, the tags will be added to the existing tags of the photo.", + PHOTO_CAMERA: "Camera", + PHOTO_CAPTURED: "Captured", + PHOTO_MAKE: "Make", + PHOTO_TYPE: "Type/Model", + PHOTO_LENS: "Lens", + PHOTO_SHUTTER: "Shutter Speed", + PHOTO_APERTURE: "Aperture", + PHOTO_FOCAL: "Focal Length", + PHOTO_ISO: "ISO %s", + PHOTO_SHARING: "Sharing", + PHOTO_SHR_PUBLIC: "Public", + PHOTO_SHR_ALB: "Yes (Album)", + PHOTO_SHR_PHT: "Yes (Photo)", + PHOTO_SHR_NO: "No", + PHOTO_DELETE: "Delete Photo", + PHOTO_KEEP: "Keep Photo", + PHOTO_DELETE_CONFIRMATION: "Are you sure you want to delete the photo “%s”? This action can’t be undone!", + PHOTO_DELETE_ALL: "Are you sure you want to delete all %d selected photo? This action can’t be undone!", + PHOTOS_NEW_TITLE: "Enter a title for all %d selected photos:", + PHOTO_MAKE_PRIVATE_ALBUM: "This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.", + PHOTO_SHOW_ALBUM: "Show Album", + PHOTO_PUBLIC: "Public", + PHOTO_PUBLIC_EXPL: "Anonymous users can view this photo, subject to the restrictions below.", + PHOTO_FULL: "Original", + PHOTO_FULL_EXPL: "Anonymous users can behold full-resolution photo.", + PHOTO_HIDDEN: "Hidden", + PHOTO_HIDDEN_EXPL: "Anonymous users need a direct link to view this photo.", + PHOTO_DOWNLOADABLE: "Downloadable", + PHOTO_DOWNLOADABLE_EXPL: "Anonymous users may download this photo.", + PHOTO_SHARE_BUTTON_VISIBLE: "Share button is visible", + PHOTO_SHARE_BUTTON_VISIBLE_EXPL: "Anonymous users can see social media sharing links.", + PHOTO_PASSWORD_PROT: "Password protected", + PHOTO_PASSWORD_PROT_EXPL: "Anonymous users need a shared password to view this photo.", + PHOTO_EDIT_SHARING_TEXT: "The sharing properties of this photo will be changed to the following:", + PHOTO_NO_EDIT_SHARING_TEXT: "Because this photo is located in a public album, it inherits that album’s visibility settings. Its current visibility is shown below for informational purposes only.", + PHOTO_EDIT_GLOBAL_SHARING_TEXT: "The visibility of this photo can be fine-tuned using global Lychee settings. Its current visibility is shown below for informational purposes only.", + PHOTO_NEW_CREATED_AT: "Enter the upload date for this photo. mm/dd/yyyy, hh:mm [am/pm]", + PHOTO_SET_CREATED_AT: "Set upload date", + LOADING: "Loading", + ERROR: "Error", + ERROR_TEXT: "Whoops, it looks like something went wrong. Please reload the site and try again!", + ERROR_UNKNOWN: "Something unexpected happened. Please try again and check your installation and server. Take a look at the readme for more information.", + ERROR_MAP_DEACTIVATED: "Map functionality has been deactivated under settings.", + ERROR_SEARCH_DEACTIVATED: "Search functionality has been deactivated under settings.", + SUCCESS: "OK", + RETRY: "Retry", + OVERRIDE: "Override", + SETTINGS_SUCCESS_LOGIN: "Login Info updated.", + SETTINGS_SUCCESS_SORT: "Sorting order updated.", + SETTINGS_SUCCESS_DROPBOX: "Dropbox Key updated.", + SETTINGS_SUCCESS_LANG: "Language updated", + SETTINGS_SUCCESS_LAYOUT: "Layout updated", + SETTINGS_SUCCESS_IMAGE_OVERLAY: "EXIF Overlay setting updated", + SETTINGS_SUCCESS_PUBLIC_SEARCH: "Public search updated", + SETTINGS_SUCCESS_LICENSE: "Default license updated", + SETTINGS_SUCCESS_MAP_DISPLAY: "Map display settings updated", + SETTINGS_SUCCESS_MAP_DISPLAY_PUBLIC: "Map display settings for public albums updated", + SETTINGS_SUCCESS_MAP_PROVIDER: "Map provider settings updated", + SETTINGS_SUCCESS_CSS: "CSS updated", + SETTINGS_SUCCESS_UPDATE: "Settings updated successfully", + SETTINGS_DROPBOX_KEY: "Dropbox API Key", + SETTINGS_ADVANCED_WARNING_EXPL: "Changing these advanced settings can be harmful to the stability, security and performance of this application. You should only modify them if you are sure of what you are doing.", + SETTINGS_ADVANCED_SAVE: "Save my modifications, I accept the risk!", + U2F_NOT_SUPPORTED: "U2F not supported. Sorry.", + U2F_NOT_SECURE: "Environment not secured. U2F not available.", + U2F_REGISTER_KEY: "Register new device.", + U2F_REGISTRATION_SUCCESS: "Registration successful!", + U2F_AUTHENTIFICATION_SUCCESS: "Authentication successful!", + U2F_CREDENTIALS: "Credentials", + U2F_CREDENTIALS_DELETED: "Credentials deleted!", + NEW_PHOTOS_NOTIFICATION: "Send new photos notification emails.", + SETTINGS_SUCCESS_NEW_PHOTOS_NOTIFICATION: "New photos notification updated", + USER_EMAIL_INSTRUCTION: "Add your email below to enable receiving email notifications. To stop receiving emails, simply remove your email below.", + LOGIN_USERNAME: "New Username", + LOGIN_PASSWORD: "New Password", + LOGIN_PASSWORD_CONFIRM: "Confirm Password", + PASSWORD_TITLE: "Enter your current password:", + PASSWORD_CURRENT: "Current Password", + PASSWORD_TEXT: "Your credentials will be changed to the following:", + PASSWORD_CHANGE: "Change Login", + EDIT_SHARING_TITLE: "Edit Sharing", + EDIT_SHARING_TEXT: "The sharing properties of this album will be changed to the following:", + SHARE_ALBUM_TEXT: "This album will be shared with the following properties:", + SORT_DIALOG_ATTRIBUTE_LABEL: "Attribute", + SORT_DIALOG_ORDER_LABEL: "Order", + SORT_ALBUM_BY: "Sort albums by %1$s in an %2$s order.", + SORT_ALBUM_SELECT_1: "Creation Time", + SORT_ALBUM_SELECT_2: "Title", + SORT_ALBUM_SELECT_3: "Description", + SORT_ALBUM_SELECT_4: "Public", + SORT_ALBUM_SELECT_5: "Latest Take Date", + SORT_ALBUM_SELECT_6: "Oldest Take Date", + SORT_PHOTO_BY: "Sort photos by %1$s in an %2$s order.", + SORT_PHOTO_SELECT_1: "Upload Time", + SORT_PHOTO_SELECT_2: "Take Date", + SORT_PHOTO_SELECT_3: "Title", + SORT_PHOTO_SELECT_4: "Description", + SORT_PHOTO_SELECT_5: "Public", + SORT_PHOTO_SELECT_6: "Star", + SORT_PHOTO_SELECT_7: "Photo Format", + SORT_ASCENDING: "Ascending", + SORT_DESCENDING: "Descending", + SORT_CHANGE: "Change Sorting", + DROPBOX_TITLE: "Set Dropbox Key", + DROPBOX_TEXT: "In order to import photos from your Dropbox, you need a valid drop-ins app key from their website. Generate yourself a personal key and enter it below:", + LANG_TEXT: "Change Lychee language for:", + LANG_TITLE: "Change Language", + CSS_TEXT: "Personalize CSS:", + CSS_TITLE: "Change CSS", + PUBLIC_SEARCH_TEXT: "Public search allowed:", + OVERLAY_TYPE: "Photo overlay:", + OVERLAY_NONE: "None", + OVERLAY_EXIF: "EXIF data", + OVERLAY_DESCRIPTION: "Description", + OVERLAY_DATE: "Date taken", + MAP_DISPLAY_TEXT: "Enable maps (provided by OpenStreetMap):", + MAP_DISPLAY_PUBLIC_TEXT: "Enable maps for public albums (provided by OpenStreetMap):", + MAP_PROVIDER: "Provider of OpenStreetMap tiles:", + MAP_PROVIDER_WIKIMEDIA: "Wikimedia", + MAP_PROVIDER_OSM_ORG: "OpenStreetMap.org (no HiDPI)", + MAP_PROVIDER_OSM_DE: "OpenStreetMap.de (no HiDPI)", + MAP_PROVIDER_OSM_FR: "OpenStreetMap.fr (no HiDPI)", + MAP_PROVIDER_RRZE: "University of Erlangen, Germany (only HiDPI)", + MAP_INCLUDE_SUBALBUMS_TEXT: "Include photos of subalbums on map:", + LOCATION_DECODING: "Decode GPS data into location name", + LOCATION_SHOW: "Show location name", + LOCATION_SHOW_PUBLIC: "Show location name for public mode", + LAYOUT_TYPE: "Layout of photos:", + LAYOUT_SQUARES: "Square thumbnails", + LAYOUT_JUSTIFIED: "With aspect, justified", + LAYOUT_UNJUSTIFIED: "With aspect, unjustified", + SET_LAYOUT: "Change layout", + NSFW_VISIBLE_TEXT_1: "Make Sensitive albums visible by default.", + NSFW_VISIBLE_TEXT_2: "If the album is public, it is still accessible, just hidden from the view and can be revealed by pressing H.", + SETTINGS_SUCCESS_NSFW_VISIBLE: "Default sensitive album visibility updated with success.", + NSFW_BANNER: "

Sensitive content

This album contains sensitive content which some people may find offensive or disturbing.

Tap to consent.

", + VIEW_NO_RESULT: "No results", + VIEW_NO_PUBLIC_ALBUMS: "No public albums", + VIEW_NO_CONFIGURATION: "No configuration", + VIEW_PHOTO_NOT_FOUND: "Photo not found", + NO_TAGS: "No Tags", + UPLOAD_MANAGE_NEW_PHOTOS: "You can now manage your new photo(s).", + UPLOAD_COMPLETE: "Upload complete", + UPLOAD_COMPLETE_FAILED: "Failed to upload one or more photos.", + UPLOAD_IMPORTING: "Importing", + UPLOAD_IMPORTING_URL: "Importing URL", + UPLOAD_UPLOADING: "Uploading", + UPLOAD_FINISHED: "Finished", + UPLOAD_PROCESSING: "Processing", + UPLOAD_FAILED: "Failed", + UPLOAD_FAILED_ERROR: "Upload failed. The server returned an error!", + UPLOAD_FAILED_WARNING: "Upload failed. The server returned a warning!", + UPLOAD_CANCELLED: "Cancelled", + UPLOAD_SKIPPED: "Skipped", + UPLOAD_UPDATED: "Updated", + UPLOAD_GENERAL: "General", + UPLOAD_IMPORT_SKIPPED_DUPLICATE: "This photo has been skipped because it’s already in your library.", + UPLOAD_IMPORT_RESYNCED_DUPLICATE: "This photo has been skipped because it’s already in your library, but its metadata has been updated.", + UPLOAD_ERROR_CONSOLE: "Please take a look at the console of your browser for further details.", + UPLOAD_UNKNOWN: "Server returned an unknown response. Please take a look at the console of your browser for further details.", + UPLOAD_ERROR_UNKNOWN: "Upload failed. The server returned an unknown error!", + UPLOAD_ERROR_POSTSIZE: "Upload failed. The PHP post_max_size may be too small! Otherwise check the FAQ.", + UPLOAD_ERROR_FILESIZE: "Upload failed. The PHP upload_max_filesize may be too small! Otherwise check the FAQ.", + UPLOAD_IN_PROGRESS: "Lychee is currently uploading!", + UPLOAD_IMPORT_WARN_ERR: "The import has been finished, but returned warnings or errors. Please take a look at the log (Settings -> Show Log) for further details.", + UPLOAD_IMPORT_COMPLETE: "Import complete", + UPLOAD_IMPORT_INSTR: "Please enter the direct link to a photo to import it:", + UPLOAD_IMPORT: "Import", + UPLOAD_IMPORT_SERVER: "Importing from server", + UPLOAD_IMPORT_SERVER_FOLD: "Folder empty or no readable files to process. Please take a look at the log (Settings -> Show Log) for further details.", + UPLOAD_IMPORT_SERVER_INSTR: "Import all photos, folders, and sub-folders located in the folders with the following absolute paths (on the server). Paths are space-separated, use \\ to escape a space in a path.", + UPLOAD_ABSOLUTE_PATH: "Absolute path to directories, space separated", + UPLOAD_IMPORT_SERVER_EMPT: "Could not start import because the folder was empty!", + UPLOAD_IMPORT_DELETE_ORIGINALS: "Delete originals", + UPLOAD_IMPORT_DELETE_ORIGINALS_EXPL: "Original files will be deleted after the import when possible.", + UPLOAD_IMPORT_VIA_SYMLINK: "Symbolic links", + UPLOAD_IMPORT_VIA_SYMLINK_EXPL: "Import files using symbolic links to originals.", + UPLOAD_IMPORT_SKIP_DUPLICATES: "Skip duplicates", + UPLOAD_IMPORT_SKIP_DUPLICATES_EXPL: "Existing media files are skipped.", + UPLOAD_IMPORT_RESYNC_METADATA: "Re-sync metadata", + UPLOAD_IMPORT_RESYNC_METADATA_EXPL: "Update metadata of existing media files.", + UPLOAD_IMPORT_LOW_MEMORY_EXPL: "The import process on the server is approaching the memory limit and may end up being terminated prematurely.", + UPLOAD_WARNING: "Warning", + UPLOAD_IMPORT_NOT_A_DIRECTORY: "The given path is not a readable directory!", + UPLOAD_IMPORT_PATH_RESERVED: "The given path is a reserved path of Lychee!", + UPLOAD_IMPORT_FAILED: "Could not import the file!", + UPLOAD_IMPORT_UNSUPPORTED: "Unsupported file type!", + UPLOAD_IMPORT_CANCELLED: "Import cancelled", + ABOUT_SUBTITLE: "Self-hosted photo-management done right", + ABOUT_DESCRIPTION: "Lychee is a free photo-management tool, which runs on your server or web-space. Installing is a matter of seconds. Upload, manage and share photos like from a native application. Lychee comes with everything you need and all your photos are stored securely.", + FOOTER_COPYRIGHT: "All images on this website are subject to copyright by %1$s © %2$s", + HOSTED_WITH_LYCHEE: "Hosted with Lychee", + URL_COPY_TO_CLIPBOARD: "Copy to clipboard", + URL_COPIED_TO_CLIPBOARD: "Copied URL to clipboard!", + PHOTO_DIRECT_LINKS_TO_IMAGES: "Direct links to image files:", + PHOTO_ORIGINAL: "Original", + PHOTO_MEDIUM: "Medium", + PHOTO_MEDIUM_HIDPI: "Medium HiDPI", + PHOTO_SMALL: "Thumb", + PHOTO_SMALL_HIDPI: "Thumb HiDPI", + PHOTO_THUMB: "Square thumb", + PHOTO_THUMB_HIDPI: "Square thumb HiDPI", + PHOTO_THUMBNAIL: "Photo thumbnail", + PHOTO_LIVE_VIDEO: "Video part of live-photo", + PHOTO_VIEW: "Lychee Photo View:", + PHOTO_EDIT_ROTATECWISE: "Rotate clockwise", + PHOTO_EDIT_ROTATECCWISE: "Rotate counter-clockwise", + ERROR_GPX: "Error loading GPX file: ", + ERROR_EITHER_ALBUMS_OR_PHOTOS: "Please select either albums or photos!", + ERROR_COULD_NOT_FIND: "Could not find what you want.", + ERROR_INVALID_EMAIL: "Not a valid email address.", + EMAIL_SUCCESS: "Email updated!", + ERROR_PHOTO_NOT_FOUND: "Error: photo %s not found!", + ERROR_EMPTY_USERNAME: "new username cannot be empty.", + ERROR_PASSWORD_DOES_NOT_MATCH: "new password does not match.", + ERROR_EMPTY_PASSWORD: "new password cannot be empty.", + ERROR_SELECT_ALBUM: "Select an album to share!", + ERROR_SELECT_USER: "Select a user to share with!", + ERROR_SELECT_SHARING: "Select a sharing to remove!", + SHARING_SUCCESS: "Sharing updated!", + SHARING_REMOVED: "Sharing removed!", + USER_CREATED: "User created!", + USER_DELETED: "User deleted!", + USER_UPDATED: "User updated!", + ENTER_EMAIL: "Enter your email address:", + ERROR_ALBUM_JSON_NOT_FOUND: "Error: Album JSON not found!", + ERROR_ALBUM_NOT_FOUND: "Error: album %s not found", + ERROR_DROPBOX_KEY: "Error: Dropbox key not set", + ERROR_SESSION: "Session expired.", + CAMERA_DATE: "Camera date", + NEW_PASSWORD: "new password", + ALLOW_UPLOADS: "Allow uploads", + ALLOW_USER_SELF_EDIT: "Allow self-management of user account", + OSM_CONTRIBUTORS: "OpenStreetMap contributors", + dateTimeFormatter: new Intl.DateTimeFormat("default", { + dateStyle: "medium", + timeStyle: "medium" + }), + /** + * Formats a number representing a filesize in bytes as a localized string + * @param {!number} filesize + * @returns {string} A formatted and localized string + */ + printFilesizeLocalized: function printFilesizeLocalized(filesize) { + var suffix = [" B", " kB", " MB", " GB"]; + var i = 0; + // Sic! We check if the number is larger than 1000 but divide by 1024 by intention + // We aim at a number which has at most 3 non-decimal digits, i.e. the result shall be in the interval + // [1000/1024, 1000) = [0.977, 1000) (lower bound included, upper bound excluded) + while (filesize >= 1000.0 && i < suffix.length) { + filesize = filesize / 1024.0; + i++; + } + + // The number of decimal digits is anti-proportional to the number of non-decimal digits + // In total, there shall always be three digits + if (filesize >= 100.0) { + filesize = Math.round(filesize); + } else if (filesize >= 10.0) { + filesize = Math.round(filesize * 10.0) / 10.0; + } else { + filesize = Math.round(filesize * 100.0) / 100.0; + } + return Number(filesize).toLocaleString() + suffix[i]; + }, + /** + * Converts a JSON encoded date/time into a localized string relative to + * the original timezone + * + * The localized string uses the JS "medium" verbosity. + * The precise definition of "medium verbosity" depends on the current locale, but for Western languages this + * means that the date portion is fully printed with digits (e.g. something like 03/30/2021 for English, + * 30/03/2021 for French and 30.03.2021 for German), and that the time portion is printed with a resolution of + * seconds with two digits for all parts either in 24h or 12h scheme (e.g. something like 02:24:13pm for English + * and 14:24:13 for French/German). + * + * @param {?string} jsonDateTime + * @returns {string} A formatted and localized time + */ + printDateTime: function printDateTime(jsonDateTime) { + if (typeof jsonDateTime !== "string" || jsonDateTime === "") return ""; + + // Unfortunately, the built-in JS Date object is rather dumb. + // It is only required to support the timezone of the runtime + // environment and UTC. + // Moreover, the method `toLocalString` may or may not convert + // the represented time to the timezone of the runtime environment + // before formatting it as a string. + // However, we want to keep the printed time in the original timezone, + // because this facilitates human interaction with a photo. + // To this end we apply a "dirty" trick here. + // We first cut off any explicit timezone indication from the JSON + // string and only pass a date/time of the form `YYYYMMDDThhmmss` to + // `Date`. + // `Date` is required to interpret those time values according to the + // local timezone (see [MDN Web Docs - Date Time String Format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#date_time_string_format)). + // Most likely, the resulting `Date` object will represent the + // wrong instant in time (given in seconds since epoch), but we only + // want to call `toLocalString` which is fine and don't do any time + // arithmetics. + // Then we add the original timezone to the string manually. + var splitDateTime = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([,.]\d{1,6})?)([-Z+])(\d{2}:\d{2})?$/.exec(jsonDateTime); + // The capturing groups are: + // - 0: the whole string + // - 1: the whole date/time segment incl. fractional seconds + // - 2: the fractional seconds (if present) + // - 3: the timezone separator, i.e. "Z", "-" or "+" (if present) + // - 4: the absolute timezone offset without the sign (if present) + console.assert(splitDateTime.length === 5, "'jsonDateTime' is not formatted acc. to ISO 8601; passed string was: " + jsonDateTime); + var result = lychee.locale.dateTimeFormatter.format(new Date(splitDateTime[1])); + if (splitDateTime[3] === "Z" || splitDateTime[4] === "00:00") { + result += " UTC"; + } else { + result += " UTC" + splitDateTime[3] + splitDateTime[4]; + } + return result; + }, + /** + * Converts a JSON encoded date/time into a localized string which only displays month and year. + * + * The month is printed as a shortened word with 3/4 letters, the year is printed with 4 digits (e.g. something like + * "Aug 2020" in English or "Août 2020" in French). + * + * @param {?string} jsonDateTime + * @returns {string} A formatted and localized month and year + */ + printMonthYear: function printMonthYear(jsonDateTime) { + if (typeof jsonDateTime !== "string" || jsonDateTime === "") return ""; + var locale = "default"; // use the user's browser settings + var format = { + month: "short", + year: "numeric" + }; + return new Date(jsonDateTime).toLocaleDateString(locale, format); + } }; /** @@ -8030,54 +7667,53 @@ lychee.locale = { */ var map_provider_layer_attribution = { - /** - * @type {MapProvider} - */ - Wikimedia: { - layer: "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}{r}.png", - attribution: 'Wikimedia' - }, - /** - * @type {MapProvider} - */ - "OpenStreetMap.org": { - layer: "https://{s}.tile.osm.org/{z}/{x}/{y}.png", - attribution: "© " + lychee.locale["OSM_CONTRIBUTORS"] + "" - }, - /** - * @type {MapProvider} - */ - "OpenStreetMap.de": { - layer: "https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png ", - attribution: "© " + lychee.locale["OSM_CONTRIBUTORS"] + "" - }, - /** - * @type {MapProvider} - */ - "OpenStreetMap.fr": { - layer: "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png ", - attribution: "© " + lychee.locale["OSM_CONTRIBUTORS"] + "" - }, - /** - * @type {MapProvider} - */ - RRZE: { - layer: "https://{s}.osm.rrze.fau.de/osmhd/{z}/{x}/{y}.png", - attribution: "© " + lychee.locale["OSM_CONTRIBUTORS"] + "" - } + /** + * @type {MapProvider} + */ + Wikimedia: { + layer: "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}{r}.png", + attribution: 'Wikimedia' + }, + /** + * @type {MapProvider} + */ + "OpenStreetMap.org": { + layer: "https://{s}.tile.osm.org/{z}/{x}/{y}.png", + attribution: "© ".concat(lychee.locale["OSM_CONTRIBUTORS"], "") + }, + /** + * @type {MapProvider} + */ + "OpenStreetMap.de": { + layer: "https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png ", + attribution: "© ".concat(lychee.locale["OSM_CONTRIBUTORS"], "") + }, + /** + * @type {MapProvider} + */ + "OpenStreetMap.fr": { + layer: "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png ", + attribution: "© ".concat(lychee.locale["OSM_CONTRIBUTORS"], "") + }, + /** + * @type {MapProvider} + */ + RRZE: { + layer: "https://{s}.osm.rrze.fau.de/osmhd/{z}/{x}/{y}.png", + attribution: "© ".concat(lychee.locale["OSM_CONTRIBUTORS"], "") + } }; - var mapview = { - /** @type {?L.Map} */ - map: null, - photoLayer: null, - trackLayer: null, - /** @type {(?LatLngBounds|?number[][])} */ - bounds: null, - /** @type {?string} */ - albumID: null, - /** @type {?string} */ - map_provider: null + /** @type {?L.Map} */ + map: null, + photoLayer: null, + trackLayer: null, + /** @type {(?LatLngBounds|?number[][])} */ + bounds: null, + /** @type {?string} */ + albumID: null, + /** @type {?string} */ + map_provider: null }; /** @@ -8099,7 +7735,7 @@ var mapview = { * @returns {boolean} */ mapview.isInitialized = function () { - return !(mapview.map === null || mapview.photoLayer === null); + return !(mapview.map === null || mapview.photoLayer === null); }; /** @@ -8109,29 +7745,29 @@ mapview.isInitialized = function () { * @returns {void} */ mapview.title = function (_albumID, _albumTitle) { - switch (_albumID) { - case SmartAlbumID.STARRED: - lychee.setMetaData(lychee.locale["STARRED"]); - break; - case SmartAlbumID.PUBLIC: - lychee.setMetaData(lychee.locale["PUBLIC"]); - break; - case SmartAlbumID.RECENT: - lychee.setMetaData(lychee.locale["RECENT"]); - break; - case SmartAlbumID.UNSORTED: - lychee.setMetaData(lychee.locale["UNSORTED"]); - break; - case SmartAlbumID.ON_THIS_DAY: - lychee.setMetaData(lychee.locale["ON_THIS_DAY"]); - break; - case null: - lychee.setMetaData(lychee.locale["ALBUMS"]); - break; - default: - lychee.setMetaData(_albumTitle ? _albumTitle : lychee.locale["UNTITLED"]); - break; - } + switch (_albumID) { + case SmartAlbumID.STARRED: + lychee.setMetaData(lychee.locale["STARRED"]); + break; + case SmartAlbumID.PUBLIC: + lychee.setMetaData(lychee.locale["PUBLIC"]); + break; + case SmartAlbumID.RECENT: + lychee.setMetaData(lychee.locale["RECENT"]); + break; + case SmartAlbumID.UNSORTED: + lychee.setMetaData(lychee.locale["UNSORTED"]); + break; + case SmartAlbumID.ON_THIS_DAY: + lychee.setMetaData(lychee.locale["ON_THIS_DAY"]); + break; + case null: + lychee.setMetaData(lychee.locale["ALBUMS"]); + break; + default: + lychee.setMetaData(_albumTitle ? _albumTitle : lychee.locale["UNTITLED"]); + break; + } }; /** @@ -8141,274 +7777,256 @@ mapview.title = function (_albumID, _albumTitle) { * @returns {void} */ mapview.open = function () { - var albumID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - - // If map functionality is disabled -> do nothing - if (!lychee.map_display || lychee.publicMode === true && !lychee.map_display_public) { - loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); - return; - } - - var mapContainer = $("#lychee_map_container"); - lychee.animate(mapContainer, "fadeIn"); - mapContainer.addClass("active"); - header.setMode("map"); - - mapview.albumID = albumID; - - // initialize container only once - if (!mapview.isInitialized()) { - // Leaflet searches for icon in same directory as js file -> paths need - // to be overwritten - delete L.Icon.Default.prototype._getIconUrl; - L.Icon.Default.mergeOptions({ - iconRetinaUrl: "img/marker-icon-2x.png", - iconUrl: "img/marker-icon.png", - shadowUrl: "img/marker-shadow.png" - }); - - // Set initial view to (0,0) - mapview.map = L.map("lychee_map_container").setView([0.0, 0.0], 13); - - L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { - attribution: map_provider_layer_attribution[lychee.map_provider].attribution - }).addTo(mapview.map); - - mapview.map_provider = lychee.map_provider; - } else { - if (mapview.map_provider !== lychee.map_provider) { - // removew all layers - mapview.map.eachLayer(function (layer) { - mapview.map.removeLayer(layer); - }); - - L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { - attribution: map_provider_layer_attribution[lychee.map_provider].attribution - }).addTo(mapview.map); - - mapview.map_provider = lychee.map_provider; - } else { - // Mapview has already shown data -> remove only photoLayer and trackLayer showing photos and tracks - mapview.photoLayer.clear(); - if (mapview.trackLayer !== null) { - mapview.map.removeLayer(mapview.trackLayer); - } - } - - // Reset bounds - mapview.bounds = null; - } - - // Define how the photos on the map should look like - mapview.photoLayer = L.photo.cluster().on("click", function (e) { - /** @type {MapPhotoEntry} */ - var photo = { - photoID: e.layer.photo.photoID, - albumID: e.layer.photo.albumID, - name: e.layer.photo.name, - url: e.layer.photo.url, - url2x: e.layer.photo.url2x, - taken_at: lychee.locale.printDateTime(e.layer.photo.taken_at) - }; - var template = ""; - - // Retina version if available - if (photo.url2x !== "") { - template = template.concat('

{name}

', build.iconic("camera-slr"), "

{taken_at}

"); - } else { - template = template.concat('

{name}

', build.iconic("camera-slr"), "

{taken_at}

"); - } - - e.layer.bindPopup(L.Util.template(template, photo), { - minWidth: 400 - }).openPopup(); - }); - - // Adjusts zoom and position of map to show all images - var updateZoom = function updateZoom() { - if (mapview.bounds) { - mapview.map.fitBounds(mapview.bounds); - } else { - mapview.map.fitWorld(); - } - }; - - /** - * Adds photos to the map. - * - * @param {(Album|TagAlbum|PositionData)} album - * - * @returns {void} - */ - var addContentsToMap = function addContentsToMap(album) { - // check if empty - if (!album.photos) return; - - /** @type {MapPhotoEntry[]} */ - var photos = []; - - /** @type {?number} */ - var min_lat = null; - /** @type {?number} */ - var min_lng = null; - /** @type {?number} */ - var max_lat = null; - /** @type {?number} */ - var max_lng = null; - - album.photos.forEach( - /** @param {Photo} element */function (element) { - if (element.latitude || element.longitude) { - photos.push({ - lat: element.latitude, - lng: element.longitude, - thumbnail: element.size_variants.thumb !== null ? element.size_variants.thumb.url : "img/placeholder.png", - thumbnail2x: element.size_variants.thumb2x !== null ? element.size_variants.thumb2x.url : null, - url: element.size_variants.small !== null ? element.size_variants.small.url : element.url, - url2x: element.size_variants.small2x !== null ? element.size_variants.small2x.url : null, - name: element.title, - taken_at: element.taken_at, - albumID: element.album_id, - photoID: element.id - }); - - // Update min/max lat/lng - if (min_lat === null || min_lat > element.latitude) { - min_lat = element.latitude; - } - if (min_lng === null || min_lng > element.longitude) { - min_lng = element.longitude; - } - if (max_lat === null || max_lat < element.latitude) { - max_lat = element.latitude; - } - if (max_lng === null || max_lng < element.longitude) { - max_lng = element.longitude; - } - } - }); - - // Add Photos to map - mapview.photoLayer.add(photos).addTo(mapview.map); - - if (photos.length > 0) { - // update map bounds - var dist_lat = max_lat - min_lat; - var dist_lng = max_lng - min_lng; - mapview.bounds = [[min_lat - 0.1 * dist_lat, min_lng - 0.1 * dist_lng], [max_lat + 0.1 * dist_lat, max_lng + 0.1 * dist_lng]]; - } - - // add track - if (album.track_url) { - mapview.trackLayer = new L.GPX(album.track_url, { - async: true, - marker_options: { - startIconUrl: null, - endIconUrl: null, - shadowUrl: null - } - }).on("error", function (e) { - lychee.error(lycche.locale["ERROR_GPX"] + e.err); - }).on("loaded", function (e) { - if (photos.length === 0) { - // no photos, update map bound to center track - mapview.bounds = e.target.getBounds(); - updateZoom(); - } - }); - mapview.trackLayer.addTo(mapview.map); - } - - // Update Zoom and Position - updateZoom(); - }; - - /** - * Calls backend, retrieves information about photos and displays them. - * - * This function is called recursively to retrieve data for sub-albums. - * Possible enhancement could be to only have a single ajax call. - * - * @param {?string} _albumID - * @param {boolean} [_includeSubAlbums=true] - */ - var getAlbumData = function getAlbumData(_albumID) { - var _includeSubAlbums = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - - /** - * @param {PositionData} data - */ - var successHandler = function successHandler(data) { - addContentsToMap(data); - mapview.title(_albumID, data.title); - }; - - if (_albumID !== "" && _albumID !== null) { - // _albumID has been specified - var params = { - albumID: _albumID, - includeSubAlbums: _includeSubAlbums - }; - - api.post("Album::getPositionData", params, successHandler); - } else { - // AlbumID is empty -> fetch all photos of all albums - api.post("Albums::getPositionData", {}, successHandler); - } - }; - - // If sub-albums are not requested and album.json already has all data, - // we reuse it - if (lychee.map_include_subalbums === false && album.json !== null && album.json.photos !== null) { - addContentsToMap(album.json); - } else { - // Not all needed data has been preloaded - we need to load everything - getAlbumData(albumID, lychee.map_include_subalbums); - } - - // Update Zoom and Position once more (for empty map) - updateZoom(); + var albumID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + // If map functionality is disabled -> do nothing + if (!lychee.map_display || lychee.publicMode === true && !lychee.map_display_public) { + loadingBar.show("error", lychee.locale["ERROR_MAP_DEACTIVATED"]); + return; + } + var mapContainer = $("#lychee_map_container"); + lychee.animate(mapContainer, "fadeIn"); + mapContainer.addClass("active"); + header.setMode("map"); + mapview.albumID = albumID; + + // initialize container only once + if (!mapview.isInitialized()) { + // Leaflet searches for icon in same directory as js file -> paths need + // to be overwritten + delete L.Icon.Default.prototype._getIconUrl; + L.Icon.Default.mergeOptions({ + iconRetinaUrl: "img/marker-icon-2x.png", + iconUrl: "img/marker-icon.png", + shadowUrl: "img/marker-shadow.png" + }); + + // Set initial view to (0,0) + mapview.map = L.map("lychee_map_container").setView([0.0, 0.0], 13); + L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { + attribution: map_provider_layer_attribution[lychee.map_provider].attribution + }).addTo(mapview.map); + mapview.map_provider = lychee.map_provider; + } else { + if (mapview.map_provider !== lychee.map_provider) { + // removew all layers + mapview.map.eachLayer(function (layer) { + mapview.map.removeLayer(layer); + }); + L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { + attribution: map_provider_layer_attribution[lychee.map_provider].attribution + }).addTo(mapview.map); + mapview.map_provider = lychee.map_provider; + } else { + // Mapview has already shown data -> remove only photoLayer and trackLayer showing photos and tracks + mapview.photoLayer.clear(); + if (mapview.trackLayer !== null) { + mapview.map.removeLayer(mapview.trackLayer); + } + } + + // Reset bounds + mapview.bounds = null; + } + + // Define how the photos on the map should look like + mapview.photoLayer = L.photo.cluster().on("click", function (e) { + /** @type {MapPhotoEntry} */ + var photo = { + photoID: e.layer.photo.photoID, + albumID: e.layer.photo.albumID, + name: e.layer.photo.name, + url: e.layer.photo.url, + url2x: e.layer.photo.url2x, + taken_at: lychee.locale.printDateTime(e.layer.photo.taken_at) + }; + var template = ""; + + // Retina version if available + if (photo.url2x !== "") { + template = template.concat('

{name}

', build.iconic("camera-slr"), "

{taken_at}

"); + } else { + template = template.concat('

{name}

', build.iconic("camera-slr"), "

{taken_at}

"); + } + e.layer.bindPopup(L.Util.template(template, photo), { + minWidth: 400 + }).openPopup(); + }); + + // Adjusts zoom and position of map to show all images + var updateZoom = function updateZoom() { + if (mapview.bounds) { + mapview.map.fitBounds(mapview.bounds); + } else { + mapview.map.fitWorld(); + } + }; + + /** + * Adds photos to the map. + * + * @param {(Album|TagAlbum|PositionData)} album + * + * @returns {void} + */ + var addContentsToMap = function addContentsToMap(album) { + // check if empty + if (!album.photos) return; + + /** @type {MapPhotoEntry[]} */ + var photos = []; + + /** @type {?number} */ + var min_lat = null; + /** @type {?number} */ + var min_lng = null; + /** @type {?number} */ + var max_lat = null; + /** @type {?number} */ + var max_lng = null; + album.photos.forEach( /** @param {Photo} element */function (element) { + if (element.latitude || element.longitude) { + photos.push({ + lat: element.latitude, + lng: element.longitude, + thumbnail: element.size_variants.thumb !== null ? element.size_variants.thumb.url : "img/placeholder.png", + thumbnail2x: element.size_variants.thumb2x !== null ? element.size_variants.thumb2x.url : null, + url: element.size_variants.small !== null ? element.size_variants.small.url : element.url, + url2x: element.size_variants.small2x !== null ? element.size_variants.small2x.url : null, + name: element.title, + taken_at: element.taken_at, + albumID: element.album_id, + photoID: element.id + }); + + // Update min/max lat/lng + if (min_lat === null || min_lat > element.latitude) { + min_lat = element.latitude; + } + if (min_lng === null || min_lng > element.longitude) { + min_lng = element.longitude; + } + if (max_lat === null || max_lat < element.latitude) { + max_lat = element.latitude; + } + if (max_lng === null || max_lng < element.longitude) { + max_lng = element.longitude; + } + } + }); + + // Add Photos to map + mapview.photoLayer.add(photos).addTo(mapview.map); + if (photos.length > 0) { + // update map bounds + var dist_lat = max_lat - min_lat; + var dist_lng = max_lng - min_lng; + mapview.bounds = [[min_lat - 0.1 * dist_lat, min_lng - 0.1 * dist_lng], [max_lat + 0.1 * dist_lat, max_lng + 0.1 * dist_lng]]; + } + + // add track + if (album.track_url) { + mapview.trackLayer = new L.GPX(album.track_url, { + async: true, + marker_options: { + startIconUrl: null, + endIconUrl: null, + shadowUrl: null + } + }).on("error", function (e) { + lychee.error(lycche.locale["ERROR_GPX"] + e.err); + }).on("loaded", function (e) { + if (photos.length === 0) { + // no photos, update map bound to center track + mapview.bounds = e.target.getBounds(); + updateZoom(); + } + }); + mapview.trackLayer.addTo(mapview.map); + } + + // Update Zoom and Position + updateZoom(); + }; + + /** + * Calls backend, retrieves information about photos and displays them. + * + * This function is called recursively to retrieve data for sub-albums. + * Possible enhancement could be to only have a single ajax call. + * + * @param {?string} _albumID + * @param {boolean} [_includeSubAlbums=true] + */ + var getAlbumData = function getAlbumData(_albumID) { + var _includeSubAlbums = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + /** + * @param {PositionData} data + */ + var successHandler = function successHandler(data) { + addContentsToMap(data); + mapview.title(_albumID, data.title); + }; + if (_albumID !== "" && _albumID !== null) { + // _albumID has been specified + var params = { + albumID: _albumID, + includeSubAlbums: _includeSubAlbums + }; + api.post("Album::getPositionData", params, successHandler); + } else { + // AlbumID is empty -> fetch all photos of all albums + api.post("Albums::getPositionData", {}, successHandler); + } + }; + + // If sub-albums are not requested and album.json already has all data, + // we reuse it + if (lychee.map_include_subalbums === false && album.json !== null && album.json.photos !== null) { + addContentsToMap(album.json); + } else { + // Not all needed data has been preloaded - we need to load everything + getAlbumData(albumID, lychee.map_include_subalbums); + } + + // Update Zoom and Position once more (for empty map) + updateZoom(); }; /** * @returns {void} */ mapview.close = function () { - // If map functionality is disabled -> do nothing - if (!lychee.map_display) return; - - var mapContainer = $("#lychee_map_container"); - lychee.animate(mapContainer, "fadeOut"); - // TODO: Reconsider the line below - // The line below is inconsistent to the corresponding code for - // the photo view (cp. `view.photo.hide()`). - // Here, we remove the `active` class immediately, in `view.photo.hide()` - // we remove that class after the animation has ended. - mapContainer.removeClass("active"); - // TODO: Fix the line below - // The map view can also be opened from a single photo and probably a - // users expect to go back to the photo if they close the photo. - // Currently, Lychee jumps back to the album of that photo. - header.setMode(mapview.albumID ? "album" : "albums"); - - // Make album focusable - tabindex.makeFocusable(lychee.content); + // If map functionality is disabled -> do nothing + if (!lychee.map_display) return; + var mapContainer = $("#lychee_map_container"); + lychee.animate(mapContainer, "fadeOut"); + // TODO: Reconsider the line below + // The line below is inconsistent to the corresponding code for + // the photo view (cp. `view.photo.hide()`). + // Here, we remove the `active` class immediately, in `view.photo.hide()` + // we remove that class after the animation has ended. + mapContainer.removeClass("active"); + // TODO: Fix the line below + // The map view can also be opened from a single photo and probably a + // users expect to go back to the photo if they close the photo. + // Currently, Lychee jumps back to the album of that photo. + header.setMode(mapview.albumID ? "album" : "albums"); + + // Make album focusable + tabindex.makeFocusable(lychee.content); }; /** * @param {jQuery} elem * @returns {void} */ -mapview.goto = function (elem) { - // If map functionality is disabled -> do nothing - if (!lychee.map_display) return; - - var photoID = elem.attr("data-id"); - var albumID = elem.attr("data-album-id"); - - if (albumID === "null") albumID = "unsorted"; - - lychee.goto(albumID + "/" + photoID); +mapview["goto"] = function (elem) { + // If map functionality is disabled -> do nothing + if (!lychee.map_display) return; + var photoID = elem.attr("data-id"); + var albumID = elem.attr("data-album-id"); + if (albumID === "null") albumID = "unsorted"; + lychee["goto"](albumID + "/" + photoID); }; /** @@ -8420,16 +8038,15 @@ mapview.goto = function (elem) { * @returns {boolean} */ var isSelectKeyPressed = function isSelectKeyPressed(e) { - return e.metaKey || e.ctrlKey; + return e.metaKey || e.ctrlKey; }; - var multiselect = { - /** @type {string[]} */ - ids: [], - albumsSelected: 0, - photosSelected: 0, - /** @type {?jQuery} */ - lastClicked: null + /** @type {string[]} */ + ids: [], + albumsSelected: 0, + photosSelected: 0, + /** @type {?jQuery} */ + lastClicked: null }; /** @@ -8445,20 +8062,18 @@ var multiselect = { * @type {?SelectionPosition} */ multiselect.position = null; - multiselect.bind = function () { - $("#lychee_view_content").on("mousedown", function (e) { - if (e.which === 1) multiselect.show(e); - }); - - return true; + $("#lychee_view_content").on("mousedown", function (e) { + if (e.which === 1) multiselect.show(e); + }); + return true; }; /** * @returns {void} */ multiselect.unbind = function () { - $("#lychee_view_content").off("mousedown"); + $("#lychee_view_content").off("mousedown"); }; /** @@ -8466,12 +8081,11 @@ multiselect.unbind = function () { * @returns {{position: number, selected: boolean}} */ multiselect.isSelected = function (id) { - var pos = multiselect.ids.indexOf(id); - - return { - selected: pos !== -1, - position: pos - }; + var pos = multiselect.ids.indexOf(id); + return { + selected: pos !== -1, + position: pos + }; }; /** @@ -8479,11 +8093,9 @@ multiselect.isSelected = function (id) { * @param {string} id */ multiselect.toggleItem = function (object, id) { - if (album.isSmartID(id) || album.isSearchID(id)) return; - - var selected = multiselect.isSelected(id).selected; - - if (selected === false) multiselect.addItem(object, id);else multiselect.removeItem(object, id); + if (album.isSmartID(id) || album.isSearchID(id)) return; + var selected = multiselect.isSelected(id).selected; + if (selected === false) multiselect.addItem(object, id);else multiselect.removeItem(object, id); }; /** @@ -8491,27 +8103,22 @@ multiselect.toggleItem = function (object, id) { * @param {string} id */ multiselect.addItem = function (object, id) { - if (album.isSmartID(id) || album.isSearchID(id)) return; - if (!lychee.rights.settings.can_edit && albums.isShared(id)) return; - if (multiselect.isSelected(id).selected === true) return; - - var isAlbum = object.hasClass("album"); - - if (isAlbum && multiselect.photosSelected > 0 || !isAlbum && multiselect.albumsSelected > 0) { - loadingBar.show("error", lychee.locale["ERROR_EITHER_ALBUMS_OR_PHOTOS"]); - return; - } - - multiselect.ids.push(id); - multiselect.select(object); - - if (isAlbum) { - multiselect.albumsSelected++; - } else { - multiselect.photosSelected++; - } - - multiselect.lastClicked = object; + if (album.isSmartID(id) || album.isSearchID(id)) return; + if (!lychee.rights.settings.can_edit && albums.isShared(id)) return; + if (multiselect.isSelected(id).selected === true) return; + var isAlbum = object.hasClass("album"); + if (isAlbum && multiselect.photosSelected > 0 || !isAlbum && multiselect.albumsSelected > 0) { + loadingBar.show("error", lychee.locale["ERROR_EITHER_ALBUMS_OR_PHOTOS"]); + return; + } + multiselect.ids.push(id); + multiselect.select(object); + if (isAlbum) { + multiselect.albumsSelected++; + } else { + multiselect.photosSelected++; + } + multiselect.lastClicked = object; }; /** @@ -8519,24 +8126,19 @@ multiselect.addItem = function (object, id) { * @param {string} id */ multiselect.removeItem = function (object, id) { - var _multiselect$isSelect = multiselect.isSelected(id), - selected = _multiselect$isSelect.selected, - position = _multiselect$isSelect.position; - - if (selected === false) return; - - multiselect.ids.splice(position, 1); - multiselect.deselect(object); - - var isAlbum = object.hasClass("album"); - - if (isAlbum) { - multiselect.albumsSelected--; - } else { - multiselect.photosSelected--; - } - - multiselect.lastClicked = object; + var _multiselect$isSelect = multiselect.isSelected(id), + selected = _multiselect$isSelect.selected, + position = _multiselect$isSelect.position; + if (selected === false) return; + multiselect.ids.splice(position, 1); + multiselect.deselect(object); + var isAlbum = object.hasClass("album"); + if (isAlbum) { + multiselect.albumsSelected--; + } else { + multiselect.photosSelected--; + } + multiselect.lastClicked = object; }; /** @@ -8546,34 +8148,31 @@ multiselect.removeItem = function (object, id) { * @returns {void} */ multiselect.albumClick = function (e, albumObj) { - var id = albumObj.attr("data-id"); - - if ((isSelectKeyPressed(e) || e.shiftKey) && album.isUploadable()) { - if (albumObj.hasClass("disabled")) return; - - if (isSelectKeyPressed(e)) { - multiselect.toggleItem(albumObj, id); - } else { - if (multiselect.albumsSelected > 0) { - // Click with Shift. Select all elements between the current - // element and the last clicked-on one. - - if (albumObj.prevAll(".album").toArray().includes(multiselect.lastClicked[0])) { - albumObj.prevUntil(multiselect.lastClicked, ".album").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - } else if (albumObj.nextAll(".album").toArray().includes(multiselect.lastClicked[0])) { - albumObj.nextUntil(multiselect.lastClicked, ".album").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - } - } - - multiselect.addItem(albumObj, id); - } - } else { - lychee.goto(id); - } + var id = albumObj.attr("data-id"); + if ((isSelectKeyPressed(e) || e.shiftKey) && album.isUploadable()) { + if (albumObj.hasClass("disabled")) return; + if (isSelectKeyPressed(e)) { + multiselect.toggleItem(albumObj, id); + } else { + if (multiselect.albumsSelected > 0) { + // Click with Shift. Select all elements between the current + // element and the last clicked-on one. + + if (albumObj.prevAll(".album").toArray().includes(multiselect.lastClicked[0])) { + albumObj.prevUntil(multiselect.lastClicked, ".album").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + } else if (albumObj.nextAll(".album").toArray().includes(multiselect.lastClicked[0])) { + albumObj.nextUntil(multiselect.lastClicked, ".album").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + } + } + multiselect.addItem(albumObj, id); + } + } else { + lychee["goto"](id); + } }; /** @@ -8583,34 +8182,31 @@ multiselect.albumClick = function (e, albumObj) { * @returns {void} */ multiselect.photoClick = function (e, photoObj) { - var id = photoObj.attr("data-id"); - - if ((isSelectKeyPressed(e) || e.shiftKey) && album.isUploadable()) { - if (photoObj.hasClass("disabled")) return; - - if (isSelectKeyPressed(e)) { - multiselect.toggleItem(photoObj, id); - } else { - if (multiselect.photosSelected > 0) { - // Click with Shift. Select all elements between the current - // element and the last clicked-on one. - - if (photoObj.prevAll(".photo").toArray().includes(multiselect.lastClicked[0])) { - photoObj.prevUntil(multiselect.lastClicked, ".photo").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - } else if (photoObj.nextAll(".photo").toArray().includes(multiselect.lastClicked[0])) { - photoObj.nextUntil(multiselect.lastClicked, ".photo").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - } - } - - multiselect.addItem(photoObj, id); - } - } else { - lychee.goto(album.getID() + "/" + id); - } + var id = photoObj.attr("data-id"); + if ((isSelectKeyPressed(e) || e.shiftKey) && album.isUploadable()) { + if (photoObj.hasClass("disabled")) return; + if (isSelectKeyPressed(e)) { + multiselect.toggleItem(photoObj, id); + } else { + if (multiselect.photosSelected > 0) { + // Click with Shift. Select all elements between the current + // element and the last clicked-on one. + + if (photoObj.prevAll(".photo").toArray().includes(multiselect.lastClicked[0])) { + photoObj.prevUntil(multiselect.lastClicked, ".photo").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + } else if (photoObj.nextAll(".photo").toArray().includes(multiselect.lastClicked[0])) { + photoObj.nextUntil(multiselect.lastClicked, ".photo").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + } + } + multiselect.addItem(photoObj, id); + } + } else { + lychee["goto"](album.getID() + "/" + id); + } }; /** @@ -8620,16 +8216,14 @@ multiselect.photoClick = function (e, photoObj) { * @returns {void} */ multiselect.albumContextMenu = function (e, albumObj) { - var id = albumObj.attr("data-id"); - var selected = multiselect.isSelected(id).selected; - - if (albumObj.hasClass("disabled")) return; - - if (selected !== false && multiselect.ids.length > 1) { - contextMenu.albumMulti(multiselect.ids, e); - } else { - contextMenu.album(id, e); - } + var id = albumObj.attr("data-id"); + var selected = multiselect.isSelected(id).selected; + if (albumObj.hasClass("disabled")) return; + if (selected !== false && multiselect.ids.length > 1) { + contextMenu.albumMulti(multiselect.ids, e); + } else { + contextMenu.album(id, e); + } }; /** @@ -8639,32 +8233,30 @@ multiselect.albumContextMenu = function (e, albumObj) { * @returns {void} */ multiselect.photoContextMenu = function (e, photoObj) { - var id = photoObj.attr("data-id"); - var selected = multiselect.isSelected(id).selected; - - if (photoObj.hasClass("disabled")) return; - - if (selected !== false && multiselect.ids.length > 1) { - contextMenu.photoMulti(multiselect.ids, e); - } else if (visible.album() || visible.search()) { - contextMenu.photo(id, e); - } else if (visible.photo()) { - // should not happen... but you never know... - contextMenu.photo(_photo3.getID(), e); - } else { - loadingBar.show("error", lychee.locale["ERROR_COULD_NOT_FIND"]); - } + var id = photoObj.attr("data-id"); + var selected = multiselect.isSelected(id).selected; + if (photoObj.hasClass("disabled")) return; + if (selected !== false && multiselect.ids.length > 1) { + contextMenu.photoMulti(multiselect.ids, e); + } else if (visible.album() || visible.search()) { + contextMenu.photo(id, e); + } else if (visible.photo()) { + // should not happen... but you never know... + contextMenu.photo(_photo3.getID(), e); + } else { + loadingBar.show("error", lychee.locale["ERROR_COULD_NOT_FIND"]); + } }; /** * @returns {void} */ multiselect.clearSelection = function () { - multiselect.deselect($(".photo.active, .album.active")); - multiselect.ids = []; - multiselect.albumsSelected = 0; - multiselect.photosSelected = 0; - multiselect.lastClicked = null; + multiselect.deselect($(".photo.active, .album.active")); + multiselect.ids = []; + multiselect.albumsSelected = 0; + multiselect.photosSelected = 0; + multiselect.lastClicked = null; }; /** @@ -8672,32 +8264,27 @@ multiselect.clearSelection = function () { * @returns {boolean} */ multiselect.show = function (e) { - if (!album.isUploadable()) return false; - if (!visible.albums() && !visible.album()) return false; - if ($(".album:hover, .photo:hover").length !== 0) return false; - if (visible.search()) return false; - if (visible.multiselect()) $("#multiselect").remove(); - - _sidebar.setSelectable(false); - - if (!isSelectKeyPressed(e) && !e.shiftKey) { - multiselect.clearSelection(); - } - - multiselect.position = { - top: e.pageY, - right: $(document).width() - e.pageX, - bottom: $(document).height() - e.pageY, - left: e.pageX - }; - - $("body").append(build.multiselect(multiselect.position.top, multiselect.position.left)); - - $(document).on("mousemove", multiselect.resize).on("mouseup", function (_e) { - if (_e.which === 1) { - multiselect.getSelection(_e); - } - }); + if (!album.isUploadable()) return false; + if (!visible.albums() && !visible.album()) return false; + if ($(".album:hover, .photo:hover").length !== 0) return false; + if (visible.search()) return false; + if (visible.multiselect()) $("#multiselect").remove(); + _sidebar.setSelectable(false); + if (!isSelectKeyPressed(e) && !e.shiftKey) { + multiselect.clearSelection(); + } + multiselect.position = { + top: e.pageY, + right: $(document).width() - e.pageX, + bottom: $(document).height() - e.pageY, + left: e.pageX + }; + $("body").append(build.multiselect(multiselect.position.top, multiselect.position.left)); + $(document).on("mousemove", multiselect.resize).on("mouseup", function (_e) { + if (_e.which === 1) { + multiselect.getSelection(_e); + } + }); }; /** @@ -8705,64 +8292,60 @@ multiselect.show = function (e) { * @returns {boolean} */ multiselect.resize = function (e) { - if (multiselect.position === null) return false; - - // Default CSS - var newCSS = { - top: null, - bottom: null, - height: null, - left: null, - right: null, - width: null - }; - - if (e.pageY >= multiselect.position.top) { - newCSS.top = multiselect.position.top; - newCSS.bottom = "inherit"; - newCSS.height = Math.min(e.pageY, $(document).height() - 3) - multiselect.position.top; - } else { - newCSS.top = "inherit"; - newCSS.bottom = multiselect.position.bottom; - newCSS.height = multiselect.position.top - Math.max(e.pageY, 2); - } - - if (e.pageX >= multiselect.position.left) { - newCSS.right = "inherit"; - newCSS.left = multiselect.position.left; - newCSS.width = Math.min(e.pageX, $(document).width() - 3) - multiselect.position.left; - } else { - newCSS.right = multiselect.position.right; - newCSS.left = "inherit"; - newCSS.width = multiselect.position.left - Math.max(e.pageX, 2); - } + if (multiselect.position === null) return false; + + // Default CSS + var newCSS = { + top: null, + bottom: null, + height: null, + left: null, + right: null, + width: null + }; + if (e.pageY >= multiselect.position.top) { + newCSS.top = multiselect.position.top; + newCSS.bottom = "inherit"; + newCSS.height = Math.min(e.pageY, $(document).height() - 3) - multiselect.position.top; + } else { + newCSS.top = "inherit"; + newCSS.bottom = multiselect.position.bottom; + newCSS.height = multiselect.position.top - Math.max(e.pageY, 2); + } + if (e.pageX >= multiselect.position.left) { + newCSS.right = "inherit"; + newCSS.left = multiselect.position.left; + newCSS.width = Math.min(e.pageX, $(document).width() - 3) - multiselect.position.left; + } else { + newCSS.right = multiselect.position.right; + newCSS.left = "inherit"; + newCSS.width = multiselect.position.left - Math.max(e.pageX, 2); + } - // Updated all CSS properties at once - $("#multiselect").css(newCSS); + // Updated all CSS properties at once + $("#multiselect").css(newCSS); }; /** * @returns {void} */ multiselect.stopResize = function () { - if (multiselect.position !== null) $(document).off("mousemove mouseup"); + if (multiselect.position !== null) $(document).off("mousemove mouseup"); }; /** * @returns {null|{top: number, left: number, width: number, height: number}} */ multiselect.getSize = function () { - if (!visible.multiselect()) return null; - - var $elem = $("#multiselect"); - var offset = $elem.offset(); - - return { - top: offset.top, - left: offset.left, - width: parseFloat($elem.css("width")), - height: parseFloat($elem.css("height")) - }; + if (!visible.multiselect()) return null; + var $elem = $("#multiselect"); + var offset = $elem.offset(); + return { + top: offset.top, + left: offset.left, + width: parseFloat($elem.css("width")), + height: parseFloat($elem.css("height")) + }; }; /** @@ -8772,29 +8355,25 @@ multiselect.getSize = function () { * @returns {void} */ multiselect.getSelection = function (e) { - var size = multiselect.getSize(); - - if (visible.contextMenu()) return; - if (!visible.multiselect()) return; - - $(".photo, .album").each(function () { - // We select if there's even a slightest overlap. Overlap between - // an object and the selection occurs if the left edge of the - // object is to the left of the right edge of the selection *and* - // the right edge of the object is to the right of the left edge of - // the selection; analogous for top/bottom. - if ($(this).offset().left < size.left + size.width && $(this).offset().left + $(this).width() > size.left && $(this).offset().top < size.top + size.height && $(this).offset().top + $(this).height() > size.top) { - var id = $(this).attr("data-id"); - - if (isSelectKeyPressed(e)) { - multiselect.toggleItem($(this), id); - } else { - multiselect.addItem($(this), id); - } - } - }); - - multiselect.hide(); + var size = multiselect.getSize(); + if (visible.contextMenu()) return; + if (!visible.multiselect()) return; + $(".photo, .album").each(function () { + // We select if there's even a slightest overlap. Overlap between + // an object and the selection occurs if the left edge of the + // object is to the left of the right edge of the selection *and* + // the right edge of the object is to the right of the left edge of + // the selection; analogous for top/bottom. + if ($(this).offset().left < size.left + size.width && $(this).offset().left + $(this).width() > size.left && $(this).offset().top < size.top + size.height && $(this).offset().top + $(this).height() > size.top) { + var id = $(this).attr("data-id"); + if (isSelectKeyPressed(e)) { + multiselect.toggleItem($(this), id); + } else { + multiselect.addItem($(this), id); + } + } + }); + multiselect.hide(); }; /** @@ -8802,8 +8381,8 @@ multiselect.getSelection = function (e) { * @returns {void} */ multiselect.select = function (elem) { - elem.addClass("selected"); - elem.addClass("active"); + elem.addClass("selected"); + elem.addClass("active"); }; /** @@ -8811,8 +8390,8 @@ multiselect.select = function (elem) { * @returns {void} */ multiselect.deselect = function (elem) { - elem.removeClass("selected"); - elem.removeClass("active"); + elem.removeClass("selected"); + elem.removeClass("active"); }; /** @@ -8820,13 +8399,13 @@ multiselect.deselect = function (elem) { * @returns {void} */ multiselect.hide = function () { - _sidebar.setSelectable(true); - multiselect.stopResize(); - multiselect.position = null; - lychee.animate($("#multiselect"), "fadeOut"); - setTimeout(function () { - return $("#multiselect").remove(); - }, 300); + _sidebar.setSelectable(true); + multiselect.stopResize(); + multiselect.position = null; + lychee.animate($("#multiselect"), "fadeOut"); + setTimeout(function () { + return $("#multiselect").remove(); + }, 300); }; /** @@ -8834,43 +8413,38 @@ multiselect.hide = function () { * @returns {void} */ multiselect.close = function () { - _sidebar.setSelectable(true); - multiselect.stopResize(); - multiselect.position = null; - lychee.animate($("#multiselect"), "fadeOut"); - setTimeout(function () { - return $("#multiselect").remove(); - }, 300); + _sidebar.setSelectable(true); + multiselect.stopResize(); + multiselect.position = null; + lychee.animate($("#multiselect"), "fadeOut"); + setTimeout(function () { + return $("#multiselect").remove(); + }, 300); }; /** * @returns {void} */ multiselect.selectAll = function () { - if (!album.isUploadable()) return; - if (visible.search()) return; - if (!visible.albums() && !visible.album) return; - if (visible.multiselect()) $("#multiselect").remove(); - - _sidebar.setSelectable(false); - - multiselect.clearSelection(); - - $(".photo").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - - if (multiselect.photosSelected === 0) { - // There are no pictures. Try albums then. - $(".album").each(function () { - multiselect.addItem($(this), $(this).attr("data-id")); - }); - } + if (!album.isUploadable()) return; + if (visible.search()) return; + if (!visible.albums() && !visible.album) return; + if (visible.multiselect()) $("#multiselect").remove(); + _sidebar.setSelectable(false); + multiselect.clearSelection(); + $(".photo").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + if (multiselect.photosSelected === 0) { + // There are no pictures. Try albums then. + $(".album").each(function () { + multiselect.addItem($(this), $(this).attr("data-id")); + }); + } }; - var notifications = { - /** @type {?EMailData} */ - json: null + /** @type {?EMailData} */ + json: null }; /** @@ -8878,26 +8452,22 @@ var notifications = { * @returns {void} */ notifications.update = function (params) { - if (params.email && params.email.length > 1) { - var regexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - - if (!regexp.test(String(params.email).toLowerCase())) { - loadingBar.show("error", lychee.locale["ERROR_INVALID_EMAIL"]); - return; - } - } - - api.post("User::setEmail", params, function () { - loadingBar.show("success", lychee.locale["EMAIL_SUCCESS"]); - }); + if (params.email && params.email.length > 1) { + var regexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + if (!regexp.test(String(params.email).toLowerCase())) { + loadingBar.show("error", lychee.locale["ERROR_INVALID_EMAIL"]); + return; + } + } + api.post("User::setEmail", params, function () { + loadingBar.show("success", lychee.locale["EMAIL_SUCCESS"]); + }); }; - notifications.load = function () { - api.post("User::getAuthenticatedUser", {}, - /** @param {EMailData} data */function (data) { - notifications.json = data.email; - view.notifications.init(); - }); + api.post("User::getAuthenticatedUser", {}, /** @param {EMailData} data */function (data) { + notifications.json = data.email; + view.notifications.init(); + }); }; /** @@ -8919,81 +8489,76 @@ var password = {}; * @param {UnlockSuccessCB} callback - called in case of success */ password.getDialog = function (albumID, callback) { - /** @param {{password: string}} data */ - var action = function action(data) { - var params = { - albumID: albumID, - password: data.password - }; - - api.post("Album::unlock", params, function () { - basicModal.close(false, callback); - }, null, function (jqXHR, params2, lycheeException) { - if ((jqXHR.status === 401 || jqXHR.status === 403) && lycheeException.message.includes("Password is invalid")) { - basicModal.focusError("password"); - return true; - } - basicModal.close(); - return false; - }); - }; - - var cancel = function cancel() { - basicModal.close(false, function () { - if (!visible.albums() && !visible.album()) lychee.goto(); - }); - }; - - var enterPasswordDialogBody = "\n\t\t

\n\t\t
\n\t\t \t
\n\t\t "; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initEnterPasswordDialog = function initEnterPasswordDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["ALBUM_PASSWORD_REQUIRED"]; - formElements.password.placeholder = lychee.locale["PASSWORD"]; - }; - - basicModal.show({ - body: enterPasswordDialogBody, - readyCB: initEnterPasswordDialog, - buttons: { - action: { - title: lychee.locale["ENTER"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: cancel - } - } - }); -}; - -/** - * @description Takes care of every action a photo can handle and execute. - */ + /** @param {{password: string}} data */ + var action = function action(data) { + var params = { + albumID: albumID, + password: data.password + }; + api.post("Album::unlock", params, function () { + basicModal.close(false, callback); + }, null, function (jqXHR, params2, lycheeException) { + if ((jqXHR.status === 401 || jqXHR.status === 403) && lycheeException.message.includes("Password is invalid")) { + basicModal.focusError("password"); + return true; + } + basicModal.close(); + return false; + }); + }; + var cancel = function cancel() { + basicModal.close(false, function () { + if (!visible.albums() && !visible.album()) lychee["goto"](); + }); + }; + var enterPasswordDialogBody = "\n\t\t

\n\t\t
\n\t\t \t
\n\t\t "; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initEnterPasswordDialog = function initEnterPasswordDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["ALBUM_PASSWORD_REQUIRED"]; + formElements.password.placeholder = lychee.locale["PASSWORD"]; + }; + basicModal.show({ + body: enterPasswordDialogBody, + readyCB: initEnterPasswordDialog, + buttons: { + action: { + title: lychee.locale["ENTER"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: cancel + } + } + }); +}; + +/** + * @description Takes care of every action a photo can handle and execute. + */ var _photo3 = { - /** @type {?Photo} */ - json: null, - cache: null, - /** @type {?boolean} indicates whether the browser supports prefetching of images; `null` if support hasn't been determined yet */ - supportsPrefetch: null, - /** @type {?LivePhotosKit.Player} */ - livePhotosObject: null + /** @type {?Photo} */ + json: null, + cache: null, + /** @type {?boolean} indicates whether the browser supports prefetching of images; `null` if support hasn't been determined yet */ + supportsPrefetch: null, + /** @type {?LivePhotosKit.Player} */ + livePhotosObject: null }; /** * @returns {?string} - the photo ID */ _photo3.getID = function () { - var id = _photo3.json ? _photo3.json.id : $(".photo:hover, .photo.active").attr("data-id"); - id = typeof id === "string" && /^[-_0-9a-zA-Z]{24}$/.test(id) ? id : null; - - return id; + var id = _photo3.json ? _photo3.json.id : $(".photo:hover, .photo.active").attr("data-id"); + id = typeof id === "string" && /^[-_0-9a-zA-Z]{24}$/.test(id) ? id : null; + return id; }; /** @@ -9005,88 +8570,86 @@ _photo3.getID = function () { * @returns {void} */ _photo3.load = function (photoID, albumID, autoplay) { - /** - * @param {Photo} data - * @returns {void} - */ - var successHandler = function successHandler(data) { - _photo3.json = data; - // TODO: `photo.json.original_album_id` is set only, but never read; do we need it? - _photo3.json.original_album_id = _photo3.json.album_id; - // TODO: Why do we overwrite the true album ID of a photo, by the externally provided one? I guess we need it, because the album which the user came from might also be a smart album or a tag album. However, in this case I would prefer to leave the `album_id untouched (don't rename it to `original_album_id`) and call this one `effective_album_id` instead. - _photo3.json.album_id = albumID; - - view.photo.show(); - view.photo.init(autoplay); - - if (!lychee.hide_content_during_imgview) { - setTimeout(function () { - lychee.content.show(); - tabindex.makeUnfocusable(lychee.content); - }, 300); - } - }; - - api.post("Photo::get", { - photoID: photoID - }, successHandler); + /** + * @param {Photo} data + * @returns {void} + */ + var successHandler = function successHandler(data) { + _photo3.json = data; + // TODO: `photo.json.original_album_id` is set only, but never read; do we need it? + _photo3.json.original_album_id = _photo3.json.album_id; + // TODO: Why do we overwrite the true album ID of a photo, by the externally provided one? I guess we need it, because the album which the user came from might also be a smart album or a tag album. However, in this case I would prefer to leave the `album_id untouched (don't rename it to `original_album_id`) and call this one `effective_album_id` instead. + _photo3.json.album_id = albumID; + view.photo.show(); + view.photo.init(autoplay); + if (!lychee.hide_content_during_imgview) { + setTimeout(function () { + lychee.content.show(); + tabindex.makeUnfocusable(lychee.content); + }, 300); + } + }; + api.post("Photo::get", { + photoID: photoID + }, successHandler); }; /** * @returns {boolean} */ _photo3.hasExif = function () { - return !!_photo3.json.make || !!_photo3.json.model || !!_photo3.json.shutter || !!_photo3.json.aperture || !!_photo3.json.focal || !!_photo3.json.iso; + return !!_photo3.json.make || !!_photo3.json.model || !!_photo3.json.shutter || !!_photo3.json.aperture || !!_photo3.json.focal || !!_photo3.json.iso; }; /** * @returns {boolean} */ _photo3.hasTakestamp = function () { - return !!_photo3.json.taken_at; + return !!_photo3.json.taken_at; }; /** * @returns {boolean} */ _photo3.hasDesc = function () { - return !!_photo3.json.description; + return !!_photo3.json.description; }; /** * @returns {boolean} */ _photo3.isLivePhoto = function () { - return !!_photo3.json && // In case it's called, but not initialized - !!_photo3.json.live_photo_url; + return !!_photo3.json && + // In case it's called, but not initialized + !!_photo3.json.live_photo_url; }; /** * @returns {boolean} */ _photo3.isLivePhotoInitialized = function () { - return !!_photo3.livePhotosObject; + return !!_photo3.livePhotosObject; }; /** * @returns {boolean} */ _photo3.isLivePhotoPlaying = function () { - return _photo3.isLivePhotoInitialized() && _photo3.livePhotosObject.isPlaying; + return _photo3.isLivePhotoInitialized() && _photo3.livePhotosObject.isPlaying; }; /** * @returns {void} */ _photo3.cycle_display_overlay = function () { - var oldType = build.check_overlay_type(_photo3.json, lychee.image_overlay_type); - var newType = build.check_overlay_type(_photo3.json, oldType, true); - if (oldType !== newType) { - lychee.image_overlay_type = newType; - $("#image_overlay").remove(); - var newOverlay = build.overlay_image(_photo3.json); - if (newOverlay !== "") lychee.imageview.append(newOverlay); - } + var oldType = build.check_overlay_type(_photo3.json, lychee.image_overlay_type); + var newType = build.check_overlay_type(_photo3.json, oldType, true); + if (oldType !== newType) { + lychee.image_overlay_type = newType; + $("#image_overlay").remove(); + var newOverlay = build.overlay_image(_photo3.json); + if (newOverlay !== "") lychee.imageview.append(newOverlay); + } }; /** @@ -9096,82 +8659,75 @@ _photo3.cycle_display_overlay = function () { * @returns {void} */ _photo3.preloadNextPrev = function (photoID) { - if (!album.json || !album.json.photos) return; - - var photo = album.getByID(photoID); - if (!photo) return; - - var imgs = $("img#image"); - // TODO: consider replacing the test for "@2x." by a simple comparison to photo.size_variants.medium2x.url. - var isUsing2xCurrently = imgs.length > 0 && imgs[0].currentSrc !== null && imgs[0].currentSrc.includes("@2x."); - - $("head [data-prefetch]").remove(); - - /** - * @param {string} preloadID - * @returns {void} - */ - var preload = function preload(preloadID) { - var preloadPhoto = album.getByID(preloadID); - var href = ""; - - if (preloadPhoto.size_variants.medium != null) { - href = preloadPhoto.size_variants.medium.url; - if (preloadPhoto.size_variants.medium2x != null && isUsing2xCurrently) { - // If the currently displayed image uses the 2x variant, - // chances are that so will the next one. - href = preloadPhoto.size_variants.medium2x.url; - } - } else if (preloadPhoto.type && preloadPhoto.type.indexOf("video") === -1) { - // Preload the original size, but only if it's not a video - href = preloadPhoto.size_variants.original.url; - } - - if (href !== "") { - if (photo.supportsPrefetch === null) { - /** - * Copied from https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/ - * - * TODO: This method should not be defined dynamically, but defined and executed upon initialization once - * - * @param {DOMTokenList} tokenList - * @param {string} token - * @returns {boolean} - */ - var DOMTokenListSupports = function DOMTokenListSupports(tokenList, token) { - try { - if (!tokenList || !tokenList.supports) { - return false; - } - return tokenList.supports(token); - } catch (e) { - if (e instanceof TypeError) { - console.log("The DOMTokenList doesn't have a supported tokens list"); - } else { - console.error("That shouldn't have happened"); - } - return false; - } - }; - photo.supportsPrefetch = DOMTokenListSupports(document.createElement("link").relList, "prefetch"); - } - - if (photo.supportsPrefetch) { - $("head").append(lychee.html(_templateObject36, href)); - } else { - // According to https://caniuse.com/#feat=link-rel-prefetch, - // as of mid-2019 it's mainly Safari (both on desktop and mobile) - new Image().src = href; - } - } - }; - - if (photo.next_photo_id) { - preload(photo.next_photo_id); - } - if (photo.previous_photo_id) { - preload(photo.previous_photo_id); - } + if (!album.json || !album.json.photos) return; + var photo = album.getByID(photoID); + if (!photo) return; + var imgs = $("img#image"); + // TODO: consider replacing the test for "@2x." by a simple comparison to photo.size_variants.medium2x.url. + var isUsing2xCurrently = imgs.length > 0 && imgs[0].currentSrc !== null && imgs[0].currentSrc.includes("@2x."); + $("head [data-prefetch]").remove(); + + /** + * @param {string} preloadID + * @returns {void} + */ + var preload = function preload(preloadID) { + var preloadPhoto = album.getByID(preloadID); + var href = ""; + if (preloadPhoto.size_variants.medium != null) { + href = preloadPhoto.size_variants.medium.url; + if (preloadPhoto.size_variants.medium2x != null && isUsing2xCurrently) { + // If the currently displayed image uses the 2x variant, + // chances are that so will the next one. + href = preloadPhoto.size_variants.medium2x.url; + } + } else if (preloadPhoto.type && preloadPhoto.type.indexOf("video") === -1) { + // Preload the original size, but only if it's not a video + href = preloadPhoto.size_variants.original.url; + } + if (href !== "") { + if (photo.supportsPrefetch === null) { + /** + * Copied from https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/ + * + * TODO: This method should not be defined dynamically, but defined and executed upon initialization once + * + * @param {DOMTokenList} tokenList + * @param {string} token + * @returns {boolean} + */ + var DOMTokenListSupports = function DOMTokenListSupports(tokenList, token) { + try { + if (!tokenList || !tokenList.supports) { + return false; + } + return tokenList.supports(token); + } catch (e) { + if (e instanceof TypeError) { + console.log("The DOMTokenList doesn't have a supported tokens list"); + } else { + console.error("That shouldn't have happened"); + } + return false; + } + }; + photo.supportsPrefetch = DOMTokenListSupports(document.createElement("link").relList, "prefetch"); + } + if (photo.supportsPrefetch) { + $("head").append(lychee.html(_templateObject39 || (_templateObject39 = _taggedTemplateLiteral([""])), href)); + } else { + // According to https://caniuse.com/#feat=link-rel-prefetch, + // as of mid-2019 it's mainly Safari (both on desktop and mobile) + new Image().src = href; + } + } + }; + if (photo.next_photo_id) { + preload(photo.next_photo_id); + } + if (photo.previous_photo_id) { + preload(photo.previous_photo_id); + } }; /** @@ -9179,24 +8735,21 @@ _photo3.preloadNextPrev = function (photoID) { * @returns {void} */ _photo3.previous = function (animate) { - var curPhoto = _photo3.getID() !== null && album.json ? album.getByID(_photo3.getID()) : null; - if (!curPhoto || !curPhoto.previous_photo_id) return; - - var delay = animate ? 200 : 0; - - if (animate) { - $("#imageview #image").css({ - WebkitTransform: "translateX(100%)", - MozTransform: "translateX(100%)", - transform: "translateX(100%)", - opacity: 0 - }); - } - - setTimeout(function () { - _photo3.livePhotosObject = null; - lychee.goto(album.getID() + "/" + curPhoto.previous_photo_id, false); - }, delay); + var curPhoto = _photo3.getID() !== null && album.json ? album.getByID(_photo3.getID()) : null; + if (!curPhoto || !curPhoto.previous_photo_id) return; + var delay = animate ? 200 : 0; + if (animate) { + $("#imageview #image").css({ + WebkitTransform: "translateX(100%)", + MozTransform: "translateX(100%)", + transform: "translateX(100%)", + opacity: 0 + }); + } + setTimeout(function () { + _photo3.livePhotosObject = null; + lychee["goto"](album.getID() + "/" + curPhoto.previous_photo_id, false); + }, delay); }; /** @@ -9204,105 +8757,97 @@ _photo3.previous = function (animate) { * @returns {void} */ _photo3.next = function (animate) { - var curPhoto = _photo3.getID() !== null && album.json ? album.getByID(_photo3.getID()) : null; - if (!curPhoto || !curPhoto.next_photo_id) return; - - var delay = animate ? 200 : 0; - - if (animate === true) { - $("#imageview #image").css({ - WebkitTransform: "translateX(-100%)", - MozTransform: "translateX(-100%)", - transform: "translateX(-100%)", - opacity: 0 - }); - } - - setTimeout(function () { - _photo3.livePhotosObject = null; - lychee.goto(album.getID() + "/" + curPhoto.next_photo_id, false); - }, delay); + var curPhoto = _photo3.getID() !== null && album.json ? album.getByID(_photo3.getID()) : null; + if (!curPhoto || !curPhoto.next_photo_id) return; + var delay = animate ? 200 : 0; + if (animate === true) { + $("#imageview #image").css({ + WebkitTransform: "translateX(-100%)", + MozTransform: "translateX(-100%)", + transform: "translateX(-100%)", + opacity: 0 + }); + } + setTimeout(function () { + _photo3.livePhotosObject = null; + lychee["goto"](album.getID() + "/" + curPhoto.next_photo_id, false); + }, delay); }; /** * @param {string[]} photoIDs * @returns {boolean} */ -_photo3.delete = function (photoIDs) { - var deletePhotos = function deletePhotos() { - var nextPhotoID = null; - var previousPhotoID = null; - - basicModal.close(); - - photoIDs.forEach(function (id, index) { - // Change reference for the next and previous photo - var curPhoto = album.getByID(id); - if (curPhoto.next_photo_id !== null || curPhoto.previous_photo_id !== null) { - nextPhotoID = curPhoto.next_photo_id; - previousPhotoID = curPhoto.previous_photo_id; - - if (previousPhotoID !== null) { - album.getByID(previousPhotoID).next_photo_id = nextPhotoID; - } - if (nextPhotoID !== null) { - album.getByID(nextPhotoID).previous_photo_id = previousPhotoID; - } - } - - album.deleteByID(id); - view.album.content.delete(id, index === photoIDs.length - 1); - }); - - albums.refresh(); - - // Go to next photo if there is a next photo and - // next photo is not the current one. Also try the previous one. - // Show album otherwise. - if (visible.photo()) { - if (nextPhotoID !== null && nextPhotoID !== _photo3.getID()) { - lychee.goto(album.getID() + "/" + nextPhotoID, false); - } else if (previousPhotoID !== null && previousPhotoID !== _photo3.getID()) { - lychee.goto(album.getID() + "/" + previousPhotoID, false); - } else { - lychee.goto(album.getID()); - } - } else if (!visible.albums()) { - lychee.goto(album.getID()); - } - - api.post("Photo::delete", { photoIDs: photoIDs }); - }; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initDeletePhotoDialog = function initDeletePhotoDialog(formElements, dialog) { - if (photoIDs.length === 1) { - var photoTitle = (visible.photo() ? _photo3.json.title : album.getByID(photoIDs[0]).title) || lychee.locale["UNTITLED"]; - dialog.querySelector("p").textContent = sprintf(lychee.locale["PHOTO_DELETE_CONFIRMATION"], photoTitle); - } else { - dialog.querySelector("p").textContent = sprintf(lychee.locale["PHOTO_DELETE_ALL"], photoIDs.length); - } - }; - - basicModal.show({ - body: "

", - readyCB: initDeletePhotoDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_DELETE"], - fn: deletePhotos, - classList: ["red"] - }, - cancel: { - title: lychee.locale["PHOTO_KEEP"], - fn: basicModal.close - } - } - }); +_photo3["delete"] = function (photoIDs) { + var deletePhotos = function deletePhotos() { + var nextPhotoID = null; + var previousPhotoID = null; + basicModal.close(); + photoIDs.forEach(function (id, index) { + // Change reference for the next and previous photo + var curPhoto = album.getByID(id); + if (curPhoto.next_photo_id !== null || curPhoto.previous_photo_id !== null) { + nextPhotoID = curPhoto.next_photo_id; + previousPhotoID = curPhoto.previous_photo_id; + if (previousPhotoID !== null) { + album.getByID(previousPhotoID).next_photo_id = nextPhotoID; + } + if (nextPhotoID !== null) { + album.getByID(nextPhotoID).previous_photo_id = previousPhotoID; + } + } + album.deleteByID(id); + view.album.content["delete"](id, index === photoIDs.length - 1); + }); + albums.refresh(); + + // Go to next photo if there is a next photo and + // next photo is not the current one. Also try the previous one. + // Show album otherwise. + if (visible.photo()) { + if (nextPhotoID !== null && nextPhotoID !== _photo3.getID()) { + lychee["goto"](album.getID() + "/" + nextPhotoID, false); + } else if (previousPhotoID !== null && previousPhotoID !== _photo3.getID()) { + lychee["goto"](album.getID() + "/" + previousPhotoID, false); + } else { + lychee["goto"](album.getID()); + } + } else if (!visible.albums()) { + lychee["goto"](album.getID()); + } + api.post("Photo::delete", { + photoIDs: photoIDs + }); + }; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initDeletePhotoDialog = function initDeletePhotoDialog(formElements, dialog) { + if (photoIDs.length === 1) { + var photoTitle = (visible.photo() ? _photo3.json.title : album.getByID(photoIDs[0]).title) || lychee.locale["UNTITLED"]; + dialog.querySelector("p").textContent = sprintf(lychee.locale["PHOTO_DELETE_CONFIRMATION"], photoTitle); + } else { + dialog.querySelector("p").textContent = sprintf(lychee.locale["PHOTO_DELETE_ALL"], photoIDs.length); + } + }; + basicModal.show({ + body: "

", + readyCB: initDeletePhotoDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_DELETE"], + fn: deletePhotos, + classList: ["red"] + }, + cancel: { + title: lychee.locale["PHOTO_KEEP"], + fn: basicModal.close + } + } + }); }; /** @@ -9311,65 +8856,58 @@ _photo3.delete = function (photoIDs) { * @returns {void} */ _photo3.setTitle = function (photoIDs) { - /** - * @param {{title: string}} data - * @returns {void} - */ - var action = function action(data) { - if (!data.title.trim()) { - basicModal.focusError("title"); - return; - } - - basicModal.close(); - - var newTitle = data.title ? data.title : null; - - if (visible.photo()) { - _photo3.json.title = newTitle; - view.photo.title(); - } - - photoIDs.forEach(function (id) { - // TODO: The line below looks suspicious: It is inconsistent to the code some lines above. - album.getByID(id).title = newTitle; - view.album.content.title(id); - }); - - api.post("Photo::setTitle", { - photoIDs: photoIDs, - title: newTitle - }); - }; - - var setPhotoTitleDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetPhotoTitleDialog = function initSetPhotoTitleDialog(formElements, dialog) { - var oldTitle = photoIDs.length === 1 ? _photo3.json ? _photo3.json.title : album.getByID(photoIDs[0]).title : ""; - dialog.querySelector("p").textContent = photoIDs.length === 1 ? lychee.locale["PHOTO_NEW_TITLE"] : sprintf(lychee.locale["PHOTOS_NEW_TITLE"], photoIDs.length); - formElements.title.placeholder = "Title"; - formElements.title.value = oldTitle; - }; - - basicModal.show({ - body: setPhotoTitleDialogBody, - readyCB: initSetPhotoTitleDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_SET_TITLE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {{title: string}} data + * @returns {void} + */ + var action = function action(data) { + if (!data.title.trim()) { + basicModal.focusError("title"); + return; + } + basicModal.close(); + var newTitle = data.title ? data.title : null; + if (visible.photo()) { + _photo3.json.title = newTitle; + view.photo.title(); + } + photoIDs.forEach(function (id) { + // TODO: The line below looks suspicious: It is inconsistent to the code some lines above. + album.getByID(id).title = newTitle; + view.album.content.title(id); + }); + api.post("Photo::setTitle", { + photoIDs: photoIDs, + title: newTitle + }); + }; + var setPhotoTitleDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetPhotoTitleDialog = function initSetPhotoTitleDialog(formElements, dialog) { + var oldTitle = photoIDs.length === 1 ? _photo3.json ? _photo3.json.title : album.getByID(photoIDs[0]).title : ""; + dialog.querySelector("p").textContent = photoIDs.length === 1 ? lychee.locale["PHOTO_NEW_TITLE"] : sprintf(lychee.locale["PHOTOS_NEW_TITLE"], photoIDs.length); + formElements.title.placeholder = "Title"; + formElements.title.value = oldTitle; + }; + basicModal.show({ + body: setPhotoTitleDialogBody, + readyCB: initSetPhotoTitleDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_SET_TITLE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9379,12 +8917,12 @@ _photo3.setTitle = function (photoIDs) { * @returns {void} */ _photo3.copyTo = function (photoIDs, albumID) { - api.post("Photo::duplicate", { - photoIDs: photoIDs, - albumID: albumID - }, function () { - return album.reload(); - }); + api.post("Photo::duplicate", { + photoIDs: photoIDs, + albumID: albumID + }, function () { + return album.reload(); + }); }; /** @@ -9393,55 +8931,50 @@ _photo3.copyTo = function (photoIDs, albumID) { * @returns {void} */ _photo3.setAlbum = function (photoIDs, albumID) { - var nextPhotoID = null; - var previousPhotoID = null; - - photoIDs.forEach(function (id, index) { - // Change reference for the next and previous photo - var curPhoto = album.getByID(id); - if (curPhoto.next_photo_id !== null || curPhoto.previous_photo_id !== null) { - nextPhotoID = curPhoto.next_photo_id; - previousPhotoID = curPhoto.previous_photo_id; - - if (previousPhotoID !== null) { - album.getByID(previousPhotoID).next_photo_id = nextPhotoID; - } - if (nextPhotoID !== null) { - album.getByID(nextPhotoID).previous_photo_id = previousPhotoID; - } - } - - album.deleteByID(id); - view.album.content.delete(id, index === photoIDs.length - 1); - }); - - albums.refresh(); - - // Go to next photo if there is a next photo and - // next photo is not the current one. Also try the previous one. - // Show album otherwise. - if (visible.photo()) { - if (nextPhotoID !== null && nextPhotoID !== _photo3.getID()) { - lychee.goto(album.getID() + "/" + nextPhotoID); - } else if (previousPhotoID !== null && previousPhotoID !== _photo3.getID()) { - lychee.goto(album.getID() + "/" + previousPhotoID); - } else { - lychee.goto(album.getID()); - } - } - - api.post("Photo::setAlbum", { - photoIDs: photoIDs, - albumID: albumID - }, function () { - // We only really need to do anything here if the destination - // is a (possibly nested) subalbum of the current album; but - // since we have no way of figuring it out (albums.json is - // null), we need to reload. - if (visible.album()) { - album.reload(); - } - }); + var nextPhotoID = null; + var previousPhotoID = null; + photoIDs.forEach(function (id, index) { + // Change reference for the next and previous photo + var curPhoto = album.getByID(id); + if (curPhoto.next_photo_id !== null || curPhoto.previous_photo_id !== null) { + nextPhotoID = curPhoto.next_photo_id; + previousPhotoID = curPhoto.previous_photo_id; + if (previousPhotoID !== null) { + album.getByID(previousPhotoID).next_photo_id = nextPhotoID; + } + if (nextPhotoID !== null) { + album.getByID(nextPhotoID).previous_photo_id = previousPhotoID; + } + } + album.deleteByID(id); + view.album.content["delete"](id, index === photoIDs.length - 1); + }); + albums.refresh(); + + // Go to next photo if there is a next photo and + // next photo is not the current one. Also try the previous one. + // Show album otherwise. + if (visible.photo()) { + if (nextPhotoID !== null && nextPhotoID !== _photo3.getID()) { + lychee["goto"](album.getID() + "/" + nextPhotoID); + } else if (previousPhotoID !== null && previousPhotoID !== _photo3.getID()) { + lychee["goto"](album.getID() + "/" + previousPhotoID); + } else { + lychee["goto"](album.getID()); + } + } + api.post("Photo::setAlbum", { + photoIDs: photoIDs, + albumID: albumID + }, function () { + // We only really need to do anything here if the destination + // is a (possibly nested) subalbum of the current album; but + // since we have no way of figuring it out (albums.json is + // null), we need to reload. + if (visible.album()) { + album.reload(); + } + }); }; /** @@ -9450,18 +8983,15 @@ _photo3.setAlbum = function (photoIDs, albumID) { * @returns {void} */ _photo3.toggleStar = function () { - _photo3.json.is_starred = !_photo3.json.is_starred; - view.photo.star(); - - album.getByID(_photo3.json.id).is_starred = _photo3.json.is_starred; - view.album.content.star(_photo3.json.id); - - albums.refresh(); - - api.post("Photo::setStar", { - photoIDs: [_photo3.json.id], - is_starred: _photo3.json.is_starred - }); + _photo3.json.is_starred = !_photo3.json.is_starred; + view.photo.star(); + album.getByID(_photo3.json.id).is_starred = _photo3.json.is_starred; + view.album.content.star(_photo3.json.id); + albums.refresh(); + api.post("Photo::setStar", { + photoIDs: [_photo3.json.id], + is_starred: _photo3.json.is_starred + }); }; /** @@ -9472,17 +9002,15 @@ _photo3.toggleStar = function () { * @returns {void} */ _photo3.setStar = function (photoIDs, isStarred) { - photoIDs.forEach(function (id) { - album.getByID(id).is_starred = isStarred; - view.album.content.star(id); - }); - - albums.refresh(); - - api.post("Photo::setStar", { - photoIDs: photoIDs, - is_starred: isStarred - }); + photoIDs.forEach(function (id) { + album.getByID(id).is_starred = isStarred; + view.album.content.star(id); + }); + albums.refresh(); + api.post("Photo::setStar", { + photoIDs: photoIDs, + is_starred: isStarred + }); }; /** @@ -9495,117 +9023,110 @@ _photo3.setStar = function (photoIDs, isStarred) { * @returns {void} */ _photo3.setProtectionPolicy = function (photoID) { - /** - * @param {{is_public: boolean}} data - */ - var action = function action(data) { - if (data.is_public !== _photo3.json.is_public) { - if (visible.photo()) { - _photo3.json.is_public = data.is_public; - view.photo.public(); - } - - album.getByID(photoID).is_public = data.is_public; - view.album.content.public(photoID); - - albums.refresh(); - - api.post("Photo::setPublic", { - photoID: photoID, - is_public: data.is_public - }); - } - - basicModal.close(); - }; - - var setPhotoProtectionPolicyBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t

\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; - - /** - * @typedef PhotoProtectionPolicyDialogFormElements - * @property {HTMLInputElement} is_public - * @property {HTMLInputElement} grants_full_photo_access - * @property {HTMLInputElement} is_link_required - * @property {HTMLInputElement} grants_download - * @property {HTMLInputElement} is_password_required - */ - - /** - * @param {PhotoProtectionPolicyDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initPhotoProtectionPolicyDialog = function initPhotoProtectionPolicyDialog(formElements, dialog) { - formElements.is_public.previousElementSibling.textContent = lychee.locale["PHOTO_PUBLIC"]; - formElements.is_public.nextElementSibling.textContent = lychee.locale["PHOTO_PUBLIC_EXPL"]; - formElements.grants_full_photo_access.previousElementSibling.textContent = lychee.locale["PHOTO_FULL"]; - formElements.grants_full_photo_access.nextElementSibling.textContent = lychee.locale["PHOTO_FULL_EXPL"]; - formElements.is_link_required.previousElementSibling.textContent = lychee.locale["PHOTO_HIDDEN"]; - formElements.is_link_required.nextElementSibling.textContent = lychee.locale["PHOTO_HIDDEN_EXPL"]; - formElements.grants_download.previousElementSibling.textContent = lychee.locale["PHOTO_DOWNLOADABLE"]; - formElements.grants_download.nextElementSibling.textContent = lychee.locale["PHOTO_DOWNLOADABLE_EXPL"]; - formElements.is_password_required.previousElementSibling.textContent = lychee.locale["PHOTO_PASSWORD_PROT"]; - formElements.is_password_required.nextElementSibling.textContent = lychee.locale["PHOTO_PASSWORD_PROT_EXPL"]; - - if (_photo3.json.album_id === null) { - // No album - - dialog.querySelector("p#ppp_dialog_no_edit_expl").remove(); - dialog.querySelector("p#ppp_dialog_global_expl").textContent = lychee.locale["PHOTO_EDIT_GLOBAL_SHARING_TEXT"]; - // Initialize values of detailed settings according to global - // configuration. - formElements.is_public.checked = _photo3.json.is_public; - formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; - formElements.is_link_required.checked = lychee.public_photos_hidden; - formElements.grants_download.checked = lychee.grants_download; - formElements.is_password_required.checked = false; - } else if (album.json && album.json.policy.is_public === false) { - // Private album - - dialog.querySelector("p#ppp_dialog_no_edit_expl").remove(); - dialog.querySelector("p#ppp_dialog_global_expl").textContent = lychee.locale["PHOTO_EDIT_GLOBAL_SHARING_TEXT"]; - // Initialize values of detailed settings according to global - // configuration. - formElements.is_public.checked = _photo3.json.is_public; - formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; - formElements.is_link_required.checked = lychee.public_photos_hidden; - formElements.grants_download.checked = lychee.grants_download; - formElements.is_password_required.checked = false; - } else { - // Public album. - dialog.querySelector("p#ppp_dialog_no_edit_expl").textContent = lychee.locale["PHOTO_NO_EDIT_SHARING_TEXT"]; - dialog.querySelector("p#ppp_dialog_global_expl").remove(); - // Initialize values of detailed settings according to album - // settings and hide action button as we can't actually change - // anything. - formElements.is_public.disabled = true; - formElements.is_public.checked = album.json.policy.is_public; - formElements.is_public.parentElement.classList.add("disabled"); - formElements.grants_full_photo_access.checked = album.json.policy.grants_full_photo_access; - // Photos in public albums are never hidden as such. It's the - // album that's hidden. Or is that distinction irrelevant to end - // users? - formElements.is_link_required.checked = album.json.policy.is_link_required; - formElements.grants_download.checked = album.json.policy.grants_download; - formElements.is_password_required.checked = album.json.policy.is_password_required; - basicModal.hideActionButton(); - } - }; - - basicModal.show({ - body: setPhotoProtectionPolicyBody, - readyCB: initPhotoProtectionPolicyDialog, - buttons: { - action: { - title: lychee.locale["SAVE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {{is_public: boolean}} data + */ + var action = function action(data) { + if (data.is_public !== _photo3.json.is_public) { + if (visible.photo()) { + _photo3.json.is_public = data.is_public; + view.photo["public"](); + } + album.getByID(photoID).is_public = data.is_public; + view.album.content["public"](photoID); + albums.refresh(); + api.post("Photo::setPublic", { + photoID: photoID, + is_public: data.is_public + }); + } + basicModal.close(); + }; + var setPhotoProtectionPolicyBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t

\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; + + /** + * @typedef PhotoProtectionPolicyDialogFormElements + * @property {HTMLInputElement} is_public + * @property {HTMLInputElement} grants_full_photo_access + * @property {HTMLInputElement} is_link_required + * @property {HTMLInputElement} grants_download + * @property {HTMLInputElement} is_password_required + */ + + /** + * @param {PhotoProtectionPolicyDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initPhotoProtectionPolicyDialog = function initPhotoProtectionPolicyDialog(formElements, dialog) { + formElements.is_public.previousElementSibling.textContent = lychee.locale["PHOTO_PUBLIC"]; + formElements.is_public.nextElementSibling.textContent = lychee.locale["PHOTO_PUBLIC_EXPL"]; + formElements.grants_full_photo_access.previousElementSibling.textContent = lychee.locale["PHOTO_FULL"]; + formElements.grants_full_photo_access.nextElementSibling.textContent = lychee.locale["PHOTO_FULL_EXPL"]; + formElements.is_link_required.previousElementSibling.textContent = lychee.locale["PHOTO_HIDDEN"]; + formElements.is_link_required.nextElementSibling.textContent = lychee.locale["PHOTO_HIDDEN_EXPL"]; + formElements.grants_download.previousElementSibling.textContent = lychee.locale["PHOTO_DOWNLOADABLE"]; + formElements.grants_download.nextElementSibling.textContent = lychee.locale["PHOTO_DOWNLOADABLE_EXPL"]; + formElements.is_password_required.previousElementSibling.textContent = lychee.locale["PHOTO_PASSWORD_PROT"]; + formElements.is_password_required.nextElementSibling.textContent = lychee.locale["PHOTO_PASSWORD_PROT_EXPL"]; + if (_photo3.json.album_id === null) { + // No album + + dialog.querySelector("p#ppp_dialog_no_edit_expl").remove(); + dialog.querySelector("p#ppp_dialog_global_expl").textContent = lychee.locale["PHOTO_EDIT_GLOBAL_SHARING_TEXT"]; + // Initialize values of detailed settings according to global + // configuration. + formElements.is_public.checked = _photo3.json.is_public; + formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; + formElements.is_link_required.checked = lychee.public_photos_hidden; + formElements.grants_download.checked = lychee.grants_download; + formElements.is_password_required.checked = false; + } else if (album.json && album.json.policy.is_public === false) { + // Private album + + dialog.querySelector("p#ppp_dialog_no_edit_expl").remove(); + dialog.querySelector("p#ppp_dialog_global_expl").textContent = lychee.locale["PHOTO_EDIT_GLOBAL_SHARING_TEXT"]; + // Initialize values of detailed settings according to global + // configuration. + formElements.is_public.checked = _photo3.json.is_public; + formElements.grants_full_photo_access.checked = lychee.grants_full_photo_access; + formElements.is_link_required.checked = lychee.public_photos_hidden; + formElements.grants_download.checked = lychee.grants_download; + formElements.is_password_required.checked = false; + } else { + // Public album. + dialog.querySelector("p#ppp_dialog_no_edit_expl").textContent = lychee.locale["PHOTO_NO_EDIT_SHARING_TEXT"]; + dialog.querySelector("p#ppp_dialog_global_expl").remove(); + // Initialize values of detailed settings according to album + // settings and hide action button as we can't actually change + // anything. + formElements.is_public.disabled = true; + formElements.is_public.checked = album.json.policy.is_public; + formElements.is_public.parentElement.classList.add("disabled"); + formElements.grants_full_photo_access.checked = album.json.policy.grants_full_photo_access; + // Photos in public albums are never hidden as such. It's the + // album that's hidden. Or is that distinction irrelevant to end + // users? + formElements.is_link_required.checked = album.json.policy.is_link_required; + formElements.grants_download.checked = album.json.policy.grants_download; + formElements.is_password_required.checked = album.json.policy.is_password_required; + basicModal.hideActionButton(); + } + }; + basicModal.show({ + body: setPhotoProtectionPolicyBody, + readyCB: initPhotoProtectionPolicyDialog, + buttons: { + action: { + title: lychee.locale["SAVE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9617,52 +9138,47 @@ _photo3.setProtectionPolicy = function (photoID) { * @returns {void} */ _photo3.setDescription = function (photoID) { - /** - * @param {{description: string}} data - */ - var action = function action(data) { - basicModal.close(); - - var description = data.description ? data.description : null; - - if (visible.photo()) { - _photo3.json.description = description; - view.photo.description(); - } - - api.post("Photo::setDescription", { - photoID: photoID, - description: description - }); - }; - - var setPhotoDescriptionDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetPhotoDescriptionDialog = function initSetPhotoDescriptionDialog(formElements, dialog) { - dialog.querySelector("p#ppp_dialog_description_expl").textContent = lychee.locale["PHOTO_NEW_DESCRIPTION"]; - formElements.description.placeholder = lychee.locale["PHOTO_DESCRIPTION"]; - formElements.description.value = _photo3.json.description ? _photo3.json.description : ""; - }; - - basicModal.show({ - body: setPhotoDescriptionDialogBody, - readyCB: initSetPhotoDescriptionDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_SET_DESCRIPTION"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {{description: string}} data + */ + var action = function action(data) { + basicModal.close(); + var description = data.description ? data.description : null; + if (visible.photo()) { + _photo3.json.description = description; + view.photo.description(); + } + api.post("Photo::setDescription", { + photoID: photoID, + description: description + }); + }; + var setPhotoDescriptionDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetPhotoDescriptionDialog = function initSetPhotoDescriptionDialog(formElements, dialog) { + dialog.querySelector("p#ppp_dialog_description_expl").textContent = lychee.locale["PHOTO_NEW_DESCRIPTION"]; + formElements.description.placeholder = lychee.locale["PHOTO_DESCRIPTION"]; + formElements.description.value = _photo3.json.description ? _photo3.json.description : ""; + }; + basicModal.show({ + body: setPhotoDescriptionDialogBody, + readyCB: initSetPhotoDescriptionDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_SET_DESCRIPTION"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9674,52 +9190,47 @@ _photo3.setDescription = function (photoID) { * @returns {void} */ _photo3.setCreatedAt = function (photoID) { - /** - * @param {{date: string}} data - */ - var action = function action(data) { - basicModal.close(); - - var created_at = data.created_at ? data.created_at.concat(":", data.tz) : null; - - if (visible.photo()) { - _photo3.json.created_at = created_at; - view.photo.uploaded(); - } - - api.post("Photo::setUploadDate", { - photoID: photoID, - date: created_at - }); - }; - - var setPhotoCreatedAtDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetPhotoCreatedAtDialog = function initSetPhotoCreatedAtDialog(formElements, dialog) { - dialog.querySelector("p#ppp_dialog_uploaddate_expl").textContent = lychee.locale["PHOTO_NEW_CREATED_AT"]; - formElements.created_at.value = _photo3.json.created_at ? _photo3.json.created_at.slice(0, 16) : ""; - formElements.tz.value = _photo3.json.created_at ? _photo3.json.created_at.slice(17) : ""; - }; - - basicModal.show({ - body: setPhotoCreatedAtDialogBody, - readyCB: initSetPhotoCreatedAtDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_SET_CREATED_AT"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {{date: string}} data + */ + var action = function action(data) { + basicModal.close(); + var created_at = data.created_at ? data.created_at.concat(":", data.tz) : null; + if (visible.photo()) { + _photo3.json.created_at = created_at; + view.photo.uploaded(); + } + api.post("Photo::setUploadDate", { + photoID: photoID, + date: created_at + }); + }; + var setPhotoCreatedAtDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetPhotoCreatedAtDialog = function initSetPhotoCreatedAtDialog(formElements, dialog) { + dialog.querySelector("p#ppp_dialog_uploaddate_expl").textContent = lychee.locale["PHOTO_NEW_CREATED_AT"]; + formElements.created_at.value = _photo3.json.created_at ? _photo3.json.created_at.slice(0, 16) : ""; + formElements.tz.value = _photo3.json.created_at ? _photo3.json.created_at.slice(17) : ""; + }; + basicModal.show({ + body: setPhotoCreatedAtDialogBody, + readyCB: initSetPhotoCreatedAtDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_SET_CREATED_AT"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9727,66 +9238,64 @@ _photo3.setCreatedAt = function (photoID) { * @returns {void} */ _photo3.editTags = function (photoIDs) { - /** @type {string[]} */ - var oldTags = []; - - // Get tags - if (visible.photo()) oldTags = _photo3.json.tags.sort();else if (visible.album() && photoIDs.length === 1) oldTags = album.getByID(photoIDs[0]).tags.sort();else if (visible.search() && photoIDs.length === 1) oldTags = album.getByID(photoIDs[0]).tags.sort();else if (visible.album() && photoIDs.length > 1) { - oldTags = album.getByID(photoIDs[0]).tags.sort(); - var areIdentical = photoIDs.every(function (id) { - var oldTags2 = album.getByID(id).tags.sort(); - if (oldTags.length !== oldTags2.length) return false; - for (var tagIdx = 0; tagIdx !== oldTags.length; tagIdx++) { - if (oldTags[tagIdx] !== oldTags2[tagIdx]) return false; - } - return true; - }); - if (!areIdentical) oldTags = []; - } - - /** - * @param {{tags: string, override: boolean}} data - * @returns {void} - */ - var action = function action(data) { - basicModal.close(); - var newTags = data.tags.split(",").map(function (tag) { - return tag.trim(); - }).filter(function (tag) { - return tag !== "" && tag.indexOf(",") === -1; - }).sort(); - _photo3.setTags(photoIDs, newTags, data.override); - }; - - var setTagDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetTagAlbumDialog = function initSetTagAlbumDialog(formElements, dialog) { - dialog.querySelector("p").textContent = photoIDs.length === 1 ? lychee.locale["PHOTO_NEW_TAGS"] : sprintf(lychee.locale["PHOTOS_NEW_TAGS"], photoIDs.length); - formElements.tags.placeholder = "Tags"; - formElements.tags.value = oldTags.join(", "); - formElements.override.previousElementSibling.textContent = lychee.locale["OVERRIDE"]; - formElements.override.nextElementSibling.textContent = lychee.locale["TAGS_OVERRIDE_INFO"]; - }; - - basicModal.show({ - body: setTagDialogBody, - readyCB: initSetTagAlbumDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_SET_TAGS"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** @type {string[]} */ + var oldTags = []; + + // Get tags + if (visible.photo()) oldTags = _photo3.json.tags.sort();else if (visible.album() && photoIDs.length === 1) oldTags = album.getByID(photoIDs[0]).tags.sort();else if (visible.search() && photoIDs.length === 1) oldTags = album.getByID(photoIDs[0]).tags.sort();else if (visible.album() && photoIDs.length > 1) { + oldTags = album.getByID(photoIDs[0]).tags.sort(); + var areIdentical = photoIDs.every(function (id) { + var oldTags2 = album.getByID(id).tags.sort(); + if (oldTags.length !== oldTags2.length) return false; + for (var tagIdx = 0; tagIdx !== oldTags.length; tagIdx++) { + if (oldTags[tagIdx] !== oldTags2[tagIdx]) return false; + } + return true; + }); + if (!areIdentical) oldTags = []; + } + + /** + * @param {{tags: string, override: boolean}} data + * @returns {void} + */ + var action = function action(data) { + basicModal.close(); + var newTags = data.tags.split(",").map(function (tag) { + return tag.trim(); + }).filter(function (tag) { + return tag !== "" && tag.indexOf(",") === -1; + }).sort(); + _photo3.setTags(photoIDs, newTags, data.override); + }; + var setTagDialogBody = "\n\t\t

\n\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t

\n\t\t\t
\n\t\t"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetTagAlbumDialog = function initSetTagAlbumDialog(formElements, dialog) { + dialog.querySelector("p").textContent = photoIDs.length === 1 ? lychee.locale["PHOTO_NEW_TAGS"] : sprintf(lychee.locale["PHOTOS_NEW_TAGS"], photoIDs.length); + formElements.tags.placeholder = "Tags"; + formElements.tags.value = oldTags.join(", "); + formElements.override.previousElementSibling.textContent = lychee.locale["OVERRIDE"]; + formElements.override.nextElementSibling.textContent = lychee.locale["TAGS_OVERRIDE_INFO"]; + }; + basicModal.show({ + body: setTagDialogBody, + readyCB: initSetTagAlbumDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_SET_TAGS"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9796,27 +9305,25 @@ _photo3.editTags = function (photoIDs) { * @returns {void} */ _photo3.setTags = function (photoIDs, tags, shall_override) { - if (visible.photo()) { - _photo3.json.tags = shall_override ? tags : _photo3.json.tags.concat(tags.filter(function (t) { - return !_photo3.json.tags.includes(t); - })); - view.photo.tags(); - } - - photoIDs.forEach(function (id) { - album.getByID(id).tags = tags; - }); - - api.post("Photo::setTags", { - photoIDs: photoIDs, - tags: tags, - shall_override: shall_override - }, function () { - // If we have any tag albums, force a refresh. - if (albums.json && albums.json.tag_albums.length !== 0) { - albums.refresh(); - } - }); + if (visible.photo()) { + _photo3.json.tags = shall_override ? tags : _photo3.json.tags.concat(tags.filter(function (t) { + return !_photo3.json.tags.includes(t); + })); + view.photo.tags(); + } + photoIDs.forEach(function (id) { + album.getByID(id).tags = tags; + }); + api.post("Photo::setTags", { + photoIDs: photoIDs, + tags: tags, + shall_override: shall_override + }, function () { + // If we have any tag albums, force a refresh. + if (albums.json && albums.json.tag_albums.length !== 0) { + albums.refresh(); + } + }); }; /** @@ -9826,8 +9333,8 @@ _photo3.setTags = function (photoIDs, tags, shall_override) { * @param {number} index */ _photo3.deleteTag = function (photoID, index) { - _photo3.json.tags.splice(index, 1); - _photo3.setTags([photoID], _photo3.json.tags, true); + _photo3.json.tags.splice(index, 1); + _photo3.setTags([photoID], _photo3.json.tags, true); }; /** @@ -9836,29 +9343,27 @@ _photo3.deleteTag = function (photoID, index) { * @returns {void} */ _photo3.share = function (photoID, service) { - if (!lychee.share_button_visible) { - return; - } - - var url = _photo3.getViewLink(photoID); - - switch (service) { - case "twitter": - window.open("https://twitter.com/share?url=" + encodeURI(url)); - break; - case "facebook": - window.open("https://www.facebook.com/sharer.php?u=" + encodeURI(url) + "&t=" + encodeURI(_photo3.json.title)); - break; - case "mail": - location.href = "mailto:?subject=" + encodeURI(_photo3.json.title) + "&body=" + encodeURI(url); - break; - case "dropbox": - lychee.loadDropbox(function () { - var filename = _photo3.json.title + "." + _photo3.getDirectLink().split(".").pop(); - Dropbox.save(_photo3.getDirectLink(), filename); - }); - break; - } + if (!lychee.share_button_visible) { + return; + } + var url = _photo3.getViewLink(photoID); + switch (service) { + case "twitter": + window.open("https://twitter.com/share?url=".concat(encodeURI(url))); + break; + case "facebook": + window.open("https://www.facebook.com/sharer.php?u=".concat(encodeURI(url), "&t=").concat(encodeURI(_photo3.json.title))); + break; + case "mail": + location.href = "mailto:?subject=".concat(encodeURI(_photo3.json.title), "&body=").concat(encodeURI(url)); + break; + case "dropbox": + lychee.loadDropbox(function () { + var filename = _photo3.json.title + "." + _photo3.getDirectLink().split(".").pop(); + Dropbox.save(_photo3.getDirectLink(), filename); + }); + break; + } }; /** @@ -9866,51 +9371,48 @@ _photo3.share = function (photoID, service) { * @returns {void} */ _photo3.setLicense = function (photoID) { - /** - * @param {{license: string}} data - */ - var action = function action(data) { - basicModal.close(); - - api.post("Photo::setLicense", { - photoID: photoID, - license: data.license - }, function () { - // update the photo JSON and reload the license in the sidebar - _photo3.json.license = data.license; - view.photo.license(); - }); - }; - - var setPhotoLicenseDialogBody = "\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t

\n\t\t
"; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initSetPhotoLicenseDialog = function initSetPhotoLicenseDialog(formElements, dialog) { - formElements.license.labels[0].textContent = lychee.locale["PHOTO_LICENSE"]; - formElements.license.item(0).textContent = lychee.locale["PHOTO_LICENSE_NONE"]; - formElements.license.item(1).textContent = lychee.locale["PHOTO_RESERVED"]; - formElements.license.value = _photo3.json.license === "" ? "none" : _photo3.json.license; - dialog.querySelector("p a").textContent = lychee.locale["PHOTO_LICENSE_HELP"]; - }; - - basicModal.show({ - body: setPhotoLicenseDialogBody, - readyCB: initSetPhotoLicenseDialog, - buttons: { - action: { - title: lychee.locale["PHOTO_SET_LICENSE"], - fn: action - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + /** + * @param {{license: string}} data + */ + var action = function action(data) { + basicModal.close(); + api.post("Photo::setLicense", { + photoID: photoID, + license: data.license + }, function () { + // update the photo JSON and reload the license in the sidebar + _photo3.json.license = data.license; + view.photo.license(); + }); + }; + var setPhotoLicenseDialogBody = "\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t

\n\t\t
"; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initSetPhotoLicenseDialog = function initSetPhotoLicenseDialog(formElements, dialog) { + formElements.license.labels[0].textContent = lychee.locale["PHOTO_LICENSE"]; + formElements.license.item(0).textContent = lychee.locale["PHOTO_LICENSE_NONE"]; + formElements.license.item(1).textContent = lychee.locale["PHOTO_RESERVED"]; + formElements.license.value = _photo3.json.license === "" ? "none" : _photo3.json.license; + dialog.querySelector("p a").textContent = lychee.locale["PHOTO_LICENSE_HELP"]; + }; + basicModal.show({ + body: setPhotoLicenseDialogBody, + readyCB: initSetPhotoLicenseDialog, + buttons: { + action: { + title: lychee.locale["PHOTO_SET_LICENSE"], + fn: action + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** @@ -9922,98 +9424,92 @@ _photo3.setLicense = function (photoID) { * @returns {void} */ _photo3.getArchive = function (photoIDs) { - var kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - - if (photoIDs.length !== 1 || kind !== null) { - location.href = "api/Photo::getArchive?photoIDs=" + photoIDs.join() + "&kind=" + kind; - return; - } - - // For a single photo without a specified kind, allow to pick the kind - // via a dialog box and re-call this method later on. - - var myPhoto = _photo3.json && _photo3.json.id === photoIDs[0] ? _photo3.json : album.getByID(photoIDs[0]); - - var kind2VariantAndLocalizedLabel = { - ORIGINAL: ["original", lychee.locale["PHOTO_ORIGINAL"]], - MEDIUM2X: ["medium2x", lychee.locale["PHOTO_MEDIUM_HIDPI"]], - MEDIUM: ["medium", lychee.locale["PHOTO_MEDIUM"]], - SMALL2X: ["small2x", lychee.locale["PHOTO_SMALL_HIDPI"]], - SMALL: ["small", lychee.locale["PHOTO_SMALL"]], - THUMB2X: ["thumb2x", lychee.locale["PHOTO_THUMB_HIDPI"]], - THUMB: ["thumb", lychee.locale["PHOTO_THUMB"]] - }; - - /** - * @param {string} kind - the kind this button is for, used to construct the ID - * @returns {string} - HTML - */ - var buildButton = function buildButton(kind) { - return "\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t"; - }; - - var getPhotoArchiveDialogBody = Object.entries(kind2VariantAndLocalizedLabel).reduce(function (html, _ref5) { - var _ref6 = _slicedToArray(_ref5, 1), - kind = _ref6[0]; - - return html + buildButton(kind); - }, "") + buildButton("LIVEPHOTOVIDEO"); - - /** @param {TouchEvent|MouseEvent} ev */ - var onClickOrTouch = function onClickOrTouch(ev) { - if (ev.currentTarget instanceof HTMLAnchorElement) { - basicModal.close(); - _photo3.getArchive(photoIDs, ev.currentTarget.dataset.photoKind); - ev.stopPropagation(); - } - }; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - */ - var initGetPhotoArchiveDialog = function initGetPhotoArchiveDialog(formElements, dialog) { - Object.entries(kind2VariantAndLocalizedLabel).forEach(function (_ref7) { - var _ref8 = _slicedToArray(_ref7, 2), - kind = _ref8[0], - _ref8$ = _slicedToArray(_ref8[1], 2), - variant = _ref8$[0], - lLabel = _ref8$[1]; - - /** @type {HTMLAnchorElement} */ - var button = dialog.querySelector('a[data-photo-kind="' + kind + '"]'); - /** @type {?SizeVariant} */ - var sv = myPhoto.size_variants[variant]; - if (sv) { - button.title = lychee.locale["DOWNLOAD"]; - lychee.addClickOrTouchListener(button, onClickOrTouch); - button.lastElementChild.textContent = lLabel + " (" + sv.width + "×" + sv.height + ", " + lychee.locale.printFilesizeLocalized(sv.filesize) + ")"; - } else { - button.remove(); - } - }); - /** @type {HTMLAnchorElement} */ - var liveButton = dialog.querySelector('a[data-photo-kind="LIVEPHOTOVIDEO"]'); - if (myPhoto.live_photo_url !== null) { - liveButton.title = lychee.locale["DOWNLOAD"]; - lychee.addClickOrTouchListener(liveButton, onClickOrTouch); - liveButton.lastElementChild.textContent = lychee.locale["PHOTO_LIVE_VIDEO"]; - } else { - liveButton.remove(); - } - }; - - basicModal.show({ - body: getPhotoArchiveDialogBody, - readyCB: initGetPhotoArchiveDialog, - classList: ["downloads"], - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); + var kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + if (photoIDs.length !== 1 || kind !== null) { + location.href = "api/Photo::getArchive?photoIDs=" + photoIDs.join() + "&kind=" + kind; + return; + } + + // For a single photo without a specified kind, allow to pick the kind + // via a dialog box and re-call this method later on. + + var myPhoto = _photo3.json && _photo3.json.id === photoIDs[0] ? _photo3.json : album.getByID(photoIDs[0]); + var kind2VariantAndLocalizedLabel = { + ORIGINAL: ["original", lychee.locale["PHOTO_ORIGINAL"]], + MEDIUM2X: ["medium2x", lychee.locale["PHOTO_MEDIUM_HIDPI"]], + MEDIUM: ["medium", lychee.locale["PHOTO_MEDIUM"]], + SMALL2X: ["small2x", lychee.locale["PHOTO_SMALL_HIDPI"]], + SMALL: ["small", lychee.locale["PHOTO_SMALL"]], + THUMB2X: ["thumb2x", lychee.locale["PHOTO_THUMB_HIDPI"]], + THUMB: ["thumb", lychee.locale["PHOTO_THUMB"]] + }; + + /** + * @param {string} kind - the kind this button is for, used to construct the ID + * @returns {string} - HTML + */ + var buildButton = function buildButton(kind) { + return "\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t"); + }; + var getPhotoArchiveDialogBody = Object.entries(kind2VariantAndLocalizedLabel).reduce(function (html, _ref5) { + var _ref6 = _slicedToArray(_ref5, 1), + kind = _ref6[0]; + return html + buildButton(kind); + }, "") + buildButton("LIVEPHOTOVIDEO"); + + /** @param {TouchEvent|MouseEvent} ev */ + var onClickOrTouch = function onClickOrTouch(ev) { + if (ev.currentTarget instanceof HTMLAnchorElement) { + basicModal.close(); + _photo3.getArchive(photoIDs, ev.currentTarget.dataset.photoKind); + ev.stopPropagation(); + } + }; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + */ + var initGetPhotoArchiveDialog = function initGetPhotoArchiveDialog(formElements, dialog) { + Object.entries(kind2VariantAndLocalizedLabel).forEach(function (_ref7) { + var _ref8 = _slicedToArray(_ref7, 2), + kind = _ref8[0], + _ref8$ = _slicedToArray(_ref8[1], 2), + variant = _ref8$[0], + lLabel = _ref8$[1]; + /** @type {HTMLAnchorElement} */ + var button = dialog.querySelector('a[data-photo-kind="' + kind + '"]'); + /** @type {?SizeVariant} */ + var sv = myPhoto.size_variants[variant]; + if (sv) { + button.title = lychee.locale["DOWNLOAD"]; + lychee.addClickOrTouchListener(button, onClickOrTouch); + button.lastElementChild.textContent = lLabel + " (" + sv.width + "×" + sv.height + ", " + lychee.locale.printFilesizeLocalized(sv.filesize) + ")"; + } else { + button.remove(); + } + }); + /** @type {HTMLAnchorElement} */ + var liveButton = dialog.querySelector('a[data-photo-kind="LIVEPHOTOVIDEO"]'); + if (myPhoto.live_photo_url !== null) { + liveButton.title = lychee.locale["DOWNLOAD"]; + lychee.addClickOrTouchListener(liveButton, onClickOrTouch); + liveButton.lastElementChild.textContent = lychee.locale["PHOTO_LIVE_VIDEO"]; + } else { + liveButton.remove(); + } + }; + basicModal.show({ + body: getPhotoArchiveDialogBody, + readyCB: initGetPhotoArchiveDialog, + classList: ["downloads"], + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); }; /** @@ -10023,65 +9519,63 @@ _photo3.getArchive = function (photoIDs) { * @returns {void} */ _photo3.qrCode = function (photoID) { - /** @type {?Photo} */ - var myPhoto = _photo3.json && _photo3.json.id === photoID ? _photo3.json : album.getByID(photoID); - - if (myPhoto == null) { - lychee.error(sprintf(lychee.locale["ERROR_PHOTO_NOT_FOUND"], photoID)); - return; - } - - // We need this indirection based on a resize observer, because the ready - // callback of the dialog is invoked _before_ the dialog is made visible - // in order to allow the ready callback to make initializations of - // form elements without causing flicker. - // However, for invisible elements `.clientWidth` returns zero, hence - // we cannot paint the QR code onto the canvas before it becomes visible. - var qrCodeCanvasObserver = function () { - var width = 0; - return new ResizeObserver(function (entries, observer) { - var qrCodeCanvas = entries[0].target; - // Avoid infinite resize events due to clearing and repainting - // the same QR code on the canvas. - if (width === qrCodeCanvas.clientWidth) { - return; - } - width = qrCodeCanvas.clientWidth; - QrCreator.render({ - text: _photo3.getViewLink(myPhoto.id), - radius: 0.0, - ecLevel: "H", - fill: "#000000", - background: "#FFFFFF", - size: width - }, qrCodeCanvas); - }); - }(); - - basicModal.show({ - body: "", - classList: ["qr-code"], - readyCB: function readyCB(formElements, dialog) { - var qrCodeCanvas = dialog.querySelector("canvas"); - qrCodeCanvasObserver.observe(qrCodeCanvas); - }, - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: function fn() { - qrCodeCanvasObserver.disconnect(); - basicModal.close(); - } - } - } - }); + /** @type {?Photo} */ + var myPhoto = _photo3.json && _photo3.json.id === photoID ? _photo3.json : album.getByID(photoID); + if (myPhoto == null) { + lychee.error(sprintf(lychee.locale["ERROR_PHOTO_NOT_FOUND"], photoID)); + return; + } + + // We need this indirection based on a resize observer, because the ready + // callback of the dialog is invoked _before_ the dialog is made visible + // in order to allow the ready callback to make initializations of + // form elements without causing flicker. + // However, for invisible elements `.clientWidth` returns zero, hence + // we cannot paint the QR code onto the canvas before it becomes visible. + var qrCodeCanvasObserver = function () { + var width = 0; + return new ResizeObserver(function (entries, observer) { + var qrCodeCanvas = entries[0].target; + // Avoid infinite resize events due to clearing and repainting + // the same QR code on the canvas. + if (width === qrCodeCanvas.clientWidth) { + return; + } + width = qrCodeCanvas.clientWidth; + QrCreator.render({ + text: _photo3.getViewLink(myPhoto.id), + radius: 0.0, + ecLevel: "H", + fill: "#000000", + background: "#FFFFFF", + size: width + }, qrCodeCanvas); + }); + }(); + basicModal.show({ + body: "", + classList: ["qr-code"], + readyCB: function readyCB(formElements, dialog) { + var qrCodeCanvas = dialog.querySelector("canvas"); + qrCodeCanvasObserver.observe(qrCodeCanvas); + }, + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: function fn() { + qrCodeCanvasObserver.disconnect(); + basicModal.close(); + } + } + } + }); }; /** * @returns {string} */ _photo3.getDirectLink = function () { - return _photo3.json && _photo3.json.size_variants && _photo3.json.size_variants.original && _photo3.json.size_variants.original.url ? _photo3.json.size_variants.original.url : ""; + return _photo3.json && _photo3.json.size_variants && _photo3.json.size_variants.original && _photo3.json.size_variants.original.url ? _photo3.json.size_variants.original.url : ""; }; /** @@ -10089,7 +9583,7 @@ _photo3.getDirectLink = function () { * @returns {string} */ _photo3.getViewLink = function (photoID) { - return lychee.getBaseUrl() + "view?p=" + photoID; + return lychee.getBaseUrl() + "view?p=" + photoID; }; /** @@ -10097,91 +9591,85 @@ _photo3.getViewLink = function (photoID) { * @returns {void} */ _photo3.showDirectLinks = function (photoID) { - if (!_photo3.json || _photo3.json.id !== photoID) { - return; - } - - /** - * @param {string} name - name of the HTML input element - * @returns {string} - HTML - */ - var buildLine = function buildLine(name) { - return "\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
"; - }; - - var localizations = { - original: lychee.locale["PHOTO_FULL"], - medium2x: lychee.locale["PHOTO_MEDIUM_HIDPI"], - medium: lychee.locale["PHOTO_MEDIUM"], - small2x: lychee.locale["PHOTO_SMALL_HIDPI"], - small: lychee.locale["PHOTO_SMALL"], - thumb2x: lychee.locale["PHOTO_THUMB_HIDPI"], - thumb: lychee.locale["PHOTO_THUMB"] - }; - - var showDirectLinksDialogBody = '' + buildLine("view") + '

' + Object.entries(localizations).reduce(function (html, _ref9) { - var _ref10 = _slicedToArray(_ref9, 1), - type = _ref10[0]; - - return html + buildLine(type); - }, "") + buildLine("live") + ""; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - */ - var initShowDirectLinksDialog = function initShowDirectLinksDialog(formElements, dialog) { - formElements.view.value = _photo3.getViewLink(photoID); - formElements.view.previousElementSibling.textContent = lychee.locale["PHOTO_VIEW"]; - formElements.view.nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; - dialog.querySelector("p").textContent = lychee.locale["PHOTO_DIRECT_LINKS_TO_IMAGES"]; - - for (var type in localizations) { - /** @type {?SizeVariant} */ - var sv = _photo3.json.size_variants[type]; - if (sv !== null) { - formElements[type].value = lychee.getBaseUrl() + sv.url; - formElements[type].previousElementSibling.textContent = localizations[type] + " (" + sv.width + "×" + sv.height + ")"; - formElements[type].nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; - } else { - // The form element is the `` element, the parent - // element is the `
` which binds the label, the input - // and the button together. - // We remove that `
` for non-existing variants. - formElements[type].parentElement.remove(); - } - } - - if (_photo3.json.live_photo_url !== null) { - formElements.live.value = lychee.getBaseUrl() + _photo3.json.live_photo_url; - formElements.live.previousElementSibling.textContent = lychee.locale["PHOTO_LIVE_VIDEO"]; - formElements.live.nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; - } else { - formElements.live.parentElement.remove(); - } - - /** @param {TouchEvent|MouseEvent} ev */ - var onClickOrTouch = function onClickOrTouch(ev) { - navigator.clipboard.writeText(ev.currentTarget.previousElementSibling.value).then(function () { - return loadingBar.show("success", lychee.locale["URL_COPIED_TO_CLIPBOARD"]); - }); - ev.stopPropagation(); - }; - dialog.querySelectorAll("a.button").forEach(function (a) { - return lychee.addClickOrTouchListener(a, onClickOrTouch); - }); - }; - - basicModal.show({ - body: showDirectLinksDialogBody, - readyCB: initShowDirectLinksDialog, - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); + if (!_photo3.json || _photo3.json.id !== photoID) { + return; + } + + /** + * @param {string} name - name of the HTML input element + * @returns {string} - HTML + */ + var buildLine = function buildLine(name) { + return "\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
"); + }; + var localizations = { + original: lychee.locale["PHOTO_FULL"], + medium2x: lychee.locale["PHOTO_MEDIUM_HIDPI"], + medium: lychee.locale["PHOTO_MEDIUM"], + small2x: lychee.locale["PHOTO_SMALL_HIDPI"], + small: lychee.locale["PHOTO_SMALL"], + thumb2x: lychee.locale["PHOTO_THUMB_HIDPI"], + thumb: lychee.locale["PHOTO_THUMB"] + }; + var showDirectLinksDialogBody = '' + buildLine("view") + '

' + Object.entries(localizations).reduce(function (html, _ref9) { + var _ref10 = _slicedToArray(_ref9, 1), + type = _ref10[0]; + return html + buildLine(type); + }, "") + buildLine("live") + ""; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + */ + var initShowDirectLinksDialog = function initShowDirectLinksDialog(formElements, dialog) { + formElements.view.value = _photo3.getViewLink(photoID); + formElements.view.previousElementSibling.textContent = lychee.locale["PHOTO_VIEW"]; + formElements.view.nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; + dialog.querySelector("p").textContent = lychee.locale["PHOTO_DIRECT_LINKS_TO_IMAGES"]; + for (var type in localizations) { + /** @type {?SizeVariant} */ + var sv = _photo3.json.size_variants[type]; + if (sv !== null) { + formElements[type].value = lychee.getBaseUrl() + sv.url; + formElements[type].previousElementSibling.textContent = localizations[type] + " (" + sv.width + "×" + sv.height + ")"; + formElements[type].nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; + } else { + // The form element is the `` element, the parent + // element is the `
` which binds the label, the input + // and the button together. + // We remove that `
` for non-existing variants. + formElements[type].parentElement.remove(); + } + } + if (_photo3.json.live_photo_url !== null) { + formElements.live.value = lychee.getBaseUrl() + _photo3.json.live_photo_url; + formElements.live.previousElementSibling.textContent = lychee.locale["PHOTO_LIVE_VIDEO"]; + formElements.live.nextElementSibling.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; + } else { + formElements.live.parentElement.remove(); + } + + /** @param {TouchEvent|MouseEvent} ev */ + var onClickOrTouch = function onClickOrTouch(ev) { + navigator.clipboard.writeText(ev.currentTarget.previousElementSibling.value).then(function () { + return loadingBar.show("success", lychee.locale["URL_COPIED_TO_CLIPBOARD"]); + }); + ev.stopPropagation(); + }; + dialog.querySelectorAll("a.button").forEach(function (a) { + return lychee.addClickOrTouchListener(a, onClickOrTouch); + }); + }; + basicModal.show({ + body: showDirectLinksDialogBody, + readyCB: initShowDirectLinksDialog, + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); }; /** @@ -10196,31 +9684,29 @@ photoeditor = {}; * @returns {void} */ photoeditor.rotate = function (photoID, direction) { - api.post("PhotoEditor::rotate", { - photoID: photoID, - direction: direction - }, - /** @param {Photo} data */ - function (data) { - _photo3.json = data; - // TODO: `photo.json.original_album_id` is set only, but never read; do we need it? - _photo3.json.original_album_id = _photo3.json.album_id; - if (album.json) { - // TODO: Why do we overwrite the true album ID of a photo, by the externally provided one? I guess we need it, because the album which the user came from might also be a smart album or a tag album. However, in this case I would prefer to leave the `album_id untouched (don't rename it to `original_album_id`) and call this one `effective_album_id` instead. - _photo3.json.album_id = album.json.id; - } - - var image = $("img#image"); - if (_photo3.json.size_variants.medium2x !== null) { - image.prop("srcset", _photo3.json.size_variants.medium.url + " " + _photo3.json.size_variants.medium.width + "w, " + _photo3.json.size_variants.medium2x.url + " " + _photo3.json.size_variants.medium2x.width + "w"); - } else { - image.prop("srcset", ""); - } - image.prop("src", _photo3.json.size_variants.medium !== null ? _photo3.json.size_variants.medium.url : _photo3.json.size_variants.original.url); - view.photo.onresize(); - view.photo.sidebar(); - album.updatePhoto(data); - }); + api.post("PhotoEditor::rotate", { + photoID: photoID, + direction: direction + }, /** @param {Photo} data */ + function (data) { + _photo3.json = data; + // TODO: `photo.json.original_album_id` is set only, but never read; do we need it? + _photo3.json.original_album_id = _photo3.json.album_id; + if (album.json) { + // TODO: Why do we overwrite the true album ID of a photo, by the externally provided one? I guess we need it, because the album which the user came from might also be a smart album or a tag album. However, in this case I would prefer to leave the `album_id untouched (don't rename it to `original_album_id`) and call this one `effective_album_id` instead. + _photo3.json.album_id = album.json.id; + } + var image = $("img#image"); + if (_photo3.json.size_variants.medium2x !== null) { + image.prop("srcset", "".concat(_photo3.json.size_variants.medium.url, " ").concat(_photo3.json.size_variants.medium.width, "w, ").concat(_photo3.json.size_variants.medium2x.url, " ").concat(_photo3.json.size_variants.medium2x.width, "w")); + } else { + image.prop("srcset", ""); + } + image.prop("src", _photo3.json.size_variants.medium !== null ? _photo3.json.size_variants.medium.url : _photo3.json.size_variants.original.url); + view.photo.onresize(); + view.photo.sidebar(); + album.updatePhoto(data); + }); }; /** @@ -10257,8 +9743,8 @@ var SearchAlbumIDPrefix = "search"; * The search object */ var search = { - /** @type {?SearchResult} */ - json: null + /** @type {?SearchResult} */ + json: null }; /** @@ -10266,125 +9752,119 @@ var search = { * @returns {void} */ search.find = function (term) { - if (term.trim() === "") return; - - /** @param {SearchResult} data */ - var successHandler = function successHandler(data) { - if (search.json && search.json.checksum === data.checksum) { - // If search result is identical to previous result, just - // update the album id with the new search term and bail out. - album.json.id = SearchAlbumIDPrefix + "/" + term; - return; - } - - search.json = data; - - // Create and assign a `SearchAlbum` - album.json = { - id: SearchAlbumIDPrefix + "/" + term, - title: lychee.locale["SEARCH_RESULTS"], - photos: search.json.photos, - albums: search.json.albums, - tag_albums: search.json.tag_albums, - thumb: null, - rights: { can_download: false }, - policy: { is_public: false } - }; - - var albumsData = ""; - var photosData = ""; - - // Build HTML for album - search.json.tag_albums.forEach(function (album) { - albums.parse(album); - albumsData += build.album(album); - }); - search.json.albums.forEach(function (album) { - albums.parse(album); - albumsData += build.album(album); - }); - - // Build HTML for photo - search.json.photos.forEach(function (photo) { - photosData += build.photo(photo); - }); - - var albums_divider = lychee.locale["ALBUMS"]; - var photos_divider = lychee.locale["PHOTOS"]; - - if (albumsData !== "") albums_divider += " (" + (search.json.tag_albums.length + search.json.albums.length) + ")"; - if (photosData !== "") { - photos_divider += " (" + search.json.photos.length + ")"; - if (lychee.layout === 1) { - photosData = '
' + photosData + "
"; - } else if (lychee.layout === 2) { - photosData = '
' + photosData + "
"; - } - } - - // 1. No albums and photos - // 2. Only photos - // 3. Only albums - // 4. Albums and photos - var html = albumsData === "" && photosData === "" ? "" : albumsData === "" ? build.divider(photos_divider) + photosData : photosData === "" ? build.divider(albums_divider) + albumsData : build.divider(albums_divider) + albumsData + build.divider(photos_divider) + photosData; - - $(".no_content").remove(); - lychee.animate(lychee.content, "contentZoomOut"); - - setTimeout(function () { - if (visible.photo()) view.photo.hide(); - if (visible.sidebar()) _sidebar.toggle(false); - if (visible.mapview()) mapview.close(); - - header.setMode("albums"); - - if (html === "") { - lychee.content.html(""); - lychee.content.append(build.no_content("magnifying-glass")); - } else { - lychee.content.html(html); - // Here we exploit the layout method of an album although - // the search result is not a proper album. - // It would be much better to have a component like - // `view.photos` (note the plural form) which takes care of - // all photo listings independent of the surrounding "thing" - // (i.e. regular album, tag album, search result) - setTimeout(function () { - view.album.content.justify(); - lychee.animate(lychee.content, "contentZoomIn"); - $("#lychee_view_container").scrollTop(0); - }, 0); - } - lychee.setMetaData(lychee.locale["SEARCH_RESULTS"]); - }, 300); - }; - - /** @returns {void} */ - var timeoutHandler = function timeoutHandler() { - if (header.dom(".header__search").val().length !== 0) { - api.post("Search::run", { term: term }, successHandler); - } else { - search.reset(); - } - }; - - clearTimeout($(window).data("timeout")); - $(window).data("timeout", setTimeout(timeoutHandler, 250)); -}; - -search.reset = function () { - header.dom(".header__search").val(""); - $(".no_content").remove(); + if (term.trim() === "") return; + + /** @param {SearchResult} data */ + var successHandler = function successHandler(data) { + if (search.json && search.json.checksum === data.checksum) { + // If search result is identical to previous result, just + // update the album id with the new search term and bail out. + album.json.id = SearchAlbumIDPrefix + "/" + term; + return; + } + search.json = data; + + // Create and assign a `SearchAlbum` + album.json = { + id: SearchAlbumIDPrefix + "/" + term, + title: lychee.locale["SEARCH_RESULTS"], + photos: search.json.photos, + albums: search.json.albums, + tag_albums: search.json.tag_albums, + thumb: null, + rights: { + can_download: false + }, + policy: { + is_public: false + } + }; + var albumsData = ""; + var photosData = ""; - if (search.json !== null) { - // Trash data - album.json = null; - _photo3.json = null; - search.json = null; + // Build HTML for album + search.json.tag_albums.forEach(function (album) { + albums.parse(album); + albumsData += build.album(album); + }); + search.json.albums.forEach(function (album) { + albums.parse(album); + albumsData += build.album(album); + }); + + // Build HTML for photo + search.json.photos.forEach(function (photo) { + photosData += build.photo(photo); + }); + var albums_divider = lychee.locale["ALBUMS"]; + var photos_divider = lychee.locale["PHOTOS"]; + if (albumsData !== "") albums_divider += " (" + (search.json.tag_albums.length + search.json.albums.length) + ")"; + if (photosData !== "") { + photos_divider += " (" + search.json.photos.length + ")"; + if (lychee.layout === 1) { + photosData = '
' + photosData + "
"; + } else if (lychee.layout === 2) { + photosData = '
' + photosData + "
"; + } + } + + // 1. No albums and photos + // 2. Only photos + // 3. Only albums + // 4. Albums and photos + var html = albumsData === "" && photosData === "" ? "" : albumsData === "" ? build.divider(photos_divider) + photosData : photosData === "" ? build.divider(albums_divider) + albumsData : build.divider(albums_divider) + albumsData + build.divider(photos_divider) + photosData; + $(".no_content").remove(); + lychee.animate(lychee.content, "contentZoomOut"); + setTimeout(function () { + if (visible.photo()) view.photo.hide(); + if (visible.sidebar()) _sidebar.toggle(false); + if (visible.mapview()) mapview.close(); + header.setMode("albums"); + if (html === "") { + lychee.content.html(""); + lychee.content.append(build.no_content("magnifying-glass")); + } else { + lychee.content.html(html); + // Here we exploit the layout method of an album although + // the search result is not a proper album. + // It would be much better to have a component like + // `view.photos` (note the plural form) which takes care of + // all photo listings independent of the surrounding "thing" + // (i.e. regular album, tag album, search result) + setTimeout(function () { + view.album.content.justify(); + lychee.animate(lychee.content, "contentZoomIn"); + $("#lychee_view_container").scrollTop(0); + }, 0); + } + lychee.setMetaData(lychee.locale["SEARCH_RESULTS"]); + }, 300); + }; - lychee.animate($(".divider"), "fadeOut"); - lychee.goto(); - } + /** @returns {void} */ + var timeoutHandler = function timeoutHandler() { + if (header.dom(".header__search").val().length !== 0) { + api.post("Search::run", { + term: term + }, successHandler); + } else { + search.reset(); + } + }; + clearTimeout($(window).data("timeout")); + $(window).data("timeout", setTimeout(timeoutHandler, 250)); +}; +search.reset = function () { + header.dom(".header__search").val(""); + $(".no_content").remove(); + if (search.json !== null) { + // Trash data + album.json = null; + _photo3.json = null; + search.json = null; + lychee.animate($(".divider"), "fadeOut"); + lychee["goto"](); + } }; /** @@ -10397,7 +9877,7 @@ var settings = {}; * @returns {void} */ settings.open = function () { - view.settings.init(); + view.settings.init(); }; /** @@ -10415,48 +9895,47 @@ settings.open = function () { * @returns {SettingsFormData} */ settings.getValues = function (formSelector) { - var values = {}; - - /** @type {?NodeListOf} */ - var inputElements = document.querySelectorAll(formSelector + " input[name]"); - - // Get value from all inputs - inputElements.forEach(function (inputElement) { - switch (inputElement.type) { - case "checkbox": - case "radio": - values[inputElement.name] = inputElement.checked; - break; - case "number": - case "range": - values[inputElement.name] = parseInt(inputElement.value, 10); - break; - case "file": - values[inputElement.name] = inputElement.files; - break; - default: - switch (inputElement.getAttribute("inputmode")) { - case "numeric": - values[inputElement.name] = parseInt(inputElement.value, 10); - break; - case "decimal": - values[inputElement.name] = parseFloat(inputElement.value); - break; - default: - values[inputElement.name] = inputElement.value; - } - } - }); - - /** @type {?NodeListOf} */ - var selectElements = document.querySelectorAll(formSelector + " select[name]"); - - // Get name of selected option from all selects - selectElements.forEach(function (selectElement) { - values[selectElement.name] = selectElement.selectedIndex !== -1 ? selectElement.options[selectElement.selectedIndex].value : null; - }); - - return values; + var values = {}; + + /** @type {?NodeListOf} */ + var inputElements = document.querySelectorAll(formSelector + " input[name]"); + + // Get value from all inputs + inputElements.forEach(function (inputElement) { + switch (inputElement.type) { + case "checkbox": + case "radio": + values[inputElement.name] = inputElement.checked; + break; + case "number": + case "range": + values[inputElement.name] = parseInt(inputElement.value, 10); + break; + case "file": + values[inputElement.name] = inputElement.files; + break; + default: + switch (inputElement.getAttribute("inputmode")) { + case "numeric": + values[inputElement.name] = parseInt(inputElement.value, 10); + break; + case "decimal": + values[inputElement.name] = parseFloat(inputElement.value); + break; + default: + values[inputElement.name] = inputElement.value; + } + } + }); + + /** @type {?NodeListOf} */ + var selectElements = document.querySelectorAll(formSelector + " select[name]"); + + // Get name of selected option from all selects + selectElements.forEach(function (selectElement) { + values[selectElement.name] = selectElement.selectedIndex !== -1 ? selectElement.options[selectElement.selectedIndex].value : null; + }); + return values; }; /** @@ -10474,9 +9953,9 @@ settings.getValues = function (formSelector) { * @param {SettingClickCB} settingClickCB */ settings.bind = function (inputSelector, formSelector, settingClickCB) { - $(inputSelector).on("click", function () { - settingClickCB(settings.getValues(formSelector)); - }); + $(inputSelector).on("click", function () { + settingClickCB(settings.getValues(formSelector)); + }); }; /** @@ -10484,33 +9963,29 @@ settings.bind = function (inputSelector, formSelector, settingClickCB) { * @returns {void} */ settings.changeLogin = function (params) { - if (params.username === "") { - params.username = null; - } - - if (params.password.length < 1) { - loadingBar.show("error", lychee.locale["ERROR_EMPTY_PASSWORD"]); - $("input[name=password]").addClass("error"); - return; - } else { - $("input[name=password]").removeClass("error"); - } - - if (params.password !== params.confirm) { - loadingBar.show("error", lychee.locale["ERROR_PASSWORD_DOES_NOT_MATCH"]); - $("input[name=confirm]").addClass("error"); - return; - } else { - $("input[name=confirm]").removeClass("error"); - } - - api.post("User::updateLogin", params, - /** @param {User} updatedUser */function (updatedUser) { - $("input[name]").removeClass("error"); - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LOGIN"]); - view.settings.content.clearLogin(); - lychee.user = updatedUser; - }); + if (params.username === "") { + params.username = null; + } + if (params.password.length < 1) { + loadingBar.show("error", lychee.locale["ERROR_EMPTY_PASSWORD"]); + $("input[name=password]").addClass("error"); + return; + } else { + $("input[name=password]").removeClass("error"); + } + if (params.password !== params.confirm) { + loadingBar.show("error", lychee.locale["ERROR_PASSWORD_DOES_NOT_MATCH"]); + $("input[name=confirm]").addClass("error"); + return; + } else { + $("input[name=confirm]").removeClass("error"); + } + api.post("User::updateLogin", params, /** @param {User} updatedUser */function (updatedUser) { + $("input[name]").removeClass("error"); + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LOGIN"]); + view.settings.content.clearLogin(); + lychee.user = updatedUser; + }); }; /** @@ -10518,14 +9993,14 @@ settings.changeLogin = function (params) { * @returns {void} */ settings.changeSorting = function (params) { - api.post("Settings::setSorting", params, function () { - lychee.sorting_albums.column = params["sorting_albums_column"]; - lychee.sorting_albums.order = params["sorting_albums_order"]; - lychee.sorting_photos.column = params["sorting_photos_column"]; - lychee.sorting_photos.order = params["sorting_photos_order"]; - albums.refresh(); - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_SORT"]); - }); + api.post("Settings::setSorting", params, function () { + lychee.sorting_albums.column = params["sorting_albums_column"]; + lychee.sorting_albums.order = params["sorting_albums_order"]; + lychee.sorting_photos.column = params["sorting_photos_column"]; + lychee.sorting_photos.order = params["sorting_photos_order"]; + albums.refresh(); + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_SORT"]); + }); }; /** @@ -10533,12 +10008,12 @@ settings.changeSorting = function (params) { * @returns {void} */ settings.changeDropboxKey = function (params) { - // if params.key == "" key is cleared - api.post("Settings::setDropboxKey", params, function () { - lychee.dropboxKey = params.key; - // if (callback) lychee.loadDropbox(callback) - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_DROPBOX"]); - }); + // if params.key == "" key is cleared + api.post("Settings::setDropboxKey", params, function () { + lychee.dropboxKey = params.key; + // if (callback) lychee.loadDropbox(callback) + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_DROPBOX"]); + }); }; /** @@ -10546,10 +10021,10 @@ settings.changeDropboxKey = function (params) { * @returns {void} */ settings.changeLang = function (params) { - api.post("Settings::setLang", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LANG"]); - lychee.init(); - }); + api.post("Settings::setLang", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LANG"]); + lychee.init(); + }); }; /** @@ -10557,10 +10032,10 @@ settings.changeLang = function (params) { * @returns {void} */ settings.setDefaultLicense = function (params) { - api.post("Settings::setDefaultLicense", params, function () { - lychee.default_license = params.license; - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LICENSE"]); - }); + api.post("Settings::setDefaultLicense", params, function () { + lychee.default_license = params.license; + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LICENSE"]); + }); }; /** @@ -10568,10 +10043,10 @@ settings.setDefaultLicense = function (params) { * @returns {void} */ settings.setLayout = function (params) { - api.post("Settings::setLayout", params, function () { - lychee.layout = params.layout; - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LAYOUT"]); - }); + api.post("Settings::setLayout", params, function () { + lychee.layout = params.layout; + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_LAYOUT"]); + }); }; /** @@ -10579,10 +10054,10 @@ settings.setLayout = function (params) { * @returns {void} */ settings.changePublicSearch = function (params) { - api.post("Settings::setPublicSearch", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_PUBLIC_SEARCH"]); - lychee.public_search = params.public_search; - }); + api.post("Settings::setPublicSearch", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_PUBLIC_SEARCH"]); + lychee.public_search = params.public_search; + }); }; /** @@ -10590,11 +10065,11 @@ settings.changePublicSearch = function (params) { * @returns {void} */ settings.setOverlayType = function (params) { - api.post("Settings::setOverlayType", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_IMAGE_OVERLAY"]); - lychee.image_overlay_type = params.image_overlay_type; - lychee.image_overlay_type_default = params.image_overlay_type; - }); + api.post("Settings::setOverlayType", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_IMAGE_OVERLAY"]); + lychee.image_overlay_type = params.image_overlay_type; + lychee.image_overlay_type_default = params.image_overlay_type; + }); }; /** @@ -10602,15 +10077,15 @@ settings.setOverlayType = function (params) { * @returns {void} */ settings.changeMapDisplay = function (params) { - api.post("Settings::setMapDisplay", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); - lychee.map_display = params.map_display; - // Map functionality is disabled - // -> map for public albums also needs to be disabled - if (!lychee.map_display && lychee.map_display_public) { - $("#MapDisplayPublic").click(); - } - }); + api.post("Settings::setMapDisplay", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); + lychee.map_display = params.map_display; + // Map functionality is disabled + // -> map for public albums also needs to be disabled + if (!lychee.map_display && lychee.map_display_public) { + $("#MapDisplayPublic").click(); + } + }); }; /** @@ -10618,15 +10093,15 @@ settings.changeMapDisplay = function (params) { * @returns {void} */ settings.changeMapDisplayPublic = function (params) { - api.post("Settings::setMapDisplayPublic", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY_PUBLIC"]); - lychee.map_display_public = params.map_display_public; - // If public map functionality is enabled, but map in general is disabled - // General map functionality needs to be enabled - if (lychee.map_display_public && !lychee.map_display) { - $("#MapDisplay").click(); - } - }); + api.post("Settings::setMapDisplayPublic", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY_PUBLIC"]); + lychee.map_display_public = params.map_display_public; + // If public map functionality is enabled, but map in general is disabled + // General map functionality needs to be enabled + if (lychee.map_display_public && !lychee.map_display) { + $("#MapDisplay").click(); + } + }); }; /** @@ -10634,10 +10109,10 @@ settings.changeMapDisplayPublic = function (params) { * @returns {void} */ settings.setMapProvider = function (params) { - api.post("Settings::setMapProvider", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_PROVIDER"]); - lychee.map_provider = params.map_provider; - }); + api.post("Settings::setMapProvider", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_PROVIDER"]); + lychee.map_provider = params.map_provider; + }); }; /** @@ -10645,10 +10120,10 @@ settings.setMapProvider = function (params) { * @returns {void} */ settings.changeMapIncludeSubAlbums = function (params) { - api.post("Settings::setMapIncludeSubAlbums", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); - lychee.map_include_subalbums = params.map_include_subalbums; - }); + api.post("Settings::setMapIncludeSubAlbums", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); + lychee.map_include_subalbums = params.map_include_subalbums; + }); }; /** @@ -10656,10 +10131,10 @@ settings.changeMapIncludeSubAlbums = function (params) { * @returns {void} */ settings.changeLocationDecoding = function (params) { - api.post("Settings::setLocationDecoding", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); - lychee.location_decoding = params.location_decoding; - }); + api.post("Settings::setLocationDecoding", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); + lychee.location_decoding = params.location_decoding; + }); }; /** @@ -10667,11 +10142,11 @@ settings.changeLocationDecoding = function (params) { * @returns {void} */ settings.changeNSFWVisible = function (params) { - api.post("Settings::setNSFWVisible", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_NSFW_VISIBLE"]); - lychee.nsfw_visible = params.nsfw_visible; - lychee.nsfw_visible_saved = lychee.nsfw_visible; - }); + api.post("Settings::setNSFWVisible", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_NSFW_VISIBLE"]); + lychee.nsfw_visible = params.nsfw_visible; + lychee.nsfw_visible_saved = lychee.nsfw_visible; + }); }; //TODO : later @@ -10684,15 +10159,15 @@ settings.changeNSFWVisible = function (params) { * @returns {void} */ settings.changeLocationShow = function (params) { - api.post("Settings::setLocationShow", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); - lychee.location_show = params.location_show; - // Don't show location - // -> location for public albums also needs to be disabled - if (!lychee.location_show && lychee.location_show_public) { - $("#LocationShowPublic").click(); - } - }); + api.post("Settings::setLocationShow", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); + lychee.location_show = params.location_show; + // Don't show location + // -> location for public albums also needs to be disabled + if (!lychee.location_show && lychee.location_show_public) { + $("#LocationShowPublic").click(); + } + }); }; /** @@ -10700,15 +10175,15 @@ settings.changeLocationShow = function (params) { * @returns {void} */ settings.changeLocationShowPublic = function (params) { - api.post("Settings::setLocationShowPublic", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); - lychee.location_show_public = params.location_show_public; - // If public map functionality is enabled, but map in general is disabled - // General map functionality needs to be enabled - if (lychee.location_show_public && !lychee.location_show) { - $("#LocationShow").click(); - } - }); + api.post("Settings::setLocationShowPublic", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_MAP_DISPLAY"]); + lychee.location_show_public = params.location_show_public; + // If public map functionality is enabled, but map in general is disabled + // General map functionality needs to be enabled + if (lychee.location_show_public && !lychee.location_show) { + $("#LocationShow").click(); + } + }); }; /** @@ -10716,23 +10191,23 @@ settings.changeLocationShowPublic = function (params) { * @returns {void} */ settings.changeNewPhotosNotification = function (params) { - api.post("Settings::setNewPhotosNotification", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_NEW_PHOTOS_NOTIFICATION"]); - lychee.new_photos_notification = params.new_photos_notification; - }); + api.post("Settings::setNewPhotosNotification", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_NEW_PHOTOS_NOTIFICATION"]); + lychee.new_photos_notification = params.new_photos_notification; + }); }; /** * @returns {void} */ settings.changeCSS = function () { - var params = { - css: $("#css").val() - }; - api.post("Settings::setCSS", params, function () { - lychee.css = params.css; - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_CSS"]); - }); + var params = { + css: $("#css").val() + }; + api.post("Settings::setCSS", params, function () { + lychee.css = params.css; + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_CSS"]); + }); }; /** @@ -10740,12 +10215,12 @@ settings.changeCSS = function () { * @returns {void} */ settings.save = function (params) { - api.post("Settings::saveAll", params, function () { - loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_UPDATE"]); - view.full_settings.init(); - // re-read settings - lychee.init(false); - }); + api.post("Settings::saveAll", params, function () { + loadingBar.show("success", lychee.locale["SETTINGS_SUCCESS_UPDATE"]); + view.full_settings.init(); + // re-read settings + lychee.init(false); + }); }; /** @@ -10753,210 +10228,196 @@ settings.save = function (params) { * @returns {void} */ settings.save_enter = function (e) { - // We only handle "enter" - if (e.which !== 13) return; - - var saveSettingsConfirmationDialogBody = - // TODO: move the style to the style file, where it belongs. - '

'; - - basicModal.show({ - body: saveSettingsConfirmationDialogBody, - readyCB: function readyCB(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["SETTINGS_ADVANCED_SAVE"]; - }, - buttons: { - action: { - title: lychee.locale["ENTER"], - fn: function fn() { - settings.save(settings.getValues("#fullSettings")); - basicModal.close(); - }, - classList: ["red"] - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); + // We only handle "enter" + if (e.which !== 13) return; + var saveSettingsConfirmationDialogBody = + // TODO: move the style to the style file, where it belongs. + '

'; + basicModal.show({ + body: saveSettingsConfirmationDialogBody, + readyCB: function readyCB(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["SETTINGS_ADVANCED_SAVE"]; + }, + buttons: { + action: { + title: lychee.locale["ENTER"], + fn: function fn() { + settings.save(settings.getValues("#fullSettings")); + basicModal.close(); + }, + classList: ["red"] + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); }; /** * @returns {void} */ settings.openTokenDialog = function () { - /** @type {string} */ - var tokenValue = ""; - /** @type {?HTMLAnchorElement} */ - var resetTokenButton = null; - /** @type {?HTMLAnchorElement} */ - var copyTokenButton = null; - /** @type {?HTMLAnchorElement} */ - var disableTokenButton = null; - /** @type {?HTMLInputElement} */ - var tokenInputElement = null; - - var bodyHtml = "\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t"; - - /** - * @returns {void} - */ - var updateTokenDialog = function updateTokenDialog() { - if (lychee.user.has_token) { - disableTokenButton.style.display = null; - - if (!!tokenValue) { - tokenInputElement.value = tokenValue; - tokenInputElement.disabled = false; - copyTokenButton.style.display = null; - } else { - tokenInputElement.value = lychee.locale["TOKEN_NOT_AVAILABLE"]; - tokenInputElement.disabled = true; - copyTokenButton.style.display = "none"; - } - } else { - tokenInputElement.value = lychee.locale["DISABLED_TOKEN_STATUS_MSG"]; - tokenInputElement.disabled = true; - copyTokenButton.style.display = "none"; - disableTokenButton.style.display = "none"; - } - }; - - /** - * @param {MouseEvent|TouchEvent} ev - */ - var onCopyToken = function onCopyToken(ev) { - navigator.clipboard.writeText(tokenValue); - ev.stopPropagation(); - }; - - /** - * @param {MouseEvent|TouchEvent} ev - */ - var onResetToken = function onResetToken(ev) { - tokenInputElement.value = ""; - ev.stopPropagation(); - api.post("User::resetToken", {}, - /** - * @param {{token: string}} data - */ - function (data) { - tokenValue = data.token; - lychee.user.has_token = true; - updateTokenDialog(); - }); - }; - - /** - * @param {MouseEvent|TouchEvent} ev - */ - var onDisableToken = function onDisableToken(ev) { - tokenInputElement.value = ""; - ev.stopPropagation(); - api.post("User::unsetToken", {}, function () { - tokenValue = ""; - lychee.user.has_token = false; - updateTokenDialog(); - }); - }; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initTokenDialog = function initTokenDialog(formElements, dialog) { - resetTokenButton = dialog.querySelector("a#button_reset_token"); - resetTokenButton.title = lychee.locale["RESET"]; - copyTokenButton = dialog.querySelector("a#button_copy_token"); - copyTokenButton.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; - disableTokenButton = dialog.querySelector("a#button_disable_token"); - disableTokenButton.title = lychee.locale["DISABLE_TOKEN_TOOLTIP"]; - tokenInputElement = formElements.token; - tokenInputElement.placeholder = lychee.locale["TOKEN_WAIT"]; - tokenInputElement.labels[0].textContent = "Token"; - tokenInputElement.blur(); - - updateTokenDialog(); - - lychee.addClickOrTouchListener(copyTokenButton, onCopyToken); - lychee.addClickOrTouchListener(resetTokenButton, onResetToken); - lychee.addClickOrTouchListener(disableTokenButton, onDisableToken); - }; - - basicModal.show({ - body: bodyHtml, - readyCB: initTokenDialog, - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); -}; + /** @type {string} */ + var tokenValue = ""; + /** @type {?HTMLAnchorElement} */ + var resetTokenButton = null; + /** @type {?HTMLAnchorElement} */ + var copyTokenButton = null; + /** @type {?HTMLAnchorElement} */ + var disableTokenButton = null; + /** @type {?HTMLInputElement} */ + var tokenInputElement = null; + var bodyHtml = "\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t"; + + /** + * @returns {void} + */ + var updateTokenDialog = function updateTokenDialog() { + if (lychee.user.has_token) { + disableTokenButton.style.display = null; + if (!!tokenValue) { + tokenInputElement.value = tokenValue; + tokenInputElement.disabled = false; + copyTokenButton.style.display = null; + } else { + tokenInputElement.value = lychee.locale["TOKEN_NOT_AVAILABLE"]; + tokenInputElement.disabled = true; + copyTokenButton.style.display = "none"; + } + } else { + tokenInputElement.value = lychee.locale["DISABLED_TOKEN_STATUS_MSG"]; + tokenInputElement.disabled = true; + copyTokenButton.style.display = "none"; + disableTokenButton.style.display = "none"; + } + }; + + /** + * @param {MouseEvent|TouchEvent} ev + */ + var onCopyToken = function onCopyToken(ev) { + navigator.clipboard.writeText(tokenValue); + ev.stopPropagation(); + }; + + /** + * @param {MouseEvent|TouchEvent} ev + */ + var onResetToken = function onResetToken(ev) { + tokenInputElement.value = ""; + ev.stopPropagation(); + api.post("User::resetToken", {}, + /** + * @param {{token: string}} data + */ + function (data) { + tokenValue = data.token; + lychee.user.has_token = true; + updateTokenDialog(); + }); + }; + + /** + * @param {MouseEvent|TouchEvent} ev + */ + var onDisableToken = function onDisableToken(ev) { + tokenInputElement.value = ""; + ev.stopPropagation(); + api.post("User::unsetToken", {}, function () { + tokenValue = ""; + lychee.user.has_token = false; + updateTokenDialog(); + }); + }; + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initTokenDialog = function initTokenDialog(formElements, dialog) { + resetTokenButton = dialog.querySelector("a#button_reset_token"); + resetTokenButton.title = lychee.locale["RESET"]; + copyTokenButton = dialog.querySelector("a#button_copy_token"); + copyTokenButton.title = lychee.locale["URL_COPY_TO_CLIPBOARD"]; + disableTokenButton = dialog.querySelector("a#button_disable_token"); + disableTokenButton.title = lychee.locale["DISABLE_TOKEN_TOOLTIP"]; + tokenInputElement = formElements.token; + tokenInputElement.placeholder = lychee.locale["TOKEN_WAIT"]; + tokenInputElement.labels[0].textContent = "Token"; + tokenInputElement.blur(); + updateTokenDialog(); + lychee.addClickOrTouchListener(copyTokenButton, onCopyToken); + lychee.addClickOrTouchListener(resetTokenButton, onResetToken); + lychee.addClickOrTouchListener(disableTokenButton, onDisableToken); + }; + basicModal.show({ + body: bodyHtml, + readyCB: initTokenDialog, + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); +}; var sharing = { - /** @type {?SharingInfo} */ - json: null + /** @type {?SharingInfo} */ + json: null }; /** * @returns {void} */ sharing.add = function () { - var params = { - /** @type {string[]} */ - albumIDs: [], - /** @type {number[]} */ - userIDs: [] - }; - - $("#albums_list_to option").each(function () { - params.albumIDs.push(this.value); - }); - - $("#user_list_to option").each(function () { - params.userIDs.push(Number.parseInt(this.value, 10)); - }); - - if (params.albumIDs.length === 0) { - loadingBar.show("error", lychee.locale["ERROR_SELECT_ALBUM"]); - return; - } - if (params.userIDs.length === 0) { - loadingBar.show("error", lychee.locale["ERROR_SELECT_USER"]); - return; - } - - api.post("Sharing::add", params, function () { - loadingBar.show("success", lychee.locale["SHARING_SUCCESS"]); - sharing.list(); // reload user list - }); + var params = { + /** @type {string[]} */ + albumIDs: [], + /** @type {number[]} */ + userIDs: [] + }; + $("#albums_list_to option").each(function () { + params.albumIDs.push(this.value); + }); + $("#user_list_to option").each(function () { + params.userIDs.push(Number.parseInt(this.value, 10)); + }); + if (params.albumIDs.length === 0) { + loadingBar.show("error", lychee.locale["ERROR_SELECT_ALBUM"]); + return; + } + if (params.userIDs.length === 0) { + loadingBar.show("error", lychee.locale["ERROR_SELECT_USER"]); + return; + } + api.post("Sharing::add", params, function () { + loadingBar.show("success", lychee.locale["SHARING_SUCCESS"]); + sharing.list(); // reload user list + }); }; /** * @returns {void} */ -sharing.delete = function () { - var params = { - /** @type {number[]} */ - shareIDs: [] - }; - - $('input[name="remove_id"]:checked').each(function () { - params.shareIDs.push(Number.parseInt(this.value, 10)); - }); - - if (params.shareIDs.length === 0) { - loadingBar.show("error", lychee.locale["ERROR_SELECT_SHARING"]); - return; - } - api.post("Sharing::delete", params, function () { - loadingBar.show("success", lychee.locale["SHARING_REMOVED"]); - sharing.list(); // reload user list - }); +sharing["delete"] = function () { + var params = { + /** @type {number[]} */ + shareIDs: [] + }; + $('input[name="remove_id"]:checked').each(function () { + params.shareIDs.push(Number.parseInt(this.value, 10)); + }); + if (params.shareIDs.length === 0) { + loadingBar.show("error", lychee.locale["ERROR_SELECT_SHARING"]); + return; + } + api.post("Sharing::delete", params, function () { + loadingBar.show("success", lychee.locale["SHARING_REMOVED"]); + sharing.list(); // reload user list + }); }; /** @@ -10972,12 +10433,13 @@ sharing.delete = function () { * @returns {void} */ sharing.list = function () { - api.post("Sharing::list", lychee.rights.is_admin ? {} : { ownerID: lychee.user.id }, - /** @param {SharingInfo} data */ - function (data) { - sharing.json = data; - view.sharing.init(); - }); + api.post("Sharing::list", lychee.rights.is_admin ? {} : { + ownerID: lychee.user.id + }, /** @param {SharingInfo} data */ + function (data) { + sharing.json = data; + view.sharing.init(); + }); }; /** @@ -10988,13 +10450,13 @@ sharing.list = function () { * @namespace */ var _sidebar = { - /** @type {jQuery} */ - _dom: $("#lychee_sidebar_container"), - types: { - DEFAULT: 0, - TAGS: 1 - }, - createStructure: {} + /** @type {jQuery} */ + _dom: $("#lychee_sidebar_container"), + types: { + DEFAULT: 0, + TAGS: 1 + }, + createStructure: {} }; /** @@ -11002,8 +10464,8 @@ var _sidebar = { * @returns {jQuery} */ _sidebar.dom = function (selector) { - if (selector == null || selector === "") return _sidebar._dom; - return _sidebar._dom.find(selector); + if (selector == null || selector === "") return _sidebar._dom; + return _sidebar._dom.find(selector); }; /** @@ -11015,47 +10477,37 @@ _sidebar.dom = function (selector) { * @returns {void} */ _sidebar.bind = function () { - var eventName = "click"; - - _sidebar.dom("#edit_title").off(eventName).on(eventName, function () { - if (visible.photo()) _photo3.setTitle([_photo3.getID()]);else if (visible.album()) album.setTitle([album.getID()]); - }); - - _sidebar.dom("#edit_description").off(eventName).on(eventName, function () { - if (visible.photo()) _photo3.setDescription(_photo3.getID());else if (visible.album()) album.setDescription(album.getID()); - }); - - _sidebar.dom("#edit_uploaded").off(eventName).on(eventName, function () { - if (visible.photo()) _photo3.setCreatedAt(_photo3.getID()); - }); - - _sidebar.dom("#edit_showtags").off(eventName).on(eventName, function () { - album.setShowTags(album.getID()); - }); - - _sidebar.dom("#edit_tags").off(eventName).on(eventName, function () { - _photo3.editTags([_photo3.getID()]); - }); - - _sidebar.dom("#tags .tag").off(eventName).on(eventName, function () { - _sidebar.triggerSearch($(this).text()); - }); - - _sidebar.dom("#tags .tag span").off(eventName).on(eventName, function () { - _photo3.deleteTag(_photo3.getID(), $(this).data("index")); - }); - - _sidebar.dom("#edit_license").off(eventName).on(eventName, function () { - if (visible.photo()) _photo3.setLicense(_photo3.getID());else if (visible.album()) album.setLicense(album.getID()); - }); - - _sidebar.dom("#edit_sorting").off(eventName).on(eventName, function () { - album.setSorting(album.getID()); - }); - - _sidebar.dom(".attr_location").off(eventName).on(eventName, function () { - _sidebar.triggerSearch($(this).text()); - }); + var eventName = "click"; + _sidebar.dom("#edit_title").off(eventName).on(eventName, function () { + if (visible.photo()) _photo3.setTitle([_photo3.getID()]);else if (visible.album()) album.setTitle([album.getID()]); + }); + _sidebar.dom("#edit_description").off(eventName).on(eventName, function () { + if (visible.photo()) _photo3.setDescription(_photo3.getID());else if (visible.album()) album.setDescription(album.getID()); + }); + _sidebar.dom("#edit_uploaded").off(eventName).on(eventName, function () { + if (visible.photo()) _photo3.setCreatedAt(_photo3.getID()); + }); + _sidebar.dom("#edit_showtags").off(eventName).on(eventName, function () { + album.setShowTags(album.getID()); + }); + _sidebar.dom("#edit_tags").off(eventName).on(eventName, function () { + _photo3.editTags([_photo3.getID()]); + }); + _sidebar.dom("#tags .tag").off(eventName).on(eventName, function () { + _sidebar.triggerSearch($(this).text()); + }); + _sidebar.dom("#tags .tag span").off(eventName).on(eventName, function () { + _photo3.deleteTag(_photo3.getID(), $(this).data("index")); + }); + _sidebar.dom("#edit_license").off(eventName).on(eventName, function () { + if (visible.photo()) _photo3.setLicense(_photo3.getID());else if (visible.album()) album.setLicense(album.getID()); + }); + _sidebar.dom("#edit_sorting").off(eventName).on(eventName, function () { + album.setSorting(album.getID()); + }); + _sidebar.dom(".attr_location").off(eventName).on(eventName, function () { + _sidebar.triggerSearch($(this).text()); + }); }; /** @@ -11063,23 +10515,22 @@ _sidebar.bind = function () { * @returns {void} */ _sidebar.triggerSearch = function (search_string) { - // If public search is disabled -> do nothing - if (lychee.publicMode && !lychee.public_search) { - // Do not display an error -> just do nothing to not confuse the user - return; - } - - search.json = null; - // We're either logged in or public search is allowed - lychee.goto(SearchAlbumIDPrefix + "/" + encodeURIComponent(search_string)); + // If public search is disabled -> do nothing + if (lychee.publicMode && !lychee.public_search) { + // Do not display an error -> just do nothing to not confuse the user + return; + } + search.json = null; + // We're either logged in or public search is allowed + lychee["goto"](SearchAlbumIDPrefix + "/" + encodeURIComponent(search_string)); }; /** * @returns {boolean} */ _sidebar.keepSidebarVisible = function () { - var v = sessionStorage.getItem("keepSidebarVisible"); - return v !== null ? v === "true" : false; + var v = sessionStorage.getItem("keepSidebarVisible"); + return v !== null ? v === "true" : false; }; /** @@ -11089,11 +10540,11 @@ _sidebar.keepSidebarVisible = function () { * @returns {void} */ _sidebar.toggle = function (is_user_initiated) { - if (visible.sidebar() || visible.sidebarbutton()) { - header.dom(".button--info").toggleClass("active"); - _sidebar.dom().toggleClass("active"); - if (is_user_initiated) sessionStorage.setItem("keepSidebarVisible", visible.sidebar() ? "true" : "false"); - } + if (visible.sidebar() || visible.sidebarbutton()) { + header.dom(".button--info").toggleClass("active"); + _sidebar.dom().toggleClass("active"); + if (is_user_initiated) sessionStorage.setItem("keepSidebarVisible", visible.sidebar() ? "true" : "false"); + } }; /** @@ -11105,9 +10556,8 @@ _sidebar.toggle = function (is_user_initiated) { * @returns {void} */ _sidebar.setSelectable = function () { - var selectable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - if (selectable) _sidebar.dom().removeClass("notSelectable");else _sidebar.dom().addClass("notSelectable"); + var selectable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + if (selectable) _sidebar.dom().removeClass("notSelectable");else _sidebar.dom().addClass("notSelectable"); }; /** @@ -11118,17 +10568,15 @@ _sidebar.setSelectable = function () { * @returns {void} */ _sidebar.changeAttr = function (attr) { - var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; - var dangerouslySetInnerHTML = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; + var dangerouslySetInnerHTML = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + if (!attr) return; + if (!value) value = ""; - if (!attr) return; - if (!value) value = ""; - - // TODO: Don't use our home-brewed `escapeHTML` method; use `jQuery#text` instead - // Escape value - if (!dangerouslySetInnerHTML) value = lychee.escapeHTML(value); - - _sidebar.dom(".attr_" + attr).html(value); + // TODO: Don't use our home-brewed `escapeHTML` method; use `jQuery#text` instead + // Escape value + if (!dangerouslySetInnerHTML) value = lychee.escapeHTML(value); + _sidebar.dom(".attr_" + attr).html(value); }; /** @@ -11136,7 +10584,7 @@ _sidebar.changeAttr = function (attr) { * @returns {void} */ _sidebar.hideAttr = function (attr) { - _sidebar.dom(".attr_" + attr).closest("tr").hide(); + _sidebar.dom(".attr_" + attr).closest("tr").hide(); }; /** @@ -11148,12 +10596,11 @@ _sidebar.hideAttr = function (attr) { * @returns {string} */ _sidebar.secondsToHMS = function (d) { - d = Number(d); - var h = Math.floor(d / 3600); - var m = Math.floor(d % 3600 / 60); - var s = Math.floor(d % 60); - - return (h > 0 ? h.toString() + "h" : "") + (m > 0 ? m.toString() + "m" : "") + (s > 0 || h === 0 && m === 0 ? s.toString() + "s" : ""); + d = Number(d); + var h = Math.floor(d / 3600); + var m = Math.floor(d % 3600 / 60); + var s = Math.floor(d % 60); + return (h > 0 ? h.toString() + "h" : "") + (m > 0 ? m.toString() + "m" : "") + (s > 0 || h === 0 && m === 0 ? s.toString() + "s" : ""); }; /** @@ -11178,172 +10625,246 @@ _sidebar.secondsToHMS = function (d) { * @returns {Section[]} */ _sidebar.createStructure.photo = function (data) { - if (!data) return []; - - var editable = data.rights.can_edit; - - var hasExif = !!data.taken_at || !!data.make || !!data.model || !!data.shutter || !!data.aperture || !!data.focal || !!data.iso; - // Attributes for geo-position are nullable floats. - // The geo-position 0°00'00'', 0°00'00'' at zero altitude is very unlikely - // but valid (it's south of the coast of Ghana in the Atlantic) - // So we must not calculate the sum and compare for zero. - var hasLocation = data.longitude !== null || data.latitude !== null || data.altitude !== null; - var structure = {}; - var isPublic = ""; - var isVideo = data.type && data.type.indexOf("video") > -1; - var license = void 0; - - // Set the license string for a photo - switch (data.license) { - // if the photo doesn't have a license - case "none": - license = ""; - break; - // Localize All Rights Reserved - case "reserved": - license = lychee.locale["PHOTO_RESERVED"]; - break; - // Display anything else that's set - default: - license = data.license; - break; - } - - // Set value for public - switch (data.is_public) { - case 0: - isPublic = lychee.locale["PHOTO_SHR_NO"]; - break; - case 1: - isPublic = lychee.locale["PHOTO_SHR_PHT"]; - break; - case 2: - isPublic = lychee.locale["PHOTO_SHR_ALB"]; - break; - default: - isPublic = "-"; - break; - } - - structure.basics = { - title: lychee.locale["PHOTO_BASICS"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["PHOTO_TITLE"], kind: "title", value: data.title, editable: editable }, { title: lychee.locale["PHOTO_UPLOADED"], kind: "uploaded", value: lychee.locale.printDateTime(data.created_at), editable: editable }, { title: lychee.locale["PHOTO_DESCRIPTION"], kind: "description", value: data.description ? data.description : "", editable: editable }] - }; - - structure.image = { - title: lychee.locale[isVideo ? "PHOTO_VIDEO" : "PHOTO_IMAGE"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["PHOTO_SIZE"], kind: "size", value: lychee.locale.printFilesizeLocalized(data.size_variants.original.filesize) }, { title: lychee.locale["PHOTO_FORMAT"], kind: "type", value: data.type }, { - title: lychee.locale["PHOTO_RESOLUTION"], - kind: "resolution", - value: data.size_variants.original.width + " x " + data.size_variants.original.height - }] - }; - - if (isVideo) { - if (data.size_variants.original.width === 0 || data.size_variants.original.height === 0) { - // Remove the "Resolution" line if we don't have the data. - structure.image.rows.splice(-1, 1); - } - - // We overload the database, storing duration (in full seconds) in - // "aperture" and frame rate (floating point with three digits after - // the decimal point) in "focal". - if (data.aperture) { - structure.image.rows.push({ title: lychee.locale["PHOTO_DURATION"], kind: "duration", value: _sidebar.secondsToHMS(data.aperture) }); - } - if (data.focal) { - structure.image.rows.push({ title: lychee.locale["PHOTO_FPS"], kind: "fps", value: data.focal + " fps" }); - } - } - - // Always create tags section - behaviour for editing - // tags handled when constructing the html code for tags - - // TODO: IDE warns, that `value` is not property and `rows` is missing; the tags should actually be stored in a row for consistency - // TODO: Consider to NOT call `build.tags` here, but simply pass the plain JSON. `build.tags` should be called in `sidebar.render` below - structure.tags = { - title: lychee.locale["PHOTO_TAGS"], - type: _sidebar.types.TAGS, - value: build.tags(data.tags), - editable: editable - }; - - // Only create EXIF section when EXIF data available - if (hasExif) { - structure.exif = { - title: lychee.locale["PHOTO_CAMERA"], - type: _sidebar.types.DEFAULT, - rows: isVideo ? [{ title: lychee.locale["PHOTO_CAPTURED"], kind: "takedate", value: lychee.locale.printDateTime(data.taken_at) }, { title: lychee.locale["PHOTO_MAKE"], kind: "make", value: data.make }, { title: lychee.locale["PHOTO_TYPE"], kind: "model", value: data.model }] : [{ title: lychee.locale["PHOTO_CAPTURED"], kind: "takedate", value: lychee.locale.printDateTime(data.taken_at) }, { title: lychee.locale["PHOTO_MAKE"], kind: "make", value: data.make }, { title: lychee.locale["PHOTO_TYPE"], kind: "model", value: data.model }, { title: lychee.locale["PHOTO_LENS"], kind: "lens", value: data.lens }, { title: lychee.locale["PHOTO_SHUTTER"], kind: "shutter", value: data.shutter }, { title: lychee.locale["PHOTO_APERTURE"], kind: "aperture", value: data.aperture }, { title: lychee.locale["PHOTO_FOCAL"], kind: "focal", value: data.focal }, { title: sprintf(lychee.locale["PHOTO_ISO"], ""), kind: "iso", value: data.iso }] - }; - } else { - structure.exif = {}; - } - - structure.sharing = { - title: lychee.locale["PHOTO_SHARING"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["PHOTO_SHR_PUBLIC"], kind: "public", value: isPublic }] - }; - - structure.license = { - title: lychee.locale["PHOTO_REUSE"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["PHOTO_LICENSE"], kind: "license", value: license, editable: editable }] - }; - - if (hasLocation) { - structure.location = { - title: lychee.locale["PHOTO_LOCATION"], - type: _sidebar.types.DEFAULT, - rows: [{ - title: lychee.locale["PHOTO_LATITUDE"], - kind: "latitude", - value: data.latitude ? DecimalToDegreeMinutesSeconds(data.latitude, true) : "" - }, { - title: lychee.locale["PHOTO_LONGITUDE"], - kind: "longitude", - value: data.longitude ? DecimalToDegreeMinutesSeconds(data.longitude, false) : "" - }, - // No point in displaying sub-mm precision; 10cm is more than enough. - { - title: lychee.locale["PHOTO_ALTITUDE"], - kind: "altitude", - value: data.altitude ? (Math.round(data.altitude * 10) / 10).toString() + "m" : "" - }, { - title: lychee.locale["PHOTO_LOCATION"], - kind: "location", - // Explode location string into an array to keep street, city etc. separate - // TODO: We should consider to keep the components apart on the server-side and send an structured object to the front-end. - value: data.location ? data.location.split(",").map(function (item) { - return item.trim(); - }) : "" - }] - }; - if (data.img_direction !== null) { - // No point in display sub-degree precision. - structure.location.rows.push({ - title: lychee.locale["PHOTO_IMGDIRECTION"], - kind: "imgDirection", - value: Math.round(data.img_direction).toString() + "°" - }); - } - } else { - structure.location = {}; - } - - // Construct all parts of the structure - var structure_ret = [structure.basics, structure.image, structure.tags, structure.exif, structure.location]; - - if (license) { - structure_ret.push(structure.license); - } - if (!lychee.publicMode) { - structure_ret.push(structure.sharing); - } - - return structure_ret; + if (!data) return []; + var editable = data.rights.can_edit; + var hasExif = !!data.taken_at || !!data.make || !!data.model || !!data.shutter || !!data.aperture || !!data.focal || !!data.iso; + // Attributes for geo-position are nullable floats. + // The geo-position 0°00'00'', 0°00'00'' at zero altitude is very unlikely + // but valid (it's south of the coast of Ghana in the Atlantic) + // So we must not calculate the sum and compare for zero. + var hasLocation = data.longitude !== null || data.latitude !== null || data.altitude !== null; + var structure = {}; + var isPublic = ""; + var isVideo = data.type && data.type.indexOf("video") > -1; + var license; + + // Set the license string for a photo + switch (data.license) { + // if the photo doesn't have a license + case "none": + license = ""; + break; + // Localize All Rights Reserved + case "reserved": + license = lychee.locale["PHOTO_RESERVED"]; + break; + // Display anything else that's set + default: + license = data.license; + break; + } + + // Set value for public + switch (data.is_public) { + case 0: + isPublic = lychee.locale["PHOTO_SHR_NO"]; + break; + case 1: + isPublic = lychee.locale["PHOTO_SHR_PHT"]; + break; + case 2: + isPublic = lychee.locale["PHOTO_SHR_ALB"]; + break; + default: + isPublic = "-"; + break; + } + structure.basics = { + title: lychee.locale["PHOTO_BASICS"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["PHOTO_TITLE"], + kind: "title", + value: data.title, + editable: editable + }, { + title: lychee.locale["PHOTO_UPLOADED"], + kind: "uploaded", + value: lychee.locale.printDateTime(data.created_at), + editable: editable + }, { + title: lychee.locale["PHOTO_DESCRIPTION"], + kind: "description", + value: data.description ? data.description : "", + editable: editable + }] + }; + structure.image = { + title: lychee.locale[isVideo ? "PHOTO_VIDEO" : "PHOTO_IMAGE"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["PHOTO_SIZE"], + kind: "size", + value: lychee.locale.printFilesizeLocalized(data.size_variants.original.filesize) + }, { + title: lychee.locale["PHOTO_FORMAT"], + kind: "type", + value: data.type + }, { + title: lychee.locale["PHOTO_RESOLUTION"], + kind: "resolution", + value: data.size_variants.original.width + " x " + data.size_variants.original.height + }] + }; + if (isVideo) { + if (data.size_variants.original.width === 0 || data.size_variants.original.height === 0) { + // Remove the "Resolution" line if we don't have the data. + structure.image.rows.splice(-1, 1); + } + + // We overload the database, storing duration (in full seconds) in + // "aperture" and frame rate (floating point with three digits after + // the decimal point) in "focal". + if (data.aperture) { + structure.image.rows.push({ + title: lychee.locale["PHOTO_DURATION"], + kind: "duration", + value: _sidebar.secondsToHMS(data.aperture) + }); + } + if (data.focal) { + structure.image.rows.push({ + title: lychee.locale["PHOTO_FPS"], + kind: "fps", + value: data.focal + " fps" + }); + } + } + + // Always create tags section - behaviour for editing + // tags handled when constructing the html code for tags + + // TODO: IDE warns, that `value` is not property and `rows` is missing; the tags should actually be stored in a row for consistency + // TODO: Consider to NOT call `build.tags` here, but simply pass the plain JSON. `build.tags` should be called in `sidebar.render` below + structure.tags = { + title: lychee.locale["PHOTO_TAGS"], + type: _sidebar.types.TAGS, + value: build.tags(data.tags), + editable: editable + }; + + // Only create EXIF section when EXIF data available + if (hasExif) { + structure.exif = { + title: lychee.locale["PHOTO_CAMERA"], + type: _sidebar.types.DEFAULT, + rows: isVideo ? [{ + title: lychee.locale["PHOTO_CAPTURED"], + kind: "takedate", + value: lychee.locale.printDateTime(data.taken_at) + }, { + title: lychee.locale["PHOTO_MAKE"], + kind: "make", + value: data.make + }, { + title: lychee.locale["PHOTO_TYPE"], + kind: "model", + value: data.model + }] : [{ + title: lychee.locale["PHOTO_CAPTURED"], + kind: "takedate", + value: lychee.locale.printDateTime(data.taken_at) + }, { + title: lychee.locale["PHOTO_MAKE"], + kind: "make", + value: data.make + }, { + title: lychee.locale["PHOTO_TYPE"], + kind: "model", + value: data.model + }, { + title: lychee.locale["PHOTO_LENS"], + kind: "lens", + value: data.lens + }, { + title: lychee.locale["PHOTO_SHUTTER"], + kind: "shutter", + value: data.shutter + }, { + title: lychee.locale["PHOTO_APERTURE"], + kind: "aperture", + value: data.aperture + }, { + title: lychee.locale["PHOTO_FOCAL"], + kind: "focal", + value: data.focal + }, { + title: sprintf(lychee.locale["PHOTO_ISO"], ""), + kind: "iso", + value: data.iso + }] + }; + } else { + structure.exif = {}; + } + structure.sharing = { + title: lychee.locale["PHOTO_SHARING"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["PHOTO_SHR_PUBLIC"], + kind: "public", + value: isPublic + }] + }; + structure.license = { + title: lychee.locale["PHOTO_REUSE"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["PHOTO_LICENSE"], + kind: "license", + value: license, + editable: editable + }] + }; + if (hasLocation) { + structure.location = { + title: lychee.locale["PHOTO_LOCATION"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["PHOTO_LATITUDE"], + kind: "latitude", + value: data.latitude ? DecimalToDegreeMinutesSeconds(data.latitude, true) : "" + }, { + title: lychee.locale["PHOTO_LONGITUDE"], + kind: "longitude", + value: data.longitude ? DecimalToDegreeMinutesSeconds(data.longitude, false) : "" + }, + // No point in displaying sub-mm precision; 10cm is more than enough. + { + title: lychee.locale["PHOTO_ALTITUDE"], + kind: "altitude", + value: data.altitude ? (Math.round(data.altitude * 10) / 10).toString() + "m" : "" + }, { + title: lychee.locale["PHOTO_LOCATION"], + kind: "location", + // Explode location string into an array to keep street, city etc. separate + // TODO: We should consider to keep the components apart on the server-side and send an structured object to the front-end. + value: data.location ? data.location.split(",").map(function (item) { + return item.trim(); + }) : "" + }] + }; + if (data.img_direction !== null) { + // No point in display sub-degree precision. + structure.location.rows.push({ + title: lychee.locale["PHOTO_IMGDIRECTION"], + kind: "imgDirection", + value: Math.round(data.img_direction).toString() + "°" + }); + } + } else { + structure.location = {}; + } + + // Construct all parts of the structure + var structure_ret = [structure.basics, structure.image, structure.tags, structure.exif, structure.location]; + if (license) { + structure_ret.push(structure.license); + } + if (!lychee.publicMode) { + structure_ret.push(structure.sharing); + } + return structure_ret; }; /** @@ -11351,99 +10872,149 @@ _sidebar.createStructure.photo = function (data) { * @returns {Section[]} */ _sidebar.createStructure.album = function (data) { - if (!data) return []; - - var editable = data.rights.can_edit; - var structure = {}; - var isPublic = !!data.policy && data.policy.is_public ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; - var requiresLink = !!data.policy && data.policy.is_link_required ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; - var isDownloadable = !!data.policy && data.policy.grant_download ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; - var hasPassword = !!data.policy && data.policy.is_password_required ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; - var license = ""; - var sorting = ""; - - // Set license string - switch (data.license) { - case "none": - license = ""; // consistency - break; - case "reserved": - license = lychee.locale["ALBUM_RESERVED"]; - break; - default: - license = data.license; - break; - } - - if (!lychee.publicMode) { - if (!data.sorting) { - sorting = lychee.locale["DEFAULT"]; - } else { - sorting = data.sorting.column + " " + data.sorting.order; - } - } - - structure.basics = { - title: lychee.locale["ALBUM_BASICS"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["ALBUM_TITLE"], kind: "title", value: data.title, editable: editable }, { title: lychee.locale["ALBUM_DESCRIPTION"], kind: "description", value: data.description ? data.description : "", editable: editable }] - }; - - if (album.isTagAlbum()) { - structure.basics.rows.push({ title: lychee.locale["ALBUM_SHOW_TAGS"], kind: "showtags", value: data.show_tags, editable: editable }); - } - - var videoCount = data.photos.reduce(function (count, photo) { - return count + (photo.type.indexOf("video") > -1 ? 1 : 0); - }, 0); - - structure.album = { - title: lychee.locale["ALBUM_ALBUM"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["ALBUM_CREATED"], kind: "created", value: lychee.locale.printDateTime(data.created_at) }] - }; - if (data.albums && data.albums.length > 0) { - structure.album.rows.push({ title: lychee.locale["ALBUM_SUBALBUMS"], kind: "subalbums", value: data.albums.length }); - } - if (data.photos) { - if (data.photos.length - videoCount > 0) { - structure.album.rows.push({ title: lychee.locale["ALBUM_IMAGES"], kind: "images", value: data.photos.length - videoCount }); - } - } - if (videoCount > 0) { - structure.album.rows.push({ title: lychee.locale["ALBUM_VIDEOS"], kind: "videos", value: videoCount }); - } - - if (data.photos && sorting !== "") { - structure.album.rows.push({ title: lychee.locale["ALBUM_ORDERING"], kind: "sorting", value: sorting, editable: editable }); - } - - structure.share = { - title: lychee.locale["ALBUM_SHARING"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["ALBUM_PUBLIC"], kind: "public", value: isPublic }, { title: lychee.locale["ALBUM_HIDDEN"], kind: "hidden", value: requiresLink }, { title: lychee.locale["ALBUM_DOWNLOADABLE"], kind: "downloadable", value: isDownloadable }, { title: lychee.locale["ALBUM_PASSWORD"], kind: "password", value: hasPassword }] - }; - - if (data.owner_name) { - structure.share.rows.push({ title: lychee.locale["ALBUM_OWNER"], kind: "owner", value: data.owner_name }); - } - - structure.license = { - title: lychee.locale["ALBUM_REUSE"], - type: _sidebar.types.DEFAULT, - rows: [{ title: lychee.locale["ALBUM_LICENSE"], kind: "license", value: license, editable: editable }] - }; - - // Construct all parts of the structure - var structure_ret = [structure.basics, structure.album]; - if (license) { - structure_ret.push(structure.license); - } - if (!lychee.publicMode) { - structure_ret.push(structure.share); - } - - return structure_ret; + if (!data) return []; + var editable = data.rights.can_edit; + var structure = {}; + var isPublic = !!data.policy && data.policy.is_public ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; + var requiresLink = !!data.policy && data.policy.is_link_required ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; + var isDownloadable = !!data.policy && data.policy.grant_download ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; + var hasPassword = !!data.policy && data.policy.is_password_required ? lychee.locale["ALBUM_SHR_YES"] : lychee.locale["ALBUM_SHR_NO"]; + var license = ""; + var sorting = ""; + + // Set license string + switch (data.license) { + case "none": + license = ""; // consistency + break; + case "reserved": + license = lychee.locale["ALBUM_RESERVED"]; + break; + default: + license = data.license; + break; + } + if (!lychee.publicMode) { + if (!data.sorting) { + sorting = lychee.locale["DEFAULT"]; + } else { + sorting = data.sorting.column + " " + data.sorting.order; + } + } + structure.basics = { + title: lychee.locale["ALBUM_BASICS"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["ALBUM_TITLE"], + kind: "title", + value: data.title, + editable: editable + }, { + title: lychee.locale["ALBUM_DESCRIPTION"], + kind: "description", + value: data.description ? data.description : "", + editable: editable + }] + }; + if (album.isTagAlbum()) { + structure.basics.rows.push({ + title: lychee.locale["ALBUM_SHOW_TAGS"], + kind: "showtags", + value: data.show_tags, + editable: editable + }); + } + var videoCount = data.photos.reduce(function (count, photo) { + return count + (photo.type.indexOf("video") > -1 ? 1 : 0); + }, 0); + structure.album = { + title: lychee.locale["ALBUM_ALBUM"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["ALBUM_CREATED"], + kind: "created", + value: lychee.locale.printDateTime(data.created_at) + }] + }; + if (data.albums && data.albums.length > 0) { + structure.album.rows.push({ + title: lychee.locale["ALBUM_SUBALBUMS"], + kind: "subalbums", + value: data.albums.length + }); + } + if (data.photos) { + if (data.photos.length - videoCount > 0) { + structure.album.rows.push({ + title: lychee.locale["ALBUM_IMAGES"], + kind: "images", + value: data.photos.length - videoCount + }); + } + } + if (videoCount > 0) { + structure.album.rows.push({ + title: lychee.locale["ALBUM_VIDEOS"], + kind: "videos", + value: videoCount + }); + } + if (data.photos && sorting !== "") { + structure.album.rows.push({ + title: lychee.locale["ALBUM_ORDERING"], + kind: "sorting", + value: sorting, + editable: editable + }); + } + structure.share = { + title: lychee.locale["ALBUM_SHARING"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["ALBUM_PUBLIC"], + kind: "public", + value: isPublic + }, { + title: lychee.locale["ALBUM_HIDDEN"], + kind: "hidden", + value: requiresLink + }, { + title: lychee.locale["ALBUM_DOWNLOADABLE"], + kind: "downloadable", + value: isDownloadable + }, { + title: lychee.locale["ALBUM_PASSWORD"], + kind: "password", + value: hasPassword + }] + }; + if (data.owner_name) { + structure.share.rows.push({ + title: lychee.locale["ALBUM_OWNER"], + kind: "owner", + value: data.owner_name + }); + } + structure.license = { + title: lychee.locale["ALBUM_REUSE"], + type: _sidebar.types.DEFAULT, + rows: [{ + title: lychee.locale["ALBUM_LICENSE"], + kind: "license", + value: license, + editable: editable + }] + }; + + // Construct all parts of the structure + var structure_ret = [structure.basics, structure.album]; + if (license) { + structure_ret.push(structure.license); + } + if (!lychee.publicMode) { + structure_ret.push(structure.share); + } + return structure_ret; }; /** @@ -11451,15 +11022,13 @@ _sidebar.createStructure.album = function (data) { * @returns {boolean} - true if the passed structure contains a "location" section */ _sidebar.has_location = function (structure) { - var _has_location = false; - - structure.forEach(function (section) { - if (section.title === lychee.locale["PHOTO_LOCATION"]) { - _has_location = true; - } - }); - - return _has_location; + var _has_location = false; + structure.forEach(function (section) { + if (section.title === lychee.locale["PHOTO_LOCATION"]) { + _has_location = true; + } + }); + return _has_location; }; /** @@ -11467,94 +11036,86 @@ _sidebar.has_location = function (structure) { * @returns {string} - HTML */ _sidebar.render = function (structure) { - /** - * @param {Section} section - * @returns {string} - */ - var renderDefault = function renderDefault(section) { - var _html = lychee.html(_templateObject37, section.title); - - if (section.title === lychee.locale["PHOTO_LOCATION"]) { - var _has_latitude = section.rows.findIndex(function (row) { - return row.kind === "latitude" && row.value; - }) !== -1; - var _has_longitude = section.rows.findIndex(function (row) { - return row.kind === "longitude" && row.value; - }) !== -1; - var idxLocation = section.rows.findIndex(function (row) { - return row.kind === "location"; - }); - // Do not show location if not enabled - if (idxLocation !== -1 && (lychee.publicMode === true && !lychee.location_show_public || !lychee.location_show)) { - section.rows.splice(idxLocation, 1); - } - // Show map if we have coordinates - if (_has_latitude && _has_longitude && lychee.map_display) { - _html += "\n\t\t\t\t\t\t
\n\t\t\t\t\t\t "; - } - } - - section.rows.forEach(function (row) { - var rawValue = row.value; - - // don't show rows which are empty and cannot be edited - if ((rawValue === "" || rawValue == null) && row.editable !== true) { - return; - } - - /** @type {string} */ - var htmlValue = void 0; - // Wrap span-element around value for easier selecting on change - if (Array.isArray(rawValue)) { - htmlValue = rawValue.reduce( - /** - * @param {string} prev - * @param {string} cur - */ - function (prev, cur) { - // Add separator if needed - if (prev !== "") { - prev += lychee.html(_templateObject38, row.kind); - } - return prev + lychee.html(_templateObject39, row.kind, cur); - }, ""); - } else { - htmlValue = lychee.html(_templateObject40, row.kind, rawValue); - } - - // Add edit-icon to the value when editable - if (row.editable === true) htmlValue += " " + build.editIcon("edit_" + row.kind); - - _html += lychee.html(_templateObject41, row.title, htmlValue); - }); - - _html += "
$", "", "
$", "", "
"; - - return _html; - }; - - /** - * @param {Section} section - * @returns {string} - */ - var renderTags = function renderTags(section) { - // TODO: IDE warns me that the `Section` has no properties `editable` nor `value`; cause of the problem is that the section `tags` is built differently, see above - // Add edit-icon to the value when editable - var htmlEditable = section.editable === true ? build.editIcon("edit_tags") : ""; + /** + * @param {Section} section + * @returns {string} + */ + var renderDefault = function renderDefault(section) { + var _html = lychee.html(_templateObject40 || (_templateObject40 = _taggedTemplateLiteral(["\n\t\t\t\t \n\t\t\t\t \n\t\t\t\t "])), section.title); + if (section.title === lychee.locale["PHOTO_LOCATION"]) { + var _has_latitude = section.rows.findIndex(function (row) { + return row.kind === "latitude" && row.value; + }) !== -1; + var _has_longitude = section.rows.findIndex(function (row) { + return row.kind === "longitude" && row.value; + }) !== -1; + var idxLocation = section.rows.findIndex(function (row) { + return row.kind === "location"; + }); + // Do not show location if not enabled + if (idxLocation !== -1 && (lychee.publicMode === true && !lychee.location_show_public || !lychee.location_show)) { + section.rows.splice(idxLocation, 1); + } + // Show map if we have coordinates + if (_has_latitude && _has_longitude && lychee.map_display) { + _html += "\n\t\t\t\t\t\t
\n\t\t\t\t\t\t "; + } + } + section.rows.forEach(function (row) { + var rawValue = row.value; - // Note: In case of tags `section.value` already contains proper - // HTML (with each tag wrapped into a ``-element), because - // `section.value` is the result of `build.renderTags`. - return lychee.html(_templateObject42, section.title, section.title.toLowerCase(), section.value, htmlEditable); - }; + // don't show rows which are empty and cannot be edited + if ((rawValue === "" || rawValue == null) && row.editable !== true) { + return; + } - var html = ""; + /** @type {string} */ + var htmlValue; + // Wrap span-element around value for easier selecting on change + if (Array.isArray(rawValue)) { + htmlValue = rawValue.reduce( + /** + * @param {string} prev + * @param {string} cur + */ + function (prev, cur) { + // Add separator if needed + if (prev !== "") { + prev += lychee.html(_templateObject41 || (_templateObject41 = _taggedTemplateLiteral([", "])), row.kind); + } + return prev + lychee.html(_templateObject42 || (_templateObject42 = _taggedTemplateLiteral(["$", ""])), row.kind, cur); + }, ""); + } else { + htmlValue = lychee.html(_templateObject43 || (_templateObject43 = _taggedTemplateLiteral(["$", ""])), row.kind, rawValue); + } - structure.forEach(function (section) { - if (section.type === _sidebar.types.DEFAULT) html += renderDefault(section);else if (section.type === _sidebar.types.TAGS) html += renderTags(section); - }); + // Add edit-icon to the value when editable + if (row.editable === true) htmlValue += " " + build.editIcon("edit_" + row.kind); + _html += lychee.html(_templateObject44 || (_templateObject44 = _taggedTemplateLiteral([""])), row.title, htmlValue); + }); + _html += "
$", "", "
"; + return _html; + }; - return html; + /** + * @param {Section} section + * @returns {string} + */ + var renderTags = function renderTags(section) { + // TODO: IDE warns me that the `Section` has no properties `editable` nor `value`; cause of the problem is that the section `tags` is built differently, see above + // Add edit-icon to the value when editable + var htmlEditable = section.editable === true ? build.editIcon("edit_tags") : ""; + + // Note: In case of tags `section.value` already contains proper + // HTML (with each tag wrapped into a ``-element), because + // `section.value` is the result of `build.renderTags`. + return lychee.html(_templateObject45 || (_templateObject45 = _taggedTemplateLiteral(["\n\t\t\t\t \n\t\t\t\t
\n\t\t\t\t\t
", "
\n\t\t\t\t\t ", "\n\t\t\t\t
\n\t\t\t\t "])), section.title, section.title.toLowerCase(), section.value, htmlEditable); + }; + var html = ""; + structure.forEach(function (section) { + if (section.type === _sidebar.types.DEFAULT) html += renderDefault(section);else if (section.type === _sidebar.types.TAGS) html += renderTags(section); + }); + return html; }; /** @@ -11568,41 +11129,40 @@ _sidebar.render = function (structure) { * @returns {string} */ function DecimalToDegreeMinutesSeconds(decimal, type) { - var d = Math.abs(decimal); - var degrees = 0; - var minutes = 0; - var seconds = 0; - var direction = void 0; - - // absolute value of decimal must be smaller than 180; - if (d > 180) { - return ""; - } - - // set direction; north assumed - if (type && decimal < 0) { - direction = "S"; - } else if (!type && decimal < 0) { - direction = "W"; - } else if (!type) { - direction = "E"; - } else { - direction = "N"; - } - - //get degrees - degrees = Math.floor(d); - - //get seconds - seconds = (d - degrees) * 3600; - - //get minutes - minutes = Math.floor(seconds / 60); - - //reset seconds - seconds = Math.floor(seconds - minutes * 60); - - return degrees + "° " + minutes + "' " + seconds + '" ' + direction; + var d = Math.abs(decimal); + var degrees = 0; + var minutes = 0; + var seconds = 0; + var direction; + + // absolute value of decimal must be smaller than 180; + if (d > 180) { + return ""; + } + + // set direction; north assumed + if (type && decimal < 0) { + direction = "S"; + } else if (!type && decimal < 0) { + direction = "W"; + } else if (!type) { + direction = "E"; + } else { + direction = "N"; + } + + //get degrees + degrees = Math.floor(d); + + //get seconds + seconds = (d - degrees) * 3600; + + //get minutes + minutes = Math.floor(seconds / 60); + + //reset seconds + seconds = Math.floor(seconds - minutes * 60); + return degrees + "° " + minutes + "' " + seconds + '" ' + direction; } /** @@ -11610,14 +11170,14 @@ function DecimalToDegreeMinutesSeconds(decimal, type) { */ var swipe = { - /** @type {?jQuery} */ - obj: null, - /** @type {number} */ - offsetX: 0, - /** @type {number} */ - offsetY: 0, - /** @type {boolean} */ - preventNextHeaderToggle: false + /** @type {?jQuery} */ + obj: null, + /** @type {number} */ + offsetX: 0, + /** @type {number} */ + offsetY: 0, + /** @type {boolean} */ + preventNextHeaderToggle: false }; /** @@ -11625,7 +11185,7 @@ var swipe = { * @returns {void} */ swipe.start = function (obj) { - swipe.obj = obj; + swipe.obj = obj; }; /** @@ -11633,24 +11193,22 @@ swipe.start = function (obj) { * @returns {void} */ swipe.move = function (e) { - if (swipe.obj === null) { - return; - } - - if (Math.abs(e.x) > Math.abs(e.y)) { - swipe.offsetX = -1 * e.x; - swipe.offsetY = 0.0; - } else { - swipe.offsetX = 0.0; - swipe.offsetY = +1 * e.y; - } - - var value = "translate(" + swipe.offsetX + "px, " + swipe.offsetY + "px)"; - swipe.obj.css({ - WebkitTransform: value, - MozTransform: value, - transform: value - }); + if (swipe.obj === null) { + return; + } + if (Math.abs(e.x) > Math.abs(e.y)) { + swipe.offsetX = -1 * e.x; + swipe.offsetY = 0.0; + } else { + swipe.offsetX = 0.0; + swipe.offsetY = +1 * e.y; + } + var value = "translate(" + swipe.offsetX + "px, " + swipe.offsetY + "px)"; + swipe.obj.css({ + WebkitTransform: value, + MozTransform: value, + transform: value + }); }; /** @@ -11670,41 +11228,39 @@ swipe.move = function (e) { * @returns {void} */ swipe.stop = function (e, left, right) { - // Only execute once - if (swipe.obj === null) { - return; - } - - if (e.y <= -lychee.swipe_tolerance_y) { - lychee.goto(album.getID()); - } else if (e.y >= lychee.swipe_tolerance_y) { - lychee.goto(album.getID()); - } else if (e.x <= -lychee.swipe_tolerance_x) { - left(true); - - // 'touchend' will be called after 'swipeEnd' - // in case of moving to next image, we want to skip - // the toggling of the header - swipe.preventNextHeaderToggle = true; - } else if (e.x >= lychee.swipe_tolerance_x) { - right(true); - - // 'touchend' will be called after 'swipeEnd' - // in case of moving to next image, we want to skip - // the toggling of the header - swipe.preventNextHeaderToggle = true; - } else { - var value = "translate(0px, 0px)"; - swipe.obj.css({ - WebkitTransform: value, - MozTransform: value, - transform: value - }); - } - - swipe.obj = null; - swipe.offsetX = 0; - swipe.offsetY = 0; + // Only execute once + if (swipe.obj === null) { + return; + } + if (e.y <= -lychee.swipe_tolerance_y) { + lychee["goto"](album.getID()); + } else if (e.y >= lychee.swipe_tolerance_y) { + lychee["goto"](album.getID()); + } else if (e.x <= -lychee.swipe_tolerance_x) { + left(true); + + // 'touchend' will be called after 'swipeEnd' + // in case of moving to next image, we want to skip + // the toggling of the header + swipe.preventNextHeaderToggle = true; + } else if (e.x >= lychee.swipe_tolerance_x) { + right(true); + + // 'touchend' will be called after 'swipeEnd' + // in case of moving to next image, we want to skip + // the toggling of the header + swipe.preventNextHeaderToggle = true; + } else { + var value = "translate(0px, 0px)"; + swipe.obj.css({ + WebkitTransform: value, + MozTransform: value, + transform: value + }); + } + swipe.obj = null; + swipe.offsetX = 0; + swipe.offsetY = 0; }; /** @@ -11712,8 +11268,8 @@ swipe.stop = function (e, left, right) { */ var tabindex = { - offset_for_header: 100, - next_tab_index: 100 + offset_for_header: 100, + next_tab_index: 100 }; /** @@ -11721,49 +11277,48 @@ var tabindex = { * @returns {void} */ tabindex.saveSettings = function (elem) { - if (!lychee.enable_tabindex) return; - - // Todo: Make shorter notation - // Get all elements which have a tabindex - // TODO @Hallenser: What did you intended by the TODO above? It seems as if the jQuery selector is already as short as possible? - var tmp = elem.find("[tabindex]"); - - // iterate over all elements and set tabindex to stored value (i.e. make is not focusable) - tmp.each( - /** - * @param {number} i - the index - * @param {Element} e - the HTML element - * @this {Element} - identical to `e` - */ - function (i, e) { - // TODO: shorter notation - // TODO @Hallenser: What do you intended by the TODO `short notation`? Moreover: Why do we use `this` and `e`? They refer to the identical instance of a HTML element. - var a = $(e).attr("tabindex"); - $(this).data("tabindex-saved", a); - }); + if (!lychee.enable_tabindex) return; + + // Todo: Make shorter notation + // Get all elements which have a tabindex + // TODO @Hallenser: What did you intended by the TODO above? It seems as if the jQuery selector is already as short as possible? + var tmp = elem.find("[tabindex]"); + + // iterate over all elements and set tabindex to stored value (i.e. make is not focusable) + tmp.each( + /** + * @param {number} i - the index + * @param {Element} e - the HTML element + * @this {Element} - identical to `e` + */ + function (i, e) { + // TODO: shorter notation + // TODO @Hallenser: What do you intended by the TODO `short notation`? Moreover: Why do we use `this` and `e`? They refer to the identical instance of a HTML element. + var a = $(e).attr("tabindex"); + $(this).data("tabindex-saved", a); + }); }; - tabindex.restoreSettings = function (elem) { - if (!lychee.enable_tabindex) return; - - // Todo: Make shorter notation - // Get all elements which have a tabindex - // TODO @Hallenser: What did you intended by the TODO above? It seems as if the jQuery selector is already as short as possible? - var tmp = $(elem).find("[tabindex]"); - - // iterate over all elements and set tabindex to stored value (i.e. make is not focussable) - tmp.each( - /** - * @param {number} i - the index - * @param {Element} e - the HTML element - * @this {Element} - identical to `e` - */ - function (i, e) { - // TODO: shorter notation - // TODO @Hallenser: What do you intended by the TODO `short notation`? Moreover: Why do we use `this` and `e`? They refer to the identical instance of a HTML element. - var a = $(e).data("tabindex-saved"); - $(e).attr("tabindex", a); - }); + if (!lychee.enable_tabindex) return; + + // Todo: Make shorter notation + // Get all elements which have a tabindex + // TODO @Hallenser: What did you intended by the TODO above? It seems as if the jQuery selector is already as short as possible? + var tmp = $(elem).find("[tabindex]"); + + // iterate over all elements and set tabindex to stored value (i.e. make is not focussable) + tmp.each( + /** + * @param {number} i - the index + * @param {Element} e - the HTML element + * @this {Element} - identical to `e` + */ + function (i, e) { + // TODO: shorter notation + // TODO @Hallenser: What do you intended by the TODO `short notation`? Moreover: Why do we use `this` and `e`? They refer to the identical instance of a HTML element. + var a = $(e).data("tabindex-saved"); + $(e).attr("tabindex", a); + }); }; /** @@ -11772,32 +11327,31 @@ tabindex.restoreSettings = function (elem) { * @returns {void} */ tabindex.makeUnfocusable = function (elem) { - var saveFocusElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - if (!lychee.enable_tabindex) return; - - // Todo: Make shorter notation - // Get all elements which have a tabindex - var tmp = elem.find("[tabindex]"); - - // iterate over all elements and set tabindex to -1 (i.e. make is not focussable) - tmp.each( - /** - * @param {number} i - the index - * @param {Element} e - the HTML element - */ - function (i, e) { - $(e).attr("tabindex", "-1"); - // Save which element had focus before we make it unfocusable - if (saveFocusElement && $(e).is(":focus")) { - $(e).data("tabindex-focus", true); - // Remove focus - $(e).blur(); - } - }); + var saveFocusElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + if (!lychee.enable_tabindex) return; + + // Todo: Make shorter notation + // Get all elements which have a tabindex + var tmp = elem.find("[tabindex]"); + + // iterate over all elements and set tabindex to -1 (i.e. make is not focussable) + tmp.each( + /** + * @param {number} i - the index + * @param {Element} e - the HTML element + */ + function (i, e) { + $(e).attr("tabindex", "-1"); + // Save which element had focus before we make it unfocusable + if (saveFocusElement && $(e).is(":focus")) { + $(e).data("tabindex-focus", true); + // Remove focus + $(e).blur(); + } + }); - // Disable input fields - elem.find("input").attr("disabled", "disabled"); + // Disable input fields + elem.find("input").attr("disabled", "disabled"); }; /** @@ -11806,141 +11360,134 @@ tabindex.makeUnfocusable = function (elem) { * @returns {void} */ tabindex.makeFocusable = function (elem) { - var restoreFocusElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - if (!lychee.enable_tabindex) return; - - // Todo: Make shorter notation - // Get all elements which have a tabindex - var tmp = elem.find("[data-tabindex]"); - - // iterate over all elements and set tabindex to stored value - tmp.each( - /** - * @param {number} i - * @param {Element} e - */ - function (i, e) { - $(e).attr("tabindex", $(e).data("tabindex")); - // restore focus element if wanted - if (restoreFocusElement) { - if ($(e).data("tabindex-focus") && lychee.active_focus_on_page_load) { - $(e).focus(); - $(e).removeData("tabindex-focus"); - } - } - }); + var restoreFocusElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + if (!lychee.enable_tabindex) return; + + // Todo: Make shorter notation + // Get all elements which have a tabindex + var tmp = elem.find("[data-tabindex]"); + + // iterate over all elements and set tabindex to stored value + tmp.each( + /** + * @param {number} i + * @param {Element} e + */ + function (i, e) { + $(e).attr("tabindex", $(e).data("tabindex")); + // restore focus element if wanted + if (restoreFocusElement) { + if ($(e).data("tabindex-focus") && lychee.active_focus_on_page_load) { + $(e).focus(); + $(e).removeData("tabindex-focus"); + } + } + }); - // Enable input fields - elem.find("input").removeAttr("disabled"); + // Enable input fields + elem.find("input").removeAttr("disabled"); }; /** * @returns {number} */ tabindex.get_next_tab_index = function () { - tabindex.next_tab_index = tabindex.next_tab_index + 1; - - return tabindex.next_tab_index - 1; + tabindex.next_tab_index = tabindex.next_tab_index + 1; + return tabindex.next_tab_index - 1; }; /** * @returns {void} */ tabindex.reset = function () { - tabindex.next_tab_index = tabindex.offset_for_header; + tabindex.next_tab_index = tabindex.offset_for_header; }; - var u2f = { - /** @type {?WebAuthnCredential[]} */ - json: null + /** @type {?WebAuthnCredential[]} */ + json: null }; /** * @returns {boolean} */ u2f.is_available = function () { - if (!window.isSecureContext && window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1") { - basicModal.show({ - body: "

", - readyCB: function readyCB(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["U2F_NOT_SECURE"]; - }, - buttons: { - cancel: { - title: lychee.locale["CLOSE"], - fn: basicModal.close - } - } - }); - - return false; - } - return true; + if (!window.isSecureContext && window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1") { + basicModal.show({ + body: "

", + readyCB: function readyCB(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["U2F_NOT_SECURE"]; + }, + buttons: { + cancel: { + title: lychee.locale["CLOSE"], + fn: basicModal.close + } + } + }); + return false; + } + return true; }; /** * @returns {void} */ u2f.login = function () { - if (!u2f.is_available()) { - return; - } - - new WebAuthn({ - login: "/api/WebAuthn::login", - loginOptions: "/api/WebAuthn::login/options" - }, {}, false).login({ - user_id: 1 // for now it is only available to Admin user via a secret key shortcut. - }).then(function () { - loadingBar.show("success", lychee.locale["U2F_AUTHENTIFICATION_SUCCESS"]); - window.location.reload(); - }).catch(function () { - return loadingBar.show("error", lychee.locale["ERROR_TEXT"]); - }); + if (!u2f.is_available()) { + return; + } + new WebAuthn({ + login: "/api/WebAuthn::login", + loginOptions: "/api/WebAuthn::login/options" + }, {}, false).login({ + user_id: 1 // for now it is only available to Admin user via a secret key shortcut. + }).then(function () { + loadingBar.show("success", lychee.locale["U2F_AUTHENTIFICATION_SUCCESS"]); + window.location.reload(); + })["catch"](function () { + return loadingBar.show("error", lychee.locale["ERROR_TEXT"]); + }); }; /** * @returns {void} */ u2f.register = function () { - if (!u2f.is_available()) { - return; - } - - var webauthn = new WebAuthn({ - register: "/api/WebAuthn::register", - registerOptions: "/api/WebAuthn::register/options" - }, {}, false); - if (WebAuthn.supportsWebAuthn()) { - webauthn.register().then(function () { - loadingBar.show("success", lychee.locale["U2F_REGISTRATION_SUCCESS"]); - u2f.list(); // reload credential list - }).catch(function () { - return loadingBar.show("error", lychee.locale["ERROR_TEXT"]); - }); - } else { - loadingBar.show("error", lychee.locale["U2F_NOT_SUPPORTED"]); - } -}; + if (!u2f.is_available()) { + return; + } + var webauthn = new WebAuthn({ + register: "/api/WebAuthn::register", + registerOptions: "/api/WebAuthn::register/options" + }, {}, false); + if (WebAuthn.supportsWebAuthn()) { + webauthn.register().then(function () { + loadingBar.show("success", lychee.locale["U2F_REGISTRATION_SUCCESS"]); + u2f.list(); // reload credential list + })["catch"](function () { + return loadingBar.show("error", lychee.locale["ERROR_TEXT"]); + }); + } else { + loadingBar.show("error", lychee.locale["U2F_NOT_SUPPORTED"]); + } +}; /** * @param {{id: string}} params - ID of WebAuthn credential */ -u2f.delete = function (params) { - api.post("WebAuthn::delete", params, function () { - loadingBar.show("success", lychee.locale["U2F_CREDENTIALS_DELETED"]); - u2f.list(); // reload credential list - }); +u2f["delete"] = function (params) { + api.post("WebAuthn::delete", params, function () { + loadingBar.show("success", lychee.locale["U2F_CREDENTIALS_DELETED"]); + u2f.list(); // reload credential list + }); }; u2f.list = function () { - api.post("WebAuthn::list", {}, - /** @param {WebAuthnCredential[]} data*/ - function (data) { - u2f.json = data; - view.u2f.init(); - }); + api.post("WebAuthn::list", {}, /** @param {WebAuthnCredential[]} data*/ + function (data) { + u2f.json = data; + view.u2f.init(); + }); }; /** @@ -11956,50 +11503,46 @@ u2f.list = function () { */ var upload = { - SCROLL_OPTIONS: { - inline: "nearest", - block: "nearest", - behavior: "smooth" - }, - - _dom: { - /** - * Holds the ordered list (`
    `) with the individual reports - * of a Progress Report dialog. - * - * @type {HTMLOListElement|null} - */ - reportList: null, - - /** - * Maps a path (as the unique identifier) to a tuple of UI elements - * which visualize the report row for that path. - * - * Note, rows for event reports which are not associated to a - * particular file or directory are not kept in this map, but - * of course they are visualized inside the list of reports. - * - * This map allows fast access to the rows without running - * (inefficient) CSS selector queries and/or relying on a specific - * order (i.e. no need for `nth-child`-selector). - * - * @type {Map|null} - */ - progressRowsByPath: null - } + SCROLL_OPTIONS: { + inline: "nearest", + block: "nearest", + behavior: "smooth" + }, + _dom: { + /** + * Holds the ordered list (`
      `) with the individual reports + * of a Progress Report dialog. + * + * @type {HTMLOListElement|null} + */ + reportList: null, + /** + * Maps a path (as the unique identifier) to a tuple of UI elements + * which visualize the report row for that path. + * + * Note, rows for event reports which are not associated to a + * particular file or directory are not kept in this map, but + * of course they are visualized inside the list of reports. + * + * This map allows fast access to the rows without running + * (inefficient) CSS selector queries and/or relying on a specific + * order (i.e. no need for `nth-child`-selector). + * + * @type {Map|null} + */ + progressRowsByPath: null + } }; - upload.showProgressReportCloseButton = function () { - basicModal.showActionButton(); - basicModal.hideCancelButton(); - // Re-activate cancel button to close modal panel if needed - basicModal.markActionButtonAsIdle(); + basicModal.showActionButton(); + basicModal.hideCancelButton(); + // Re-activate cancel button to close modal panel if needed + basicModal.markActionButtonAsIdle(); }; - upload.closeProgressReportDialog = function () { - basicModal.close(); - upload._dom.reportList = null; - upload._dom.progressRowsByPath = null; + basicModal.close(); + upload._dom.reportList = null; + upload._dom.progressRowsByPath = null; }; /** @@ -12013,16 +11556,19 @@ upload.closeProgressReportDialog = function () { * @returns {ProgressReportDialogRow} */ upload.buildReportRow = function (caption) { - var listEntry = document.createElement("li"); - - var header = listEntry.appendChild(document.createElement("h2")); - header.textContent = caption.length <= 40 ? caption : caption.substring(0, 19) + "…" + caption.substring(caption.length - 20, caption.length); - var status = listEntry.appendChild(document.createElement("p")); - status.classList.add("status"); - var notice = listEntry.appendChild(document.createElement("p")); - notice.classList.add("notice"); - - return { listEntry: listEntry, header: header, status: status, notice: notice }; + var listEntry = document.createElement("li"); + var header = listEntry.appendChild(document.createElement("h2")); + header.textContent = caption.length <= 40 ? caption : caption.substring(0, 19) + "…" + caption.substring(caption.length - 20, caption.length); + var status = listEntry.appendChild(document.createElement("p")); + status.classList.add("status"); + var notice = listEntry.appendChild(document.createElement("p")); + notice.classList.add("notice"); + return { + listEntry: listEntry, + header: header, + status: status, + notice: notice + }; }; /** @@ -12041,13 +11587,13 @@ upload.buildReportRow = function (caption) { * @returns {void} */ upload.buildReportList = function (files) { - upload._dom.reportList = document.createElement("ol"); - upload._dom.progressRowsByPath = new Map(); - for (var idx = 0; idx !== files.length; idx++) { - var row = upload.buildReportRow(files[idx].name); - upload._dom.progressRowsByPath.set(files[idx].name, row); - upload._dom.reportList.appendChild(row.listEntry); - } + upload._dom.reportList = document.createElement("ol"); + upload._dom.progressRowsByPath = new Map(); + for (var idx = 0; idx !== files.length; idx++) { + var row = upload.buildReportRow(files[idx].name); + upload._dom.progressRowsByPath.set(files[idx].name, row); + upload._dom.reportList.appendChild(row.listEntry); + } }; /** @@ -12057,60 +11603,56 @@ upload.buildReportList = function (files) { * @param {?ModalDialogButtonCB} cancel_callback */ upload.showProgressReportDialog = function (title, files, run_callback) { - var cancel_callback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initImportProgressReportDialog = function initImportProgressReportDialog(formElements, dialog) { - // Initially, the normal Action (aka "Close") button is hidden and - // remains hidden as long as an import is running. - // Users must use the Cancel button to interrupt an ongoing import. - // The Action button becomes visible after the import has been - // terminated (either successfully, with error or due to interruption). - basicModal.hideActionButton(); - - var caption = dialog.querySelector("h1"); - caption.textContent = title; - upload.buildReportList(files); - dialog.appendChild(upload._dom.reportList); - - setTimeout(function () { - return run_callback(formElements, dialog); - }, 0); - }; - - basicModal.show({ - body: "

      ", - classList: ["import"], - readyCB: initImportProgressReportDialog, - buttons: { - action: { - title: lychee.locale["CLOSE"], - fn: function fn() { - return upload.closeProgressReportDialog(); - } - }, - cancel: { - title: lychee.locale["CANCEL"], - classList: ["red"], - fn: function fn(resultData) { - // If Action button is visible, the Cancel button behaves - // like the Close button; otherwise the button only calls - // the callback to cancel the import - if (basicModal.isActionButtonVisible()) { - upload.closeProgressReportDialog(); - } else { - if (cancel_callback) { - cancel_callback(resultData); - } - } - } - } - } - }); + var cancel_callback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initImportProgressReportDialog = function initImportProgressReportDialog(formElements, dialog) { + // Initially, the normal Action (aka "Close") button is hidden and + // remains hidden as long as an import is running. + // Users must use the Cancel button to interrupt an ongoing import. + // The Action button becomes visible after the import has been + // terminated (either successfully, with error or due to interruption). + basicModal.hideActionButton(); + var caption = dialog.querySelector("h1"); + caption.textContent = title; + upload.buildReportList(files); + dialog.appendChild(upload._dom.reportList); + setTimeout(function () { + return run_callback(formElements, dialog); + }, 0); + }; + basicModal.show({ + body: "

      ", + classList: ["import"], + readyCB: initImportProgressReportDialog, + buttons: { + action: { + title: lychee.locale["CLOSE"], + fn: function fn() { + return upload.closeProgressReportDialog(); + } + }, + cancel: { + title: lychee.locale["CANCEL"], + classList: ["red"], + fn: function fn(resultData) { + // If Action button is visible, the Cancel button behaves + // like the Close button; otherwise the button only calls + // the callback to cancel the import + if (basicModal.isActionButtonVisible()) { + upload.closeProgressReportDialog(); + } else { + if (cancel_callback) { + cancel_callback(resultData); + } + } + } + } + } + }); }; /** @@ -12119,861 +11661,815 @@ upload.showProgressReportDialog = function (title, files, run_callback) { * @returns {void} */ upload.notify = function (title) { - var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; - - if (text === "") text = lychee.locale["UPLOAD_MANAGE_NEW_PHOTOS"]; - - if (!window.webkitNotifications) return; - - if (window.webkitNotifications.checkPermission() !== 0) window.webkitNotifications.requestPermission(); - - if (window.webkitNotifications.checkPermission() === 0 && title) { - var popup = window.webkitNotifications.createNotification("", title, text); - popup.show(); - } + var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; + if (text === "") text = lychee.locale["UPLOAD_MANAGE_NEW_PHOTOS"]; + if (!window.webkitNotifications) return; + if (window.webkitNotifications.checkPermission() !== 0) window.webkitNotifications.requestPermission(); + if (window.webkitNotifications.checkPermission() === 0 && title) { + var popup = window.webkitNotifications.createNotification("", title, text); + popup.show(); + } }; - upload.start = { - /** - * @param {(FileList|File[])} files - */ - local: function local(files) { - if (files.length <= 0) return; - - var albumID = album.getID(); - var hasErrorOccurred = false; - var hasWarningOccurred = false; - /** - * The number of requests which are "on the fly", i.e. for which a - * response has not yet completely been received. - * - * Note, that Lychee supports a restricted kind of "parallelism" - * which is limited by the configuration option - * `lychee.upload_processing_limit`: - * While always only a single file is uploaded at once, upload of the - * next file already starts after transmission of the previous file - * has been finished, the response to the previous file might still be - * outstanding as the uploaded file is processed at the server-side. - * - * @type {number} - */ - var outstandingResponsesCount = 0; - /** - * The latest (aka highest) index of a file which is being or has - * been uploaded to the server. - * - * @type {number} - */ - var latestFileIdx = 0; - /** - * Semaphore whether a file is currently being uploaded. - * - * This is used as a semaphore to serialize the upload transmissions - * between several instances of the method {@link process}. - * - * @type {boolean} + /** + * @param {(FileList|File[])} files */ - var isUploadRunning = false; - /** - * Semaphore whether a further upload shall be cancelled on the next - * occasion. - * - * @type {boolean} - */ - var shallCancelUpload = false; + local: function local(files) { + if (files.length <= 0) return; + var albumID = album.getID(); + var hasErrorOccurred = false; + var hasWarningOccurred = false; + /** + * The number of requests which are "on the fly", i.e. for which a + * response has not yet completely been received. + * + * Note, that Lychee supports a restricted kind of "parallelism" + * which is limited by the configuration option + * `lychee.upload_processing_limit`: + * While always only a single file is uploaded at once, upload of the + * next file already starts after transmission of the previous file + * has been finished, the response to the previous file might still be + * outstanding as the uploaded file is processed at the server-side. + * + * @type {number} + */ + var outstandingResponsesCount = 0; + /** + * The latest (aka highest) index of a file which is being or has + * been uploaded to the server. + * + * @type {number} + */ + var latestFileIdx = 0; + /** + * Semaphore whether a file is currently being uploaded. + * + * This is used as a semaphore to serialize the upload transmissions + * between several instances of the method {@link process}. + * + * @type {boolean} + */ + var isUploadRunning = false; + /** + * Semaphore whether a further upload shall be cancelled on the next + * occasion. + * + * @type {boolean} + */ + var shallCancelUpload = false; - /** - * This callback is invoked when the last file has been processed. - * - * It closes the modal dialog or shows the close button and - * reloads the album. - */ - var finish = function finish() { - window.onbeforeunload = null; - - $("#upload_files").val(""); - - if (!hasErrorOccurred && !hasWarningOccurred) { - // Success - upload.closeProgressReportDialog(); - upload.notify(lychee.locale["UPLOAD_COMPLETE"]); - } else if (!hasErrorOccurred && hasWarningOccurred) { - // Warning - upload.showProgressReportCloseButton(); - upload.notify(lychee.locale["UPLOAD_COMPLETE"]); - } else { - // Error - upload.showProgressReportCloseButton(); - if (shallCancelUpload) { - var row = upload.buildReportRow(lychee.locale["UPLOAD_GENERAL"]); - row.status.textContent = lychee.locale["UPLOAD_CANCELLED"]; - row.status.classList.add("warning"); - upload._dom.reportList.appendChild(row.listEntry); - } - upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); - } - - album.reload(); - }; - - /** - * Processes the upload and response for a single file. - * - * Note that up to `lychee.upload_processing_limit` "instances" of - * this method can be "alive" simultaneously. - * The parameter `fileIdx` is limited by `latestFileIdx`. - * - * @param {number} fileIdx the index of the file being processed - */ - var process = function process(fileIdx) { - /** - * The upload progress of the file with index `fileIdx` so far. - * - * @type {number} - */ - var uploadProgress = 0; - - /** - * A function to be called when the upload has transmitted more data. - * - * This method updates the upload percentage counter in the dialog. - * - * If the progress equals 100%, i.e. if the upload has been - * completed, this method - * - * - unsets the semaphore for a running upload, - * - scrolls the dialog such that the file with index `fileIdx` - * becomes visible, and - * - changes the status text to "Upload processing". - * - * After the current upload has reached 100%, this method starts a - * new upload, if - * - * - there are more files to be uploaded, - * - no other upload is currently running, and - * - the number of outstanding responses does not exceed the - * processing limit of Lychee. - * - * @param {ProgressEvent} e - * @this XMLHttpRequest - */ - var onUploadProgress = function onUploadProgress(e) { - if (e.lengthComputable !== true) return; - - // Calculate progress - var progress = e.loaded / e.total * 100 | 0; - - // Set progress when progress has changed - if (progress > uploadProgress) { - uploadProgress = progress; - var row = upload._dom.progressRowsByPath.get(files[fileIdx].name); - row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); - row.status.textContent = "" + uploadProgress + "%"; - - if (progress >= 100) { - row.status.textContent = lychee.locale["UPLOAD_PROCESSING"]; - isUploadRunning = false; - - // Start a new upload, if there are still pending - // files - if (!isUploadRunning && !shallCancelUpload && (outstandingResponsesCount < lychee.upload_processing_limit || lychee.upload_processing_limit === 0) && latestFileIdx + 1 < files.length) { - latestFileIdx++; - process(latestFileIdx); - } - } - } - }; - - /** - * A function to be called when a response has been received. - * - * This method updates the status of the affected file. - * - * @this XMLHttpRequest - */ - var onLoaded = function onLoaded() { - var row = upload._dom.progressRowsByPath.get(files[fileIdx].name); - /** @type {?LycheeException} */ - var lycheeException = this.status >= 400 ? this.response : null; - - switch (this.status) { - case 200: - case 201: - case 204: - row.status.textContent = lychee.locale["UPLOAD_FINISHED"]; - row.status.classList.add("success"); - break; - case 409: - row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; - row.status.classList.add("warning"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; - hasWarningOccurred = true; - break; - case 413: - row.status.textContent = lychee.locale["UPLOAD_FAILED"]; - row.status.classList.add("error"); - row.notice.textContent = lychee.locale["UPLOAD_ERROR_POSTSIZE"]; - hasErrorOccurred = true; - api.onError(this, { albumID: albumID }, lycheeException); - break; - default: - row.status.textContent = lychee.locale["UPLOAD_FAILED"]; - row.status.classList.add("error"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; - hasErrorOccurred = true; - api.onError(this, { albumID: albumID }, lycheeException); - break; - } - }; - - /** - * A function to be called when any response has been received - * (after specific success and error callbacks have been executed) - * - * This method starts a new upload, if - * - * - there are more files to be uploaded, - * - no other upload is currently running, and - * - the number of outstanding responses does not exceed the - * processing limit of Lychee. - * - * This method calls {@link finish}, if - * - * - the process shall be cancelled or no more files are left for processing, - * - no upload is running anymore, and - * - no response is outstanding - * - * @this XMLHttpRequest - */ - var onComplete = function onComplete() { - outstandingResponsesCount--; - - if (!isUploadRunning && !shallCancelUpload && (outstandingResponsesCount < lychee.upload_processing_limit || lychee.upload_processing_limit === 0) && latestFileIdx + 1 < files.length) { - latestFileIdx++; - process(latestFileIdx); - } - - if ((shallCancelUpload || latestFileIdx + 1 === files.length) && !isUploadRunning && outstandingResponsesCount === 0) { - finish(); - } - }; - - var formData = new FormData(); - var xhr = new XMLHttpRequest(); - - // For form data, a `null` value is indicated by the empty - // string `""`. Form data falsely converts the value `null` to the - // literal string `"null"`. - formData.append("albumID", albumID ? albumID : ""); - formData.append("file", files[fileIdx]); - - // We must not use the `onload` event of the `XMLHttpRequestUpload` - // object. - // Instead, we only use the `onprogress` event and check within - // the event handler if the progress counter reached 100%. - // The reason is that `upload.onload` is not immediately called - // after the browser has completed the upload (as the name - // suggests), but only after the browser has already received the - // response header. - // For our purposes this is too late, as this way we would never - // show the "processing" status, during which the backend has - // received the upload, but has not yet started to send a response. - xhr.upload.onprogress = onUploadProgress; - xhr.onload = onLoaded; - xhr.onloadend = onComplete; - xhr.responseType = "json"; - xhr.open("POST", "api/Photo::add"); - xhr.setRequestHeader("X-XSRF-TOKEN", csrf.getCSRFCookieValue()); - xhr.setRequestHeader("Accept", "application/json"); - - outstandingResponsesCount++; - isUploadRunning = true; - xhr.send(formData); - }; - - window.onbeforeunload = function () { - return lychee.locale["UPLOAD_IN_PROGRESS"]; - }; - - upload.showProgressReportDialog(lychee.locale["UPLOAD_UPLOADING"], files, function () { - // Upload first file - basicModal.showCancelButton(); - process(0); - }, function () { - shallCancelUpload = true; - hasErrorOccurred = true; - }); - }, - - /** - * @param {string} preselectedUrl - */ - url: function url() { - var preselectedUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; - - var albumID = album.getID(); - - /** @param {{url: string}} data */ - var importFromUrl = function importFromUrl(data) { - var runImport = function runImport() { - var successHandler = function successHandler() { - // Same code as in import.dropbox() - upload.closeProgressReportDialog(); - upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"]); - album.reload(); - }; - - /** - * @param {XMLHttpRequest} jqXHR - * @param {Object} params - * @param {?LycheeException} lycheeException - * @returns {boolean} + /** + * This callback is invoked when the last file has been processed. + * + * It closes the modal dialog or shows the close button and + * reloads the album. */ - var errorHandler = function errorHandler(jqXHR, params, lycheeException) { - // Same code as in import.dropbox() - /** @type {ProgressReportDialogRow} */ - var row = upload._dom.progressRowsByPath.get(data.url); - - switch (jqXHR.status) { - case 409: - row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; - row.status.classList.add("warning"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; - break; - default: - row.status.textContent = lychee.locale["UPLOAD_FAILED"]; - row.status.classList.add("error"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; - break; - } - - // Show close button - basicModal.showActionButton(); - upload.notify(lychee.locale["UPLOAD_IMPORT_WARN_ERR"]); - album.reload(); - return true; - }; - - upload._dom.progressRowsByPath.get(data.url).status.textContent = lychee.locale["UPLOAD_IMPORTING"]; - - // In theory, the backend is prepared to download a list of - // URLs (note that `data.url`) is wrapped into an array. - // However, we need a better dialog which allows input of a - // list of URLs. - // Another problem which already exists even for a single - // URL concerns timeouts. - // Below, we transmit a single HTTP request which must respond - // within about 500ms either with a success or error response. - // Otherwise, JS assumes that the AJAX request just timed out. - // But the server, first need to download the image from the - // specified URL, process it and then generate a HTTP response. - // Probably, it would be much better to use a streamed - // response here like we already have for imports from the - // local server. - // This way, the server could also report its own progress of - // downloading the images. - // TODO: Use a streamed response (see description above). - api.post("Import::url", { - urls: [data.url], - albumID: albumID - }, successHandler, null, errorHandler); - }; - - upload.showProgressReportDialog(lychee.locale["UPLOAD_IMPORTING_URL"], [{ name: data.url }], runImport); - }; - - /** @param {{url: string}} data */ - var processImportFromUrlDialog = function processImportFromUrlDialog(data) { - if (data.url && data.url.trim().length > 3) { - basicModal.close(false, function () { - return importFromUrl(data); - }); - } else basicModal.focusError("url"); - }; - - var importFromUrlDialogBody = "\n\t\t\t

      \n\t\t\t
      \n\t\t\t\t
      \n\t\t\t
      "; - - /** - * @param {ModalDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initImportFromUrlDialog = function initImportFromUrlDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["UPLOAD_IMPORT_INSTR"]; - formElements.url.placeholder = "https://"; - formElements.url.value = preselectedUrl; - }; - - basicModal.show({ - body: importFromUrlDialogBody, - readyCB: initImportFromUrlDialog, - buttons: { - action: { - title: lychee.locale["UPLOAD_IMPORT"], - fn: processImportFromUrlDialog - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); - }, - - server: function server() { - var albumID = album.getID(); - - /** - * @typedef ImportFromServerDialogFormElements - * - * @property {HTMLInputElement} paths - * @property {HTMLInputElement} delete_imported - * @property {HTMLInputElement} import_via_symlink - * @property {HTMLInputElement} skip_duplicates - * @property {HTMLInputElement} resync_metadata - */ + var finish = function finish() { + window.onbeforeunload = null; + $("#upload_files").val(""); + if (!hasErrorOccurred && !hasWarningOccurred) { + // Success + upload.closeProgressReportDialog(); + upload.notify(lychee.locale["UPLOAD_COMPLETE"]); + } else if (!hasErrorOccurred && hasWarningOccurred) { + // Warning + upload.showProgressReportCloseButton(); + upload.notify(lychee.locale["UPLOAD_COMPLETE"]); + } else { + // Error + upload.showProgressReportCloseButton(); + if (shallCancelUpload) { + var row = upload.buildReportRow(lychee.locale["UPLOAD_GENERAL"]); + row.status.textContent = lychee.locale["UPLOAD_CANCELLED"]; + row.status.classList.add("warning"); + upload._dom.reportList.appendChild(row.listEntry); + } + upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); + } + album.reload(); + }; - /** - * @param {ImportFromServerDialogFormElements} formElements - * @param {HTMLDivElement} dialog - * @returns {void} - */ - var initImportFromServerDialog = function initImportFromServerDialog(formElements, dialog) { - dialog.querySelector("p").textContent = lychee.locale["UPLOAD_IMPORT_SERVER_INSTR"]; - formElements.paths.placeholder = lychee.locale["UPLOAD_ABSOLUTE_PATH"]; - formElements.paths.value = lychee.location + "uploads/import/"; - formElements.delete_imported.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_DELETE_ORIGINALS"]; - formElements.delete_imported.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_DELETE_ORIGINALS_EXPL"]; - formElements.import_via_symlink.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_VIA_SYMLINK"]; - formElements.import_via_symlink.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_VIA_SYMLINK_EXPL"]; - formElements.skip_duplicates.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_SKIP_DUPLICATES"]; - formElements.skip_duplicates.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_SKIP_DUPLICATES_EXPL"]; - formElements.resync_metadata.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_RESYNC_METADATA"]; - formElements.resync_metadata.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_RESYNC_METADATA_EXPL"]; - - // Initialize form elements (and dependent form elements) based on - // global configuration settings. - if (lychee.delete_imported) { - formElements.delete_imported.checked = true; - formElements.import_via_symlink.checked = false; - formElements.import_via_symlink.disabled = true; - formElements.import_via_symlink.parentElement.classList.add("disabled"); - } else { - if (lychee.import_via_symlink) { - formElements.delete_imported.checked = false; - formElements.delete_imported.disabled = true; - formElements.delete_imported.parentElement.classList.add("disabled"); - formElements.import_via_symlink.checked = true; - } - } - - if (lychee.skip_duplicates) { - formElements.skip_duplicates.checked = true; - formElements.resync_metadata.checked = lychee.resync_metadata; - } else { - formElements.skip_duplicates.checked = false; - formElements.resync_metadata.checked = false; - formElements.resync_metadata.disabled = true; - formElements.resync_metadata.parentElement.classList.add("disabled"); - } - - // Checkbox action handler to visualize contradictory settings - formElements.delete_imported.addEventListener("change", function () { - if (formElements.delete_imported.checked) { - formElements.import_via_symlink.checked = false; - formElements.import_via_symlink.disabled = true; - formElements.import_via_symlink.parentElement.classList.add("disabled"); - } else { - formElements.import_via_symlink.disabled = false; - formElements.import_via_symlink.parentElement.classList.remove("disabled"); - } - }); - - formElements.import_via_symlink.addEventListener("change", function () { - if (formElements.import_via_symlink.checked) { - formElements.delete_imported.checked = false; - formElements.delete_imported.disabled = true; - formElements.delete_imported.parentElement.classList.add("disabled"); - } else { - formElements.delete_imported.disabled = false; - formElements.delete_imported.parentElement.classList.remove("disabled"); - } - }); - - formElements.skip_duplicates.addEventListener("change", function () { - if (formElements.skip_duplicates.checked) { - formElements.resync_metadata.disabled = false; - formElements.resync_metadata.parentElement.classList.remove("disabled"); - } else { - formElements.resync_metadata.checked = false; - formElements.resync_metadata.disabled = true; - formElements.resync_metadata.parentElement.classList.add("disabled"); - } - }); - }; - - /** - * @typedef ServerImportDialogResult - * @property {string|string[]} paths - * @property {boolean} delete_imported - * @property {boolean} import_via_symlink - * @property {boolean} skip_duplicates - * @property {boolean} resync_metadata - */ - - /** @param {ServerImportDialogResult} data */ - var importFromServer = function importFromServer(data) { - var isUploadCancelled = false; - - var cancelUpload = function cancelUpload() { - if (!isUploadCancelled) { - api.post("Import::serverCancel", {}, function () { - isUploadCancelled = true; - }); - } - }; - - var runUpload = function runUpload() { - basicModal.showCancelButton(); - - // Variables holding state across the invocations of - // processIncremental(). - var lastReadIdx = 0; - var encounteredProblems = false; - - /** - * Worker function invoked from both the response progress - * callback and the completion callback. + /** + * Processes the upload and response for a single file. * - * @param {(ImportProgressReport|ImportEventReport)[]} reports + * Note that up to `lychee.upload_processing_limit` "instances" of + * this method can be "alive" simultaneously. + * The parameter `fileIdx` is limited by `latestFileIdx`. + * + * @param {number} fileIdx the index of the file being processed */ - var processIncremental = function processIncremental(reports) { - reports.slice(lastReadIdx).forEach(function (report) { - if (report.type === "progress") { - // Gets existing row for the current path or creates a new one - /** @type {ProgressReportDialogRow} */ - var row = upload._dom.progressRowsByPath.get(report.path) || upload.buildReportRow(report.path); - upload._dom.progressRowsByPath.set(report.path, row); - // Always unconditionally append the list entry to - // the end of the list even if the `reportList` - // already contains `listEntry`. - // 1. If `listEntry` is not yet an element of - // `reportList` (e.g. this happens for - // new directories), then appending the - // element does the obvious thing - // 2. If `listEntry` is already an element - // of `reportList` (e.g. this happens for - // follow-up reports), then `appendChild` - // *moves* `listEntry` the end of the list. - // We don't need to take care of accidentally - // duplicating the entry, the DOM tree is - // clever enough. - // Moving `listEntry` is an intended effect, - // as we always want the most recent entry at - // the end of the list. - upload._dom.reportList.appendChild(row.listEntry); - row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); - - if (report.progress !== 100) { - row.status.textContent = "" + report.progress + "%"; - } else { - // Final status report for this directory. - row.status.textContent = lychee.locale["UPLOAD_FINISHED"]; - row.status.classList.add("success"); - } - } else if (report.type === "event") { - var _row = void 0; - if (!!report.path) { - // The event report refers to a specific path, - // hence get the existing row for that path - // or create a new one. - /** @type {ProgressReportDialogRow} */ - _row = upload._dom.progressRowsByPath.get(report.path) || upload.buildReportRow(report.path); - upload._dom.progressRowsByPath.set(report.path, _row); - // Always unconditionally append the list entry to - // the end of the list even if the `reportList` - // already contains `listEntry`. - // 1. If `listEntry` is not yet an element of - // `reportList` (e.g. this happens for - // new directories), then appending the - // element does the obvious thing - // 2. If `listEntry` is already an element - // of `reportList` (e.g. this happens for - // follow-up reports), then `appendChild` - // *moves* `listEntry` the end of the list. - // We don't need to take care of accidentally - // duplicating the entry, the DOM tree is - // clever enough. - // Moving `listEntry` is an intended effect, - // as we always want the most recent entry at - // the end of the list. - upload._dom.reportList.appendChild(_row.listEntry); - } else { - // The event report does not refer to a - // specific directory. - _row = upload.buildReportRow(lychee.locale["UPLOAD_GENERAL"]); - upload._dom.reportList.appendChild(_row.listEntry); - } - _row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); - - var severityClass = ""; - var statusText = ""; - var noteText = ""; - - switch (report.severity) { - case "debug": - case "info": - break; - case "notice": - case "warning": - severityClass = "warning"; - break; - case "error": - case "critical": - case "emergency": - severityClass = "error"; - break; - } - - switch (report.subtype) { - case "mem_limit": - statusText = lychee.locale["UPLOAD_WARNING"]; - noteText = lychee.locale["UPLOAD_IMPORT_LOW_MEMORY_EXPL"]; - break; - case "FileOperationException": - case "MediaFileOperationException": - statusText = lychee.locale["UPLOAD_SKIPPED"]; - noteText = lychee.locale["UPLOAD_IMPORT_FAILED"]; - break; - case "MediaFileUnsupportedException": - statusText = lychee.locale["UPLOAD_SKIPPED"]; - noteText = lychee.locale["UPLOAD_IMPORT_UNSUPPORTED"]; - break; - case "InvalidDirectoryException": - statusText = lychee.locale["UPLOAD_FAILED"]; - noteText = lychee.locale["UPLOAD_IMPORT_NOT_A_DIRECTORY"]; - break; - case "ReservedDirectoryException": - statusText = lychee.locale["UPLOAD_FAILED"]; - noteText = lychee.locale["UPLOAD_IMPORT_PATH_RESERVED"]; - break; - case "PhotoSkippedException": - statusText = lychee.locale["UPLOAD_SKIPPED"]; - noteText = lychee.locale["UPLOAD_IMPORT_SKIPPED_DUPLICATE"]; - break; - case "PhotoResyncedException": - statusText = lychee.locale["UPLOAD_UPDATED"]; - noteText = lychee.locale["UPLOAD_IMPORT_RESYNCED_DUPLICATE"]; - break; - case "ImportCancelledException": - statusText = lychee.locale["UPLOAD_CANCELLED"]; - noteText = lychee.locale["UPLOAD_IMPORT_CANCELLED"]; - break; - default: - statusText = lychee.locale["UPLOAD_SKIPPED"]; - noteText = report.message; - break; - } - - _row.notice.textContent = noteText; - _row.status.textContent = statusText; - _row.status.classList.add(severityClass); - - encounteredProblems = true; - } - }); // forEach (resp) - lastReadIdx = reports.length; - }; // processIncremental - - /** - * @param {ImportReport[]} reports + var process = function process(fileIdx) { + /** + * The upload progress of the file with index `fileIdx` so far. + * + * @type {number} + */ + var uploadProgress = 0; + + /** + * A function to be called when the upload has transmitted more data. + * + * This method updates the upload percentage counter in the dialog. + * + * If the progress equals 100%, i.e. if the upload has been + * completed, this method + * + * - unsets the semaphore for a running upload, + * - scrolls the dialog such that the file with index `fileIdx` + * becomes visible, and + * - changes the status text to "Upload processing". + * + * After the current upload has reached 100%, this method starts a + * new upload, if + * + * - there are more files to be uploaded, + * - no other upload is currently running, and + * - the number of outstanding responses does not exceed the + * processing limit of Lychee. + * + * @param {ProgressEvent} e + * @this XMLHttpRequest + */ + var onUploadProgress = function onUploadProgress(e) { + if (e.lengthComputable !== true) return; + + // Calculate progress + var progress = e.loaded / e.total * 100 | 0; + + // Set progress when progress has changed + if (progress > uploadProgress) { + uploadProgress = progress; + var row = upload._dom.progressRowsByPath.get(files[fileIdx].name); + row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); + row.status.textContent = "" + uploadProgress + "%"; + if (progress >= 100) { + row.status.textContent = lychee.locale["UPLOAD_PROCESSING"]; + isUploadRunning = false; + + // Start a new upload, if there are still pending + // files + if (!isUploadRunning && !shallCancelUpload && (outstandingResponsesCount < lychee.upload_processing_limit || lychee.upload_processing_limit === 0) && latestFileIdx + 1 < files.length) { + latestFileIdx++; + process(latestFileIdx); + } + } + } + }; + + /** + * A function to be called when a response has been received. + * + * This method updates the status of the affected file. + * + * @this XMLHttpRequest + */ + var onLoaded = function onLoaded() { + var row = upload._dom.progressRowsByPath.get(files[fileIdx].name); + /** @type {?LycheeException} */ + var lycheeException = this.status >= 400 ? this.response : null; + switch (this.status) { + case 200: + case 201: + case 204: + row.status.textContent = lychee.locale["UPLOAD_FINISHED"]; + row.status.classList.add("success"); + break; + case 409: + row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; + row.status.classList.add("warning"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; + hasWarningOccurred = true; + break; + case 413: + row.status.textContent = lychee.locale["UPLOAD_FAILED"]; + row.status.classList.add("error"); + row.notice.textContent = lychee.locale["UPLOAD_ERROR_POSTSIZE"]; + hasErrorOccurred = true; + api.onError(this, { + albumID: albumID + }, lycheeException); + break; + default: + row.status.textContent = lychee.locale["UPLOAD_FAILED"]; + row.status.classList.add("error"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; + hasErrorOccurred = true; + api.onError(this, { + albumID: albumID + }, lycheeException); + break; + } + }; + + /** + * A function to be called when any response has been received + * (after specific success and error callbacks have been executed) + * + * This method starts a new upload, if + * + * - there are more files to be uploaded, + * - no other upload is currently running, and + * - the number of outstanding responses does not exceed the + * processing limit of Lychee. + * + * This method calls {@link finish}, if + * + * - the process shall be cancelled or no more files are left for processing, + * - no upload is running anymore, and + * - no response is outstanding + * + * @this XMLHttpRequest + */ + var onComplete = function onComplete() { + outstandingResponsesCount--; + if (!isUploadRunning && !shallCancelUpload && (outstandingResponsesCount < lychee.upload_processing_limit || lychee.upload_processing_limit === 0) && latestFileIdx + 1 < files.length) { + latestFileIdx++; + process(latestFileIdx); + } + if ((shallCancelUpload || latestFileIdx + 1 === files.length) && !isUploadRunning && outstandingResponsesCount === 0) { + finish(); + } + }; + var formData = new FormData(); + var xhr = new XMLHttpRequest(); + + // For form data, a `null` value is indicated by the empty + // string `""`. Form data falsely converts the value `null` to the + // literal string `"null"`. + formData.append("albumID", albumID ? albumID : ""); + formData.append("file", files[fileIdx]); + + // We must not use the `onload` event of the `XMLHttpRequestUpload` + // object. + // Instead, we only use the `onprogress` event and check within + // the event handler if the progress counter reached 100%. + // The reason is that `upload.onload` is not immediately called + // after the browser has completed the upload (as the name + // suggests), but only after the browser has already received the + // response header. + // For our purposes this is too late, as this way we would never + // show the "processing" status, during which the backend has + // received the upload, but has not yet started to send a response. + xhr.upload.onprogress = onUploadProgress; + xhr.onload = onLoaded; + xhr.onloadend = onComplete; + xhr.responseType = "json"; + xhr.open("POST", "api/Photo::add"); + xhr.setRequestHeader("X-XSRF-TOKEN", csrf.getCSRFCookieValue()); + xhr.setRequestHeader("Accept", "application/json"); + outstandingResponsesCount++; + isUploadRunning = true; + xhr.send(formData); + }; + window.onbeforeunload = function () { + return lychee.locale["UPLOAD_IN_PROGRESS"]; + }; + upload.showProgressReportDialog(lychee.locale["UPLOAD_UPLOADING"], files, function () { + // Upload first file + basicModal.showCancelButton(); + process(0); + }, function () { + shallCancelUpload = true; + hasErrorOccurred = true; + }); + }, + /** + * @param {string} preselectedUrl + */ + url: function url() { + var preselectedUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; + var albumID = album.getID(); + + /** @param {{url: string}} data */ + var importFromUrl = function importFromUrl(data) { + var runImport = function runImport() { + var successHandler = function successHandler() { + // Same code as in import.dropbox() + upload.closeProgressReportDialog(); + upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"]); + album.reload(); + }; + + /** + * @param {XMLHttpRequest} jqXHR + * @param {Object} params + * @param {?LycheeException} lycheeException + * @returns {boolean} + */ + var errorHandler = function errorHandler(jqXHR, params, lycheeException) { + // Same code as in import.dropbox() + /** @type {ProgressReportDialogRow} */ + var row = upload._dom.progressRowsByPath.get(data.url); + switch (jqXHR.status) { + case 409: + row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; + row.status.classList.add("warning"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; + break; + default: + row.status.textContent = lychee.locale["UPLOAD_FAILED"]; + row.status.classList.add("error"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; + break; + } + + // Show close button + basicModal.showActionButton(); + upload.notify(lychee.locale["UPLOAD_IMPORT_WARN_ERR"]); + album.reload(); + return true; + }; + upload._dom.progressRowsByPath.get(data.url).status.textContent = lychee.locale["UPLOAD_IMPORTING"]; + + // In theory, the backend is prepared to download a list of + // URLs (note that `data.url`) is wrapped into an array. + // However, we need a better dialog which allows input of a + // list of URLs. + // Another problem which already exists even for a single + // URL concerns timeouts. + // Below, we transmit a single HTTP request which must respond + // within about 500ms either with a success or error response. + // Otherwise, JS assumes that the AJAX request just timed out. + // But the server, first need to download the image from the + // specified URL, process it and then generate a HTTP response. + // Probably, it would be much better to use a streamed + // response here like we already have for imports from the + // local server. + // This way, the server could also report its own progress of + // downloading the images. + // TODO: Use a streamed response (see description above). + api.post("Import::url", { + urls: [data.url], + albumID: albumID + }, successHandler, null, errorHandler); + }; + upload.showProgressReportDialog(lychee.locale["UPLOAD_IMPORTING_URL"], [{ + name: data.url + }], runImport); + }; + + /** @param {{url: string}} data */ + var processImportFromUrlDialog = function processImportFromUrlDialog(data) { + if (data.url && data.url.trim().length > 3) { + basicModal.close(false, function () { + return importFromUrl(data); + }); + } else basicModal.focusError("url"); + }; + var importFromUrlDialogBody = "\n\t\t\t

      \n\t\t\t
      \n\t\t\t\t
      \n\t\t\t
      "; + + /** + * @param {ModalDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} */ - var successHandler = function successHandler(reports) { - // reports is already JSON-parsed. - processIncremental(reports); + var initImportFromUrlDialog = function initImportFromUrlDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["UPLOAD_IMPORT_INSTR"]; + formElements.url.placeholder = "https://"; + formElements.url.value = preselectedUrl; + }; + basicModal.show({ + body: importFromUrlDialogBody, + readyCB: initImportFromUrlDialog, + buttons: { + action: { + title: lychee.locale["UPLOAD_IMPORT"], + fn: processImportFromUrlDialog + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); + }, + server: function server() { + var albumID = album.getID(); - upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"], encounteredProblems ? lychee.locale["UPLOAD_COMPLETE_FAILED"] : null); + /** + * @typedef ImportFromServerDialogFormElements + * + * @property {HTMLInputElement} paths + * @property {HTMLInputElement} delete_imported + * @property {HTMLInputElement} import_via_symlink + * @property {HTMLInputElement} skip_duplicates + * @property {HTMLInputElement} resync_metadata + */ - album.reload(); + /** + * @param {ImportFromServerDialogFormElements} formElements + * @param {HTMLDivElement} dialog + * @returns {void} + */ + var initImportFromServerDialog = function initImportFromServerDialog(formElements, dialog) { + dialog.querySelector("p").textContent = lychee.locale["UPLOAD_IMPORT_SERVER_INSTR"]; + formElements.paths.placeholder = lychee.locale["UPLOAD_ABSOLUTE_PATH"]; + formElements.paths.value = lychee.location + "uploads/import/"; + formElements.delete_imported.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_DELETE_ORIGINALS"]; + formElements.delete_imported.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_DELETE_ORIGINALS_EXPL"]; + formElements.import_via_symlink.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_VIA_SYMLINK"]; + formElements.import_via_symlink.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_VIA_SYMLINK_EXPL"]; + formElements.skip_duplicates.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_SKIP_DUPLICATES"]; + formElements.skip_duplicates.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_SKIP_DUPLICATES_EXPL"]; + formElements.resync_metadata.previousElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_RESYNC_METADATA"]; + formElements.resync_metadata.nextElementSibling.textContent = lychee.locale["UPLOAD_IMPORT_RESYNC_METADATA_EXPL"]; + + // Initialize form elements (and dependent form elements) based on + // global configuration settings. + if (lychee.delete_imported) { + formElements.delete_imported.checked = true; + formElements.import_via_symlink.checked = false; + formElements.import_via_symlink.disabled = true; + formElements.import_via_symlink.parentElement.classList.add("disabled"); + } else { + if (lychee.import_via_symlink) { + formElements.delete_imported.checked = false; + formElements.delete_imported.disabled = true; + formElements.delete_imported.parentElement.classList.add("disabled"); + formElements.import_via_symlink.checked = true; + } + } + if (lychee.skip_duplicates) { + formElements.skip_duplicates.checked = true; + formElements.resync_metadata.checked = lychee.resync_metadata; + } else { + formElements.skip_duplicates.checked = false; + formElements.resync_metadata.checked = false; + formElements.resync_metadata.disabled = true; + formElements.resync_metadata.parentElement.classList.add("disabled"); + } - if (encounteredProblems) upload.showProgressReportCloseButton();else upload.closeProgressReportDialog(); - }; + // Checkbox action handler to visualize contradictory settings + formElements.delete_imported.addEventListener("change", function () { + if (formElements.delete_imported.checked) { + formElements.import_via_symlink.checked = false; + formElements.import_via_symlink.disabled = true; + formElements.import_via_symlink.parentElement.classList.add("disabled"); + } else { + formElements.import_via_symlink.disabled = false; + formElements.import_via_symlink.parentElement.classList.remove("disabled"); + } + }); + formElements.import_via_symlink.addEventListener("change", function () { + if (formElements.import_via_symlink.checked) { + formElements.delete_imported.checked = false; + formElements.delete_imported.disabled = true; + formElements.delete_imported.parentElement.classList.add("disabled"); + } else { + formElements.delete_imported.disabled = false; + formElements.delete_imported.parentElement.classList.remove("disabled"); + } + }); + formElements.skip_duplicates.addEventListener("change", function () { + if (formElements.skip_duplicates.checked) { + formElements.resync_metadata.disabled = false; + formElements.resync_metadata.parentElement.classList.remove("disabled"); + } else { + formElements.resync_metadata.checked = false; + formElements.resync_metadata.disabled = true; + formElements.resync_metadata.parentElement.classList.add("disabled"); + } + }); + }; - /** - * @this {XMLHttpRequest} + /** + * @typedef ServerImportDialogResult + * @property {string|string[]} paths + * @property {boolean} delete_imported + * @property {boolean} import_via_symlink + * @property {boolean} skip_duplicates + * @property {boolean} resync_metadata */ - var progressHandler = function progressHandler() { - /** @type {string} */ - var response = this.response; - /** @type {ImportReport[]} */ - var reports = []; - // We received a possibly partial response. - // We must ensure that the last object in the - // array is complete and terminate the array. - while (response.length > 2 && reports.length === 0) { - // Search the last '}', assume that this terminates - // the last JSON object, cut the string and terminate - // the array with `]`. - var fixedResponse = response.substring(0, response.lastIndexOf("}") + 1) + "]"; - try { - // If the assumption is wrong and the last found - // '}' does not terminate the last object, then - // `JSON.parse` will fail and tell us where the - // problem occurred. - reports = JSON.parse(fixedResponse); - } catch (e) { - if (e instanceof SyntaxError) { - var errorPos = e.columnNumber; - var lastBrace = response.lastIndexOf("}"); - var cutResponse = errorPos < lastBrace ? errorPos : lastBrace; - response = response.substring(0, cutResponse); - } else { - // Something else went wrong - upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); - - album.reload(); - - upload.showProgressReportCloseButton(); - - return; - } - } - } - // The rest of the work is the same as for the full - // response. - processIncremental(reports); - }; - - var params = { - albumID: albumID, - paths: data.paths, - delete_imported: data.delete_imported, - import_via_symlink: data.import_via_symlink, - skip_duplicates: data.skip_duplicates, - resync_metadata: data.resync_metadata - }; - - api.post("Import::server", params, successHandler, progressHandler); - }; - - upload.showProgressReportDialog(lychee.locale["UPLOAD_IMPORT_SERVER"], [], runUpload, cancelUpload); - }; // importFromServer - - /** @param {ServerImportDialogResult} data */ - var processImportFromServerDialog = function processImportFromServerDialog(data) { - if (!data.paths.trim()) { - basicModal.focusError("paths"); - return; - } - - // Consolidate `data` before we close the modal dialog - // We split the given path string at unescaped spaces into an - // array or more precisely we create an array whose entries - // match strings with non-space characters or escaped spaces. - // After splitting, the escaped spaces must be replaced by - // proper spaces as escaping of spaces is a GUI-only thing to - // allow input of several paths into a single input field. - data.paths = data.paths.match(/(?:\\ |\S)+/g).map(function (path) { - return path.replace(/\\ /g, " "); - }); - basicModal.close(false, function () { - return importFromServer(data); - }); - }; - - var importFromServerDialogBody = "\n\t\t\t

      \n\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t
      "; - - basicModal.show({ - body: importFromServerDialogBody, - readyCB: initImportFromServerDialog, - buttons: { - action: { - title: lychee.locale["UPLOAD_IMPORT"], - fn: processImportFromServerDialog - }, - cancel: { - title: lychee.locale["CANCEL"], - fn: basicModal.close - } - } - }); - }, - - dropbox: function dropbox() { - var albumID = album.getID(); - - /** - * @param {DropboxFile[]} files - */ - var action = function action(files) { - var runImport = function runImport() { - var successHandler = function successHandler() { - // Same code as in import.url() - upload.closeProgressReportDialog(); - upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"]); - album.reload(); - }; - - /** - * @param {XMLHttpRequest} jqXHR - * @param {Object} params - * @param {?LycheeException} lycheeException - * @returns {boolean} + + /** @param {ServerImportDialogResult} data */ + var importFromServer = function importFromServer(data) { + var isUploadCancelled = false; + var cancelUpload = function cancelUpload() { + if (!isUploadCancelled) { + api.post("Import::serverCancel", {}, function () { + isUploadCancelled = true; + }); + } + }; + var runUpload = function runUpload() { + basicModal.showCancelButton(); + + // Variables holding state across the invocations of + // processIncremental(). + var lastReadIdx = 0; + var encounteredProblems = false; + + /** + * Worker function invoked from both the response progress + * callback and the completion callback. + * + * @param {(ImportProgressReport|ImportEventReport)[]} reports + */ + var processIncremental = function processIncremental(reports) { + reports.slice(lastReadIdx).forEach(function (report) { + if (report.type === "progress") { + // Gets existing row for the current path or creates a new one + /** @type {ProgressReportDialogRow} */ + var row = upload._dom.progressRowsByPath.get(report.path) || upload.buildReportRow(report.path); + upload._dom.progressRowsByPath.set(report.path, row); + // Always unconditionally append the list entry to + // the end of the list even if the `reportList` + // already contains `listEntry`. + // 1. If `listEntry` is not yet an element of + // `reportList` (e.g. this happens for + // new directories), then appending the + // element does the obvious thing + // 2. If `listEntry` is already an element + // of `reportList` (e.g. this happens for + // follow-up reports), then `appendChild` + // *moves* `listEntry` the end of the list. + // We don't need to take care of accidentally + // duplicating the entry, the DOM tree is + // clever enough. + // Moving `listEntry` is an intended effect, + // as we always want the most recent entry at + // the end of the list. + upload._dom.reportList.appendChild(row.listEntry); + row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); + if (report.progress !== 100) { + row.status.textContent = "" + report.progress + "%"; + } else { + // Final status report for this directory. + row.status.textContent = lychee.locale["UPLOAD_FINISHED"]; + row.status.classList.add("success"); + } + } else if (report.type === "event") { + var _row; + if (!!report.path) { + // The event report refers to a specific path, + // hence get the existing row for that path + // or create a new one. + /** @type {ProgressReportDialogRow} */ + _row = upload._dom.progressRowsByPath.get(report.path) || upload.buildReportRow(report.path); + upload._dom.progressRowsByPath.set(report.path, _row); + // Always unconditionally append the list entry to + // the end of the list even if the `reportList` + // already contains `listEntry`. + // 1. If `listEntry` is not yet an element of + // `reportList` (e.g. this happens for + // new directories), then appending the + // element does the obvious thing + // 2. If `listEntry` is already an element + // of `reportList` (e.g. this happens for + // follow-up reports), then `appendChild` + // *moves* `listEntry` the end of the list. + // We don't need to take care of accidentally + // duplicating the entry, the DOM tree is + // clever enough. + // Moving `listEntry` is an intended effect, + // as we always want the most recent entry at + // the end of the list. + upload._dom.reportList.appendChild(_row.listEntry); + } else { + // The event report does not refer to a + // specific directory. + _row = upload.buildReportRow(lychee.locale["UPLOAD_GENERAL"]); + upload._dom.reportList.appendChild(_row.listEntry); + } + _row.listEntry.scrollIntoView(upload.SCROLL_OPTIONS); + var severityClass = ""; + var statusText = ""; + var noteText = ""; + switch (report.severity) { + case "debug": + case "info": + break; + case "notice": + case "warning": + severityClass = "warning"; + break; + case "error": + case "critical": + case "emergency": + severityClass = "error"; + break; + } + switch (report.subtype) { + case "mem_limit": + statusText = lychee.locale["UPLOAD_WARNING"]; + noteText = lychee.locale["UPLOAD_IMPORT_LOW_MEMORY_EXPL"]; + break; + case "FileOperationException": + case "MediaFileOperationException": + statusText = lychee.locale["UPLOAD_SKIPPED"]; + noteText = lychee.locale["UPLOAD_IMPORT_FAILED"]; + break; + case "MediaFileUnsupportedException": + statusText = lychee.locale["UPLOAD_SKIPPED"]; + noteText = lychee.locale["UPLOAD_IMPORT_UNSUPPORTED"]; + break; + case "InvalidDirectoryException": + statusText = lychee.locale["UPLOAD_FAILED"]; + noteText = lychee.locale["UPLOAD_IMPORT_NOT_A_DIRECTORY"]; + break; + case "ReservedDirectoryException": + statusText = lychee.locale["UPLOAD_FAILED"]; + noteText = lychee.locale["UPLOAD_IMPORT_PATH_RESERVED"]; + break; + case "PhotoSkippedException": + statusText = lychee.locale["UPLOAD_SKIPPED"]; + noteText = lychee.locale["UPLOAD_IMPORT_SKIPPED_DUPLICATE"]; + break; + case "PhotoResyncedException": + statusText = lychee.locale["UPLOAD_UPDATED"]; + noteText = lychee.locale["UPLOAD_IMPORT_RESYNCED_DUPLICATE"]; + break; + case "ImportCancelledException": + statusText = lychee.locale["UPLOAD_CANCELLED"]; + noteText = lychee.locale["UPLOAD_IMPORT_CANCELLED"]; + break; + default: + statusText = lychee.locale["UPLOAD_SKIPPED"]; + noteText = report.message; + break; + } + _row.notice.textContent = noteText; + _row.status.textContent = statusText; + _row.status.classList.add(severityClass); + encounteredProblems = true; + } + }); // forEach (resp) + lastReadIdx = reports.length; + }; // processIncremental + + /** + * @param {ImportReport[]} reports + */ + var successHandler = function successHandler(reports) { + // reports is already JSON-parsed. + processIncremental(reports); + upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"], encounteredProblems ? lychee.locale["UPLOAD_COMPLETE_FAILED"] : null); + album.reload(); + if (encounteredProblems) upload.showProgressReportCloseButton();else upload.closeProgressReportDialog(); + }; + + /** + * @this {XMLHttpRequest} + */ + var progressHandler = function progressHandler() { + /** @type {string} */ + var response = this.response; + /** @type {ImportReport[]} */ + var reports = []; + // We received a possibly partial response. + // We must ensure that the last object in the + // array is complete and terminate the array. + while (response.length > 2 && reports.length === 0) { + // Search the last '}', assume that this terminates + // the last JSON object, cut the string and terminate + // the array with `]`. + var fixedResponse = response.substring(0, response.lastIndexOf("}") + 1) + "]"; + try { + // If the assumption is wrong and the last found + // '}' does not terminate the last object, then + // `JSON.parse` will fail and tell us where the + // problem occurred. + reports = JSON.parse(fixedResponse); + } catch (e) { + if (e instanceof SyntaxError) { + var errorPos = e.columnNumber; + var lastBrace = response.lastIndexOf("}"); + var cutResponse = errorPos < lastBrace ? errorPos : lastBrace; + response = response.substring(0, cutResponse); + } else { + // Something else went wrong + upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); + album.reload(); + upload.showProgressReportCloseButton(); + return; + } + } + } + // The rest of the work is the same as for the full + // response. + processIncremental(reports); + }; + var params = { + albumID: albumID, + paths: data.paths, + delete_imported: data.delete_imported, + import_via_symlink: data.import_via_symlink, + skip_duplicates: data.skip_duplicates, + resync_metadata: data.resync_metadata + }; + api.post("Import::server", params, successHandler, progressHandler); + }; + upload.showProgressReportDialog(lychee.locale["UPLOAD_IMPORT_SERVER"], [], runUpload, cancelUpload); + }; // importFromServer + + /** @param {ServerImportDialogResult} data */ + var processImportFromServerDialog = function processImportFromServerDialog(data) { + if (!data.paths.trim()) { + basicModal.focusError("paths"); + return; + } + + // Consolidate `data` before we close the modal dialog + // We split the given path string at unescaped spaces into an + // array or more precisely we create an array whose entries + // match strings with non-space characters or escaped spaces. + // After splitting, the escaped spaces must be replaced by + // proper spaces as escaping of spaces is a GUI-only thing to + // allow input of several paths into a single input field. + data.paths = data.paths.match(/(?:\\ |\S)+/g).map(function (path) { + return path.replace(/\\ /g, " "); + }); + basicModal.close(false, function () { + return importFromServer(data); + }); + }; + var importFromServerDialogBody = "\n\t\t\t

      \n\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t
      \n\t\t\t
      "; + basicModal.show({ + body: importFromServerDialogBody, + readyCB: initImportFromServerDialog, + buttons: { + action: { + title: lychee.locale["UPLOAD_IMPORT"], + fn: processImportFromServerDialog + }, + cancel: { + title: lychee.locale["CANCEL"], + fn: basicModal.close + } + } + }); + }, + dropbox: function dropbox() { + var albumID = album.getID(); + + /** + * @param {DropboxFile[]} files */ - var errorHandler = function errorHandler(jqXHR, params, lycheeException) { - // Same code as in import.url() - // Note, this is complete rubbish: - // Dropbox allows to import several photos at once, but - // here we assume that `files` has only a single entry. - // This seems to be a long-standing, open bug - /** @type {ProgressReportDialogRow} */ - var row = upload._dom.progressRowsByPath.get(files[0].link); - - switch (jqXHR.status) { - case 409: - row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; - row.status.classList.add("warning"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; - break; - default: - row.status.textContent = lychee.locale["UPLOAD_FAILED"]; - row.status.classList.add("error"); - row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; - break; - } - - // Show close button - basicModal.showActionButton(); - upload.notify(lychee.locale["UPLOAD_IMPORT_WARN_ERR"]); - album.reload(); - return true; - }; - - upload._dom.progressRowsByPath.get(files[0].link).status.textContent = lychee.locale["UPLOAD_IMPORTING"]; - - // TODO: Use a streamed response; see long comment in `import.url()` for the reasons - api.post("Import::url", { - urls: files.map(function (file) { - return file.link; - }), - albumID: albumID - }, successHandler, null, errorHandler); - }; - - files.forEach(function (file) { - return file.name = file.link; - }); - upload.showProgressReportDialog("Importing from Dropbox", files, runImport); - }; - - lychee.loadDropbox(function () { - Dropbox.choose({ - linkType: "direct", - multiselect: true, - success: action - }); - }); - } + var action = function action(files) { + var runImport = function runImport() { + var successHandler = function successHandler() { + // Same code as in import.url() + upload.closeProgressReportDialog(); + upload.notify(lychee.locale["UPLOAD_IMPORT_COMPLETE"]); + album.reload(); + }; + + /** + * @param {XMLHttpRequest} jqXHR + * @param {Object} params + * @param {?LycheeException} lycheeException + * @returns {boolean} + */ + var errorHandler = function errorHandler(jqXHR, params, lycheeException) { + // Same code as in import.url() + // Note, this is complete rubbish: + // Dropbox allows to import several photos at once, but + // here we assume that `files` has only a single entry. + // This seems to be a long-standing, open bug + /** @type {ProgressReportDialogRow} */ + var row = upload._dom.progressRowsByPath.get(files[0].link); + switch (jqXHR.status) { + case 409: + row.status.textContent = lychee.locale["UPLOAD_SKIPPED"]; + row.status.classList.add("warning"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; + break; + default: + row.status.textContent = lychee.locale["UPLOAD_FAILED"]; + row.status.classList.add("error"); + row.notice.textContent = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_IMPORT_WARN_ERR"]; + break; + } + + // Show close button + basicModal.showActionButton(); + upload.notify(lychee.locale["UPLOAD_IMPORT_WARN_ERR"]); + album.reload(); + return true; + }; + upload._dom.progressRowsByPath.get(files[0].link).status.textContent = lychee.locale["UPLOAD_IMPORTING"]; + + // TODO: Use a streamed response; see long comment in `import.url()` for the reasons + api.post("Import::url", { + urls: files.map(function (file) { + return file.link; + }), + albumID: albumID + }, successHandler, null, errorHandler); + }; + files.forEach(function (file) { + return file.name = file.link; + }); + upload.showProgressReportDialog("Importing from Dropbox", files, runImport); + }; + lychee.loadDropbox(function () { + Dropbox.choose({ + linkType: "direct", + multiselect: true, + success: action + }); + }); + } }; /** @@ -12982,89 +12478,79 @@ upload.start = { * @returns {void} */ upload.uploadTrack = function (files) { - var albumID = album.getID(); - if (files.length <= 0 || albumID === null) return; - - var runUpload = function runUpload() { - // Only a single track can be uploaded at once, hence the only - // file is at position 0. - var row = upload._dom.progressRowsByPath.get(files[0].name); - - /** - * A function to be called when a response has been received. - * - * It closes the modal dialog or shows the close button and - * reloads the album. - * - * @this XMLHttpRequest - */ - var finish = function finish() { - /** @type {?LycheeException} */ - var lycheeException = this.status >= 400 ? this.response : null; - var errorText = ""; - var statusText = void 0; - var statusClass = void 0; - - $("#upload_track_file").val(""); - - switch (this.status) { - case 200: - case 201: - case 204: - statusText = lychee.locale["UPLOAD_FINISHED"]; - statusClass = "success"; - break; - case 413: - statusText = lychee.locale["UPLOAD_FAILED"]; - errorText = lychee.locale["UPLOAD_ERROR_POSTSIZE"]; - statusClass = "error"; - break; - default: - statusText = lychee.locale["UPLOAD_FAILED"]; - errorText = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; - statusClass = "error"; - break; - } - - row.status.textContent = statusText; - - if (errorText !== "") { - row.notice.textContent = errorText; - - api.onError(this, { albumID: albumID }, lycheeException); - upload.showProgressReportCloseButton(); - upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); - } else { - upload.closeProgressReportDialog(); - upload.notify(lychee.locale["UPLOAD_COMPLETE"]); - } - - album.reload(); - }; // finish - - row.status.textContent = lychee.locale["UPLOAD_UPLOADING"]; - - var formData = new FormData(); - var xhr = new XMLHttpRequest(); - - formData.append("albumID", albumID); - formData.append("file", files[0]); - - xhr.onload = finish; - xhr.responseType = "json"; - xhr.open("POST", "api/Album::setTrack"); - xhr.setRequestHeader("X-XSRF-TOKEN", csrf.getCSRFCookieValue()); - xhr.setRequestHeader("Accept", "application/json"); - - xhr.send(formData); - }; // runUpload - - upload.showProgressReportDialog(lychee.locale["UPLOAD_UPLOADING"], files, runUpload); + var albumID = album.getID(); + if (files.length <= 0 || albumID === null) return; + var runUpload = function runUpload() { + // Only a single track can be uploaded at once, hence the only + // file is at position 0. + var row = upload._dom.progressRowsByPath.get(files[0].name); + + /** + * A function to be called when a response has been received. + * + * It closes the modal dialog or shows the close button and + * reloads the album. + * + * @this XMLHttpRequest + */ + var finish = function finish() { + /** @type {?LycheeException} */ + var lycheeException = this.status >= 400 ? this.response : null; + var errorText = ""; + var statusText; + var statusClass; + $("#upload_track_file").val(""); + switch (this.status) { + case 200: + case 201: + case 204: + statusText = lychee.locale["UPLOAD_FINISHED"]; + statusClass = "success"; + break; + case 413: + statusText = lychee.locale["UPLOAD_FAILED"]; + errorText = lychee.locale["UPLOAD_ERROR_POSTSIZE"]; + statusClass = "error"; + break; + default: + statusText = lychee.locale["UPLOAD_FAILED"]; + errorText = lycheeException ? lycheeException.message : lychee.locale["UPLOAD_ERROR_UNKNOWN"]; + statusClass = "error"; + break; + } + row.status.textContent = statusText; + if (errorText !== "") { + row.notice.textContent = errorText; + api.onError(this, { + albumID: albumID + }, lycheeException); + upload.showProgressReportCloseButton(); + upload.notify(lychee.locale["UPLOAD_COMPLETE"], lychee.locale["UPLOAD_COMPLETE_FAILED"]); + } else { + upload.closeProgressReportDialog(); + upload.notify(lychee.locale["UPLOAD_COMPLETE"]); + } + album.reload(); + }; // finish + + row.status.textContent = lychee.locale["UPLOAD_UPLOADING"]; + var formData = new FormData(); + var xhr = new XMLHttpRequest(); + formData.append("albumID", albumID); + formData.append("file", files[0]); + xhr.onload = finish; + xhr.responseType = "json"; + xhr.open("POST", "api/Album::setTrack"); + xhr.setRequestHeader("X-XSRF-TOKEN", csrf.getCSRFCookieValue()); + xhr.setRequestHeader("Accept", "application/json"); + xhr.send(formData); + }; // runUpload + + upload.showProgressReportDialog(lychee.locale["UPLOAD_UPLOADING"], files, runUpload); }; - var users = { - /** @type {?UserDTO[]} */ - json: null + /** @type {?UserDTO[]} */ + json: null }; /** @@ -13077,23 +12563,22 @@ var users = { * @returns {void} */ users.update = function (params) { - if (params.username.length < 1) { - loadingBar.show("error", lychee.locale["ERROR_EMPTY_USERNAME"]); - return; - } - - // If the password is empty, then the password shall not be changed. - // In this case, the password must not be an attribute of the object at - // all. - // An existing, but empty password, would indicate to clear the password. - if (params.password.length === 0) { - delete params.password; - } + if (params.username.length < 1) { + loadingBar.show("error", lychee.locale["ERROR_EMPTY_USERNAME"]); + return; + } - api.post("Users::save", params, function () { - loadingBar.show("success", lychee.locale["USER_UPDATED"]); - users.list(); // reload user list - }); + // If the password is empty, then the password shall not be changed. + // In this case, the password must not be an attribute of the object at + // all. + // An existing, but empty password, would indicate to clear the password. + if (params.password.length === 0) { + delete params.password; + } + api.post("Users::save", params, function () { + loadingBar.show("success", lychee.locale["USER_UPDATED"]); + users.list(); // reload user list + }); }; /** @@ -13106,19 +12591,18 @@ users.update = function (params) { * @returns {void} */ users.create = function (params) { - if (params.username.length < 1) { - loadingBar.show("error", lychee.locale["ERROR_EMPTY_USERNAME"]); - return; - } - if (params.password.length < 1) { - loadingBar.show("error", lychee.locale["ERROR_EMPTY_PASSWORD"]); - return; - } - - api.post("Users::create", params, function () { - loadingBar.show("success", lychee.locale["USER_CREATED"]); - users.list(); // reload user list - }); + if (params.username.length < 1) { + loadingBar.show("error", lychee.locale["ERROR_EMPTY_USERNAME"]); + return; + } + if (params.password.length < 1) { + loadingBar.show("error", lychee.locale["ERROR_EMPTY_PASSWORD"]); + return; + } + api.post("Users::create", params, function () { + loadingBar.show("success", lychee.locale["USER_CREATED"]); + users.list(); // reload user list + }); }; /** @@ -13130,23 +12614,22 @@ users.create = function (params) { * @param {{id: number}} params * @returns {boolean} */ -users.delete = function (params) { - api.post("Users::delete", params, function () { - loadingBar.show("success", lychee.locale["USER_DELETED"]); - users.list(); // reload user list - }); +users["delete"] = function (params) { + api.post("Users::delete", params, function () { + loadingBar.show("success", lychee.locale["USER_DELETED"]); + users.list(); // reload user list + }); }; /** * @returns {void} */ users.list = function () { - api.post("Users::list", {}, - /** @param {UserDTO[]} data */ - function (data) { - users.json = data; - view.users.init(); - }); + api.post("Users::list", {}, /** @param {UserDTO[]} data */ + function (data) { + users.json = data; + view.users.init(); + }); }; /** @@ -13154,1776 +12637,1549 @@ users.list = function () { */ var view = { - /** @type {ResizeObserver} */ - resizeObserver: function () { - /** - * @type {HTMLDivElement} - */ - var viewContainer = document.getElementById("lychee_view_container"); - - var resizeHandler = function () { - var viewContainerWidth = 0; - - return function () { - // Avoid infinite loops - // The layout method `view.album.content.justify()` below will - // change the height of the container as the height depends on - // the amount of content. - // Hence, `view.album.content.justify()` re-triggers the - // event handler. - // However, we are only interested into changes of the width, - // which is independent of content but solely depends on - // window size. - // We bail out early if the width has not changed since last - // time. - if (viewContainer.clientWidth === viewContainerWidth) return; - viewContainerWidth = viewContainer.clientWidth; - view.album.content.justify(); - if (_photo3.isLivePhotoInitialized()) { - _photo3.livePhotosObject.updateSize(); - } - }; - }(); - - var observer = new ResizeObserver(resizeHandler); - observer.observe(viewContainer); - - return observer; - }() + /** @type {ResizeObserver} */ + resizeObserver: function () { + /** + * @type {HTMLDivElement} + */ + var viewContainer = document.getElementById("lychee_view_container"); + var resizeHandler = function () { + var viewContainerWidth = 0; + return function () { + // Avoid infinite loops + // The layout method `view.album.content.justify()` below will + // change the height of the container as the height depends on + // the amount of content. + // Hence, `view.album.content.justify()` re-triggers the + // event handler. + // However, we are only interested into changes of the width, + // which is independent of content but solely depends on + // window size. + // We bail out early if the width has not changed since last + // time. + if (viewContainer.clientWidth === viewContainerWidth) return; + viewContainerWidth = viewContainer.clientWidth; + view.album.content.justify(); + if (_photo3.isLivePhotoInitialized()) { + _photo3.livePhotosObject.updateSize(); + } + }; + }(); + var observer = new ResizeObserver(resizeHandler); + observer.observe(viewContainer); + return observer; + }() }; - view.albums = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - view.albums.title(); - view.albums.content.init(); - }, - - /** @returns {void} */ - title: function title() { - if (lychee.landing_page_enable) { - lychee.setMetaData(); - } else { - lychee.setMetaData(lychee.locale["ALBUMS"]); - } - }, - - content: { - /** @returns {void} */ - init: function init() { - var smartData = ""; - var tagAlbumsData = ""; - var albumsData = ""; - var sharedData = ""; - - // Smart Albums - if (lychee.publicMode === false && (albums.json.smart_albums.public || albums.json.smart_albums.recent || albums.json.smart_albums.starred || albums.json.smart_albums.unsorted || albums.json.smart_albums.on_this_day || albums.json.tag_albums.length > 0)) { - smartData = build.divider(lychee.locale["SMART_ALBUMS"]); - } - if (albums.json.smart_albums.unsorted) { - albums.parse(albums.json.smart_albums.unsorted); - smartData += build.album(albums.json.smart_albums.unsorted, !lychee.rights.root_album.can_edit); - } - if (albums.json.smart_albums.public) { - albums.parse(albums.json.smart_albums.public); - smartData += build.album(albums.json.smart_albums.public, !lychee.rights.root_album.can_edit); - } - if (albums.json.smart_albums.starred) { - albums.parse(albums.json.smart_albums.starred); - smartData += build.album(albums.json.smart_albums.starred, !lychee.rights.root_album.can_edit); - } - if (albums.json.smart_albums.recent) { - albums.parse(albums.json.smart_albums.recent); - smartData += build.album(albums.json.smart_albums.recent, !lychee.rights.root_album.can_edit); - } - if (albums.json.smart_albums.on_this_day) { - albums.parse(albums.json.smart_albums.on_this_day); - smartData += build.album(albums.json.smart_albums.on_this_day); - } - - // Tag albums - tagAlbumsData += albums.json.tag_albums.reduce(function (html, tagAlbum) { - albums.parse(tagAlbum); - return html + build.album(tagAlbum, !lychee.rights.root_album.can_edit); - }, ""); - - // Albums - if (lychee.publicMode === false && albums.json.albums.length > 0) albumsData = build.divider(lychee.locale["ALBUMS"]); - albumsData += albums.json.albums.reduce(function (html, album) { - albums.parse(album); - return html + build.album(album, !lychee.rights.root_album.can_edit); - }, ""); - - var current_owner = ""; - // Shared - sharedData += albums.json.shared_albums.reduce(function (html, album) { - albums.parse(album); - if (current_owner !== album.owner_name && lychee.publicMode === false) { - html += build.divider(album.owner_name); - current_owner = album.owner_name; - } - return html + build.album(album, !lychee.rights.settings.can_edit); - }, ""); - - if (smartData === "" && tagAlbumsData === "" && albumsData === "" && sharedData === "") { - lychee.content.html(""); - lychee.content.append(build.no_content("eye")); - } else { - lychee.content.html(smartData + tagAlbumsData + albumsData + sharedData); - } - - album.apply_nsfw_filter(); - view.album.content.restoreScroll(); - }, - - /** - * @param {string} albumID - * @returns {void} - */ - title: function title(albumID) { - var album = albums.getByID(albumID); - var title = album.title ? album.title : lychee.locale["UNTITLED"]; - - $('.album[data-id="' + albumID + '"] .overlay h1').text(title).attr("title", title); - }, - - /** - * @param {string} albumID - * @returns {void} - */ - delete: function _delete(albumID) { - $('.album[data-id="' + albumID + '"]').css("opacity", 0).animate({ - width: 0, - marginLeft: 0 - }, 300, function () { - $(this).remove(); - if (albums.json.albums.length <= 0) lychee.content.find(".divider:last-child").remove(); - }); - } - } -}; - -view.album = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - view.album.sidebar(); - view.album.title(); - view.album.public(); - view.album.nsfw(); - view.album.nsfw_warning.init(); - view.album.content.init(); - - // TODO: `init` is not a property of the Album JSON; this is a property of the view. Consider to move it to `view.album.isInitialized` - album.json.init = true; - }, - - /** @returns {void} */ - title: function title() { - if ((visible.album() || !album.json.init) && !visible.photo()) { - switch (album.getID()) { - case SmartAlbumID.STARRED: - lychee.setMetaData(lychee.locale["STARRED"]); - break; - case SmartAlbumID.PUBLIC: - lychee.setMetaData(lychee.locale["PUBLIC"]); - break; - case SmartAlbumID.RECENT: - lychee.setMetaData(lychee.locale["RECENT"]); - break; - case SmartAlbumID.UNSORTED: - lychee.setMetaData(lychee.locale["UNSORTED"]); - break; - case SmartAlbumID.ON_THIS_DAY: - lychee.setMetaData(lychee.locale["ON_THIS_DAY"]); - break; - default: - if (album.json.init) _sidebar.changeAttr("title", album.json.title); - lychee.setMetaData(album.json.title, true, album.json.description); - break; - } - } - }, - - nsfw_warning: { - /** @returns {void} */ - init: function init() { - if (!lychee.nsfw_warning) { - $("#sensitive_warning").removeClass("active"); - return; - } - - if (album.json.policy.is_nsfw && !lychee.nsfw_unlocked_albums.includes(album.json.id)) { - $("#sensitive_warning").addClass("active"); - } else { - $("#sensitive_warning").removeClass("active"); - } - }, - - /** @returns {void} */ - next: function next() { - lychee.nsfw_unlocked_albums.push(album.json.id); - $("#sensitive_warning").removeClass("active"); - } - }, - - content: { - /** @returns {void} */ - init: function init() { - var photosData = ""; - var albumsData = ""; - var html = ""; - - if (album.json.albums) { - album.json.albums.forEach(function (_album) { - albums.parse(_album); - albumsData += build.album(_album, !album.json.rights.can_edit); - }); - } - if (album.json.photos) { - // Build photos - album.json.photos.forEach(function (_photo) { - photosData += build.photo(_photo, !album.json.rights.can_edit); - }); - } - - if (photosData !== "") { - if (lychee.layout === 1) { - // The CSS class 'laying-out' prevents the DIV from being - // rendered. - // The CSS class will eventually be removed by the - // layout routine `view.album.content.justify` after all - // child nodes have been arranged. - // ---- Update 2022-10-20, temporary fix ---- - // However, the reported width of hidden elements is zero. - // Hence, using the CSS class `laying-out` currently - // prevent `view.album.content.justify` from calculating - // the correct width of the container. - // TODO: Re-add the CSS class `laying-out` here after https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged. - photosData = '
      ' + photosData + "
      "; - } else if (lychee.layout === 2) { - photosData = '
      ' + photosData + "
      "; - } - } - - if (albumsData !== "" && photosData !== "") { - html = build.divider(lychee.locale["ALBUMS"]); - } - html += albumsData; - if (albumsData !== "" && photosData !== "") { - html += build.divider(lychee.locale["PHOTOS"]); - } - html += photosData; - - // Add photos to view - lychee.content.html(html); - album.apply_nsfw_filter(); - - setTimeout(function () { - view.album.content.justify(); - }, 0); - }, - - /** @returns {void} */ - restoreScroll: function restoreScroll() { - // Restore scroll position - var urls = JSON.parse(localStorage.getItem("scroll")); - var urlWindow = window.location.href; - $("#lychee_view_container").scrollTop(urls != null && urls[urlWindow] ? urls[urlWindow] : 0); - }, - - /** - * @param {string} photoID - * @returns {void} - */ - title: function title(photoID) { - var photo = album.getByID(photoID); - var title = photo.title ? photo.title : lychee.locale["UNTITLED"]; - - $('.photo[data-id="' + photoID + '"] .overlay h1').text(title).attr("title", title); - }, - - /** - * @param {string} albumID - * @returns {void} - */ - titleSub: function titleSub(albumID) { - var subalbum = album.getSubByID(albumID); - var title = subalbum.title ? subalbum.title : lychee.locale["UNTITLED"]; - - $('.album[data-id="' + albumID + '"] .overlay h1').text(title).attr("title", title); - }, - - /** - * @param {string} photoID - * @returns {void} - */ - star: function star(photoID) { - var $badge = $('.photo[data-id="' + photoID + '"] .icn-star'); - - if (album.getByID(photoID).is_starred) $badge.addClass("badge--star");else $badge.removeClass("badge--star"); - }, - - /** - * @param {string} photoID - * @returns {void} - */ - public: function _public(photoID) { - var $badge = $('.photo[data-id="' + photoID + '"] .icn-share'); - - if (album.getByID(photoID).is_public === 1) $badge.addClass("badge--visible badge--hidden");else $badge.removeClass("badge--visible badge--hidden"); - }, + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + view.albums.title(); + view.albums.content.init(); + }, + /** @returns {void} */ + title: function title() { + if (lychee.landing_page_enable) { + lychee.setMetaData(); + } else { + lychee.setMetaData(lychee.locale["ALBUMS"]); + } + }, + content: { + /** @returns {void} */ + init: function init() { + var smartData = ""; + var tagAlbumsData = ""; + var albumsData = ""; + var sharedData = ""; + + // Smart Albums + if (lychee.publicMode === false && (albums.json.smart_albums["public"] || albums.json.smart_albums.recent || albums.json.smart_albums.starred || albums.json.smart_albums.unsorted || albums.json.smart_albums.on_this_day || albums.json.tag_albums.length > 0)) { + smartData = build.divider(lychee.locale["SMART_ALBUMS"]); + } + if (albums.json.smart_albums.unsorted) { + albums.parse(albums.json.smart_albums.unsorted); + smartData += build.album(albums.json.smart_albums.unsorted, !lychee.rights.root_album.can_edit); + } + if (albums.json.smart_albums["public"]) { + albums.parse(albums.json.smart_albums["public"]); + smartData += build.album(albums.json.smart_albums["public"], !lychee.rights.root_album.can_edit); + } + if (albums.json.smart_albums.starred) { + albums.parse(albums.json.smart_albums.starred); + smartData += build.album(albums.json.smart_albums.starred, !lychee.rights.root_album.can_edit); + } + if (albums.json.smart_albums.recent) { + albums.parse(albums.json.smart_albums.recent); + smartData += build.album(albums.json.smart_albums.recent, !lychee.rights.root_album.can_edit); + } + if (albums.json.smart_albums.on_this_day) { + albums.parse(albums.json.smart_albums.on_this_day); + smartData += build.album(albums.json.smart_albums.on_this_day); + } - /** - * @param {string} photoID - * @returns {void} - */ - cover: function cover(photoID) { - $(".album .icn-cover").removeClass("badge--cover"); - $(".photo .icn-cover").removeClass("badge--cover"); - - if (album.json.cover_id === photoID) { - var badge = $('.photo[data-id="' + photoID + '"] .icn-cover'); - if (badge.length > 0) { - badge.addClass("badge--cover"); - } else { - $.each(album.json.albums, function () { - if (this.thumb.id === photoID) { - $('.album[data-id="' + this.id + '"] .icn-cover').addClass("badge--cover"); - return false; - } - }); - } - } - }, - - /** - * @param {Photo} data - * @returns {void} - */ - updatePhoto: function updatePhoto(data) { - var src = void 0, - srcset = ""; - - // This mimicks the structure of build.photo - if (lychee.layout === 0) { - src = data.size_variants.thumb.url; - if (data.size_variants.thumb2x !== null) { - srcset = data.size_variants.thumb2x.url + " 2x"; - } - } else { - if (data.size_variants.small !== null) { - src = data.size_variants.small.url; - if (data.size_variants.small2x !== null) { - srcset = data.size_variants.small.url + " " + data.size_variants.small.width + "w, " + data.size_variants.small2x.url + " " + data.size_variants.small2x.width + "w"; - } - } else if (data.size_variants.medium !== null) { - src = data.size_variants.medium.url; - if (data.size_variants.medium2x !== null) { - srcset = data.size_variants.medium.url + " " + data.size_variants.medium.width + "w, " + data.size_variants.medium2x.url + " " + data.size_variants.medium2x.width + "w"; - } - } else if (!data.type || data.type.indexOf("video") !== 0) { - src = data.size_variants.original.url; - } else { - src = data.size_variants.thumb.url; - if (data.size_variants.thumb2x !== null) { - srcset = data.size_variants.thumb.url + " " + data.size_variants.thumb.width + "w, " + data.size_variants.thumb2x.url + " " + data.size_variants.thumb2x.width + "w"; - } - } - } - - $('.photo[data-id="' + data.id + '"] > span.thumbimg > img').attr("data-src", src).attr("data-srcset", srcset).addClass("lazyload"); - - setTimeout(function () { - return view.album.content.justify(); - }, 0); - }, - - /** - * @param {string} photoID - * @param {boolean} [justify=false] - * @returns {void} - */ - delete: function _delete(photoID) { - var justify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - - $('.photo[data-id="' + photoID + '"]').css("opacity", 0).animate({ - width: 0, - marginLeft: 0 - }, 300, function () { - $(this).remove(); - // Only when search is not active - if (album.json) { - if (visible.sidebar()) { - var videoCount = 0; - $.each(album.json.photos, function () { - if (this.type && this.type.indexOf("video") > -1) { - videoCount++; - } - }); - if (album.json.photos.length - videoCount > 0) { - _sidebar.changeAttr("images", (album.json.photos.length - videoCount).toString()); - } else { - _sidebar.hideAttr("images"); - } - if (videoCount > 0) { - _sidebar.changeAttr("videos", videoCount.toString()); - } else { - _sidebar.hideAttr("videos"); - } - } - if (album.json.photos.length <= 0) { - lychee.content.find(".divider").remove(); - } - if (justify) { - setTimeout(function () { - return view.album.content.justify(); - }, 0); - } - } - }); - }, - - /** - * @param {string} albumID - * @returns {void} - */ - deleteSub: function deleteSub(albumID) { - $('.album[data-id="' + albumID + '"]').css("opacity", 0).animate({ - width: 0, - marginLeft: 0 - }, 300, function () { - $(this).remove(); - if (album.json) { - if (album.json.albums.length <= 0) { - lychee.content.find(".divider").remove(); - } - if (visible.sidebar()) { - if (album.json.albums.length > 0) { - _sidebar.changeAttr("subalbums", album.json.albums.length.toString()); - } else { - _sidebar.hideAttr("subalbums"); - } - } - } - }); - }, - - /** - * Lays out the photos inside an album or a search result. - * - * This method is a misnomer, because it does not necessarily - * create a justified layout, but the configured layout as specified - * by `lychee.layout` which can also be a non-justified layout. - * - * Also note that this method is bastardized by `search.find`. - * Hence, this method would better not be part of `view.album.content`, - * because it is not exclusively used for an album. - * - * TODO: Livewire front-end will make this a pure CSS solution. - * - * @returns {void} - */ - justify: function justify() { - // Note, this also works for search results as the search creates - // a virtual "search smart album" which fills `album.json`. - if (album.json === null || album.json.photos.length === 0) return; - /** - * @type {Photo[]} - */ - var photos = album.json.photos; - - if (lychee.layout === 1) { - /** @type {jQuery} */ - var jqJustifiedLayout = $(".justified-layout"); - var containerWidth = parseFloat(jqJustifiedLayout.width()); - if (containerWidth === 0) { - // The reported width is zero, if `.justified-layout` - // or any parent element is hidden via `display: none`. - // Currently, this happens when a page reload is triggered - // in photo view due to dorky timing constraints. - // (In short: `lychee.load` initially hides the parent - // container `.content`, and the parent container only - // becomes visible _after_ the photo has been loaded which - // is too late for this method.) - // Also note, that this container and the parent - // container are normally always visible, even if a photo - // is shown as the photo view is drawn in the foreground - // and covers this container. - // Hence, this edge case here is really only a problem - // during a full page reload in combination with - // `lychee.load`. - // Also note that the code below is wrong and outdated. - // The alternative way to calculate the container width - // depends on the window width and (falsely) assumes that - // neither the left menu nor the right sidebar are open, - // but that the `.content` box covers the whole viewport. - // That was a correct assumption in the past, as the - // sidebar was always closed after a full page reload, but - // this assumption isn't true anymore since Lychee - // remembers the state of the sidebar. - // Luckily, this whole problem vanishes with the new - // box model after - // https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged. - // Then, we can use the view of the view container which - // is always visible and always has the correct width - // even for opened sidebars. - // TODO: Unconditionally use the width of the view container and remove this alternative width calculation after https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged - containerWidth = $(window).width() - 2 * parseFloat(jqJustifiedLayout.css("margin")); - } - /** @type {number[]} */ - var ratio = photos.map(function (_photo) { - var height = _photo.size_variants.original.height; - var width = _photo.size_variants.original.width; - var ratio = height > 0 ? width / height : 1; - // If there is no small and medium size variants for videos, - // we have to fall back to square thumbs - return _photo.type && _photo.type.indexOf("video") !== -1 && _photo.size_variants.small === null && _photo.size_variants.medium === null ? 1 : ratio; - }); - - /** - * An album listing has potentially hundreds of photos, hence - * only query for them once. - * @type {jQuery} + // Tag albums + tagAlbumsData += albums.json.tag_albums.reduce(function (html, tagAlbum) { + albums.parse(tagAlbum); + return html + build.album(tagAlbum, !lychee.rights.root_album.can_edit); + }, ""); + + // Albums + if (lychee.publicMode === false && albums.json.albums.length > 0) albumsData = build.divider(lychee.locale["ALBUMS"]); + albumsData += albums.json.albums.reduce(function (html, album) { + albums.parse(album); + return html + build.album(album, !lychee.rights.root_album.can_edit); + }, ""); + var current_owner = ""; + // Shared + sharedData += albums.json.shared_albums.reduce(function (html, album) { + albums.parse(album); + if (current_owner !== album.owner_name && lychee.publicMode === false) { + html += build.divider(album.owner_name); + current_owner = album.owner_name; + } + return html + build.album(album, !lychee.rights.settings.can_edit); + }, ""); + if (smartData === "" && tagAlbumsData === "" && albumsData === "" && sharedData === "") { + lychee.content.html(""); + lychee.content.append(build.no_content("eye")); + } else { + lychee.content.html(smartData + tagAlbumsData + albumsData + sharedData); + } + album.apply_nsfw_filter(); + view.album.content.restoreScroll(); + }, + /** + * @param {string} albumID + * @returns {void} */ - var jqPhotoElements = $(".justified-layout > div.photo"); - var photoDefaultHeight = parseFloat(jqPhotoElements.css("--lychee-default-height")); - - var layoutGeometry = require("justified-layout")(ratio, { - containerWidth: containerWidth, - containerPadding: 0, - targetRowHeight: photoDefaultHeight - }); - // if (lychee.rights.settings.can_edit) console.log(layoutGeometry); - $(".justified-layout").css("height", layoutGeometry.containerHeight + "px"); - $(".justified-layout > div").each(function (i) { - if (!layoutGeometry.boxes[i]) { - // Race condition in search.find -- window content - // and `photos` can get out of sync as search - // query is being modified. - return false; - } - var imgs = $(this).css({ - top: layoutGeometry.boxes[i].top + "px", - width: layoutGeometry.boxes[i].width + "px", - height: layoutGeometry.boxes[i].height + "px", - left: layoutGeometry.boxes[i].left + "px" - }).find(".thumbimg > img"); - - if (imgs.length > 0 && imgs[0].getAttribute("data-srcset")) { - imgs[0].setAttribute("sizes", layoutGeometry.boxes[i].width + "px"); - } - }); - // Show updated layout - jqJustifiedLayout.removeClass("laying-out"); - } else if (lychee.layout === 2) { - /** @type {jQuery} */ - var jqUnjustifiedLayout = $(".unjustified-layout"); - var _containerWidth = parseFloat(jqUnjustifiedLayout.width()); - if (_containerWidth === 0) { - // Triggered on Reload in photo view. - _containerWidth = $(window).width() - 2 * parseFloat(jqUnjustifiedLayout.css("margin")); - } - /** - * An album listing has potentially hundreds of photos, hence - * only query for them once. - * @type {jQuery} + title: function title(albumID) { + var album = albums.getByID(albumID); + var title = album.title ? album.title : lychee.locale["UNTITLED"]; + $('.album[data-id="' + albumID + '"] .overlay h1').text(title).attr("title", title); + }, + /** + * @param {string} albumID + * @returns {void} */ - var _jqPhotoElements = $(".unjustified-layout > div.photo"); - var photoMaxHeight = parseFloat(_jqPhotoElements.css("max-height")); - var photoMargin = parseFloat(_jqPhotoElements.css("margin-right")); - // Temporarily hide the container such that not each - // modification of every photo triggers a UI update. - jqUnjustifiedLayout.addClass("laying-out"); - _jqPhotoElements.each(function (i) { - if (!photos[i]) { - // Race condition in search.find -- window content - // and `photos` can get out of sync as search - // query is being modified. - return false; - } - var ratio = photos[i].size_variants.original.height > 0 ? photos[i].size_variants.original.width / photos[i].size_variants.original.height : 1; - if (photos[i].type && photos[i].type.indexOf("video") > -1) { - // Video. If there's no small and medium, we have - // to fall back to the square thumb. - if (photos[i].size_variants.small === null && photos[i].size_variants.medium === null) { - ratio = 1; - } - } - - var height = photoMaxHeight; - var width = height * ratio; - - if (width > _containerWidth - photoMargin) { - width = _containerWidth - photoMargin; - height = width / ratio; - } - - var imgs = $(this).css({ - width: width + "px", - height: height + "px" - }).find(".thumbimg > img"); - if (imgs.length > 0 && imgs[0].getAttribute("data-srcset")) { - imgs[0].setAttribute("sizes", width + "px"); - } - }); - // Show updated layout - jqUnjustifiedLayout.removeClass("laying-out"); - } - view.album.content.restoreScroll(); - } - }, - - /** - * @returns {void} - */ - description: function description() { - _sidebar.changeAttr("description", album.json.description ? album.json.description : ""); - }, - - /** - * @returns {void} - */ - show_tags: function show_tags() { - _sidebar.changeAttr("show_tags", album.json.show_tags.join(", ")); - }, - - /** - * @returns {void} - */ - license: function license() { - var license = void 0; - switch (album.json.license) { - case "none": - // TODO: If we do not use `"none"` as a literal string, we should convert `license` to a nullable DB attribute and use `null` for none to be consistent which everything else - license = ""; // none is displayed as - thus is empty. - break; - case "reserved": - license = lychee.locale["ALBUM_RESERVED"]; - break; - default: - license = album.json.license; - // console.log('default'); - break; - } - - _sidebar.changeAttr("license", license); - }, - - /** - * @returns {void} - */ - public: function _public() { - $("#button_visibility_album, #button_sharing_album_users").removeClass("active--not-hidden active--hidden"); - - if (album.json.policy.is_public) { - if (album.json.policy.is_link_required) { - $("#button_visibility_album, #button_sharing_album_users").addClass("active--hidden"); - } else { - $("#button_visibility_album, #button_sharing_album_users").addClass("active--not-hidden"); - } - - $(".photo .iconic-share").remove(); - - if (album.json.init) _sidebar.changeAttr("public", lychee.locale["ALBUM_SHR_YES"]); - } else { - if (album.json.init) _sidebar.changeAttr("public", lychee.locale["ALBUM_SHR_NO"]); - } - }, - - /** - * @returns {void} - */ - requiresLink: function requiresLink() { - if (album.json.policy.is_link_required) _sidebar.changeAttr("hidden", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("hidden", lychee.locale["ALBUM_SHR_NO"]); - }, - - /** - * @returns {void} - */ - nsfw: function nsfw() { - if (album.json.policy.is_nsfw) { - // Sensitive - $("#button_nsfw_album").addClass("active").attr("title", lychee.locale["ALBUM_UNMARK_NSFW"]); - } else { - // Not Sensitive - $("#button_nsfw_album").removeClass("active").attr("title", lychee.locale["ALBUM_MARK_NSFW"]); - } - }, - - /** - * @returns {void} - */ - downloadable: function downloadable() { - if (album.json.policy.grants_download) _sidebar.changeAttr("downloadable", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("downloadable", lychee.locale["ALBUM_SHR_NO"]); - }, - - /** - * @returns {void} - */ - password: function password() { - if (album.json.policy.is_password_required) _sidebar.changeAttr("password", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("password", lychee.locale["ALBUM_SHR_NO"]); - }, - - /** - * @returns {void} - */ - sidebar: function sidebar() { - if ((visible.album() || album.json && !album.json.init) && !visible.photo()) { - var structure = _sidebar.createStructure.album(album.json); - var html = _sidebar.render(structure); - - _sidebar.dom("#lychee_sidebar_content").html(html); - _sidebar.bind(); - } - } -}; - -view.photo = { - /** - * @param {boolean} autoplay - * @returns {void} - */ - init: function init(autoplay) { - multiselect.clearSelection(); - - view.photo.sidebar(); - view.photo.title(); - view.photo.star(); - view.photo.public(); - view.photo.header(); - view.photo.photo(autoplay); - - // TODO: `init` is not a property of the Photo JSON; this is a property of the view. Consider to move it to `view.photo.isInitialized` - _photo3.json.init = true; - }, - - /** - * @returns {void} - */ - show: function show() { - // Change header - lychee.content.addClass("view"); - header.setMode("photo"); - - if (!visible.photo()) { - // Fullscreen - var timeout = null; - $(document).bind("mousemove", function () { - clearTimeout(timeout); - // For live Photos: header animation only if LivePhoto is not playing - if (!_photo3.isLivePhotoPlaying() && lychee.header_auto_hide) { - header.show(); - timeout = setTimeout(header.hideIfLivePhotoNotPlaying, 2500); - } - }); - - // we also put this timeout to enable it by default when you directly click on a picture. - if (lychee.header_auto_hide) { - setTimeout(header.hideIfLivePhotoNotPlaying, 2500); - } - - lychee.animate(lychee.imageview, "fadeIn"); - lychee.imageview.addClass("active"); - } - }, - - /** - * @returns {void} - */ - hide: function hide() { - header.show(); - - lychee.content.removeClass("view"); - header.setMode("album"); - - // Disable Fullscreen - $(document).unbind("mousemove"); - if ($("video").length) { - $("video")[$("video").length - 1].pause(); - } - - // Hide Photo - lychee.animate(lychee.imageview, "fadeOut"); - // TODO: Reconsider the lines below - // The lines below are inconsistent to the corresponding code for - // the mapview (cp. `mapview.close()`). - // Here, we remove the `active` class after the animation has ended, - // in `mapview.close()` we remove that class immediately. - setTimeout(function () { - lychee.imageview.removeClass("active"); - view.album.sidebar(); - }, 300); - }, - - /** - * @returns {void} - */ - title: function title() { - if (_photo3.json.init) _sidebar.changeAttr("title", _photo3.json.title ? _photo3.json.title : ""); - var photoUrl = _photo3.json.size_variants.medium ? _photo3.json.size_variants.medium.url : _photo3.json.size_variants.original.url; - lychee.setMetaData(_photo3.json.title ? _photo3.json.title : lychee.locale["UNTITLED"], true, _photo3.json.description, photoUrl); - }, - - /** - * @returns {void} - */ - description: function description() { - if (_photo3.json.init) _sidebar.changeAttr("description", _photo3.json.description ? _photo3.json.description : ""); - }, - - /** - * @returns {void} - */ - uploaded: function uploaded() { - if (_photo3.json.init) _sidebar.changeAttr("uploaded", _photo3.json.created_at ? lychee.locale.printDateTime(_photo3.json.created_at) : ""); - }, - - /** - * @returns {void} - */ - license: function license() { - var license = void 0; - - // Process key to display correct string - switch (_photo3.json.license) { - case "none": - // TODO: If we do not use `"none"` as a literal string, we should convert `license` to a nullable DB attribute and use `null` for none to be consistent which everything else - license = ""; // none is displayed as - thus is empty (uniformity of the display). - break; - case "reserved": - license = lychee.locale["PHOTO_RESERVED"]; - break; - default: - license = _photo3.json.license; - break; - } - - // Update the sidebar if the photo is visible - if (_photo3.json.init) _sidebar.changeAttr("license", license); - }, - - /** - * @returns {void} - */ - star: function star() { - if (_photo3.json.is_starred) { - // Starred - $("#button_star").addClass("active").attr("title", lychee.locale["UNSTAR_PHOTO"]); - } else { - // Unstarred - $("#button_star").removeClass("active").attr("title", lychee.locale["STAR_PHOTO"]); - } - }, - - /** - * @returns {void} - */ - public: function _public() { - $("#button_visibility").removeClass("active--hidden active--not-hidden"); - - if (_photo3.json.is_public === true) { - $("#button_visibility").addClass("active--hidden"); - if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_YES"]); - } else if (_photo3.json.album_id !== null && album.json.policy.is_public === true) { - // part of a visible album - $("#button_visibility").addClass("active--not-hidden"); - if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_YES"]); - } else { - // Photo private - if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_NO"]); - } - }, - - /** - * @returns {void} - */ - tags: function tags() { - _sidebar.changeAttr("tags", build.tags(_photo3.json.tags), true); - _sidebar.bind(); - }, - - /** - * @param {boolean} autoplay - * @returns {void} - */ - photo: function photo(autoplay) { - var ret = build.imageview(_photo3.json, visible.header(), autoplay); - lychee.imageview.html(ret.html); - tabindex.makeFocusable(lychee.imageview); - - // Init Live Photo if needed - if (_photo3.isLivePhoto()) { - // Package gives warning that function will be remove and - // shoud be replaced by LivePhotosKit.augementElementAsPlayer - // But, LivePhotosKit.augementElementAsPlayer is not yet available - _photo3.livePhotosObject = LivePhotosKit.Player(document.getElementById("livephoto")); - } - - view.photo.onresize(); - - var $nextArrow = lychee.imageview.find("a#next"); - var $previousArrow = lychee.imageview.find("a#previous"); - var photoID = _photo3.getID(); - /** @type {?Photo} */ - var photoInAlbum = album.json && album.json.photos ? album.getByID(photoID) : null; - /** @type {?Photo} */ - var nextPhotoInAlbum = photoInAlbum && photoInAlbum.next_photo_id ? album.getByID(photoInAlbum.next_photo_id) : null; - /** @type {?Photo} */ - var prevPhotoInAlbum = photoInAlbum && photoInAlbum.previous_photo_id ? album.getByID(photoInAlbum.previous_photo_id) : null; - - var img = $("img#image"); - if (img.length > 0) { - if (!img[0].complete || img[0].currentSrc !== null && img[0].currentSrc === "") { - // Image is still loading. Display the thumb version in the - // background. - if (ret.thumb !== "") { - img.css("background-image", lychee.html(_templateObject43, ret.thumb)); - } - - // Don't preload next/prev until the requested image is - // fully loaded. - img.on("load", function () { - _photo3.preloadNextPrev(_photo3.getID()); - }); - } else { - _photo3.preloadNextPrev(_photo3.getID()); - } - } - - if (nextPhotoInAlbum === null || lychee.viewMode === true) { - $nextArrow.hide(); - } else { - // Check if thumbUrl exists (for videos w/o ffmpeg, we add a play-icon) - var thumbUrl = "img/placeholder.png"; - if (nextPhotoInAlbum.size_variants.thumb !== null) { - thumbUrl = nextPhotoInAlbum.size_variants.thumb.url; - } else if (nextPhotoInAlbum.type.indexOf("video") > -1) { - thumbUrl = "img/play-icon.png"; - } - $nextArrow.css("background-image", lychee.html(_templateObject44, thumbUrl)); - } - - if (prevPhotoInAlbum === null || lychee.viewMode === true) { - $previousArrow.hide(); - } else { - // Check if thumbUrl exists (for videos w/o ffmpeg, we add a play-icon) - var _thumbUrl = "img/placeholder.png"; - if (prevPhotoInAlbum.size_variants.thumb !== null) { - _thumbUrl = prevPhotoInAlbum.size_variants.thumb.url; - } else if (prevPhotoInAlbum.type.indexOf("video") > -1) { - _thumbUrl = "img/play-icon.png"; - } - $previousArrow.css("background-image", lychee.html(_templateObject44, _thumbUrl)); - } - }, - - /** - * @returns {void} - */ - sidebar: function sidebar() { - var structure = _sidebar.createStructure.photo(_photo3.json); - var html = _sidebar.render(structure); - var has_location = !!(_photo3.json.latitude && _photo3.json.longitude); - - _sidebar.dom("#lychee_sidebar_content").html(html); - _sidebar.bind(); - - if (has_location && lychee.map_display) { - // Leaflet searches for icon in same directory as js file -> paths needs - // to be overwritten - delete L.Icon.Default.prototype._getIconUrl; - L.Icon.Default.mergeOptions({ - iconRetinaUrl: "img/marker-icon-2x.png", - iconUrl: "img/marker-icon.png", - shadowUrl: "img/marker-shadow.png" - }); - - var myMap = L.map("leaflet_map_single_photo").setView([_photo3.json.latitude, _photo3.json.longitude], 13); - - L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { - attribution: map_provider_layer_attribution[lychee.map_provider].attribution - }).addTo(myMap); - - if (!lychee.map_display_direction || !_photo3.json.img_direction) { - // Add Marker to map, direction is not set - L.marker([_photo3.json.latitude, _photo3.json.longitude]).addTo(myMap); - } else { - // Add Marker, direction has been set - var viewDirectionIcon = L.icon({ - iconUrl: "img/view-angle-icon.png", - iconRetinaUrl: "img/view-angle-icon-2x.png", - iconSize: [100, 58], // size of the icon - iconAnchor: [50, 49] // point of the icon which will correspond to marker's location - }); - var marker = L.marker([_photo3.json.latitude, _photo3.json.longitude], { icon: viewDirectionIcon }).addTo(myMap); - marker.setRotationAngle(_photo3.json.img_direction); - } - } - }, - - /** - * @returns {void} - */ - header: function header() { - /* Note: the condition below is duplicated in contextMenu.photoMore() */ - if (_photo3.json.type && (_photo3.json.type.indexOf("video") === 0 || _photo3.json.type === "raw") || _photo3.json.live_photo_url !== "" && _photo3.json.live_photo_url !== null) { - $("#button_rotate_cwise, #button_rotate_ccwise").hide(); - } - }, - - /** - * @returns {void} - */ - onresize: function onresize() { - if (!_photo3.json || _photo3.json.size_variants.medium === null || _photo3.json.size_variants.medium2x === null) return; - - // Calculate the width of the image in the current window without - // borders and set 'sizes' to it. - var imgWidth = _photo3.json.size_variants.medium.width; - var imgHeight = _photo3.json.size_variants.medium.height; - var containerWidth = $(window).outerWidth(); - var containerHeight = $(window).outerHeight(); - - // Image can be no larger than its natural size, but it can be - // smaller depending on the size of the window. - var width = imgWidth < containerWidth ? imgWidth : containerWidth; - var height = width * imgHeight / imgWidth; - if (height > containerHeight) { - width = containerHeight * imgWidth / imgHeight; - } - - $("img#image").attr("sizes", width + "px"); - } + "delete": function _delete(albumID) { + $('.album[data-id="' + albumID + '"]').css("opacity", 0).animate({ + width: 0, + marginLeft: 0 + }, 300, function () { + $(this).remove(); + if (albums.json.albums.length <= 0) lychee.content.find(".divider:last-child").remove(); + }); + } + } }; +view.album = { + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + view.album.sidebar(); + view.album.title(); + view.album["public"](); + view.album.nsfw(); + view.album.nsfw_warning.init(); + view.album.content.init(); + + // TODO: `init` is not a property of the Album JSON; this is a property of the view. Consider to move it to `view.album.isInitialized` + album.json.init = true; + }, + /** @returns {void} */ + title: function title() { + if ((visible.album() || !album.json.init) && !visible.photo()) { + switch (album.getID()) { + case SmartAlbumID.STARRED: + lychee.setMetaData(lychee.locale["STARRED"]); + break; + case SmartAlbumID.PUBLIC: + lychee.setMetaData(lychee.locale["PUBLIC"]); + break; + case SmartAlbumID.RECENT: + lychee.setMetaData(lychee.locale["RECENT"]); + break; + case SmartAlbumID.UNSORTED: + lychee.setMetaData(lychee.locale["UNSORTED"]); + break; + case SmartAlbumID.ON_THIS_DAY: + lychee.setMetaData(lychee.locale["ON_THIS_DAY"]); + break; + default: + if (album.json.init) _sidebar.changeAttr("title", album.json.title); + lychee.setMetaData(album.json.title, true, album.json.description); + break; + } + } + }, + nsfw_warning: { + /** @returns {void} */ + init: function init() { + if (!lychee.nsfw_warning) { + $("#sensitive_warning").removeClass("active"); + return; + } + if (album.json.policy.is_nsfw && !lychee.nsfw_unlocked_albums.includes(album.json.id)) { + $("#sensitive_warning").addClass("active"); + } else { + $("#sensitive_warning").removeClass("active"); + } + }, + /** @returns {void} */ + next: function next() { + lychee.nsfw_unlocked_albums.push(album.json.id); + $("#sensitive_warning").removeClass("active"); + } + }, + content: { + /** @returns {void} */ + init: function init() { + var photosData = ""; + var albumsData = ""; + var html = ""; + if (album.json.albums) { + album.json.albums.forEach(function (_album) { + albums.parse(_album); + albumsData += build.album(_album, !album.json.rights.can_edit); + }); + } + if (album.json.photos) { + // Build photos + album.json.photos.forEach(function (_photo) { + photosData += build.photo(_photo, !album.json.rights.can_edit); + }); + } + if (photosData !== "") { + if (lychee.layout === 1) { + // The CSS class 'laying-out' prevents the DIV from being + // rendered. + // The CSS class will eventually be removed by the + // layout routine `view.album.content.justify` after all + // child nodes have been arranged. + // ---- Update 2022-10-20, temporary fix ---- + // However, the reported width of hidden elements is zero. + // Hence, using the CSS class `laying-out` currently + // prevent `view.album.content.justify` from calculating + // the correct width of the container. + // TODO: Re-add the CSS class `laying-out` here after https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged. + photosData = '
      ' + photosData + "
      "; + } else if (lychee.layout === 2) { + photosData = '
      ' + photosData + "
      "; + } + } + if (albumsData !== "" && photosData !== "") { + html = build.divider(lychee.locale["ALBUMS"]); + } + html += albumsData; + if (albumsData !== "" && photosData !== "") { + html += build.divider(lychee.locale["PHOTOS"]); + } + html += photosData; + + // Add photos to view + lychee.content.html(html); + album.apply_nsfw_filter(); + setTimeout(function () { + view.album.content.justify(); + }, 0); + }, + /** @returns {void} */ + restoreScroll: function restoreScroll() { + // Restore scroll position + var urls = JSON.parse(localStorage.getItem("scroll")); + var urlWindow = window.location.href; + $("#lychee_view_container").scrollTop(urls != null && urls[urlWindow] ? urls[urlWindow] : 0); + }, + /** + * @param {string} photoID + * @returns {void} + */ + title: function title(photoID) { + var photo = album.getByID(photoID); + var title = photo.title ? photo.title : lychee.locale["UNTITLED"]; + $('.photo[data-id="' + photoID + '"] .overlay h1').text(title).attr("title", title); + }, + /** + * @param {string} albumID + * @returns {void} + */ + titleSub: function titleSub(albumID) { + var subalbum = album.getSubByID(albumID); + var title = subalbum.title ? subalbum.title : lychee.locale["UNTITLED"]; + $('.album[data-id="' + albumID + '"] .overlay h1').text(title).attr("title", title); + }, + /** + * @param {string} photoID + * @returns {void} + */ + star: function star(photoID) { + var $badge = $('.photo[data-id="' + photoID + '"] .icn-star'); + if (album.getByID(photoID).is_starred) $badge.addClass("badge--star");else $badge.removeClass("badge--star"); + }, + /** + * @param {string} photoID + * @returns {void} + */ + "public": function _public(photoID) { + var $badge = $('.photo[data-id="' + photoID + '"] .icn-share'); + if (album.getByID(photoID).is_public === 1) $badge.addClass("badge--visible badge--hidden");else $badge.removeClass("badge--visible badge--hidden"); + }, + /** + * @param {string} photoID + * @returns {void} + */ + cover: function cover(photoID) { + $(".album .icn-cover").removeClass("badge--cover"); + $(".photo .icn-cover").removeClass("badge--cover"); + if (album.json.cover_id === photoID) { + var badge = $('.photo[data-id="' + photoID + '"] .icn-cover'); + if (badge.length > 0) { + badge.addClass("badge--cover"); + } else { + $.each(album.json.albums, function () { + if (this.thumb.id === photoID) { + $('.album[data-id="' + this.id + '"] .icn-cover').addClass("badge--cover"); + return false; + } + }); + } + } + }, + /** + * @param {Photo} data + * @returns {void} + */ + updatePhoto: function updatePhoto(data) { + var src, + srcset = ""; + + // This mimicks the structure of build.photo + if (lychee.layout === 0) { + src = data.size_variants.thumb.url; + if (data.size_variants.thumb2x !== null) { + srcset = "".concat(data.size_variants.thumb2x.url, " 2x"); + } + } else { + if (data.size_variants.small !== null) { + src = data.size_variants.small.url; + if (data.size_variants.small2x !== null) { + srcset = "".concat(data.size_variants.small.url, " ").concat(data.size_variants.small.width, "w, ").concat(data.size_variants.small2x.url, " ").concat(data.size_variants.small2x.width, "w"); + } + } else if (data.size_variants.medium !== null) { + src = data.size_variants.medium.url; + if (data.size_variants.medium2x !== null) { + srcset = "".concat(data.size_variants.medium.url, " ").concat(data.size_variants.medium.width, "w, ").concat(data.size_variants.medium2x.url, " ").concat(data.size_variants.medium2x.width, "w"); + } + } else if (!data.type || data.type.indexOf("video") !== 0) { + src = data.size_variants.original.url; + } else { + src = data.size_variants.thumb.url; + if (data.size_variants.thumb2x !== null) { + srcset = "".concat(data.size_variants.thumb.url, " ").concat(data.size_variants.thumb.width, "w, ").concat(data.size_variants.thumb2x.url, " ").concat(data.size_variants.thumb2x.width, "w"); + } + } + } + $('.photo[data-id="' + data.id + '"] > span.thumbimg > img').attr("data-src", src).attr("data-srcset", srcset).addClass("lazyload"); + setTimeout(function () { + return view.album.content.justify(); + }, 0); + }, + /** + * @param {string} photoID + * @param {boolean} [justify=false] + * @returns {void} + */ + "delete": function _delete(photoID) { + var justify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + $('.photo[data-id="' + photoID + '"]').css("opacity", 0).animate({ + width: 0, + marginLeft: 0 + }, 300, function () { + $(this).remove(); + // Only when search is not active + if (album.json) { + if (visible.sidebar()) { + var videoCount = 0; + $.each(album.json.photos, function () { + if (this.type && this.type.indexOf("video") > -1) { + videoCount++; + } + }); + if (album.json.photos.length - videoCount > 0) { + _sidebar.changeAttr("images", (album.json.photos.length - videoCount).toString()); + } else { + _sidebar.hideAttr("images"); + } + if (videoCount > 0) { + _sidebar.changeAttr("videos", videoCount.toString()); + } else { + _sidebar.hideAttr("videos"); + } + } + if (album.json.photos.length <= 0) { + lychee.content.find(".divider").remove(); + } + if (justify) { + setTimeout(function () { + return view.album.content.justify(); + }, 0); + } + } + }); + }, + /** + * @param {string} albumID + * @returns {void} + */ + deleteSub: function deleteSub(albumID) { + $('.album[data-id="' + albumID + '"]').css("opacity", 0).animate({ + width: 0, + marginLeft: 0 + }, 300, function () { + $(this).remove(); + if (album.json) { + if (album.json.albums.length <= 0) { + lychee.content.find(".divider").remove(); + } + if (visible.sidebar()) { + if (album.json.albums.length > 0) { + _sidebar.changeAttr("subalbums", album.json.albums.length.toString()); + } else { + _sidebar.hideAttr("subalbums"); + } + } + } + }); + }, + /** + * Lays out the photos inside an album or a search result. + * + * This method is a misnomer, because it does not necessarily + * create a justified layout, but the configured layout as specified + * by `lychee.layout` which can also be a non-justified layout. + * + * Also note that this method is bastardized by `search.find`. + * Hence, this method would better not be part of `view.album.content`, + * because it is not exclusively used for an album. + * + * TODO: Livewire front-end will make this a pure CSS solution. + * + * @returns {void} + */ + justify: function justify() { + // Note, this also works for search results as the search creates + // a virtual "search smart album" which fills `album.json`. + if (album.json === null || album.json.photos.length === 0) return; + /** + * @type {Photo[]} + */ + var photos = album.json.photos; + if (lychee.layout === 1) { + /** @type {jQuery} */ + var jqJustifiedLayout = $(".justified-layout"); + var containerWidth = parseFloat(jqJustifiedLayout.width()); + if (containerWidth === 0) { + // The reported width is zero, if `.justified-layout` + // or any parent element is hidden via `display: none`. + // Currently, this happens when a page reload is triggered + // in photo view due to dorky timing constraints. + // (In short: `lychee.load` initially hides the parent + // container `.content`, and the parent container only + // becomes visible _after_ the photo has been loaded which + // is too late for this method.) + // Also note, that this container and the parent + // container are normally always visible, even if a photo + // is shown as the photo view is drawn in the foreground + // and covers this container. + // Hence, this edge case here is really only a problem + // during a full page reload in combination with + // `lychee.load`. + // Also note that the code below is wrong and outdated. + // The alternative way to calculate the container width + // depends on the window width and (falsely) assumes that + // neither the left menu nor the right sidebar are open, + // but that the `.content` box covers the whole viewport. + // That was a correct assumption in the past, as the + // sidebar was always closed after a full page reload, but + // this assumption isn't true anymore since Lychee + // remembers the state of the sidebar. + // Luckily, this whole problem vanishes with the new + // box model after + // https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged. + // Then, we can use the view of the view container which + // is always visible and always has the correct width + // even for opened sidebars. + // TODO: Unconditionally use the width of the view container and remove this alternative width calculation after https://github.com/LycheeOrg/Lychee-front/pull/335 has been merged + containerWidth = $(window).width() - 2 * parseFloat(jqJustifiedLayout.css("margin")); + } + /** @type {number[]} */ + var ratio = photos.map(function (_photo) { + var height = _photo.size_variants.original.height; + var width = _photo.size_variants.original.width; + var ratio = height > 0 ? width / height : 1; + // If there is no small and medium size variants for videos, + // we have to fall back to square thumbs + return _photo.type && _photo.type.indexOf("video") !== -1 && _photo.size_variants.small === null && _photo.size_variants.medium === null ? 1 : ratio; + }); -view.settings = { - /** - * @returns {void} - */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.settings.title(); - header.setMode("config"); - view.settings.content.init(); - }, - - /** - * @returns {void} - */ - title: function title() { - lychee.setMetaData(lychee.locale["SETTINGS"]); - }, - - /** - * @returns {void} - */ - clearContent: function clearContent() { - lychee.content.html('
      '); - }, - - content: { - /** - * @returns {void} - */ - init: function init() { - view.settings.clearContent(); - if (lychee.rights.user.can_edit) { - view.settings.content.setLogin(); - } - if (lychee.rights.settings.can_edit) { - view.settings.content.setSorting(); - view.settings.content.setDropboxKey(); - view.settings.content.setLang(); - view.settings.content.setDefaultLicense(); - view.settings.content.setLayout(); - view.settings.content.setPublicSearch(); - view.settings.content.setOverlayType(); - view.settings.content.setMapDisplay(); - view.settings.content.setNSFWVisible(); - view.settings.content.setNotification(); - view.settings.content.setCSS(); - view.settings.content.moreButton(); - } - }, - - /** - * @returns {void} - */ - setLogin: function setLogin() { - var username_type = "hidden"; - if (lychee.allow_username_change) { - username_type = "text"; - } - - var msg = lychee.html(_templateObject45, lychee.locale["PASSWORD_TITLE"], lychee.locale["PASSWORD_CURRENT"], lychee.locale["PASSWORD_TEXT"], username_type, lychee.locale["LOGIN_USERNAME"], lychee.locale["LOGIN_PASSWORD"], lychee.locale["LOGIN_PASSWORD_CONFIRM"], lychee.locale["PASSWORD_CHANGE"], lychee.locale["TOKEN_BUTTON"]); - - $(".settings_view").append(msg); - - settings.bind("#basicModal__action_password_change", ".setLogin", settings.changeLogin); - settings.bind("#basicModal__action_token", ".setLogin", settings.openTokenDialog); - }, - - /** + /** + * An album listing has potentially hundreds of photos, hence + * only query for them once. + * @type {jQuery} + */ + var jqPhotoElements = $(".justified-layout > div.photo"); + var photoDefaultHeight = parseFloat(jqPhotoElements.css("--lychee-default-height")); + var layoutGeometry = require("justified-layout")(ratio, { + containerWidth: containerWidth, + containerPadding: 0, + targetRowHeight: photoDefaultHeight + }); + // if (lychee.rights.settings.can_edit) console.log(layoutGeometry); + $(".justified-layout").css("height", layoutGeometry.containerHeight + "px"); + $(".justified-layout > div").each(function (i) { + if (!layoutGeometry.boxes[i]) { + // Race condition in search.find -- window content + // and `photos` can get out of sync as search + // query is being modified. + return false; + } + var imgs = $(this).css({ + top: layoutGeometry.boxes[i].top + "px", + width: layoutGeometry.boxes[i].width + "px", + height: layoutGeometry.boxes[i].height + "px", + left: layoutGeometry.boxes[i].left + "px" + }).find(".thumbimg > img"); + if (imgs.length > 0 && imgs[0].getAttribute("data-srcset")) { + imgs[0].setAttribute("sizes", layoutGeometry.boxes[i].width + "px"); + } + }); + // Show updated layout + jqJustifiedLayout.removeClass("laying-out"); + } else if (lychee.layout === 2) { + /** @type {jQuery} */ + var jqUnjustifiedLayout = $(".unjustified-layout"); + var _containerWidth = parseFloat(jqUnjustifiedLayout.width()); + if (_containerWidth === 0) { + // Triggered on Reload in photo view. + _containerWidth = $(window).width() - 2 * parseFloat(jqUnjustifiedLayout.css("margin")); + } + /** + * An album listing has potentially hundreds of photos, hence + * only query for them once. + * @type {jQuery} + */ + var _jqPhotoElements = $(".unjustified-layout > div.photo"); + var photoMaxHeight = parseFloat(_jqPhotoElements.css("max-height")); + var photoMargin = parseFloat(_jqPhotoElements.css("margin-right")); + // Temporarily hide the container such that not each + // modification of every photo triggers a UI update. + jqUnjustifiedLayout.addClass("laying-out"); + _jqPhotoElements.each(function (i) { + if (!photos[i]) { + // Race condition in search.find -- window content + // and `photos` can get out of sync as search + // query is being modified. + return false; + } + var ratio = photos[i].size_variants.original.height > 0 ? photos[i].size_variants.original.width / photos[i].size_variants.original.height : 1; + if (photos[i].type && photos[i].type.indexOf("video") > -1) { + // Video. If there's no small and medium, we have + // to fall back to the square thumb. + if (photos[i].size_variants.small === null && photos[i].size_variants.medium === null) { + ratio = 1; + } + } + var height = photoMaxHeight; + var width = height * ratio; + if (width > _containerWidth - photoMargin) { + width = _containerWidth - photoMargin; + height = width / ratio; + } + var imgs = $(this).css({ + width: width + "px", + height: height + "px" + }).find(".thumbimg > img"); + if (imgs.length > 0 && imgs[0].getAttribute("data-srcset")) { + imgs[0].setAttribute("sizes", width + "px"); + } + }); + // Show updated layout + jqUnjustifiedLayout.removeClass("laying-out"); + } + view.album.content.restoreScroll(); + } + }, + /** * @returns {void} */ - clearLogin: function clearLogin() { - $("input[name=oldUsername], input[name=oldPassword], input[name=username], input[name=password], input[name=confirm]").val(""); - }, - - /** - * Renders the area of the settings related to sorting - * - * TODO: Note, the method is a misnomer. - * It does not **set** any sorting, see {@link settings.changeSorting} - * for that. - * This method only creates the HTML GUI. - * + description: function description() { + _sidebar.changeAttr("description", album.json.description ? album.json.description : ""); + }, + /** * @returns {void} */ - setSorting: function setSorting() { - var msg = lychee.html(_templateObject46, sprintf(lychee.locale["SORT_ALBUM_BY"], "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t"), sprintf(lychee.locale["SORT_PHOTO_BY"], "\n\t\t\t\t\t\t\t\n\t\t\t\t \t\t", "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t"), lychee.locale["SORT_CHANGE"]); - - $(".settings_view").append(msg); - - if (lychee.sorting_albums) { - $(".setSorting select#settings_albums_sorting_column").val(lychee.sorting_albums.column); - $(".setSorting select#settings_albums_sorting_order").val(lychee.sorting_albums.order); - } - - if (lychee.sorting_photos) { - $(".setSorting select#settings_photos_sorting_column").val(lychee.sorting_photos.column); - $(".setSorting select#settings_photos_sorting_order").val(lychee.sorting_photos.order); - } - - settings.bind("#basicModal__action_sorting_change", ".setSorting", settings.changeSorting); - }, - - /** + show_tags: function show_tags() { + _sidebar.changeAttr("show_tags", album.json.show_tags.join(", ")); + }, + /** * @returns {void} */ - setDropboxKey: function setDropboxKey() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["DROPBOX_TEXT"] + "\n\t\t\t \n\t\t\t

      \n\t\t\t\t\n\t\t\t
      \n\t\t\t "; - - $(".settings_view").append(msg); - settings.bind("#basicModal__action_dropbox_change", ".setDropBox", settings.changeDropboxKey); - }, - - /** + license: function license() { + var license; + switch (album.json.license) { + case "none": + // TODO: If we do not use `"none"` as a literal string, we should convert `license` to a nullable DB attribute and use `null` for none to be consistent which everything else + license = ""; // none is displayed as - thus is empty. + break; + case "reserved": + license = lychee.locale["ALBUM_RESERVED"]; + break; + default: + license = album.json.license; + // console.log('default'); + break; + } + _sidebar.changeAttr("license", license); + }, + /** * @returns {void} */ - setLang: function setLang() { - var msg = "\n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t" + lychee.locale["LANG_TEXT"] + "\n\t\t\t \t\t\t\n\t\t\t\t\t\t\t\n\t\t\t \t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t
      \n\t\t\t\t\t\t" + lychee.locale["LANG_TITLE"] + "\n\t\t\t\t\t
      \n\t\t\t\t
      "; - - $(".settings_view").append(msg); - settings.bind("#basicModal__action_set_lang", ".setLang", settings.changeLang); - }, - - /** + "public": function _public() { + $("#button_visibility_album, #button_sharing_album_users").removeClass("active--not-hidden active--hidden"); + if (album.json.policy.is_public) { + if (album.json.policy.is_link_required) { + $("#button_visibility_album, #button_sharing_album_users").addClass("active--hidden"); + } else { + $("#button_visibility_album, #button_sharing_album_users").addClass("active--not-hidden"); + } + $(".photo .iconic-share").remove(); + if (album.json.init) _sidebar.changeAttr("public", lychee.locale["ALBUM_SHR_YES"]); + } else { + if (album.json.init) _sidebar.changeAttr("public", lychee.locale["ALBUM_SHR_NO"]); + } + }, + /** * @returns {void} */ - setDefaultLicense: function setDefaultLicense() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["DEFAULT_LICENSE"] + "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t
      \n\t\t\t" + lychee.locale["PHOTO_LICENSE_HELP"] + "\n\t\t\t

      \n\t\t\t\n\t\t\t
      \n\t\t\t"; - $(".settings_view").append(msg); - $("select#license").val(lychee.default_license === "" ? "none" : lychee.default_license); - settings.bind("#basicModal__action_set_license", ".setDefaultLicense", settings.setDefaultLicense); - }, - - /** + requiresLink: function requiresLink() { + if (album.json.policy.is_link_required) _sidebar.changeAttr("hidden", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("hidden", lychee.locale["ALBUM_SHR_NO"]); + }, + /** * @returns {void} */ - setLayout: function setLayout() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["LAYOUT_TYPE"] + "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t\n\t\t\t
      \n\t\t\t"; - $(".settings_view").append(msg); - $("select#layout").val(lychee.layout); - settings.bind("#basicModal__action_set_layout", ".setLayout", settings.setLayout); - }, - - /** + nsfw: function nsfw() { + if (album.json.policy.is_nsfw) { + // Sensitive + $("#button_nsfw_album").addClass("active").attr("title", lychee.locale["ALBUM_UNMARK_NSFW"]); + } else { + // Not Sensitive + $("#button_nsfw_album").removeClass("active").attr("title", lychee.locale["ALBUM_MARK_NSFW"]); + } + }, + /** * @returns {void} */ - setPublicSearch: function setPublicSearch() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["PUBLIC_SEARCH_TEXT"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.public_search) $("#PublicSearch").click(); - - settings.bind("#PublicSearch", ".setPublicSearch", settings.changePublicSearch); - }, - - /** + downloadable: function downloadable() { + if (album.json.policy.grants_download) _sidebar.changeAttr("downloadable", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("downloadable", lychee.locale["ALBUM_SHR_NO"]); + }, + /** * @returns {void} */ - setNSFWVisible: function setNSFWVisible() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["NSFW_VISIBLE_TEXT_1"] + "\n\t\t\t

      \n\t\t\t

      " + lychee.locale["NSFW_VISIBLE_TEXT_2"] + "\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.nsfw_visible_saved) { - $("#NSFWVisible").click(); - } - - settings.bind("#NSFWVisible", ".setNSFWVisible", settings.changeNSFWVisible); - }, - // TODO: extend to the other settings. - - /** + password: function password() { + if (album.json.policy.is_password_required) _sidebar.changeAttr("password", lychee.locale["ALBUM_SHR_YES"]);else _sidebar.changeAttr("password", lychee.locale["ALBUM_SHR_NO"]); + }, + /** * @returns {void} */ - setOverlayType: function setOverlayType() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["OVERLAY_TYPE"] + "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - - $("select#ImgOverlayType").val(!lychee.image_overlay_type_default ? "exif" : lychee.image_overlay_type_default); - settings.bind("#basicModal__action_set_overlay_type", ".setOverlayType", settings.setOverlayType); - }, - - /** + sidebar: function sidebar() { + if ((visible.album() || album.json && !album.json.init) && !visible.photo()) { + var structure = _sidebar.createStructure.album(album.json); + var html = _sidebar.render(structure); + _sidebar.dom("#lychee_sidebar_content").html(html); + _sidebar.bind(); + } + } +}; +view.photo = { + /** + * @param {boolean} autoplay * @returns {void} */ - setMapDisplay: function setMapDisplay() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["MAP_DISPLAY_TEXT"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.map_display) $("#MapDisplay").click(); - - settings.bind("#MapDisplay", ".setMapDisplay", settings.changeMapDisplay); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["MAP_DISPLAY_PUBLIC_TEXT"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.map_display_public) $("#MapDisplayPublic").click(); - - settings.bind("#MapDisplayPublic", ".setMapDisplayPublic", settings.changeMapDisplayPublic); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["MAP_PROVIDER"] + "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - - $("select#MapProvider").val(!lychee.map_provider ? "Wikimedia" : lychee.map_provider); - settings.bind("#basicModal__action_set_map_provider", ".setMapProvider", settings.setMapProvider); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["MAP_INCLUDE_SUBALBUMS_TEXT"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.map_include_subalbums) $("#MapIncludeSubAlbums").click(); - - settings.bind("#MapIncludeSubAlbums", ".setMapIncludeSubAlbums", settings.changeMapIncludeSubAlbums); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["LOCATION_DECODING"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.location_decoding) $("#LocationDecoding").click(); - - settings.bind("#LocationDecoding", ".setLocationDecoding", settings.changeLocationDecoding); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["LOCATION_SHOW"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.location_show) $("#LocationShow").click(); - - settings.bind("#LocationShow", ".setLocationShow", settings.changeLocationShow); - - msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["LOCATION_SHOW_PUBLIC"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.location_show_public) $("#LocationShowPublic").click(); - - settings.bind("#LocationShowPublic", ".setLocationShowPublic", settings.changeLocationShowPublic); - }, - - /** + init: function init(autoplay) { + multiselect.clearSelection(); + view.photo.sidebar(); + view.photo.title(); + view.photo.star(); + view.photo["public"](); + view.photo.header(); + view.photo.photo(autoplay); + + // TODO: `init` is not a property of the Photo JSON; this is a property of the view. Consider to move it to `view.photo.isInitialized` + _photo3.json.init = true; + }, + /** * @returns {void} */ - setNotification: function setNotification() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["NEW_PHOTOS_NOTIFICATION"] + "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"; - - $(".settings_view").append(msg); - if (lychee.new_photos_notification) $("#NewPhotosNotification").click(); - - settings.bind("#NewPhotosNotification", ".setNewPhotosNotification", settings.changeNewPhotosNotification); - }, + show: function show() { + // Change header + lychee.content.addClass("view"); + header.setMode("photo"); + if (!visible.photo()) { + // Fullscreen + var timeout = null; + $(document).bind("mousemove", function () { + clearTimeout(timeout); + // For live Photos: header animation only if LivePhoto is not playing + if (!_photo3.isLivePhotoPlaying() && lychee.header_auto_hide) { + header.show(); + timeout = setTimeout(header.hideIfLivePhotoNotPlaying, 2500); + } + }); - /** + // we also put this timeout to enable it by default when you directly click on a picture. + if (lychee.header_auto_hide) { + setTimeout(header.hideIfLivePhotoNotPlaying, 2500); + } + lychee.animate(lychee.imageview, "fadeIn"); + lychee.imageview.addClass("active"); + } + }, + /** * @returns {void} */ - setCSS: function setCSS() { - var msg = "\n\t\t\t
      \n\t\t\t

      " + lychee.locale["CSS_TEXT"] + "

      \n\t\t\t\n\t\t\t\n\t\t\t
      "; - - $(".settings_view").append(msg); - - var css_addr = $($("link")[1]).attr("href"); - - api.getCSS(css_addr, function (data) { - $("#css").html(data); - }); + hide: function hide() { + header.show(); + lychee.content.removeClass("view"); + header.setMode("album"); + + // Disable Fullscreen + $(document).unbind("mousemove"); + if ($("video").length) { + $("video")[$("video").length - 1].pause(); + } - settings.bind("#basicModal__action_set_css", ".setCSS", settings.changeCSS); - }, + // Hide Photo + lychee.animate(lychee.imageview, "fadeOut"); + // TODO: Reconsider the lines below + // The lines below are inconsistent to the corresponding code for + // the mapview (cp. `mapview.close()`). + // Here, we remove the `active` class after the animation has ended, + // in `mapview.close()` we remove that class immediately. + setTimeout(function () { + lychee.imageview.removeClass("active"); + view.album.sidebar(); + }, 300); + }, + /** + * @returns {void} + */ + title: function title() { + if (_photo3.json.init) _sidebar.changeAttr("title", _photo3.json.title ? _photo3.json.title : ""); + var photoUrl = _photo3.json.size_variants.medium ? _photo3.json.size_variants.medium.url : _photo3.json.size_variants.original.url; + lychee.setMetaData(_photo3.json.title ? _photo3.json.title : lychee.locale["UNTITLED"], true, _photo3.json.description, photoUrl); + }, + /** + * @returns {void} + */ + description: function description() { + if (_photo3.json.init) _sidebar.changeAttr("description", _photo3.json.description ? _photo3.json.description : ""); + }, + /** + * @returns {void} + */ + uploaded: function uploaded() { + if (_photo3.json.init) _sidebar.changeAttr("uploaded", _photo3.json.created_at ? lychee.locale.printDateTime(_photo3.json.created_at) : ""); + }, + /** + * @returns {void} + */ + license: function license() { + var license; + + // Process key to display correct string + switch (_photo3.json.license) { + case "none": + // TODO: If we do not use `"none"` as a literal string, we should convert `license` to a nullable DB attribute and use `null` for none to be consistent which everything else + license = ""; // none is displayed as - thus is empty (uniformity of the display). + break; + case "reserved": + license = lychee.locale["PHOTO_RESERVED"]; + break; + default: + license = _photo3.json.license; + break; + } - /** + // Update the sidebar if the photo is visible + if (_photo3.json.init) _sidebar.changeAttr("license", license); + }, + /** + * @returns {void} + */ + star: function star() { + if (_photo3.json.is_starred) { + // Starred + $("#button_star").addClass("active").attr("title", lychee.locale["UNSTAR_PHOTO"]); + } else { + // Unstarred + $("#button_star").removeClass("active").attr("title", lychee.locale["STAR_PHOTO"]); + } + }, + /** * @returns {void} */ - moreButton: function moreButton() { - var msg = lychee.html(_templateObject47, lychee.locale["MORE"]); + "public": function _public() { + $("#button_visibility").removeClass("active--hidden active--not-hidden"); + if (_photo3.json.is_public === true) { + $("#button_visibility").addClass("active--hidden"); + if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_YES"]); + } else if (_photo3.json.album_id !== null && album.json.policy.is_public === true) { + // part of a visible album + $("#button_visibility").addClass("active--not-hidden"); + if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_YES"]); + } else { + // Photo private + if (_photo3.json.init) _sidebar.changeAttr("public", lychee.locale["PHOTO_SHR_NO"]); + } + }, + /** + * @returns {void} + */ + tags: function tags() { + _sidebar.changeAttr("tags", build.tags(_photo3.json.tags), true); + _sidebar.bind(); + }, + /** + * @param {boolean} autoplay + * @returns {void} + */ + photo: function photo(autoplay) { + var ret = build.imageview(_photo3.json, visible.header(), autoplay); + lychee.imageview.html(ret.html); + tabindex.makeFocusable(lychee.imageview); + + // Init Live Photo if needed + if (_photo3.isLivePhoto()) { + // Package gives warning that function will be remove and + // shoud be replaced by LivePhotosKit.augementElementAsPlayer + // But, LivePhotosKit.augementElementAsPlayer is not yet available + _photo3.livePhotosObject = LivePhotosKit.Player(document.getElementById("livephoto")); + } + view.photo.onresize(); + var $nextArrow = lychee.imageview.find("a#next"); + var $previousArrow = lychee.imageview.find("a#previous"); + var photoID = _photo3.getID(); + /** @type {?Photo} */ + var photoInAlbum = album.json && album.json.photos ? album.getByID(photoID) : null; + /** @type {?Photo} */ + var nextPhotoInAlbum = photoInAlbum && photoInAlbum.next_photo_id ? album.getByID(photoInAlbum.next_photo_id) : null; + /** @type {?Photo} */ + var prevPhotoInAlbum = photoInAlbum && photoInAlbum.previous_photo_id ? album.getByID(photoInAlbum.previous_photo_id) : null; + var img = $("img#image"); + if (img.length > 0) { + if (!img[0].complete || img[0].currentSrc !== null && img[0].currentSrc === "") { + // Image is still loading. Display the thumb version in the + // background. + if (ret.thumb !== "") { + img.css("background-image", lychee.html(_templateObject46 || (_templateObject46 = _taggedTemplateLiteral(["url(\"", "\")"])), ret.thumb)); + } - $(".settings_view").append(msg); + // Don't preload next/prev until the requested image is + // fully loaded. + img.on("load", function () { + _photo3.preloadNextPrev(_photo3.getID()); + }); + } else { + _photo3.preloadNextPrev(_photo3.getID()); + } + } + if (nextPhotoInAlbum === null || lychee.viewMode === true) { + $nextArrow.hide(); + } else { + // Check if thumbUrl exists (for videos w/o ffmpeg, we add a play-icon) + var thumbUrl = "img/placeholder.png"; + if (nextPhotoInAlbum.size_variants.thumb !== null) { + thumbUrl = nextPhotoInAlbum.size_variants.thumb.url; + } else if (nextPhotoInAlbum.type.indexOf("video") > -1) { + thumbUrl = "img/play-icon.png"; + } + $nextArrow.css("background-image", lychee.html(_templateObject47 || (_templateObject47 = _taggedTemplateLiteral(["linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url(\"", "\")"])), thumbUrl)); + } + if (prevPhotoInAlbum === null || lychee.viewMode === true) { + $previousArrow.hide(); + } else { + // Check if thumbUrl exists (for videos w/o ffmpeg, we add a play-icon) + var _thumbUrl = "img/placeholder.png"; + if (prevPhotoInAlbum.size_variants.thumb !== null) { + _thumbUrl = prevPhotoInAlbum.size_variants.thumb.url; + } else if (prevPhotoInAlbum.type.indexOf("video") > -1) { + _thumbUrl = "img/play-icon.png"; + } + $previousArrow.css("background-image", lychee.html(_templateObject48 || (_templateObject48 = _taggedTemplateLiteral(["linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url(\"", "\")"])), _thumbUrl)); + } + }, + /** + * @returns {void} + */ + sidebar: function sidebar() { + var structure = _sidebar.createStructure.photo(_photo3.json); + var html = _sidebar.render(structure); + var has_location = !!(_photo3.json.latitude && _photo3.json.longitude); + _sidebar.dom("#lychee_sidebar_content").html(html); + _sidebar.bind(); + if (has_location && lychee.map_display) { + // Leaflet searches for icon in same directory as js file -> paths needs + // to be overwritten + delete L.Icon.Default.prototype._getIconUrl; + L.Icon.Default.mergeOptions({ + iconRetinaUrl: "img/marker-icon-2x.png", + iconUrl: "img/marker-icon.png", + shadowUrl: "img/marker-shadow.png" + }); + var myMap = L.map("leaflet_map_single_photo").setView([_photo3.json.latitude, _photo3.json.longitude], 13); + L.tileLayer(map_provider_layer_attribution[lychee.map_provider].layer, { + attribution: map_provider_layer_attribution[lychee.map_provider].attribution + }).addTo(myMap); + if (!lychee.map_display_direction || !_photo3.json.img_direction) { + // Add Marker to map, direction is not set + L.marker([_photo3.json.latitude, _photo3.json.longitude]).addTo(myMap); + } else { + // Add Marker, direction has been set + var viewDirectionIcon = L.icon({ + iconUrl: "img/view-angle-icon.png", + iconRetinaUrl: "img/view-angle-icon-2x.png", + iconSize: [100, 58], + // size of the icon + iconAnchor: [50, 49] // point of the icon which will correspond to marker's location + }); - $("#basicModal__action_more").on("click", view.full_settings.init); - } - } + var marker = L.marker([_photo3.json.latitude, _photo3.json.longitude], { + icon: viewDirectionIcon + }).addTo(myMap); + marker.setRotationAngle(_photo3.json.img_direction); + } + } + }, + /** + * @returns {void} + */ + header: function header() { + /* Note: the condition below is duplicated in contextMenu.photoMore() */ + if (_photo3.json.type && (_photo3.json.type.indexOf("video") === 0 || _photo3.json.type === "raw") || _photo3.json.live_photo_url !== "" && _photo3.json.live_photo_url !== null) { + $("#button_rotate_cwise, #button_rotate_ccwise").hide(); + } + }, + /** + * @returns {void} + */ + onresize: function onresize() { + if (!_photo3.json || _photo3.json.size_variants.medium === null || _photo3.json.size_variants.medium2x === null) return; + + // Calculate the width of the image in the current window without + // borders and set 'sizes' to it. + var imgWidth = _photo3.json.size_variants.medium.width; + var imgHeight = _photo3.json.size_variants.medium.height; + var containerWidth = $(window).outerWidth(); + var containerHeight = $(window).outerHeight(); + + // Image can be no larger than its natural size, but it can be + // smaller depending on the size of the window. + var width = imgWidth < containerWidth ? imgWidth : containerWidth; + var height = width * imgHeight / imgWidth; + if (height > containerHeight) { + width = containerHeight * imgWidth / imgHeight; + } + $("img#image").attr("sizes", width + "px"); + } }; +view.settings = { + /** + * @returns {void} + */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.settings.title(); + header.setMode("config"); + view.settings.content.init(); + }, + /** + * @returns {void} + */ + title: function title() { + lychee.setMetaData(lychee.locale["SETTINGS"]); + }, + /** + * @returns {void} + */ + clearContent: function clearContent() { + lychee.content.html('
      '); + }, + content: { + /** + * @returns {void} + */ + init: function init() { + view.settings.clearContent(); + if (lychee.rights.user.can_edit) { + view.settings.content.setLogin(); + } + if (lychee.rights.settings.can_edit) { + view.settings.content.setSorting(); + view.settings.content.setDropboxKey(); + view.settings.content.setLang(); + view.settings.content.setDefaultLicense(); + view.settings.content.setLayout(); + view.settings.content.setPublicSearch(); + view.settings.content.setOverlayType(); + view.settings.content.setMapDisplay(); + view.settings.content.setNSFWVisible(); + view.settings.content.setNotification(); + view.settings.content.setCSS(); + view.settings.content.moreButton(); + } + }, + /** + * @returns {void} + */ + setLogin: function setLogin() { + var username_type = "hidden"; + if (lychee.allow_username_change) { + username_type = "text"; + } + var msg = lychee.html(_templateObject49 || (_templateObject49 = _taggedTemplateLiteral(["\n\t\t\t
      \n\t\t\t
      \n\t\t\t

      $", "\n\t\t\t\t \n\t\t\t

      \n\t\t\t

      $", "\n\t\t\t\t \n\t\t\t\t \n\t\t\t\t \n\t\t\t

      \n\t\t\t
      \n\t\t\t\t\n\t\t\t\t$", "\n\t\t\t\t$", "\n\t\t\t
      \n\t\t\t
      \n\t\t\t
      "])), lychee.locale["PASSWORD_TITLE"], lychee.locale["PASSWORD_CURRENT"], lychee.locale["PASSWORD_TEXT"], username_type, lychee.locale["LOGIN_USERNAME"], lychee.locale["LOGIN_PASSWORD"], lychee.locale["LOGIN_PASSWORD_CONFIRM"], lychee.locale["PASSWORD_CHANGE"], lychee.locale["TOKEN_BUTTON"]); + $(".settings_view").append(msg); + settings.bind("#basicModal__action_password_change", ".setLogin", settings.changeLogin); + settings.bind("#basicModal__action_token", ".setLogin", settings.openTokenDialog); + }, + /** + * @returns {void} + */ + clearLogin: function clearLogin() { + $("input[name=oldUsername], input[name=oldPassword], input[name=username], input[name=password], input[name=confirm]").val(""); + }, + /** + * Renders the area of the settings related to sorting + * + * TODO: Note, the method is a misnomer. + * It does not **set** any sorting, see {@link settings.changeSorting} + * for that. + * This method only creates the HTML GUI. + * + * @returns {void} + */ + setSorting: function setSorting() { + var msg = lychee.html(_templateObject50 || (_templateObject50 = _taggedTemplateLiteral(["\n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t", "\n\t\t\t\t\t

      \n\t\t\t\t\t

      \n\t\t\t\t\t\t", "\n\t\t\t\t\t

      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t\t$", "\n\t\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t"])), sprintf(lychee.locale["SORT_ALBUM_BY"], "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t"), "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t")), sprintf(lychee.locale["SORT_PHOTO_BY"], "\n\t\t\t\t\t\t\t\n\t\t\t\t \t\t"), "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t")), lychee.locale["SORT_CHANGE"]); + $(".settings_view").append(msg); + if (lychee.sorting_albums) { + $(".setSorting select#settings_albums_sorting_column").val(lychee.sorting_albums.column); + $(".setSorting select#settings_albums_sorting_order").val(lychee.sorting_albums.order); + } + if (lychee.sorting_photos) { + $(".setSorting select#settings_photos_sorting_column").val(lychee.sorting_photos.column); + $(".setSorting select#settings_photos_sorting_order").val(lychee.sorting_photos.order); + } + settings.bind("#basicModal__action_sorting_change", ".setSorting", settings.changeSorting); + }, + /** + * @returns {void} + */ + setDropboxKey: function setDropboxKey() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["DROPBOX_TEXT"], "\n\t\t\t \n\t\t\t

      \n\t\t\t\t\n\t\t\t
      \n\t\t\t "); + $(".settings_view").append(msg); + settings.bind("#basicModal__action_dropbox_change", ".setDropBox", settings.changeDropboxKey); + }, + /** + * @returns {void} + */ + setLang: function setLang() { + var msg = "\n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t".concat(lychee.locale["LANG_TEXT"], "\n\t\t\t \t\t\t\n\t\t\t\t\t\t\t\n\t\t\t \t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t\n\t\t\t\t
      "); + $(".settings_view").append(msg); + settings.bind("#basicModal__action_set_lang", ".setLang", settings.changeLang); + }, + /** + * @returns {void} + */ + setDefaultLicense: function setDefaultLicense() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["DEFAULT_LICENSE"], "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t
      \n\t\t\t").concat(lychee.locale["PHOTO_LICENSE_HELP"], "\n\t\t\t

      \n\t\t\t\n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + $("select#license").val(lychee.default_license === "" ? "none" : lychee.default_license); + settings.bind("#basicModal__action_set_license", ".setDefaultLicense", settings.setDefaultLicense); + }, + /** + * @returns {void} + */ + setLayout: function setLayout() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["LAYOUT_TYPE"], "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t\n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + $("select#layout").val(lychee.layout); + settings.bind("#basicModal__action_set_layout", ".setLayout", settings.setLayout); + }, + /** + * @returns {void} + */ + setPublicSearch: function setPublicSearch() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["PUBLIC_SEARCH_TEXT"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.public_search) $("#PublicSearch").click(); + settings.bind("#PublicSearch", ".setPublicSearch", settings.changePublicSearch); + }, + /** + * @returns {void} + */ + setNSFWVisible: function setNSFWVisible() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["NSFW_VISIBLE_TEXT_1"], "\n\t\t\t

      \n\t\t\t

      ").concat(lychee.locale["NSFW_VISIBLE_TEXT_2"], "\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.nsfw_visible_saved) { + $("#NSFWVisible").click(); + } + settings.bind("#NSFWVisible", ".setNSFWVisible", settings.changeNSFWVisible); + }, + // TODO: extend to the other settings. + /** + * @returns {void} + */ + setOverlayType: function setOverlayType() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["OVERLAY_TYPE"], "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + $("select#ImgOverlayType").val(!lychee.image_overlay_type_default ? "exif" : lychee.image_overlay_type_default); + settings.bind("#basicModal__action_set_overlay_type", ".setOverlayType", settings.setOverlayType); + }, + /** + * @returns {void} + */ + setMapDisplay: function setMapDisplay() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["MAP_DISPLAY_TEXT"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.map_display) $("#MapDisplay").click(); + settings.bind("#MapDisplay", ".setMapDisplay", settings.changeMapDisplay); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["MAP_DISPLAY_PUBLIC_TEXT"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.map_display_public) $("#MapDisplayPublic").click(); + settings.bind("#MapDisplayPublic", ".setMapDisplayPublic", settings.changeMapDisplayPublic); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["MAP_PROVIDER"], "\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + $("select#MapProvider").val(!lychee.map_provider ? "Wikimedia" : lychee.map_provider); + settings.bind("#basicModal__action_set_map_provider", ".setMapProvider", settings.setMapProvider); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["MAP_INCLUDE_SUBALBUMS_TEXT"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.map_include_subalbums) $("#MapIncludeSubAlbums").click(); + settings.bind("#MapIncludeSubAlbums", ".setMapIncludeSubAlbums", settings.changeMapIncludeSubAlbums); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["LOCATION_DECODING"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.location_decoding) $("#LocationDecoding").click(); + settings.bind("#LocationDecoding", ".setLocationDecoding", settings.changeLocationDecoding); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["LOCATION_SHOW"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.location_show) $("#LocationShow").click(); + settings.bind("#LocationShow", ".setLocationShow", settings.changeLocationShow); + msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["LOCATION_SHOW_PUBLIC"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.location_show_public) $("#LocationShowPublic").click(); + settings.bind("#LocationShowPublic", ".setLocationShowPublic", settings.changeLocationShowPublic); + }, + /** + * @returns {void} + */ + setNotification: function setNotification() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["NEW_PHOTOS_NOTIFICATION"], "\n\t\t\t\n\t\t\t

      \n\t\t\t
      \n\t\t\t"); + $(".settings_view").append(msg); + if (lychee.new_photos_notification) $("#NewPhotosNotification").click(); + settings.bind("#NewPhotosNotification", ".setNewPhotosNotification", settings.changeNewPhotosNotification); + }, + /** + * @returns {void} + */ + setCSS: function setCSS() { + var msg = "\n\t\t\t
      \n\t\t\t

      ".concat(lychee.locale["CSS_TEXT"], "

      \n\t\t\t\n\t\t\t\n\t\t\t
      "); + $(".settings_view").append(msg); + var css_addr = $($("link")[1]).attr("href"); + api.getCSS(css_addr, function (data) { + $("#css").html(data); + }); + settings.bind("#basicModal__action_set_css", ".setCSS", settings.changeCSS); + }, + /** + * @returns {void} + */ + moreButton: function moreButton() { + var msg = lychee.html(_templateObject51 || (_templateObject51 = _taggedTemplateLiteral(["\n\t\t\t
      \n\t\t\t\t", "\n\t\t\t
      \n\t\t\t"])), lychee.locale["MORE"]); + $(".settings_view").append(msg); + $("#basicModal__action_more").on("click", view.full_settings.init); + } + } +}; view.full_settings = { - /** - * @returns {void} - */ - init: function init() { - multiselect.clearSelection(); - - view.full_settings.title(); - view.full_settings.content.init(); - }, - - /** - * @returns {void} - */ - title: function title() { - lychee.setMetaData(lychee.locale["FULL_SETTINGS"]); - }, - - /** - * @returns {void} - */ - clearContent: function clearContent() { - lychee.content.html('
      '); - }, - - content: { - init: function init() { - view.full_settings.clearContent(); - - api.post("Settings::getAll", {}, - /** @param {ConfigSetting[]} data */ - function (data) { - var msg = lychee.html(_templateObject48, lychee.locale["SETTINGS_ADVANCED_WARNING_EXPL"]); - - var prev = ""; - data.forEach(function (_config) { - if (_config.cat && prev !== _config.cat) { - msg += lychee.html(_templateObject49, _config.cat); - prev = _config.cat; - } - // prevent 'null' string for empty values - var val = _config.value ? _config.value : ""; - msg += lychee.html(_templateObject50, _config.key, _config.key, val); - }); - - msg += lychee.html(_templateObject51, lychee.locale["SETTINGS_ADVANCED_SAVE"]); - - $(".settings_view").append(msg); - - settings.bind("#FullSettingsSave_button", "#fullSettings", settings.save); - - $("#fullSettings").on("keypress", function (e) { - settings.save_enter(e); - }); - }); - } - } + /** + * @returns {void} + */ + init: function init() { + multiselect.clearSelection(); + view.full_settings.title(); + view.full_settings.content.init(); + }, + /** + * @returns {void} + */ + title: function title() { + lychee.setMetaData(lychee.locale["FULL_SETTINGS"]); + }, + /** + * @returns {void} + */ + clearContent: function clearContent() { + lychee.content.html('
      '); + }, + content: { + init: function init() { + view.full_settings.clearContent(); + api.post("Settings::getAll", {}, /** @param {ConfigSetting[]} data */ + function (data) { + var msg = lychee.html(_templateObject52 || (_templateObject52 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t
      \n\t\t\t\t\t\t
      \n\t\t\t\t\t\t

      \n\t\t\t\t\t\t", "\n\t\t\t\t\t\t

      \n\t\t\t\t\t\t
      \n\t\t\t\t\t\t"])), lychee.locale["SETTINGS_ADVANCED_WARNING_EXPL"]); + var prev = ""; + data.forEach(function (_config) { + if (_config.cat && prev !== _config.cat) { + msg += lychee.html(_templateObject53 || (_templateObject53 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t

      $", "

      \n\t\t\t\t\t\t\t\t
      "])), _config.cat); + prev = _config.cat; + } + // prevent 'null' string for empty values + var val = _config.value ? _config.value : ""; + msg += lychee.html(_templateObject54 || (_templateObject54 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t

      \n\t\t\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t

      \n\t\t\t\t\t\t\t
      "])), _config.key, _config.key, val); + }); + msg += lychee.html(_templateObject55 || (_templateObject55 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t", "\n\t\t\t\t\t\t
      "])), lychee.locale["SETTINGS_ADVANCED_SAVE"]); + $(".settings_view").append(msg); + settings.bind("#FullSettingsSave_button", "#fullSettings", settings.save); + $("#fullSettings").on("keypress", function (e) { + settings.save_enter(e); + }); + }); + } + } }; - view.notifications = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.notifications.title(); - header.setMode("config"); - view.notifications.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["NOTIFICATIONS"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - lychee.content.html('
      '); - }, - - content: { - /** @returns {void} */ - init: function init() { - view.notifications.clearContent(); - - var html = "\n\t\t\t\t
      \n\t\t\t\t\t

      " + lychee.locale["USER_EMAIL_INSTRUCTION"] + "

      \n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t" + lychee.locale["ENTER_EMAIL"] + "\n\t\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t
      \n\t\t\t\t\t\t" + lychee.locale["SAVE"] + "\n\t\t\t\t\t
      \n\t\t\t\t
      "; - - $(".settings_view").append(html); - settings.bind("#UserUpdate_button", "#UserUpdate", notifications.update); - } - } + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.notifications.title(); + header.setMode("config"); + view.notifications.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["NOTIFICATIONS"]); + }, + /** @returns {void} */ + clearContent: function clearContent() { + lychee.content.html('
      '); + }, + content: { + /** @returns {void} */ + init: function init() { + view.notifications.clearContent(); + var html = "\n\t\t\t\t
      \n\t\t\t\t\t

      ".concat(lychee.locale["USER_EMAIL_INSTRUCTION"], "

      \n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t").concat(lychee.locale["ENTER_EMAIL"], "\n\t\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t
      \n\t\t\t\t\t\t").concat(lychee.locale["SAVE"], "\n\t\t\t\t\t
      \n\t\t\t\t
      "); + $(".settings_view").append(html); + settings.bind("#UserUpdate_button", "#UserUpdate", notifications.update); + } + } }; - view.users = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.users.title(); - header.setMode("config"); - view.users.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["USERS"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - lychee.content.html('
      '); - }, - - content: { - /** @returns {void} */ - init: function init() { - view.users.clearContent(); - - if (users.json.length === 0) { - $(".users_view").append('

      User list is empty!

      '); - } - - var html = "\n\t\t\t\t

      \n\t\t\t\t\t" + lychee.locale["USERNAME"] + "\n\t\t\t\t\t" + lychee.locale["NEW_PASSWORD"] + "\n\t\t\t\t\t\n\t\t\t\t\t\t" + build.iconic("data-transfer-upload") + "\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t" + build.iconic("lock-unlocked") + "\n\t\t\t\t\t\n\t\t\t\t

      "; - - $(".users_view").append(html); - - users.json.forEach(function (_user) { - $(".users_view").append(build.user(_user)); - // TODO: Instead of binding an event handler to each input element it would be much more efficient, to bind a single event handler to the common parent view, let the event bubble up the DOM tree and use the `originalElement` property of the event to get the input element which caused the event. - settings.bind("#UserUpdate" + _user.id, "#UserData" + _user.id, users.update); - settings.bind("#UserDelete" + _user.id, "#UserData" + _user.id, users.delete); - if (_user.may_upload) { - $("#UserData" + _user.id + ' .choice input[name="may_upload"]').click(); - } - if (_user.may_edit_own_settings) { - $("#UserData" + _user.id + ' .choice input[name="may_edit_own_settings"]').click(); - } - }); - - html = "\n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t" + lychee.locale["CREATE"] + "\n\t\t\t\t
      "; - $(".users_view").append(html); - settings.bind("#UserCreate_button", "#UserCreate", users.create); - } - } + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.users.title(); + header.setMode("config"); + view.users.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["USERS"]); + }, + /** @returns {void} */ + clearContent: function clearContent() { + lychee.content.html('
      '); + }, + content: { + /** @returns {void} */ + init: function init() { + view.users.clearContent(); + if (users.json.length === 0) { + $(".users_view").append('

      User list is empty!

      '); + } + var html = "\n\t\t\t\t

      \n\t\t\t\t\t".concat(lychee.locale["USERNAME"], "\n\t\t\t\t\t").concat(lychee.locale["NEW_PASSWORD"], "\n\t\t\t\t\t\n\t\t\t\t\t\t").concat(build.iconic("data-transfer-upload"), "\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t").concat(build.iconic("lock-unlocked"), "\n\t\t\t\t\t\n\t\t\t\t

      "); + $(".users_view").append(html); + users.json.forEach(function (_user) { + $(".users_view").append(build.user(_user)); + // TODO: Instead of binding an event handler to each input element it would be much more efficient, to bind a single event handler to the common parent view, let the event bubble up the DOM tree and use the `originalElement` property of the event to get the input element which caused the event. + settings.bind("#UserUpdate" + _user.id, "#UserData" + _user.id, users.update); + settings.bind("#UserDelete" + _user.id, "#UserData" + _user.id, users["delete"]); + if (_user.may_upload) { + $("#UserData" + _user.id + ' .choice input[name="may_upload"]').click(); + } + if (_user.may_edit_own_settings) { + $("#UserData" + _user.id + ' .choice input[name="may_edit_own_settings"]').click(); + } + }); + html = "\n\t\t\t\t
      \n\t\t\t\t\t

      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t

      \n\t\t\t\t\t").concat(lychee.locale["CREATE"], "\n\t\t\t\t
      "); + $(".users_view").append(html); + settings.bind("#UserCreate_button", "#UserCreate", users.create); + } + } }; - view.sharing = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.sharing.title(); - header.setMode("config"); - view.sharing.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["SHARING"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - lychee.content.html(''); - }, - - content: { - /** @returns {void} */ - init: function init() { - view.sharing.clearContent(); - - if (sharing.json.shared.length === 0) { - $(".sharing_view").append(''); - } - - var albumOptions = sharing.json.albums.reduce(function (acc, _album) { - return acc + lychee.html(_templateObject52, _album.id, _album.title); - }, ""); - - var userOptions = sharing.json.users.reduce(function (acc, _user) { - return acc + lychee.html(_templateObject52, _user.id, _user.username); - }, ""); - - var sharingOptions = sharing.json.shared.reduce(function (acc, _shareInfo) { - return acc + lychee.html(_templateObject53, _shareInfo.title, _shareInfo.username, _shareInfo.id); - }, ""); - - var html = "\n\t\t\t\t

      Share

      \n\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t

      with

      \n\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\n\t\t\t\t
      \n\t\t\t\t\t" + sharingOptions + "\n\t\t\t\t
      "; - - if (sharing.json.shared.length !== 0) { - html += ""; - } - - $(".sharing_view").append(html); - - $("#albums_list").multiselect(); - $("#user_list").multiselect(); - $("#Share_button").on("click", sharing.add).on("mouseenter", function () { - $("#albums_list_to, #user_list_to").addClass("borderBlue"); - }).on("mouseleave", function () { - $("#albums_list_to, #user_list_to").removeClass("borderBlue"); - }); - - $("#Remove_button").on("click", sharing.delete); - } - } + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.sharing.title(); + header.setMode("config"); + view.sharing.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["SHARING"]); + }, + /** @returns {void} */ + clearContent: function clearContent() { + lychee.content.html(''); + }, + content: { + /** @returns {void} */ + init: function init() { + view.sharing.clearContent(); + if (sharing.json.shared.length === 0) { + $(".sharing_view").append(''); + } + var albumOptions = sharing.json.albums.reduce(function (acc, _album) { + return acc + lychee.html(_templateObject56 || (_templateObject56 = _taggedTemplateLiteral([""])), _album.id, _album.title); + }, ""); + var userOptions = sharing.json.users.reduce(function (acc, _user) { + return acc + lychee.html(_templateObject57 || (_templateObject57 = _taggedTemplateLiteral([""])), _user.id, _user.username); + }, ""); + var sharingOptions = sharing.json.shared.reduce(function (acc, _shareInfo) { + return acc + lychee.html(_templateObject58 || (_templateObject58 = _taggedTemplateLiteral(["\n\t\t\t\t\t\t

      \n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t$", "\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t

      "])), _shareInfo.title, _shareInfo.username, _shareInfo.id); + }, ""); + var html = "\n\t\t\t\t

      Share

      \n\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t

      with

      \n\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t\n\t\t\t\t\t
      \n\t\t\t\t
      \n\t\t\t\t\n\t\t\t\t
      \n\t\t\t\t\t").concat(sharingOptions, "\n\t\t\t\t
      "); + if (sharing.json.shared.length !== 0) { + html += ""); + } + $(".sharing_view").append(html); + $("#albums_list").multiselect(); + $("#user_list").multiselect(); + $("#Share_button").on("click", sharing.add).on("mouseenter", function () { + $("#albums_list_to, #user_list_to").addClass("borderBlue"); + }).on("mouseleave", function () { + $("#albums_list_to, #user_list_to").removeClass("borderBlue"); + }); + $("#Remove_button").on("click", sharing["delete"]); + } + } }; - view.logs = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.logs.title(); - header.setMode("config"); - view.logs.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["LOGS"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - var html = ""; - if (lychee.rights.settings.can_clear_logs) { - html += lychee.html(_templateObject54, lychee.locale["CLEAN_LOGS"], lychee.locale["CLEAR"]); - } - html += lychee.html(_templateObject55); - lychee.content.html(html); - - $("#Clean_Noise").on("click", function () { - api.post("Logs::clearNoise", {}, view.logs.init); - }); - $("#Clear").on("click", function () { - api.post("Logs::clear", {}, view.logs.init); - }); - }, - - content: { - /** @returns {void} */ - init: function init() { - /** - * @param {LogEntry[]} logEntries - * @returns {void} - */ - var successHandler = function successHandler(logEntries) { - /** - * TODO: Consider moving this method to `lychee.locale` - * @param {Date} datetime - * @returns {string} - */ - var formatDateTime = function formatDateTime(datetime) { - return "" + datetime.getUTCFullYear() + "-" + String(datetime.getUTCMonth() + 1).padStart(2, "0") + "-" + String(datetime.getUTCDate()).padStart(2, "0") + " " + String(datetime.getUTCHours()).padStart(2, "0") + ":" + String(datetime.getUTCMinutes()).padStart(2, "0") + ":" + String(datetime.getUTCSeconds()).padStart(2, "0") + " UTC"; - }; - var preformattedLog = logEntries.reduce(function (acc, logEntry) { - return acc + formatDateTime(new Date(logEntry.created_at)) + " -- " + logEntry.type.padEnd(7) + " -- " + logEntry.function + " -- " + logEntry.line + " -- " + logEntry.text + "\n"; - }, ""); - /** @type {HTMLDivElement} */ - var logView = document.querySelector(".logs_diagnostics_view"); - logView.replaceChildren(); - logView.appendChild(document.createElement("pre")).textContent = preformattedLog; - }; - - view.logs.clearContent(); - api.post("Logs::list", {}, successHandler); - } - } + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.logs.title(); + header.setMode("config"); + view.logs.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["LOGS"]); + }, + /** @returns {void} */ + clearContent: function clearContent() { + var html = ""; + if (lychee.rights.settings.can_clear_logs) { + html += lychee.html(_templateObject59 || (_templateObject59 = _taggedTemplateLiteral(["\n\t\t\t"])), lychee.locale["CLEAN_LOGS"], lychee.locale["CLEAR"]); + } + html += lychee.html(_templateObject60 || (_templateObject60 = _taggedTemplateLiteral(["\n\t\t\t
      "])));
      +    lychee.content.html(html);
      +    $("#Clean_Noise").on("click", function () {
      +      api.post("Logs::clearNoise", {}, view.logs.init);
      +    });
      +    $("#Clear").on("click", function () {
      +      api.post("Logs::clear", {}, view.logs.init);
      +    });
      +  },
      +  content: {
      +    /** @returns {void} */
      +    init: function init() {
      +      /**
      +       * @param {LogEntry[]} logEntries
      +       * @returns {void}
      +       */
      +      var successHandler = function successHandler(logEntries) {
      +        /**
      +         * TODO: Consider moving this method to `lychee.locale`
      +         * @param {Date} datetime
      +         * @returns {string}
      +         */
      +        var formatDateTime = function formatDateTime(datetime) {
      +          return "" + datetime.getUTCFullYear() + "-" + String(datetime.getUTCMonth() + 1).padStart(2, "0") + "-" + String(datetime.getUTCDate()).padStart(2, "0") + " " + String(datetime.getUTCHours()).padStart(2, "0") + ":" + String(datetime.getUTCMinutes()).padStart(2, "0") + ":" + String(datetime.getUTCSeconds()).padStart(2, "0") + " UTC";
      +        };
      +        var preformattedLog = logEntries.reduce(function (acc, logEntry) {
      +          return acc + formatDateTime(new Date(logEntry.created_at)) + " -- " + logEntry.type.padEnd(7) + " -- " + logEntry["function"] + " -- " + logEntry.line + " -- " + logEntry.text + "\n";
      +        }, "");
      +        /** @type {HTMLDivElement} */
      +        var logView = document.querySelector(".logs_diagnostics_view");
      +        logView.replaceChildren();
      +        logView.appendChild(document.createElement("pre")).textContent = preformattedLog;
      +      };
      +      view.logs.clearContent();
      +      api.post("Logs::list", {}, successHandler);
      +    }
      +  }
       };
      -
       view.diagnostics = {
      -	/** @returns {void} */
      -	init: function init() {
      -		multiselect.clearSelection();
      -
      -		if (visible.photo()) view.photo.hide();
      -		view.diagnostics.title();
      -		header.setMode("config");
      -		view.diagnostics.content.init();
      -	},
      -
      -	/** @returns {void} */
      -	title: function title() {
      -		lychee.setMetaData(lychee.locale["DIAGNOSTICS"]);
      -	},
      -
      -	/**
      -  * @param {number} update - The update status: `0`: not on master branch;
      -  *                          `1`: up-to-date; `2`: not up-to-date;
      -  *                          `3`: requires migration
      -  * @returns {void}
      -  */
      -	clearContent: function clearContent(update) {
      -		var html = "";
      -
      -		if (update === 2) {
      -			html = view.diagnostics.button("", lychee.locale["UPDATE_AVAILABLE"]);
      -		} else if (update === 3) {
      -			html = view.diagnostics.button("", lychee.locale["MIGRATION_AVAILABLE"]);
      -		} else if (update > 0) {
      -			html = view.diagnostics.button("Check_", lychee.locale["CHECK_FOR_UPDATE"]);
      -		}
      -
      -		html += '
      ';
      -		lychee.content.html(html);
      -	},
      -
      -	/**
      -  * @param {string} type
      -  * @param {string} locale
      -  * @returns {string} - HTML
      -  */
      -	button: function button(type, locale) {
      -		return "\n\t\t\t
      \n\t\t\t\t" + locale + "\n\t\t\t
      "; - }, - - /** @returns {string} */ - bind: function bind() { - $("#Update_Lychee").on("click", view.diagnostics.call_apply_update); - $("#Check_Update_Lychee").on("click", view.diagnostics.call_check_update); - }, - - content: { - /** @returns {void} */ - init: function init() { - view.diagnostics.clearContent(0); - api.post("Diagnostics::get", {}, view.diagnostics.content.parseResponse); - }, - - /** - * @param {DiagnosticInfo} data + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.diagnostics.title(); + header.setMode("config"); + view.diagnostics.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["DIAGNOSTICS"]); + }, + /** + * @param {number} update - The update status: `0`: not on master branch; + * `1`: up-to-date; `2`: not up-to-date; + * `3`: requires migration * @returns {void} */ - parseResponse: function parseResponse(data) { - view.diagnostics.clearContent(data.update); - var html = ""; - - html += view.diagnostics.content.block("error", "Diagnostics", data.errors); - html += view.diagnostics.content.block("sys", "System Information", data.infos); - html += ''; - html += ''; - html += lychee.html(_templateObject16, lychee.locale["DIAGNOSTICS_GET_SIZE"]); - html += ""; - html += view.diagnostics.content.block("conf", "Config Information", data.configs); - - $(".logs_diagnostics_view").html(html); - - view.diagnostics.bind(); - - $("#Get_Size_Lychee").on("click", view.diagnostics.call_get_size); - }, - - /** - * @param {string} id - * @param {string} title - * @param {string[]} arr + clearContent: function clearContent(update) { + var html = ""; + if (update === 2) { + html = view.diagnostics.button("", lychee.locale["UPDATE_AVAILABLE"]); + } else if (update === 3) { + html = view.diagnostics.button("", lychee.locale["MIGRATION_AVAILABLE"]); + } else if (update > 0) { + html = view.diagnostics.button("Check_", lychee.locale["CHECK_FOR_UPDATE"]); + } + html += '
      ';
      +    lychee.content.html(html);
      +  },
      +  /**
      +   * @param {string} type
      +   * @param {string} locale
          * @returns {string} - HTML
          */
      -		block: function block(id, title, arr) {
      -			var html = "";
      -			html += '
      \n\n\n\n';
      -			html += "    " + title + "\n";
      -			html += "    ".padEnd(title.length, "-") + "\n";
      -			html += arr.reduce(function (acc, line) {
      -				return acc + "    " + line + "\n";
      -			}, "");
      -			html += "
      \n"; - return html; - } - }, - - /** @returns {void} */ - call_check_update: function call_check_update() { - api.post("Update::check", {}, - /** @param {{updateStatus: string}} data */ - function (data) { - loadingBar.show("success", data.updateStatus); - $("#Check_Update_Lychee").remove(); - }); - }, - - /** @returns {void} */ - call_apply_update: function call_apply_update() { - api.post("Update::apply", {}, - /** @param {{updateMsgs: string[]}} data */ - function (data) { - var html = view.preify(data.updateMsgs, ""); - $("#Update_Lychee").remove(); - $(html).prependTo(".logs_diagnostics_view"); - }); - }, - - /** @returns {void} */ - call_get_size: function call_get_size() { - api.post("Diagnostics::getSize", {}, - /** @param {string[]} data */ - function (data) { - var html = view.preify(data, ""); - $("#Get_Size_Lychee").remove(); - $(html).appendTo("#content_diag_sys"); - }); - } + button: function button(type, locale) { + return "\n\t\t\t
      \n\t\t\t\t").concat(locale, "\n\t\t\t
      "); + }, + /** @returns {string} */ + bind: function bind() { + $("#Update_Lychee").on("click", view.diagnostics.call_apply_update); + $("#Check_Update_Lychee").on("click", view.diagnostics.call_check_update); + }, + content: { + /** @returns {void} */ + init: function init() { + view.diagnostics.clearContent(0); + api.post("Diagnostics::get", {}, view.diagnostics.content.parseResponse); + }, + /** + * @param {DiagnosticInfo} data + * @returns {void} + */ + parseResponse: function parseResponse(data) { + view.diagnostics.clearContent(data.update); + var html = ""; + html += view.diagnostics.content.block("error", "Diagnostics", data.errors); + html += view.diagnostics.content.block("sys", "System Information", data.infos); + html += ''; + html += ''; + html += lychee.html(_templateObject61 || (_templateObject61 = _taggedTemplateLiteral(["", ""])), lychee.locale["DIAGNOSTICS_GET_SIZE"]); + html += ""; + html += view.diagnostics.content.block("conf", "Config Information", data.configs); + $(".logs_diagnostics_view").html(html); + view.diagnostics.bind(); + $("#Get_Size_Lychee").on("click", view.diagnostics.call_get_size); + }, + /** + * @param {string} id + * @param {string} title + * @param {string[]} arr + * @returns {string} - HTML + */ + block: function block(id, title, arr) { + var html = ""; + html += '
      \n\n\n\n';
      +      html += "    " + title + "\n";
      +      html += "    ".padEnd(title.length, "-") + "\n";
      +      html += arr.reduce(function (acc, line) {
      +        return acc + "    " + line + "\n";
      +      }, "");
      +      html += "
      \n"; + return html; + } + }, + /** @returns {void} */ + call_check_update: function call_check_update() { + api.post("Update::check", {}, /** @param {{updateStatus: string}} data */ + function (data) { + loadingBar.show("success", data.updateStatus); + $("#Check_Update_Lychee").remove(); + }); + }, + /** @returns {void} */ + call_apply_update: function call_apply_update() { + api.post("Update::apply", {}, /** @param {{updateMsgs: string[]}} data */ + function (data) { + var html = view.preify(data.updateMsgs, ""); + $("#Update_Lychee").remove(); + $(html).prependTo(".logs_diagnostics_view"); + }); + }, + /** @returns {void} */ + call_get_size: function call_get_size() { + api.post("Diagnostics::getSize", {}, /** @param {string[]} data */ + function (data) { + var html = view.preify(data, ""); + $("#Get_Size_Lychee").remove(); + $(html).appendTo("#content_diag_sys"); + }); + } }; - view.update = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.update.title(); - header.setMode("config"); - view.update.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["UPDATE"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - var html = '
      ';
      -		lychee.content.html(html);
      -	},
      -
      -	content: {
      -		init: function init() {
      -			view.update.clearContent();
      -
      -			// code duplicate
      -			api.post("Update::apply", {},
      -			/** @param {{updateMsgs: string[]}} data */
      -			function (data) {
      -				var html = view.preify(data.updateMsgs, "");
      -				lychee.content.html(html);
      -			});
      -		}
      -	}
      +  /** @returns {void} */
      +  init: function init() {
      +    multiselect.clearSelection();
      +    if (visible.photo()) view.photo.hide();
      +    view.update.title();
      +    header.setMode("config");
      +    view.update.content.init();
      +  },
      +  /** @returns {void} */
      +  title: function title() {
      +    lychee.setMetaData(lychee.locale["UPDATE"]);
      +  },
      +  /** @returns {void} */
      +  clearContent: function clearContent() {
      +    var html = '
      ';
      +    lychee.content.html(html);
      +  },
      +  content: {
      +    init: function init() {
      +      view.update.clearContent();
      +
      +      // code duplicate
      +      api.post("Update::apply", {}, /** @param {{updateMsgs: string[]}} data */
      +      function (data) {
      +        var html = view.preify(data.updateMsgs, "");
      +        lychee.content.html(html);
      +      });
      +    }
      +  }
       };
       
       /**
      @@ -14932,56 +14188,47 @@ view.update = {
        * @returns {string} - HTML which wraps `data` into a `
      `-tag
        */
       view.preify = function (data, cssClass) {
      -	return data.reduce(function (acc, line) {
      -		return acc + "    " + line + "\n";
      -	}, '
      ') + "
      "; + return data.reduce(function (acc, line) { + return acc + " " + line + "\n"; + }, '
      ') + "
      "; }; - view.u2f = { - /** @returns {void} */ - init: function init() { - multiselect.clearSelection(); - - if (visible.photo()) view.photo.hide(); - view.u2f.title(); - header.setMode("config"); - view.u2f.content.init(); - }, - - /** @returns {void} */ - title: function title() { - lychee.setMetaData(lychee.locale["U2F"]); - }, - - /** @returns {void} */ - clearContent: function clearContent() { - lychee.content.html('
      '); - }, - - content: { - /** @returns {void} */ - init: function init() { - view.u2f.clearContent(); - - if (u2f.json.length === 0) { - $(".u2f_view").append('

      Credentials list is empty!

      '); - } else { - var _html2 = "\n\t\t\t\t\t

      \n\t\t\t\t\t\t" + lychee.locale["U2F_CREDENTIALS"] + "\n\t\t\t\t\t

      "; - - $(".u2f_view").append(_html2); - - u2f.json.forEach(function (credential) { - // TODO: Don't query the DOM for the same element in each loop iteration - $(".u2f_view").append(build.u2f(credential)); - settings.bind("#CredentialDelete" + credential.id, "#CredentialData" + credential.id, u2f.delete); - }); - } - - var html = "\n\t\t\t\t"; - $(".u2f_view").append(html); - $("#RegisterU2FButton").on("click", u2f.register); - } - } + /** @returns {void} */ + init: function init() { + multiselect.clearSelection(); + if (visible.photo()) view.photo.hide(); + view.u2f.title(); + header.setMode("config"); + view.u2f.content.init(); + }, + /** @returns {void} */ + title: function title() { + lychee.setMetaData(lychee.locale["U2F"]); + }, + /** @returns {void} */ + clearContent: function clearContent() { + lychee.content.html('
      '); + }, + content: { + /** @returns {void} */ + init: function init() { + view.u2f.clearContent(); + if (u2f.json.length === 0) { + $(".u2f_view").append('

      Credentials list is empty!

      '); + } else { + var _html2 = "\n\t\t\t\t\t

      \n\t\t\t\t\t\t".concat(lychee.locale["U2F_CREDENTIALS"], "\n\t\t\t\t\t

      "); + $(".u2f_view").append(_html2); + u2f.json.forEach(function (credential) { + // TODO: Don't query the DOM for the same element in each loop iteration + $(".u2f_view").append(build.u2f(credential)); + settings.bind("#CredentialDelete" + credential.id, "#CredentialData" + credential.id, u2f["delete"]); + }); + } + var html = "\n\t\t\t\t"); + $(".u2f_view").append(html); + $("#RegisterU2FButton").on("click", u2f.register); + } + } }; /** @@ -14995,62 +14242,62 @@ var visible = {}; * @returns {boolean} */ visible.albums = function () { - return !!header.dom("#lychee_toolbar_public").hasClass("visible") || !!header.dom("#lychee_toolbar_albums").hasClass("visible"); + return !!header.dom("#lychee_toolbar_public").hasClass("visible") || !!header.dom("#lychee_toolbar_albums").hasClass("visible"); }; /** @returns {boolean} */ visible.album = function () { - return !!header.dom("#lychee_toolbar_album").hasClass("visible"); + return !!header.dom("#lychee_toolbar_album").hasClass("visible"); }; /** @returns {boolean} */ visible.photo = function () { - return $("#imageview.fadeIn").length > 0; + return $("#imageview.fadeIn").length > 0; }; /** @returns {boolean} */ visible.mapview = function () { - return $("#lychee_map_container.fadeIn").length > 0; + return $("#lychee_map_container.fadeIn").length > 0; }; /** @returns {boolean} */ visible.config = function () { - return !!header.dom("#lychee_toolbar_config").hasClass("visible"); + return !!header.dom("#lychee_toolbar_config").hasClass("visible"); }; /** @returns {boolean} */ visible.search = function () { - return visible.albums() && album.json !== null && album.isSearchID(album.json.id); + return visible.albums() && album.json !== null && album.isSearchID(album.json.id); }; /** @returns {boolean} */ visible.sidebar = function () { - return !!_sidebar.dom().hasClass("active"); + return !!_sidebar.dom().hasClass("active"); }; /** @returns {boolean} */ visible.sidebarbutton = function () { - return visible.photo() || visible.album() && $("#button_info_album:visible").length > 0; + return visible.photo() || visible.album() && $("#button_info_album:visible").length > 0; }; /** @returns {boolean} */ visible.header = function () { - return !header.dom().hasClass("hidden"); + return !header.dom().hasClass("hidden"); }; /** @returns {boolean} */ visible.contextMenu = function () { - return basicContext.visible(); + return basicContext.visible(); }; /** @returns {boolean} */ visible.multiselect = function () { - return $("#multiselect").length > 0; + return $("#multiselect").length > 0; }; /** @returns {boolean} */ visible.leftMenu = function () { - return !!leftMenu.dom().hasClass("visible"); + return !!leftMenu.dom().hasClass("visible"); }; /** @@ -15251,11 +14498,11 @@ visible.leftMenu = function () { * @type {Readonly<{RECENT: string, STARRED: string, PUBLIC: string, UNSORTED: string, ON_THIS_DAY: string}>} */ var SmartAlbumID = Object.freeze({ - UNSORTED: "unsorted", - STARRED: "starred", - PUBLIC: "public", - RECENT: "recent", - ON_THIS_DAY: "on_this_day" + UNSORTED: "unsorted", + STARRED: "starred", + PUBLIC: "public", + RECENT: "recent", + ON_THIS_DAY: "on_this_day" }); /** diff --git a/public/dist/landing.js b/public/dist/landing.js index dfd548d53a3..9404021db14 100644 --- a/public/dist/landing.js +++ b/public/dist/landing.js @@ -12,201 +12,191 @@ var landing = {}; * @returns {void} */ landing.runInitAnimations = function () { - $("#loader_wrap").fadeOut(1000); - - $(".animate-down").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - $(".animate-up").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - $(".pop-in").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - $(".pop-out").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); + $("#loader_wrap").fadeOut(1000); + $(".animate-down").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + $(".animate-up").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + $(".pop-in").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + $(".pop-out").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); }; /** * @returns {void} */ landing.runInitAnimationsHome = function () { - $(".pop-in").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - var onFadedOut = function onFadedOut() { - $(".pop-in-last").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - $(".animate-down").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - - $(".animate-up").each(function (index) { - setTimeout(function (elem) { - return elem.addClass("toggled"); - }, 100 * index, $(this)); - }); - }; - - setTimeout(function () { - return $("#intro").fadeOut(1000, onFadedOut); - }, 2500); + $(".pop-in").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + var onFadedOut = function onFadedOut() { + $(".pop-in-last").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + $(".animate-down").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + $(".animate-up").each(function (index) { + setTimeout(function (elem) { + return elem.addClass("toggled"); + }, 100 * index, $(this)); + }); + }; + setTimeout(function () { + return $("#intro").fadeOut(1000, onFadedOut); + }, 2500); }; - $(document).ready(function () { - // Prevent users from saving images - - /* - $("body").on("contextmenu",function(){ - return false; - }); - */ - - // Toggle menu and menu setup - - $("#intro_content").css({ - paddingTop: ($(window).height() - 50) / 2 + "px" - }); - - $(".sub-menu").hide(); - - // $('#menu a').each(function() { - - // var $this = $(this); - // var href = $(this).attr("href"); - // var text = $(this).html(); - - // // if ( $this.html() == "Store" || $this.closest("ul").hasClass("sub-menu") ) { - // // - // // } else { - // // $("#mobile_menu_wrap").prepend('' + text + ''); - // // } - - // }); - - // $('.sub-menu a').each(function() { - // - // var $this = $(this); - // var href = $(this).attr("href"); - // var text = $(this).html(); - // - // $("#mobile_menu_wrap").append('' + text + ''); - // - // }); - - $("#menu li").hover(function () { - if ($(this).find(".sub-menu").length > 0) { - $(this).find(".sub-menu").show(); - } - }, function () { - if ($(this).find(".sub-menu").length > 0) { - $(this).find(".sub-menu").hide(); - } - }); - - // $('.hamburger').on("click", function() { - // - // $(this).toggleClass("is-active"); - // - // if ( $(this).hasClass("is-active") == true ) { - // $("#mobile_menu_wrap").fadeIn(800); - // - // $("#mobile_menu_wrap a").each(function(index) { - // var $this = $(this); - // setTimeout(function() { - // $this.addClass("popped"); - // }, 100 * index); - // }); - // - // } else { - // $("#mobile_menu_wrap").fadeOut(800); - // $("#mobile_menu_wrap a").removeClass("popped"); - // } - // - // return false; - // }); - - // var homeslider = $('#slides'); - // - // if( homeslider ) { - // - // var homeslider_slides = homeslider.find("li").length; - // var playSpeed = 0; - // - // if ( homeslider_slides > 1 ) { - // playSpeed = 5000; - // } - // - // homeslider.superslides({ - // play : playSpeed, - // pagination : true, - // animation : "fade", - // animation_speed : 1500 - // }); - // } - - // Gallery page - - // $('#gallery_nav a').on("click", function() { - // - // var targets = $(this).data("category"); - // - // $(this).addClass("active").parent().siblings("li").find('a').removeClass("active"); - // - // if ( targets == "all" ) { - // - // $('.grid-item').show(); - // - // } else { - // - // $('.grid-item').each(function() { - // - // var $this = $(this); - // var thisCat = $(this).data("category"); - // - // if ( thisCat.indexOf(targets) >= 0 ) { - // $this.show(); - // } else { - // $this.hide(); - // } - // - // }); - // - // } - // - // galleryGrid.masonry(); - // - // console.log(targets); - // - // return false; - // }); - - if ($("#intro").length > 0) { - landing.runInitAnimationsHome(); - } else { - landing.runInitAnimations(); - } + // Prevent users from saving images + + /* + $("body").on("contextmenu",function(){ + return false; + }); + */ + + // Toggle menu and menu setup + + $("#intro_content").css({ + paddingTop: ($(window).height() - 50) / 2 + "px" + }); + $(".sub-menu").hide(); + + // $('#menu a').each(function() { + + // var $this = $(this); + // var href = $(this).attr("href"); + // var text = $(this).html(); + + // // if ( $this.html() == "Store" || $this.closest("ul").hasClass("sub-menu") ) { + // // + // // } else { + // // $("#mobile_menu_wrap").prepend('' + text + ''); + // // } + + // }); + + // $('.sub-menu a').each(function() { + // + // var $this = $(this); + // var href = $(this).attr("href"); + // var text = $(this).html(); + // + // $("#mobile_menu_wrap").append('' + text + ''); + // + // }); + + $("#menu li").hover(function () { + if ($(this).find(".sub-menu").length > 0) { + $(this).find(".sub-menu").show(); + } + }, function () { + if ($(this).find(".sub-menu").length > 0) { + $(this).find(".sub-menu").hide(); + } + }); + + // $('.hamburger').on("click", function() { + // + // $(this).toggleClass("is-active"); + // + // if ( $(this).hasClass("is-active") == true ) { + // $("#mobile_menu_wrap").fadeIn(800); + // + // $("#mobile_menu_wrap a").each(function(index) { + // var $this = $(this); + // setTimeout(function() { + // $this.addClass("popped"); + // }, 100 * index); + // }); + // + // } else { + // $("#mobile_menu_wrap").fadeOut(800); + // $("#mobile_menu_wrap a").removeClass("popped"); + // } + // + // return false; + // }); + + // var homeslider = $('#slides'); + // + // if( homeslider ) { + // + // var homeslider_slides = homeslider.find("li").length; + // var playSpeed = 0; + // + // if ( homeslider_slides > 1 ) { + // playSpeed = 5000; + // } + // + // homeslider.superslides({ + // play : playSpeed, + // pagination : true, + // animation : "fade", + // animation_speed : 1500 + // }); + // } + + // Gallery page + + // $('#gallery_nav a').on("click", function() { + // + // var targets = $(this).data("category"); + // + // $(this).addClass("active").parent().siblings("li").find('a').removeClass("active"); + // + // if ( targets == "all" ) { + // + // $('.grid-item').show(); + // + // } else { + // + // $('.grid-item').each(function() { + // + // var $this = $(this); + // var thisCat = $(this).data("category"); + // + // if ( thisCat.indexOf(targets) >= 0 ) { + // $this.show(); + // } else { + // $this.hide(); + // } + // + // }); + // + // } + // + // galleryGrid.masonry(); + // + // console.log(targets); + // + // return false; + // }); + + if ($("#intro").length > 0) { + landing.runInitAnimationsHome(); + } else { + landing.runInitAnimations(); + } }); // $(window).load(function() {