diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index a2291a053..48156d350 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -39,7 +39,7 @@
3000
],
// Use 'postCreateCommand' to run commands after the container is created.
- "postCreateCommand": "sudo service redis-server start",
+ "postCreateCommand": "npm run lanraragi-installer install-front && sudo service redis-server start",
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "koyomi"
-}
+}
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
index ae1a43137..5e4312d9c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,5 +1,4 @@
tools/_screenshots
tools/Documentation
tools/build/windows
-tools/build/homebrew
-tools/build/vagrant
+tools/build/homebrew
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index 5ac783acf..652830df3 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,7 +5,8 @@
"jquery": true
},
"extends": [
- "airbnb-base"
+ "airbnb-base",
+ "eslint:recommended"
],
"parserOptions": {
"ecmaVersion": 12,
@@ -23,6 +24,7 @@
"IndexTable": "readonly",
"Swiper": "readonly"
},
+ "ignorePatterns": ["**/vendor/*.js"],
"rules": {
"func-names": ["error", "never"],
"indent": ["error", 4],
@@ -41,6 +43,8 @@
],
"one-var": "off",
"one-var-declaration-per-line": ["error", "initializations"],
- "prefer-destructuring": ["error", {"object": true, "array": false}]
+ "prefer-destructuring": ["error", {"object": true, "array": false}],
+ "function-paren-newline": "off",
+ "function-call-argument-newline": "off"
}
}
diff --git a/.github/workflows/push-brewtest.yml b/.github/workflows/push-brewtest.yml
index 173cfedf4..7caf9cc91 100644
--- a/.github/workflows/push-brewtest.yml
+++ b/.github/workflows/push-brewtest.yml
@@ -11,6 +11,5 @@ jobs:
cd tools/build/homebrew
echo "Replacing commit hash in formula with current hash $(git rev-parse --verify HEAD)"
sed -i.bck "s/COMMIT_HASH/$(git rev-parse --verify HEAD)/" Lanraragi.rb
- brew unlink node@14
brew install --force --verbose --build-from-source Lanraragi.rb
brew test --verbose Lanraragi.rb
\ No newline at end of file
diff --git a/.github/workflows/push-continous-delivery.yml b/.github/workflows/push-continous-delivery.yml
index 559409a60..0c5e7f4d0 100644
--- a/.github/workflows/push-continous-delivery.yml
+++ b/.github/workflows/push-continous-delivery.yml
@@ -3,6 +3,7 @@ on:
branches:
- dev
- test-builds
+ - actions-testing
name: Continuous Delivery
jobs:
buildNightlyDocker:
@@ -43,7 +44,7 @@ jobs:
- uses: actions/checkout@master
- name: Docker Build and export
run: |
- docker build -t difegue/lanraragi -f ./tools/build/docker/Dockerfile .
+ docker build -t difegue/lanraragi -f ./tools/build/docker/Dockerfile-legacy .
docker create --name rootfs difegue/lanraragi
docker export --output=package.tar rootfs
- name: Upload rootfs
diff --git a/README.md b/README.md
index 83eb343aa..817572ab9 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ LANraragi
Open source server for archival of comics/manga, running on Mojolicious + Redis.
-#### π¬ Talk with other fellow LANraragi Users on [Discord](https://discord.gg/aRQxtbg) or [Github Discussions](https://github.com/Difegue/LANraragi/discussions)
+#### π¬ Talk with other fellow LANraragi Users on [Discord](https://discord.gg/aRQxtbg) or [GitHub Discussions](https://github.com/Difegue/LANraragi/discussions)
#### [π Documentation](https://sugoi.gitbook.io/lanraragi/v/dev) | [β¬ Download](https://github.com/Difegue/LANraragi/releases/latest) | [π Demo](https://lrr.tvc-16.science) | [πͺπ Windows Nightlies](https://nightly.link/Difegue/LANraragi/workflows/push-continous-delivery/dev) | [π΅ Sponsor Development](https://ko-fi.com/T6T2UP5N)
diff --git a/lib/LANraragi/Controller/Api/Archive.pm b/lib/LANraragi/Controller/Api/Archive.pm
index f8372147f..45ecaf1e4 100644
--- a/lib/LANraragi/Controller/Api/Archive.pm
+++ b/lib/LANraragi/Controller/Api/Archive.pm
@@ -62,25 +62,12 @@ sub get_categories {
my $self = shift;
my $id = check_id_parameter( $self, "find_arc_categories" ) || return;
- my @categories = LANraragi::Model::Category->get_category_list;
- @categories = grep { %$_{"search"} eq "" } @categories;
-
- my @filteredcats = ();
-
- # Check if the id is in any categories
- for my $category (@categories) {
-
- my @archives = @{ $category->{"archives"} };
-
- if ( grep( /^$id$/, @archives ) ) {
- push @filteredcats, $category;
- }
- }
+ my @categories = LANraragi::Model::Category::get_categories_containing_archive($id);
$self->render(
json => {
operation => "find_arc_categories",
- categories => \@filteredcats,
+ categories => \@categories,
success => 1
}
);
diff --git a/lib/LANraragi/Controller/Api/Search.pm b/lib/LANraragi/Controller/Api/Search.pm
index fca79ef27..9ac081e75 100644
--- a/lib/LANraragi/Controller/Api/Search.pm
+++ b/lib/LANraragi/Controller/Api/Search.pm
@@ -71,8 +71,8 @@ sub handle_api {
my $start = $req->param('start');
my $sortkey = $req->param('sortby');
my $sortorder = $req->param('order');
- my $newfilter = $req->param('newonly');
- my $untaggedf = $req->param('untaggedonly');
+ my $newfilter = $req->param('newonly') || "false";
+ my $untaggedf = $req->param('untaggedonly') || "false";
$sortorder = ( $sortorder && $sortorder eq 'desc' ) ? 1 : 0;
diff --git a/lib/LANraragi/Controller/Batch.pm b/lib/LANraragi/Controller/Batch.pm
index 9495b5acf..f9cf55626 100644
--- a/lib/LANraragi/Controller/Batch.pm
+++ b/lib/LANraragi/Controller/Batch.pm
@@ -7,7 +7,7 @@ use Mojo::JSON qw(decode_json);
use LANraragi::Utils::Generic qw(generate_themes_header);
use LANraragi::Utils::Tags qw(rewrite_tags split_tags_to_array restore_CRLF);
-use LANraragi::Utils::Database qw(redis_decode get_computed_tagrules);
+use LANraragi::Utils::Database qw(get_computed_tagrules);
use LANraragi::Utils::Plugins qw(get_plugins get_plugin get_plugin_parameters);
use LANraragi::Utils::Logging qw(get_logger);
diff --git a/lib/LANraragi/Controller/Category.pm b/lib/LANraragi/Controller/Category.pm
index 10c27838f..54d554c95 100644
--- a/lib/LANraragi/Controller/Category.pm
+++ b/lib/LANraragi/Controller/Category.pm
@@ -37,7 +37,7 @@ sub index {
if ( -e $zipfile ) {
$arclist .=
- "
";
+ "";
$arclist .= "";
}
}
diff --git a/lib/LANraragi/Controller/Config.pm b/lib/LANraragi/Controller/Config.pm
index c4c430ae0..6ecfdb014 100644
--- a/lib/LANraragi/Controller/Config.pm
+++ b/lib/LANraragi/Controller/Config.pm
@@ -2,7 +2,7 @@ package LANraragi::Controller::Config;
use Mojo::Base 'Mojolicious::Controller';
use LANraragi::Utils::Generic qw(generate_themes_header remove_spaces remove_newlines);
-use LANraragi::Utils::Database qw(redis_encode redis_decode save_computed_tagrules);
+use LANraragi::Utils::Database qw(redis_encode save_computed_tagrules);
use LANraragi::Utils::TempFolder qw(get_tempsize);
use LANraragi::Utils::Tags qw(tags_rules_to_array replace_CRLF restore_CRLF);
use Mojo::JSON qw(encode_json);
@@ -42,6 +42,8 @@ sub index {
theme => $self->LRR_CONF->get_style,
usedateadded => $self->LRR_CONF->enable_dateadded,
usedatemodified => $self->LRR_CONF->use_lastmodified,
+ enablecryptofs => $self->LRR_CONF->enable_cryptofs,
+ hqthumbpages => $self->LRR_CONF->get_hqthumbpages,
csshead => generate_themes_header($self),
tempsize => get_tempsize
);
@@ -79,7 +81,9 @@ sub save_config {
tagruleson => ( scalar $self->req->param('tagruleson') ? '1' : '0' ),
nofunmode => ( scalar $self->req->param('nofunmode') ? '1' : '0' ),
usedateadded => ( scalar $self->req->param('usedateadded') ? '1' : '0' ),
- usedatemodified => ( scalar $self->req->param('usedatemodified') ? '1' : '0' )
+ usedatemodified => ( scalar $self->req->param('usedatemodified') ? '1' : '0' ),
+ enablecryptofs => ( scalar $self->req->param('enablecryptofs') ? '1' : '0' ),
+ hqthumbpages => ( scalar $self->req->param('hqthumbpages') ? '1' : '0' ),
);
# Only add newpassword field as password if enablepass = 1
diff --git a/lib/LANraragi/Controller/Login.pm b/lib/LANraragi/Controller/Login.pm
index 2c18ba5c0..c4250fce2 100644
--- a/lib/LANraragi/Controller/Login.pm
+++ b/lib/LANraragi/Controller/Login.pm
@@ -16,10 +16,16 @@ sub check {
my $ppr = Authen::Passphrase->from_rfc2307( $self->LRR_CONF->get_password );
if ( $ppr->match($pw) ) {
+
+ $self->LRR_LOGGER->info( "Successful login attempt from " . $self->tx->remote_address );
+
$self->session( is_logged => 1 );
$self->session( expiration => 60 * 60 * 24 );
$self->redirect_to('index');
} else {
+
+ $self->LRR_LOGGER->warn( "Failed login attempt with password '$pw' from " . $self->tx->remote_address );
+
$self->render(
template => "login",
title => $self->LRR_CONF->get_htmltitle,
@@ -50,7 +56,7 @@ sub logged_in_api {
# The API key is in the Authentication header.
my $expected_key = $self->LRR_CONF->get_apikey;
- my $auth_header = $self->req->headers->authorization || "";
+ my $auth_header = $self->req->headers->authorization || "";
my $expected_header = "Bearer " . encode_base64( $expected_key, "" );
return 1
diff --git a/lib/LANraragi/Controller/Reader.pm b/lib/LANraragi/Controller/Reader.pm
index 654d47bd8..f96516f7e 100644
--- a/lib/LANraragi/Controller/Reader.pm
+++ b/lib/LANraragi/Controller/Reader.pm
@@ -5,7 +5,6 @@ use Mojo::URL;
use Encode;
use LANraragi::Utils::Generic qw(generate_themes_header);
-use LANraragi::Utils::Database qw(redis_decode);
use LANraragi::Model::Reader;
@@ -24,7 +23,7 @@ sub index {
# Get query string from referrer URL, if there's one
my $referrer = $self->req->headers->referrer;
my $query = "";
-
+
if ($referrer) {
$query = Mojo::URL->new($referrer)->query->to_string;
}
diff --git a/lib/LANraragi/Controller/Upload.pm b/lib/LANraragi/Controller/Upload.pm
index 30d70975e..c473a65cb 100644
--- a/lib/LANraragi/Controller/Upload.pm
+++ b/lib/LANraragi/Controller/Upload.pm
@@ -7,7 +7,7 @@ use File::Copy;
use File::Find;
use File::Basename;
-use LANraragi::Utils::Generic qw(generate_themes_header is_archive);
+use LANraragi::Utils::Generic qw(generate_themes_header is_archive get_bytelength);
sub process_upload {
my $self = shift;
@@ -23,7 +23,19 @@ sub process_upload {
if ( is_archive($filename) ) {
# Move file to a temp folder (not the default LRR one)
- my $tempdir = tempdir();
+ my $tempdir = tempdir();
+
+ my ( $fn, $path, $ext ) = fileparse( $filename, qr/\.[^.]*/ );
+ my $byte_limit = LANraragi::Model::Config->enable_cryptofs ? 143 : 255;
+
+ # don't allow the main filename to exceed 143/255 bytes after accounting
+ # for extension and .upload prefix used by `handle_incoming_file`
+ $filename = $fn;
+ while ( get_bytelength( $filename . $ext . ".upload" ) > $byte_limit ) {
+ $filename = substr( $filename, 0, -1 );
+ }
+ $filename = $filename . $ext;
+
my $tempfile = $tempdir . '/' . $filename;
$file->move_to($tempfile) or die "Couldn't move uploaded file.";
@@ -44,11 +56,12 @@ sub process_upload {
# Reply with a reference to the job so the client can check on its progress.
$self->render(
json => {
- operation => "upload",
- name => $file->filename,
- type => $uploadMime,
- success => 1,
- job => $jobid
+ operation => "upload",
+ name => $file->filename,
+ debug_name => $filename,
+ type => $uploadMime,
+ success => 1,
+ job => $jobid
}
);
diff --git a/lib/LANraragi/Model/Archive.pm b/lib/LANraragi/Model/Archive.pm
index c2f6dfe7c..64043af48 100644
--- a/lib/LANraragi/Model/Archive.pm
+++ b/lib/LANraragi/Model/Archive.pm
@@ -155,7 +155,7 @@ sub update_thumbnail {
my $newthumb = "";
# Get the required thumbnail we want to make the main one
- eval { $newthumb = extract_thumbnail( $thumbdir, $id, $page ) };
+ eval { $newthumb = extract_thumbnail( $thumbdir, $id, $page, 1 ) };
if ( $@ || !$newthumb ) {
render_api_response( $self, "update_thumbnail", $@ );
diff --git a/lib/LANraragi/Model/Backup.pm b/lib/LANraragi/Model/Backup.pm
index 16f04f723..0764046bd 100644
--- a/lib/LANraragi/Model/Backup.pm
+++ b/lib/LANraragi/Model/Backup.pm
@@ -29,6 +29,7 @@ sub build_backup_JSON {
# Parse the category list and add them to JSON.
foreach my $key (@cats) {
+
# Use an eval block in case decode_json fails. This'll drop the category from the backup,
# But it's probably dinged anyways...
eval {
@@ -53,7 +54,7 @@ sub build_backup_JSON {
# Backup archives themselves next
my @keys = $redis->keys('????????????????????????????????????????'); #40-character long keys only => Archive IDs
- #Parse the archive list and add them to JSON.
+ # Parse the archive list and add them to JSON.
foreach my $id (@keys) {
eval {
@@ -63,7 +64,7 @@ sub build_backup_JSON {
( $_ = redis_decode($_) ) for ( $name, $title, $tags );
( remove_newlines($_) ) for ( $name, $title, $tags );
- #Backup all user-generated metadata, alongside the unique ID.
+ # Backup all user-generated metadata, alongside the unique ID.
my %arc = (
arcid => $id,
title => $title,
@@ -129,7 +130,7 @@ sub restore_from_JSON {
$redis->hset( $id, "tags", $tags );
if ( $redis->hexists( $id, "thumbhash" )
- && $redis->hget( $id, "thumbhash" ) eq "" ) {
+ && $redis->hget( $id, "thumbhash" ) ne "" ) {
$redis->hset( $id, "thumbhash", $thumbhash );
}
diff --git a/lib/LANraragi/Model/Category.pm b/lib/LANraragi/Model/Category.pm
index 404681ad7..71c8f1766 100644
--- a/lib/LANraragi/Model/Category.pm
+++ b/lib/LANraragi/Model/Category.pm
@@ -29,6 +29,32 @@ sub get_category_list {
return @result;
}
+# get_categories_containing_archive(id)
+# Returns a list of all the categories that contain the given archive.
+sub get_categories_containing_archive {
+ my $archive_id = shift;
+
+ my $logger = get_logger( "Categories", "lanraragi" );
+ $logger->debug("Finding categories containing $archive_id");
+
+ my @categories = get_category_list();
+ @categories = grep { %$_{"search"} eq "" } @categories;
+
+ my @filteredcats = ();
+
+ # Check if the id is in any categories
+ for my $category (@categories) {
+ my @archives = @{ $category->{"archives"} };
+
+ if ( grep( /^$archive_id$/, @archives ) ) {
+ $logger->debug( "$archive_id is in '" . $category->{name} . "'" );
+ push @filteredcats, $category;
+ }
+ }
+
+ return @filteredcats;
+}
+
# get_category(id)
# Returns the category matching the given id.
# Returns undef if the id doesn't exist.
diff --git a/lib/LANraragi/Model/Config.pm b/lib/LANraragi/Model/Config.pm
index 8ef364d01..48991c031 100644
--- a/lib/LANraragi/Model/Config.pm
+++ b/lib/LANraragi/Model/Config.pm
@@ -147,5 +147,7 @@ sub get_readquality { return &get_redis_conf( "readerquality", "50" ) }
sub get_style { return &get_redis_conf( "theme", "modern.css" ) }
sub enable_dateadded { return &get_redis_conf( "usedateadded", "1" ) }
sub use_lastmodified { return &get_redis_conf( "usedatemodified", "0" ) }
+sub enable_cryptofs { return &get_redis_conf( "enablecryptofs", "0" ) }
+sub get_hqthumbpages { return &get_redis_conf( "hqthumbpages", "0" ) }
1;
diff --git a/lib/LANraragi/Model/Plugins.pm b/lib/LANraragi/Model/Plugins.pm
index 5231a81bd..e6775a5f3 100644
--- a/lib/LANraragi/Model/Plugins.pm
+++ b/lib/LANraragi/Model/Plugins.pm
@@ -208,7 +208,7 @@ sub exec_metadata_plugin {
my $thumbdir = LANraragi::Model::Config->get_thumbdir;
# Eval the thumbnail extraction, as it can error out and die
- eval { extract_thumbnail( $thumbdir, $id, 0 ) };
+ eval { extract_thumbnail( $thumbdir, $id, 0, 1 ) };
if ($@) {
$logger->warn("Error building thumbnail: $@");
$thumbhash = "";
@@ -226,6 +226,7 @@ sub exec_metadata_plugin {
# Bundle all the potentially interesting info in a hash
my %infohash = (
+ archive_id => $id,
archive_title => $title,
existing_tags => $tags,
thumbnail_hash => $thumbhash,
diff --git a/lib/LANraragi/Model/Reader.pm b/lib/LANraragi/Model/Reader.pm
index 6990a12a7..64f67a08d 100644
--- a/lib/LANraragi/Model/Reader.pm
+++ b/lib/LANraragi/Model/Reader.pm
@@ -27,6 +27,12 @@ sub resize_image {
#Is the file size higher than the threshold?
if ( ( int( ( -s $imgpath ) / 1024 * 10 ) / 10 ) > $threshold ) {
+
+ # For JPEG, the size option (or jpeg:size option) provides a hint to the JPEG decoder
+ # that it can reduce the size on-the-fly during decoding. This saves memory because
+ # it never has to allocate memory for the full-sized image
+ $img->Set( option => 'jpeg:size=1064x' );
+
$img->Read($imgpath);
my ( $origw, $origh ) = $img->Get( 'width', 'height' );
diff --git a/lib/LANraragi/Model/Upload.pm b/lib/LANraragi/Model/Upload.pm
index f63474792..27f6a368f 100644
--- a/lib/LANraragi/Model/Upload.pm
+++ b/lib/LANraragi/Model/Upload.pm
@@ -13,7 +13,7 @@ use File::Copy qw(move);
use LANraragi::Utils::Database qw(invalidate_cache compute_id);
use LANraragi::Utils::Logging qw(get_logger);
use LANraragi::Utils::Database qw(redis_encode);
-use LANraragi::Utils::Generic qw(is_archive remove_spaces remove_newlines trim_url);
+use LANraragi::Utils::Generic qw(is_archive remove_spaces remove_newlines trim_url get_bytelength);
use LANraragi::Model::Config;
use LANraragi::Model::Plugins;
@@ -179,10 +179,14 @@ sub download_url {
$filename =~ s@[\\/:"*?<>|]+@@g;
my ( $fn, $path, $ext ) = fileparse( $filename, qr/\.[^.]*/ );
+ my $byte_limit = LANraragi::Model::Config->enable_cryptofs ? 143 : 255;
- # don't allow the main filename to exceed 255 chars after accounting
+ # don't allow the main filename to exceed the given byte limit
# for extension and .upload prefix used by `handle_incoming_file`
- $filename = substr $fn, 0, 254 - length($ext) - length(".upload");
+ $filename = $fn;
+ while ( get_bytelength( $filename . $ext . ".upload" ) > $byte_limit ) {
+ $filename = substr( $filename, 0, -1 );
+ }
$filename = $filename . $ext;
$logger->debug("Filename post clean: $filename");
$tx->result->save_to("$tempdir\/$filename");
diff --git a/lib/LANraragi/Plugin/Login/nHentai.pm b/lib/LANraragi/Plugin/Login/nHentai.pm
new file mode 100644
index 000000000..92b01f85a
--- /dev/null
+++ b/lib/LANraragi/Plugin/Login/nHentai.pm
@@ -0,0 +1,83 @@
+package LANraragi::Plugin::Login::nHentai;
+
+use strict;
+use warnings;
+no warnings 'uninitialized';
+
+use Mojo::UserAgent;
+use LANraragi::Utils::Logging qw(get_logger);
+
+#Meta-information about your plugin.
+sub plugin_info {
+
+ return (
+ #Standard metadata
+ name => "nHentai CF Bypass",
+ type => "login",
+ namespace => "nhentaicfbypass",
+ author => "Pheromir",
+ version => "0.1",
+ description =>
+ "Bypasses the Cloudflare Javascript-challenge by re-using cookies from your browser. Both CF cookies and the user-agent must originate from the same webbrowser.",
+ parameters => [
+ { type => "string", desc => "Browser UserAgent string (Can be found at http://useragentstring.com/ for your browser)" },
+ { type => "string", desc => "csrftoken cookie for domain nhentai.net" },
+ { type => "string", desc => "cf_clearance cookie for domain nhentai.net" }
+ ]
+ );
+
+}
+
+
+# Mandatory function to be implemented by your login plugin
+# Returns a Mojo::UserAgent object only!
+sub do_login {
+
+ # Login plugins only receive the parameters entered by the user.
+ shift;
+ my ( $useragent, $csrftoken, $cf_clearance ) = @_;
+ return get_user_agent( $useragent, $csrftoken, $cf_clearance );
+}
+
+# get_user_agent(useragent, cf cookies)
+# Try crafting a Mojo::UserAgent object that can access nHentai.
+# Returns the UA object created.
+sub get_user_agent {
+
+ my ( $useragent, $csrftoken, $cf_clearance ) = @_;
+
+ my $logger = get_logger( "nHentai Cloudflare Bypass", "plugins" );
+ my $ua = Mojo::UserAgent->new;
+
+ if ( $useragent ne "" && $csrftoken ne "" && $cf_clearance ne "") {
+ $logger->info("Useragent and Cookies provided ($useragent $csrftoken $cf_clearance)!");
+ $ua->transactor->name($useragent);
+
+ #Setup the needed cookies
+ $ua->cookie_jar->add(
+ Mojo::Cookie::Response->new(
+ name => 'csrftoken',
+ value => $csrftoken,
+ domain => 'nhentai.net',
+ path => '/'
+ )
+ );
+
+ $ua->cookie_jar->add(
+ Mojo::Cookie::Response->new(
+ name => 'cf_clearance',
+ value => $cf_clearance,
+ domain => 'nhentai.net',
+ path => '/'
+ )
+ );
+
+ } else {
+ $logger->info("No cookies provided, returning blank UserAgent.");
+ }
+
+ return $ua;
+
+}
+
+1;
\ No newline at end of file
diff --git a/lib/LANraragi/Plugin/Metadata/EHentai.pm b/lib/LANraragi/Plugin/Metadata/EHentai.pm
index 77d2dba2e..5df933036 100644
--- a/lib/LANraragi/Plugin/Metadata/EHentai.pm
+++ b/lib/LANraragi/Plugin/Metadata/EHentai.pm
@@ -20,13 +20,14 @@ sub plugin_info {
return (
#Standard metadata
- name => "E-Hentai",
- type => "metadata",
- namespace => "ehplugin",
- login_from => "ehlogin",
- author => "Difegue and others",
- version => "2.5.1",
- description => "Searches g.e-hentai for tags matching your archive.",
+ name => "E-Hentai",
+ type => "metadata",
+ namespace => "ehplugin",
+ login_from => "ehlogin",
+ author => "Difegue and others",
+ version => "2.5.1",
+ description =>
+ "Searches g.e-hentai for tags matching your archive.
This plugin will use the source: tag of the archive if it exists.",
icon =>
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI\nWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wYBFg0JvyFIYgAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl\nYXRlZCB3aXRoIEdJTVBkLmUHAAAEo0lEQVQ4y02UPWhT7RvGf8/5yMkxMU2NKaYIFtKAHxWloYNU\ncRDeQTsUFPwAFwUHByu4ODq4Oghdiri8UIrooCC0Lx01ONSKfYOioi1WpWmaxtTm5PTkfNzv0H/D\n/9oeePjdPNd13Y8aHR2VR48eEUURpmmiaRqmaXbOAK7r4vs+IsLk5CSTk5P4vo9hGIgIsViMra0t\nCoUCRi6XY8+ePVSrVTRN61yybZuXL1/y7t078vk8mUyGvXv3cuLECWZnZ1lbW6PdbpNIJHAcB8uy\nePr0KYZlWTSbTRKJBLquo5TCMAwmJia4f/8+Sini8Ti1Wo0oikin09i2TbPZJJPJUK/XefDgAefO\nnWNlZQVD0zSUUvi+TxAE6LqOrut8/fqVTCaDbdvkcjk0TSOdTrOysoLrujiOw+bmJmEYMjAwQLVa\nJZVKYXR1ddFut/F9H9M0MU0T3/dZXV3FdV36+/vp7u7m6NGj7Nq1i0qlwuLiIqVSib6+Pubn5wGw\nbZtYLIaxMymVSuH7PpZlEUURSina7TZBEOD7Pp8/fyYMQ3zfZ25ujv3795NOp3n48CE9PT3ouk4Q\nBBi/fv3Ctm0cx6Grq4utrS26u7sREQzDIIoifv78SU9PD5VKhTAMGRoaYnV1leHhYa5evUoQBIRh\niIigiQhRFKHrOs1mE9u2iaKIkydPYhgGAKZp8v79e+LxOPl8Htd1uXbtGrdv3yYMQ3ZyAODFixeb\nrVZLvn//Lq7rSqVSkfX1dREROXz4sBw/flyUUjI6OipXrlyRQ4cOSbPZlCiKxHVdCcNQHMcRz/PE\ndV0BGL53756sra1JrVaT9fV1cRxHRESGhoakr69PUqmUvHr1SsrlsuzI931ptVriuq78+fNHPM+T\nVqslhoikjh075p09e9ba6aKu6/T39zM4OMjS0hIzMzM0Gg12794N0LEIwPd9YrEYrusShiEK4Nmz\nZ41yudyVy+XI5/MMDAyQzWap1+tks1lEhIWFBQqFArZto5QiCAJc1+14t7m5STweRwOo1WoSBAEj\nIyMUi0WSySQiQiqV6lRoYWGhY3673e7sfRAEiAjZbBbHcbaBb9++5cCBA2SzWZLJJLZt43kesViM\nHX379g1d1wnDsNNVEQEgCAIajQZ3797dBi4tLWGaJq7rYpompVKJmZkZ2u12B3j58mWUUmiahoiw\nsbFBEASdD2VsbIwnT55gACil+PHjB7Ozs0xPT/P7929u3ryJZVmEYUgYhhQKBZRSiAie52EYBkop\nLMvi8ePHTE1NUSwWt0OZn5/3hoeHzRs3bqhcLseXL1+YmJjowGzbRtO07RT/F8jO09+8ecP58+dJ\nJBKcPn0abW5uThWLRevOnTv/Li4u8vr1a3p7e9E0jXg8zsePHymVSnz69Kmzr7quY9s2U1NTXLp0\nCc/zOHLkCPv27UPxf6rX63+NjIz8IyKMj48zPT3NwYMHGRwcpLe3FwARodVqcf36dS5evMj4+DhB\nEHDmzBkymQz6DqxSqZDNZr8tLy//DYzdunWL5eVlqtUqHz58IJVKkUwmaTQalMtlLly4gIjw/Plz\nTp06RT6fZ2Njg/8AqMV7tO07rnsAAAAASUVORK5CYII=",
parameters => [
diff --git a/lib/LANraragi/Plugin/Metadata/Hitomi.pm b/lib/LANraragi/Plugin/Metadata/Hitomi.pm
new file mode 100644
index 000000000..7377dbddc
--- /dev/null
+++ b/lib/LANraragi/Plugin/Metadata/Hitomi.pm
@@ -0,0 +1,236 @@
+package LANraragi::Plugin::Metadata::Hitomi;
+
+use strict;
+use warnings;
+
+#Plugins can freely use all Perl packages already installed on the system
+#Try however to restrain yourself to the ones already installed for LRR (see tools/cpanfile) to avoid extra installations by the end-user.
+use URI::Escape;
+use Mojo::UserAgent;
+use Mojo::JSON qw(decode_json);
+
+#You can also use the LRR Internal API when fitting.
+use LANraragi::Model::Plugins;
+use LANraragi::Utils::Logging qw(get_plugin_logger);
+
+#Meta-information about your plugin.
+sub plugin_info {
+
+ return (
+ #Standard metadata
+ name => "Hitomi",
+ type => "metadata",
+ namespace => "hitomiplugin",
+ author => "doublewelp",
+ version => "0.1",
+ description => "Searches Hitomi.la for tags matching your archive.
+
Supports reading the ID from files formatted as \"{Id} Title\" (curly brackets optional)
+
This plugin will use the source: tag of the archive if it exists (ex.: source:https://hitomi.la/XXXXX/XXXXX).",
+ parameters => [{ type => "bool", desc => "Save archive title" }],
+ icon =>
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIsAAAAoCAYAAADOkQm/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABDYSURBVHja7Jx5dBRVvsc/Vd1ZSEJIgixhE5C1RZYe2dEWxgXXByIuOKLo+DS+o4Py3MenwijwEAUVl0HGZRQFQXFBR46jUyM7pFgiBY8lkLAEQvaNTndX1/ujq4uq6iUBFckxv3P6nKpbv3vr1u9+72+797agaRrN1EyNIae9wOX2pAGjgA6ABniB1YosHWwW12+bBLNmcbk9/w3MicErAzmKLG1sFttvHCy6RqlugF9WZOl3zWJrNkOjwhffff0xrdLT0dBQAyqff7WK52bPB3C73J4URZbqmkX32wbLZQCCINC+XVsCgUCoNCGRtNRUc51k4IyBxeX2pAAORZaqm4frLNMsF40ciqqqOJ0nH7VsmWaukwKU/cIAcQJjgWcBt162TfeZ1jUP268PljoAr9cbwaQFg5bbXxAkA4HXgWFRHg/Qnw1sHrZGybItMAjoDhwB1iqydPxnDZ1/ZbIAJTkpiWlT7yXB6eSZ5+YCDHC5PYMUWdrSDIeYIBmjy7FXlGf/p2vn70+nbfEs+khnGCgPPXAPK5a8Te7ab7hl4jjGX3cl0RzxZoqQ4Z3A19GAolNv4DOX2zOxSYMFyA5fnNe9K716dkcQBERRRBDEs1kbhgdK+JXf/zCwCEgEaJXoZOA56VzepQ2D22aQnmiIrSWw1OX2jD4TZugml9szCmir3/uBXYSyvB/E+Zh0XSv00f2eUr1Ovs7SMfYrLW6S5nJ7pup1N8d533D9fcNMfRV032wPsBr4XJGl2gYGYajeTkCfXKsVWdrkcnvOBcbpz7KBkS63Zw2wCpAUWZJs7fQHrtL5M/RiL5Cnt7k8Th+66vXa6N+wW69ToT+/FPjfMH+PVqnc5erCea1SCSN4d0UNr/14gMM1hk/6usvtGa/LpxOwxdxmNDIn5b4HLhly4UAWvf4iDofDYPrn9z9w/7Q/NxZMOYosvWH72EeA2TH4NwLnAa3DBQvmzWT0xSMMhkAgQP8hv49W96D+vpWmd90FvGAakIZos97GZlufr9Ztf+codYrMmjAG7QFydJC9DvRtgL9a78cHpj4M0usOjVHncUWWZrncnj1AD4BBbVoxdUB3UpyOCOZjdfU8sX4nVb5AvH78qPdj9c/i4Pbu1YPWWRnGQObvL6SktMyM2Mn6C7fpGmV2nOaG2AsqKqss90FrNGamzrowu7jcnsv06/Msfe15Hll6XwVBwOut50DhIcrKysMsFwKbXG7PQ4osvWRztjs3ZDJ79ujGOa2zuNA9kDXrNiJvzTMeAd/aK/Y7vw/peiqivt7H7r35VFfXhM3D+y6352JFlu4x9WFoHNnNdLk9FWGgZCQlMKVP56hAAWiXksS47tm8tyvuMl8/4AeX2zNVkaX5pw2WZ56cxtVXXkpSYhKCzUIXHjzMbXfdT1l5BcBw/UNHAOEP51//WE7r1ploGtTX17P0ky+Y89JrAPx90SsM7N+PoBYkwWntliiKbN3wLaLppWvXb+beBx4F6KyD893ws44d2jPn+f+hf7++BINBBFM9Tdfjx4+Xsuqf/2bW3FfCj150uT1tFFl6QjcxnQHeeHk2I4cPJqSBBTZu3sIf75vGww/exw3jr6FFcpLR/t133opDFPnm23/x0KPPWL5h1ownGXvZaN0HO9kXURDYsXM3t9/9p3Da4j9dbk8BoTW6oQDT/nQvN024juTkZAQBysor8Fx+vTEshn/QowPZqclxx3DQOeksdYh41SAd05Lp1SqN1skJ7K+qY1dFDbV+Ncw6T3eEcxRZyms0WMrKK1m2eCF9evVAFKP7xN26dkFa9QmL3v2Qea8uBBjucnuuCUcvrj69yMrKNMybw9HCmGHh5w6HiCOKz+10RnazT+8e5lsDKPPmTGeMZxROfXbF6m/79m35wy3Xc+mYixh/853h2f24y+05Auw3v8dskrt368LLL/yF0Z4RlnIzXXHpJXy7cilXXHszCYmJrPzkPdq3a2sBrZkuOL8PG6QvGX3lxLDGew74Kvw8LTWF1NQUo35mRgb9+7nY/qMC0A4gNcHB+VktGxzLrOREOqW1wNOxNb/vdA4JJvkcqfXyllJIXqmh2Ufqk35Uo6IhTdMYc8lI+vbuGVPwYXI4HNw95VaSEhPDRa8B9QA+vz+i3WDQ5LieYiwRTfDLFi/ksjEXG0BpMBQURTpkt+PblUtITzcE/QqgxjKBWZkZjPaMjAmUcN86ZLdj3pzpLFu8kOz27WICJUwJCQmsWPI3e87JIi+zw+/z+yz10xKcpCU2PPeTHCKPuHswtktbC1AAOqQmM3VAd7JTks3DMdLl9iQ1CiyCINA6KxNBEFBVlerqGuSteWzK3UppWTmqqkbwz39hhtmnaBTtP1BIMBhE07SINgOqiqZplt/+A4UWnr++Ooe+vXtaBiUYDFLv87F7Tz4/rN1A3o5dVFRUEghY22+ZlsbS9980F82IN6gOh4iqqpSXV7Apdyt5O3bht00GAM9FI+japZMx2IFAAHlrHpvlbZRXVEZ8Z1ZmBg89YFjtYacyeQJBjUCw4eS6QxDITEqI+Tw90cnlXdrQr3W65VNOyWcJBAJ8/MkXzJg1z1L+1GNTuXHCdZaZNnSw+5Sd5hsm3U1qagp+v5/pTz3CdVdffnLQ1SCDhl2G0+EwwOCtr7eYsGFDfmcBiqqqLF3+eUR/AQb2P58P3l5g4e/UIZvHpt0f9mEujDd5VFVl2adf8uzzL1rKly1eSN/ePU3m02EAZVuewqQ77rO09ZenH2XctWMNjS2KIqOGD+HFl988FdHVASmlXh9Ftd64QGgs9cxIxasGzeZoFLCqUUk5TdNYvmJlVMHPmDWPw0VHI8xR504dTrmTtbV1+Hx++8IlohgaoHqfD299vQUoAHNnPW0xPaqq8tY7i6P2F2Dr9h1cO2HyyZV1faCuverSRvVzy7YfLUAJy2jKPQ+iRonc6upORAAF4M/PzsYfsIaxXbt2PlWxHQpffH+49GdJ8CU5RFKcFmikNzqDG1BVs3B26yrSUJPrN+RabKogQMcORnTZvpF9HN9Ivtn6bxNA2zbn0KmjNeWxL7+A+QveMsZW72s3QouQnwHkHyhkx87dVnPUsiXdunZpQMOqTJ9pRNgleuR3B0BVVTWlJWURYf+HH68wF90CXGQkNXbssg5UYmJcfygKFYQv1hSVsbO85ieDpczr52idZULuazRYFKtQb1dkaYMiSxuAbQCfr1wV4QiKonCqSwq5jeQ7CjwNDAaYcttNNvMTDEdjAD7gDr2/BxRZ2gYYMef6jbkWv0EUBW6ZOK4BLRtk7z4jWJqhyNJ6YKuR+DpeEqFx3vn7EuOViix9BCiGWjhcFOkXOU8po1GrJxbxB4O8u+sgPjX4k8Cyp6KGvZW19sRp4wbyWPHxqEgOX3u99dgPCZg0zS+xpcHIzrZr18YWpWnhkBJgoSJL2y3Al6UgsBZg2adfWvotCALZ7dvGT7PW1NozyABGhq/0ZLLPCPP03BPAchOIQ76X14v9hMVpnLjI0ZcO2FdZy0Kl8LQFW+MPsKaonD0Vxnf6dcXQOLA4rCGWiC3gDc3sM3qkpJ2RgMtuHyUvZAzO/hj1N4TNhkkDIggCmZnxVwlsGlS1Twj7QAc1C7/PnihoKKRulOYPLVXkGMnPwyWsO1p+Wm19tOcIRXXG+pHf3O7ZtOp8KmQYddFh/QSbg+mNE0FwwlsfMVgNmQAbf4MjLcS9/flIkaV3gLnh+xX5RY0KpQ1b5ldZpBTyTWGxufgmRZYWnVa6/ywiI6nh91ujicQES+iYFc+MpbdMIxgMWhzK8opKmjA9rmuClPyqOraXVuFu0yqC6URApazeT2W9nxKvj9ziSpTyairqLbmi1xVZ+tSSSW+iQjl60kE8wsD+51vyQd27nUv+/gJ0ZzZagu3isL9jNhuaplF09FiTRYoiS36X2zMDmAnw3aESAyw+NciOsmq2lVax8Vg51b4A/qCGGt0/elORpYhYvymaIc3sIB49dtwS0TgcDm67ZYKRf9N3j51M4Lk91wMXAEy6cbxFq6hqkOWffUUTJ2O7RlGdl1q/ypqiMp5Yv5Pnc/ew8sAxjp/w4VWD0YCyF7hKkaV7ozXcJMBi8xPuBAxbMX/BW5ZoSBAErh93tRkEi1xuj+xye+a63J53TREJYy8fbWnb5/dF5D2aIB0IXxw/4ePl7fm8mrefguoTsfjXA/cCHRVZ6qnI0texGJuMZmndOjN82d/i0Koqm+VtEU7q8sVvmYEwCHgImBwueOev80hp0cJigr7+5rumDhT081Xbwr6JfLzS7OhWAU/qOaqWQIIiS8MVWXpTkaUjDbV9NvkshrY4UHAQTdOMwRZFkVdffJ4nn56J3x/gj1NuJSkpkceeeg6A+6Y+zsZ/f2XREr16dmfz6n+w4ouv+WDJpxQXl5CZ0YqePbvzxMMPRGwZqKyq5qnpxs7EvLCpaqKUQ2jVeoAtsZajyJJ8uo2ebQ7uRmDI+x8u57ZJN+DUTYkgCAy4wMWKpW8DAqIomHfmUVtbx9/e+4jbb73RskbUokUyN08cx8Trr0UURYJBDVEUIrZaBIMa10yYbC56hNAu+aaqXdbp/lp43/NqRZaqfmq7Tvt1tHUJwSpcIXodawrB1E7UdmMko94AhhQdPcb2PAX3QOvkNm+CstefO/8NRFFk8qQbLO8SBMGoF207jqqqeK6YYN5mOd1snkXryQL7e8WGkmy2+wYXfTRNi8gdRYpdMCZSvAmvA+Rn89jNvToMcKy4xLJvJBgM2vdqmBMbRQDHS0pBwFKvuLjE0m5JaSmCiSe0d8V60lGRpbfRj8ZOuedBCg8eRo2xzmHK0gLsBJjz0muMHnsDR48VW1aUow2Iqqrk7djF4FFXmoHylSJLTxNabwll9errLd9Vb13xrrXLJOAPGPtyNE3D57PIrsIU0Rl9MfMDnDjhtWm+kKzCPIKgy9w0BmeCzKhcDdyUv7+AjZu2kJHZClUNAcW22FVqq3Nn0dFjrNuwmazMDDQtlEbfvTffzDOhtLScNes2kZWVqQvkhH1jdp3J3i7x+/2M/Y9JLJg3E/fAC2il72Tz+/0UFB5i9VrL38T8QbfRQ0pKyxhz5USeePgBRgwfTKeO2ZZEXV3dCfblH+D+aX+m2Lro944iS1P0a8Our9+YS3VNDUE1iCCK7Ny1xx5JABiLZwUHD7EtT8HpdCIIAgcKLOs0q23fSkVlFfLWPJKTkxFFIerCYlV1DblbtpOSkhKaKGXlHDs5GVefsajUdBTEARQS+senWLRBkaVhtrzFQULnTqJREaGtAXuIv2tunSJLI0xt/hfwqp3JIYrR9osYfYr1Z0SiKFpmro0ijmDobW21OYh22q7I0gAT/3ri78TfpchSXxP/GkIb2mPRXqC4AZ6Diix1OVNgEU32TdVndawQagOmRSWb530oBlByFFmq13linT9YZ29XkaUFhA5kFVj8iyhAMddVZOkFQkdLciPVeARQqoDJiiylxzgclxMOQaMBJYoscvT+RAVKDP61cYCS0wDPwRjj8ctrFtus6k5o/UQltA6zR5Elf7yG9FNzmbo9rlZkaV8UnnN1HoATwF4dpPHa7azPrj66g1hMaP9Gbrw+udyeFoT+rmMgoROJmv4rIPQPVtsbIyCX29OS0LkcVZ9c+fEiC5fbk0DorLFT90oPKbJUEodfJHTGKFlvv0iRpaNReHoQ+rsTgHJFlgrOeHK0+d8qm+l0oqFmaqa49P8DAD4ZCBrP5ktjAAAAAElFTkSuQmCC",
+ oneshot_arg => "Hitomi Gallery URL (Will attach tags matching this exact gallery to your archive)"
+ );
+
+}
+
+#Mandatory function to be implemented by your plugin
+sub get_tags {
+
+ shift;
+ my $lrr_info = shift; # Global info hash
+ my ($savetitle) = @_; # Plugin parameters
+ my $logger = get_plugin_logger();
+
+ # Work your magic here - You can create subs below to organize the code better
+ my $galleryID = "";
+
+ # Quick regex to get the hitomi gallery id from the provided url or source tag.
+ $logger->debug("Input param is " . $lrr_info->{oneshot_param});
+ $logger->debug("Regex is " . "/[.*-|.*\/]([0-9]+)\.html.*/");
+ if ( $lrr_info->{oneshot_param} =~ /[.*-|.*\/]([0-9]+)\.html.*/ ) {
+ $galleryID = $1;
+ $logger->debug("Skipping search and using gallery $galleryID from oneshot args");
+ } elsif ( $lrr_info->{existing_tags} =~ /.*source:\s*hitomi\.la\/(?:manga|doujinshi|galleries|cg)\/.*-?([0-9]*)\.html.*/gi ) {
+
+ $galleryID = $1;
+ $logger->debug("Skipping search and using gallery $galleryID from source tag");
+ } else {
+
+ #Get Gallery ID by hand if the user didn't specify a URL
+ $galleryID = get_gallery_id_from_title( $lrr_info->{archive_title} );
+ }
+
+ # Did we detect a Hitomi gallery?
+ if ( defined $galleryID ) {
+ $logger->debug("Detected Hitomi gallery id is $galleryID");
+ } else {
+ $logger->info("No matching Hitomi Gallery Found!");
+ return ( error => "No matching Hitomi Gallery Found!" );
+ }
+
+ #If no tokens were found, return a hash containing an error message.
+ #LRR will display that error to the client.
+ if ( $galleryID eq "" ) {
+ $logger->info("No matching Hitomi Gallery Found!");
+ return ( error => "No matching Hitomi Gallery Found!" );
+ }
+
+ my %hashdata = get_tags_from_Hitomi( $galleryID, $savetitle);
+
+ $logger->info( "Sending the following tags to LRR: " . $hashdata{tags} );
+
+ #Return a hash containing the new metadata - it will be integrated in LRR.
+ return %hashdata;
+}
+
+######
+## Hitomi Specific Methods
+######
+
+sub get_gallery_id_from_title {
+
+ my ($title) = @_;
+
+ my $logger = get_plugin_logger();
+
+ $logger->debug("Attempting to parse id from title $title");
+ if ( $title =~ /\{?(\d+)\}?/gm ) {
+ $logger->debug("Got $1 from file.");
+ return $1;
+ }
+
+ return;
+}
+
+# retrieves js from Hitomi
+sub get_js_from_hitomi {
+
+ my ($gID) = @_;
+
+ my $logger = get_plugin_logger();
+
+ my $gJS = "https://ltn.hitomi.la/galleries/$gID.js";
+
+ $logger->debug("Hitomi JS: $gJS");
+
+ my $ua = Mojo::UserAgent->new;
+ my $res = $ua->get($gJS)->result;
+ $logger->debug("Hitomi raw JS: ". $res->body);
+
+ if ( $res->is_error ) {
+ return;
+ }
+
+ my $jsonstring = "{}";
+ if ( $res->body =~ /var.*galleryinfo.*= (.*)/gmi ) {
+ $jsonstring = $1;
+ }
+
+ $logger->debug("Tentative new JSON: $jsonstring");
+
+ $logger->debug("Beginning JSON decode");
+ my $json = decode_json $jsonstring;
+ $logger->debug("JSON decode successful");
+ return $json;
+
+}
+
+#Extract tags from Hitomi JSON
+sub get_tags_from_taglist {
+
+ my ($json) = @_;
+
+ my $logger = get_plugin_logger();
+
+ my @tags = ();
+
+ if(defined $json->{"tags"}) {
+ $logger->debug("Extracting tags array");
+ my @tags_list = @{ $json->{"tags"} };
+
+ $logger->debug("Cycling tags array");
+ foreach my $tag (@tags_list) {
+ my $name = $tag->{"tag"};
+ my $male = $tag->{"male"};
+ my $female = $tag->{"female"};
+
+ if($male){
+ $name = "male:$name";
+ }
+
+ if($female){
+ $name = "female:$name";
+ }
+
+ push( @tags, $name );
+ }
+ }
+
+ if(defined $json->{"parodys"}) {
+ push (@tags, extract_tags($logger, $json, "parodys", "parody"));
+ }
+
+ if(defined $json->{"artists"}) {
+ push (@tags, extract_tags($logger, $json, "artists", "artist"));
+ }
+
+ if(defined $json->{"groups"}) {
+ push (@tags, extract_tags($logger, $json, "groups", "group"));
+ }
+
+ if(defined $json->{"characters"}) {
+ push (@tags, extract_tags($logger, $json, "characters", "character"));
+ }
+
+ $logger->debug("Extracting type value");
+ push( @tags, "type:" . $json->{"type"});
+
+ $logger->debug("Extracting language value");
+ push( @tags, "language:" . $json->{"language"});
+
+ return @tags;
+}
+
+sub extract_tags {
+ my ( $logger, $json, $arrayname, $namespace) = @_;
+ my @tags;
+
+ $logger->debug("Extracting $arrayname array");
+ my @list = @{ $json->{$arrayname} };
+ $logger->debug("Cycling $arrayname array");
+
+ foreach my $tag (@list) {
+ my $name = $tag->{$namespace};
+ push( @tags, "$namespace:$name");
+ }
+ return @tags;
+}
+
+sub get_title_from_json {
+ my ($json) = @_;
+ return $json->{"title"};
+}
+
+sub get_tags_from_Hitomi {
+
+ my ( $gID , $savetitle) = @_;
+
+ my %hashdata = ( tags => "" );
+
+ my $logger = get_plugin_logger();
+
+ my $json = get_js_from_hitomi($gID);
+ $logger->debug("Got fully formed JS from Hitomi");
+
+ if ($json) {
+ my @tags = get_tags_from_taglist($json);
+ push( @tags, "source:https://hitomi.la/galleries/$gID.html" ) if ( @tags > 0 );
+ $hashdata{tags} = join( ', ', @tags );
+ $hashdata{title} = get_title_from_json($json) if ($savetitle);
+ }
+
+ return %hashdata;
+}
+
+1;
diff --git a/lib/LANraragi/Plugin/Metadata/MEMS.pm b/lib/LANraragi/Plugin/Metadata/MEMS.pm
index efacd2d29..702c3f5d3 100644
--- a/lib/LANraragi/Plugin/Metadata/MEMS.pm
+++ b/lib/LANraragi/Plugin/Metadata/MEMS.pm
@@ -12,8 +12,9 @@ sub plugin_info {
name => 'Mayriad\'s EH Master Script',
type => 'metadata',
namespace => 'memsplugin',
+ login_from => "ehlogin",
author => 'Mayriad',
- version => '1.1.0',
+ version => '1.1.1',
description => 'Accurately retrieves metadata from e-hentai.org using the identifiers appeneded to the '
. 'filenames of archives downloaded by Mayriad\'s EH Master Script.',
icon => 'data:image/png;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAA'
diff --git a/lib/LANraragi/Plugin/Metadata/nHentai.pm b/lib/LANraragi/Plugin/Metadata/nHentai.pm
index 861cd37be..65f712a57 100644
--- a/lib/LANraragi/Plugin/Metadata/nHentai.pm
+++ b/lib/LANraragi/Plugin/Metadata/nHentai.pm
@@ -21,9 +21,12 @@ sub plugin_info {
name => "nHentai",
type => "metadata",
namespace => "nhplugin",
- author => "Difegue",
- version => "1.7.1",
- description => "Searches nHentai for tags matching your archive.
Supports reading the ID from files formatted as \"{Id} Title\" and if not, tries to search for a matching gallery.",
+ login_from => "nhentaicfbypass",
+ author => "Difegue and others",
+ version => "1.7.2",
+ description => "Searches nHentai for tags matching your archive.
+
Supports reading the ID from files formatted as \"{Id} Title\" and if not, tries to search for a matching gallery.
+
This plugin will use the source: tag of the archive if it exists.",
icon =>
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\nB3RJTUUH4wYCFA8s1yKFJwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\nAAACL0lEQVQ4y6XTz0tUURQH8O+59773nLFcaGWTk4UUVCBFiJs27VxEQRH0AyRo4x8Q/Qtt2rhr\nU6soaCG0KYKSwIhMa9Ah+yEhZM/5oZMG88N59717T4sxM8eZCM/ycD6Xwznn0pWhG34mh/+PA8mk\n8jO5heziP0sFYwfgMDFQJg4IUjmquSFGG+OIlb1G9li5kykgTgvzSoUCaIYlo8/Igcjpj5wOkARp\n8AupP0uzJLijCY4zzoXOxdBLshAgABr8VOp7bpAXDEI7IBrhdksnjNr3WzI4LaIRV9fk2iAaYV/y\nA1dPiYjBAALgpQxnhV2XzTCAGWGeq7ACBvCdzKQyTH+voAm2hGlpcmQt2Bc2K+ymAhWPxTzPDQLt\nOKo1FiNBQaArq9WNRQwEgKl7XQ1duzSRSn/88vX0qf7DPQddx1nI5UfHxt+m0sLYPiP3shRAG8MD\nok1XEEXR/EI2ly94nrNYWG6Nx0/2Hp2b94dv34mlZge1e4hVCJ4jc6tl9ZP803n3/i4lpdyzq2N0\n7M3DkSeF5ZVYS8v1qxcGz5+5eey4nPDbmGdE9FpGeWErVNe2tTabX3r0+Nk3PwOgXFkdfz99+exA\nMtFZITEt9F23mpLG0hYTVQCKpfKPlZ/rqWKpYoAPcTmpginW76QBbb0OBaBaDdjaDbNlJmQE3/d0\nMYoaybU9126oPkrEhpr+U2wjtoVVGBowkslEsVSupRKdu0Mduq7q7kqExjSS3V2dvwDLavx0eczM\neAAAAABJRU5ErkJggg==",
parameters => [ { type => "bool", desc => "Save archive title" } ],
@@ -38,7 +41,8 @@ sub get_tags {
shift;
my $lrr_info = shift; # Global info hash
my ($savetitle) = @_; # Plugin parameters
-
+ my $ua = $lrr_info->{user_agent}; # UserAgent from login plugin
+
my $logger = get_plugin_logger();
# Work your magic here - You can create subs below to organize the code better
@@ -49,12 +53,14 @@ sub get_tags {
$galleryID = $1;
$logger->debug("Skipping search and using gallery $galleryID from oneshot args");
} elsif ( $lrr_info->{existing_tags} =~ /.*source:\s*(?:https?:\/\/)?nhentai\.net\/g\/([0-9]*).*/gi ) {
+
# Matching URL Scheme like 'https://' is only for backward compatible purpose.
$galleryID = $1;
- $logger->debug("Skipping search and using gallery $galleryID from source tag")
+ $logger->debug("Skipping search and using gallery $galleryID from source tag");
} else {
+
#Get Gallery ID by hand if the user didn't specify a URL
- $galleryID = get_gallery_id_from_title( $lrr_info->{archive_title} );
+ $galleryID = get_gallery_id_from_title( $lrr_info->{archive_title}, $ua );
}
# Did we detect a nHentai gallery?
@@ -72,9 +78,9 @@ sub get_tags {
return ( error => "No matching nHentai Gallery Found!" );
}
- my %hashdata = get_tags_from_NH( $galleryID, $savetitle );
+ my %hashdata = get_tags_from_NH( $galleryID, $savetitle, $ua );
- $logger->info("Sending the following tags to LRR: " . $hashdata{tags});
+ $logger->info( "Sending the following tags to LRR: " . $hashdata{tags} );
#Return a hash containing the new metadata - it will be integrated in LRR.
return %hashdata;
@@ -87,7 +93,7 @@ sub get_tags {
#Uses the website's search to find a gallery and returns its content.
sub get_gallery_dom_by_title {
- my ( $title ) = @_;
+ my ($title, $ua) = @_;
my $logger = get_plugin_logger();
@@ -97,11 +103,11 @@ sub get_gallery_dom_by_title {
my $URL = "https://nhentai.net/search/?q=" . uri_escape_utf8($title);
$logger->debug("Using URL $URL to search on nH.");
- my $ua = Mojo::UserAgent->new;
my $res = $ua->get($URL)->result;
+ $logger->debug( "Got response " . $res->body );
- if ($res->is_error) {
+ if ( $res->is_error ) {
return;
}
@@ -110,7 +116,7 @@ sub get_gallery_dom_by_title {
sub get_gallery_id_from_title {
- my ( $title ) = @_;
+ my ($title, $ua) = @_;
my $logger = get_plugin_logger();
@@ -119,14 +125,17 @@ sub get_gallery_id_from_title {
return $1;
}
- my $dom = get_gallery_dom_by_title($title);
+ my $dom = get_gallery_dom_by_title($title, $ua);
if ($dom) {
+
# Get the first gallery url of the search results
- my $gURL = ( $dom->at('.cover') )
- ? $dom->at('.cover')->attr('href')
- : "";
+ my $gURL =
+ ( $dom->at('.cover') )
+ ? $dom->at('.cover')->attr('href')
+ : "";
+ $logger->debug("Got $gURL from parsing.");
if ( $gURL =~ /\/g\/(\d*)\//gm ) {
return $1;
}
@@ -138,14 +147,13 @@ sub get_gallery_id_from_title {
# retrieves html page from NH
sub get_html_from_NH {
- my ( $gID ) = @_;
-
+ my ($gID, $ua) = @_;
+
my $URL = "https://nhentai.net/g/$gID/";
- my $ua = Mojo::UserAgent->new;
my $res = $ua->get($URL)->result;
- if ($res->is_error) {
+ if ( $res->is_error ) {
return;
}
@@ -156,7 +164,7 @@ sub get_html_from_NH {
#It's located under a N.gallery JS object.
sub get_json_from_html {
- my ( $html ) = @_;
+ my ($html) = @_;
my $logger = get_plugin_logger();
@@ -178,20 +186,20 @@ sub get_json_from_html {
sub get_tags_from_json {
- my ( $json ) = @_;
+ my ($json) = @_;
my @json_tags = @{ $json->{"tags"} };
- my @tags = ();
+ my @tags = ();
foreach my $tag (@json_tags) {
my $namespace = $tag->{"type"};
- my $name = $tag->{"name"};
+ my $name = $tag->{"name"};
if ( $namespace eq "tag" ) {
- push ( @tags, $name );
+ push( @tags, $name );
} else {
- push ( @tags, "$namespace:$name" );
+ push( @tags, "$namespace:$name" );
}
}
@@ -199,25 +207,25 @@ sub get_tags_from_json {
}
sub get_title_from_json {
- my ( $json ) = @_;
+ my ($json) = @_;
return $json->{"title"}{"pretty"};
}
sub get_tags_from_NH {
- my ( $gID, $savetitle ) = @_;
+ my ( $gID, $savetitle, $ua ) = @_;
my %hashdata = ( tags => "" );
- my $html = get_html_from_NH($gID);
+ my $html = get_html_from_NH($gID, $ua);
my $json = get_json_from_html($html);
- if ( $json ) {
+ if ($json) {
my @tags = get_tags_from_json($json);
push( @tags, "source:nhentai.net/g/$gID" ) if ( @tags > 0 );
# Use NH's "pretty" names (romaji titles without extraneous data we already have like (Event)[Artist], etc)
- $hashdata{tags} = join(', ', @tags);
+ $hashdata{tags} = join( ', ', @tags );
$hashdata{title} = get_title_from_json($json) if ($savetitle);
}
diff --git a/lib/LANraragi/Utils/Archive.pm b/lib/LANraragi/Utils/Archive.pm
index 9aa9d6ea9..0b01c03fd 100644
--- a/lib/LANraragi/Utils/Archive.pm
+++ b/lib/LANraragi/Utils/Archive.pm
@@ -36,13 +36,19 @@ sub is_pdf {
return ( $suffix eq ".pdf" );
}
-# generate_thumbnail(original_image, thumbnail_location)
+# generate_thumbnail(original_image, thumbnail_location, use_hq)
# use ImageMagick to make a thumbnail, height = 500px (view in index is 280px tall)
+# If use_hq is true, the scale algorithm will be used instead of sample.
sub generate_thumbnail {
- my ( $orig_path, $thumb_path ) = @_;
+ my ( $orig_path, $thumb_path, $use_hq ) = @_;
my $img = Image::Magick->new;
+ # For JPEG, the size option (or jpeg:size option) provides a hint to the JPEG decoder
+ # that it can reduce the size on-the-fly during decoding. This saves memory because
+ # it never has to allocate memory for the full-sized image
+ $img->Set( option => 'jpeg:size=500x' );
+
# If the image is a gif, only take the first frame
if ( $orig_path =~ /\.gif$/ ) {
$img->Read( $orig_path . "[0]" );
@@ -50,7 +56,12 @@ sub generate_thumbnail {
$img->Read($orig_path);
}
- $img->Thumbnail( geometry => '500x1000' );
+ # The "-scale" resize operator is a simplified, faster form of the resize command.
+ if ($use_hq) {
+ $img->Scale( geometry => '500x1000' );
+ } else { # Sample is very fast due to not applying filters.
+ $img->Sample( geometry => '500x1000' );
+ }
$img->Set( quality => "50", magick => "jpg" );
$img->Write($thumb_path);
undef $img;
@@ -133,12 +144,13 @@ sub extract_pdf {
return $destination;
}
-# extract_thumbnail(thumbnaildir, id, page)
+# extract_thumbnail(thumbnaildir, id, page, use_hq)
# Extracts a thumbnail from the specified archive ID and page. Returns the path to the thumbnail.
# Non-cover thumbnails land in a folder named after the ID. Specify page=0 if you want the cover.
+# Thumbnails will be generated at low quality by default unless you specify use_hq=1.
sub extract_thumbnail {
- my ( $thumbdir, $id, $page ) = @_;
+ my ( $thumbdir, $id, $page, $use_hq ) = @_;
my $logger = get_logger( "Archive", "lanraragi" );
# Another subfolder with the first two characters of the id is used for FS optimization.
@@ -179,7 +191,7 @@ sub extract_thumbnail {
}
# Thumbnail generation
- generate_thumbnail( $arcimg, $thumbname );
+ generate_thumbnail( $arcimg, $thumbname, $use_hq );
# Clean up safe folder
remove_tree($temppath);
@@ -190,7 +202,7 @@ sub extract_thumbnail {
sub expand {
my $file = shift;
$file =~ s{(\d+)}{sprintf "%04d", $1}eg;
- return $file;
+ return lc($file);
}
# get_filelist($archive)
@@ -233,11 +245,13 @@ sub get_filelist {
}
- # TODO: @images = nsort(@images); would theorically be better, but Sort::Naturally's nsort puts letters before numbers,
- # which isn't what we want at all for pages in an archive.
- # To investigate further, perhaps with custom sorting algorithms?
@files = sort { &expand($a) cmp &expand($b) } @files;
+ # Move any pages containing "credit" to the end of the array.
+ my @credit_pages = grep { /credit/i } @files;
+ my @non_credit_pages = grep { !/credit/i } @files;
+ @files = ( @non_credit_pages, @credit_pages );
+
# Return files and sizes in a hashref
return ( \@files, \@sizes );
}
diff --git a/lib/LANraragi/Utils/Database.pm b/lib/LANraragi/Utils/Database.pm
index 544d66987..dbf3664b0 100644
--- a/lib/LANraragi/Utils/Database.pm
+++ b/lib/LANraragi/Utils/Database.pm
@@ -50,6 +50,32 @@ sub add_archive_to_redis {
return $name;
}
+#change_archive_id($old_id,$new_id,$redis)
+# Updates the DB entry for the given ID to reflect the new ID.
+# This is used in case the file changes substantially and its hash becomes different.
+sub change_archive_id {
+ my ( $old_id, $new_id, $redis ) = @_;
+ my $logger = get_logger( "Archive", "lanraragi" );
+
+ $logger->debug("Changing ID $old_id to $new_id");
+
+ if ( $redis->exists($old_id) ) {
+ $redis->rename( $old_id, $new_id );
+ }
+
+ # We also need to update categories that contain the ID.
+ # TODO: When meta-archives are implemented, this will need to be updated.
+ $logger->debug("Updating categories that contained $old_id to $new_id.");
+ my @categories = LANraragi::Model::Category::get_categories_containing_archive($old_id);
+
+ foreach my $cat (@categories) {
+ my $catid = %{$cat}{"id"};
+ $logger->warn("Updating category $catid");
+ LANraragi::Model::Category::remove_from_category( $catid, $old_id );
+ LANraragi::Model::Category::add_to_category( $catid, $new_id );
+ }
+}
+
# add_timestamp_tag(redis, id)
# Adds a timestamp tag to the given ID.
sub add_timestamp_tag {
@@ -216,6 +242,7 @@ sub clean_database {
eval {
# Save an autobackup somewhere before cleaning
my $outfile = getcwd() . "/autobackup.json";
+ $logger->info("Saving automatic backup to $outfile");
open( my $fh, '>', $outfile );
print $fh LANraragi::Model::Backup::build_backup_JSON();
close $fh;
@@ -254,10 +281,28 @@ sub clean_database {
next;
}
+ # If the linked file exists, check if its ID is in the filemap
unless ( $file eq "" || exists $filemap{$id} ) {
- $logger->warn("File exists but its ID is no longer $id -- Removing file reference in its database entry.");
- $redis->hset( $id, "file", "" );
- $unlinked_arcs++;
+ $logger->warn("File exists but its ID is no longer $id!");
+ $logger->warn("Trying to find its new ID in the Shinobu filemap...");
+
+ if ( $redis->hexists( "LRR_FILEMAP", $file ) ) {
+ my $newid = $redis->hget( "LRR_FILEMAP", $file );
+ $logger->warn("Found $newid in the filemap! Changing ID from $id to it.");
+
+ if ( $redis->exists($newid) ) {
+ $logger->warn("ID $newid already exists in the database! Unlinking old ID.");
+ $redis->hset( $id, "file", "" );
+ } else {
+ change_archive_id( $id, $newid, $redis );
+ }
+
+ } else {
+ $logger->warn("File $file not found in the filemap! Removing file reference in the database entry for $id.");
+ $redis->hset( $id, "file", "" );
+ $unlinked_arcs++;
+ }
+
}
}
diff --git a/lib/LANraragi/Utils/Generic.pm b/lib/LANraragi/Utils/Generic.pm
index 5706c0dac..d3f9b5279 100644
--- a/lib/LANraragi/Utils/Generic.pm
+++ b/lib/LANraragi/Utils/Generic.pm
@@ -21,7 +21,7 @@ use LANraragi::Utils::Logging qw(get_logger);
use Exporter 'import';
our @EXPORT_OK =
qw(remove_spaces remove_newlines trim_url is_image is_archive render_api_response get_tag_with_namespace shasum start_shinobu
- split_workload_by_cpu start_minion get_css_list generate_themes_header flat);
+ split_workload_by_cpu start_minion get_css_list generate_themes_header flat get_bytelength);
# Remove spaces before and after a word
sub remove_spaces {
@@ -42,8 +42,8 @@ sub trim_url {
remove_spaces( $_[0] );
- # Remove scheme and www. if present. Other subdomains are not removed
- if ( $_[0] =~ /https?:\/\/(www\.)?(.*)/gm ) {
+ # Remove scheme, www. and query parameters if present. Other subdomains are not removed
+ if ( $_[0] =~ /https?:\/\/(www\.)?([^\?]*)\??.*/gm ) {
$_[0] = $2;
}
@@ -118,7 +118,7 @@ sub split_workload_by_cpu {
# Start a Minion worker if there aren't any available.
sub start_minion {
- my $mojo = shift;
+ my $mojo = shift;
my $logger = get_logger( "Minion", "minion" );
my $numcpus = Sys::CpuAffinity::getNumCpus();
@@ -146,8 +146,8 @@ sub start_minion {
}
sub _spawn {
- my ( $job, $pid ) = @_;
- my ( $id, $task ) = ( $job->id, $job->task );
+ my ( $job, $pid ) = @_;
+ my ( $id, $task ) = ( $job->id, $job->task );
my $logger = get_logger( "Minion Worker", "minion" );
$job->app->log->debug(qq{Process $pid is performing job "$id" with task "$task"});
}
@@ -206,9 +206,9 @@ sub get_css_list {
# Print a dropdown list to select CSS, and adds tags for all the style sheets present in the /style folder.
sub generate_themes_header {
- my $self = shift;
+ my $self = shift;
my $version = $self->LRR_VERSION;
- my @css = get_css_list;
+ my @css = get_css_list;
# Html that we'll insert in the header to declare all the available styles.
my $html = "";
@@ -217,7 +217,7 @@ sub generate_themes_header {
for ( my $i = 0; $i < $#css + 1; $i++ ) {
my $css_file = $css[$i];
- my ($css_name, $css_color) = css_default_data( $css_file );
+ my ( $css_name, $css_color ) = css_default_data($css_file);
# If this is the default sheet, set it up as so.
if ( $css[$i] eq LANraragi::Model::Config->get_style ) {
@@ -240,12 +240,12 @@ sub generate_themes_header {
# All this sub does is give .css files prettier names in the dropdown. Files without a name here will simply show as their filename to the users.
sub css_default_data {
given ( $_[0] ) {
- when ("g.css") { return ("H-Verse", "#5F0D1F") }
- when ("modern.css") { return ("Hachikuji", "#34353B") }
- when ("modern_clear.css") { return ("Yotsugi", "#34495E") }
- when ("modern_red.css") { return ("Nadeko", "#D83B66") }
- when ("ex.css") { return ("Sad Panda", "#43464E") }
- default { return ($_[0], "#34353B") }
+ when ("g.css") { return ( "H-Verse", "#5F0D1F" ) }
+ when ("modern.css") { return ( "Hachikuji", "#34353B" ) }
+ when ("modern_clear.css") { return ( "Yotsugi", "#34495E" ) }
+ when ("modern_red.css") { return ( "Nadeko", "#D83B66" ) }
+ when ("ex.css") { return ( "Sad Panda", "#43464E" ) }
+ default { return ( $_[0], "#34353B" ) }
}
}
@@ -253,4 +253,10 @@ sub flat {
return map { ref eq 'ARRAY' ? @$_ : $_ } @_;
}
+# Get the byte length of a string.
+sub get_bytelength {
+ use bytes;
+ return length shift;
+}
+
1;
diff --git a/lib/LANraragi/Utils/Logging.pm b/lib/LANraragi/Utils/Logging.pm
index 2b8f58e34..60584f707 100644
--- a/lib/LANraragi/Utils/Logging.pm
+++ b/lib/LANraragi/Utils/Logging.pm
@@ -73,7 +73,14 @@ sub get_logger {
sub {
my ( $time, $level, @lines ) = @_;
my $time2 = strftime( "%Y-%m-%d %H:%M:%S", localtime($time) );
- return "[$time2] [$pgname] [$level] " . join( "\n", @lines ) . "\n";
+
+ my $logstring = join( "\n", @lines );
+
+ # We'd like to make sure we always show proper UTF-8.
+ # redis_decode, while not initially designed for this, does the job.
+ $logstring = LANraragi::Utils::Database::redis_decode($logstring);
+
+ return "[$time2] [$pgname] [$level] $logstring\n";
}
);
diff --git a/lib/LANraragi/Utils/Minion.pm b/lib/LANraragi/Utils/Minion.pm
index f9bb5dd61..724b1f613 100644
--- a/lib/LANraragi/Utils/Minion.pm
+++ b/lib/LANraragi/Utils/Minion.pm
@@ -27,7 +27,9 @@ sub add_tasks {
my ( $job, @args ) = @_;
my ( $thumbdir, $id, $page ) = @args;
- my $thumbname = extract_thumbnail( $thumbdir, $id, $page );
+ # Non-cover thumbnails are rendered in low quality by default.
+ my $use_hq = $page eq 0 || LANraragi::Model::Config->get_hqthumbpages;
+ my $thumbname = extract_thumbnail( $thumbdir, $id, $page, $use_hq );
$job->finish($thumbname);
}
);
@@ -65,7 +67,7 @@ sub add_tasks {
unless ( $force == 0 && -e $thumbname ) {
eval {
$logger->debug("Regenerating for $id...");
- extract_thumbnail( $thumbdir, $id, 0 );
+ extract_thumbnail( $thumbdir, $id, 0, 1 );
};
if ($@) {
@@ -137,9 +139,7 @@ sub add_tasks {
or die "Bullshit! File path could not be converted back to a byte sequence!"
; # This error happening would not make any sense at all so it deserves the EYE reference
- # For display however, we'd like to make sure we always show proper UTF-8.
- # redis_decode, while not initially designed for this, does the job.
- $logger->info( "Processing uploaded file" . redis_decode($file) . "..." );
+ $logger->info("Processing uploaded file $file...");
# Since we already have a file, this goes straight to handle_incoming_file.
my ( $status, $id, $title, $message ) = LANraragi::Model::Upload::handle_incoming_file( $file, $catid, "" );
@@ -148,7 +148,7 @@ sub add_tasks {
{ success => $status,
id => $id,
category => $catid,
- title => redis_decode($title), # Ditto, to fix display issues in the response
+ title => redis_decode($title), # Fix display issues in the response
message => $message
}
);
diff --git a/lib/LANraragi/Utils/Plugins.pm b/lib/LANraragi/Utils/Plugins.pm
index 94cda7e76..0cf873dac 100644
--- a/lib/LANraragi/Utils/Plugins.pm
+++ b/lib/LANraragi/Utils/Plugins.pm
@@ -157,10 +157,10 @@ sub use_plugin {
} else {
%pluginfo = $plugin->plugin_info();
- #Get the plugin settings in Redis
+ # Get the plugin settings in Redis
my @settings = get_plugin_parameters($plugname);
- #Execute the plugin, appending the custom args at the end
+ # Execute the plugin, appending the custom args at the end
if ( $pluginfo{type} eq "script" ) {
eval { %plugin_result = LANraragi::Model::Plugins::exec_script_plugin( $plugin, $input, @settings ); };
}
@@ -172,6 +172,11 @@ sub use_plugin {
if ($@) {
$plugin_result{error} = $@;
}
+
+ # Decode the error value if there's one to avoid garbled characters
+ if ( exists $plugin_result{error} ) {
+ $plugin_result{error} = redis_decode( $plugin_result{error} );
+ }
}
return ( \%pluginfo, \%plugin_result );
diff --git a/lib/Shinobu.pm b/lib/Shinobu.pm
index 34d9c3e07..fa3c29aea 100644
--- a/lib/Shinobu.pm
+++ b/lib/Shinobu.pm
@@ -217,12 +217,22 @@ sub add_to_filemap {
# If the id already exists on the server, throw a warning about duplicates
if ( $redis->hexists( "LRR_FILEMAP", $file ) ) {
- my $id = $redis->hget( "LRR_FILEMAP", $file );
+ my $filemap_id = $redis->hget( "LRR_FILEMAP", $file );
- $logger->debug( "$file was logged again but is already in the filemap, duplicate inotify events? "
- . "Cleaning cache just to make sure" );
+ $logger->debug("$file was logged but is already in the filemap!");
+
+ if ( $filemap_id ne $id ) {
+ $logger->debug("$file has a different ID than the one in the filemap! ($filemap_id)");
+ $logger->info("$file has been modified, updating its ID from $filemap_id to $id.");
+
+ LANraragi::Utils::Database::change_archive_id( $filemap_id, $id, $redis );
+
+ } else {
+ $logger->debug(
+ "$file has the same ID as the one in the filemap. Duplicate inotify events? Cleaning cache just to make sure");
+ invalidate_cache();
+ }
- invalidate_cache();
return;
} else {
diff --git a/package.json b/package.json
index cfc6ade76..1d200d82c 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
"name": "lanraragi",
- "version": "0.8.5",
- "version_name": "Sex and the Church",
+ "version": "0.8.6",
+ "version_name": "Buddha of Suburbia",
"description": "I'm under Japanese influence and my honor's at stake!",
"scripts": {
"test": "prove -r -l -v tests/",
"lanraragi-installer": "perl ./tools/install.pl",
+ "lint": "eslint --ext .js public/",
"start": "perl ./script/launcher.pl -f ./script/lanraragi",
- "dev-server": "perl ./script/launcher.pl -m -v ./script/lanraragi ",
+ "dev-server": "perl ./script/launcher.pl -m -v ./script/lanraragi",
"docker-build": "docker build -t difegue/lanraragi -f ./tools/build/docker/Dockerfile .",
"critic": "perlcritic ./lib/* ./script/* ./tools/install.pl",
"backup-db": "perl ./script/backup"
@@ -23,26 +24,30 @@
},
"homepage": "https://github.com/Difegue/LANraragi#readme",
"dependencies": {
- "@fortawesome/fontawesome-free": "^5.13.0",
- "@jcubic/tagger": "^0.4.0",
+ "@fortawesome/fontawesome-free": "^5.15.4",
+ "@jcubic/tagger": "^0.4.2",
"allcollapsible": "^1.1.0",
"awesomplete": "^1.1.5",
- "blueimp-file-upload": "^10.28.0",
- "datatables.net": "^1.10.25",
- "inter-ui": "^3.3.2",
+ "blueimp-file-upload": "^10.32.0",
+ "clsx": "^1.1.1",
+ "datatables.net": "^1.11.5",
+ "fscreen": "^1.2.0",
+ "inter-ui": "^3.19.3",
"jqcloud2": "^2.0.3",
"jquery": "^3.6.0",
"jquery-contextmenu": "^2.9.2",
- "jquery-toast-plugin": "^1.3.2",
- "marked": "^4.0.10",
+ "marked": "^4.0.14",
"open-sans-fontface": "^1.4.0",
+ "preact": "^10.7.1",
+ "react-toastify": "^9.0.0-rc-2",
"roboto-fontface": "^0.8.0",
- "swiper": "^7.2.0",
- "tippy.js": "^6.3.1"
+ "sweetalert2": "^11.4.10",
+ "swiper": "^8.1.4",
+ "tippy.js": "^6.3.7"
},
"devDependencies": {
- "eslint": "^7.24.0",
- "eslint-config-airbnb-base": "^14.2.1",
- "eslint-plugin-import": "^2.22.1"
+ "eslint": "^7.32.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-plugin-import": "^2.26.0"
}
}
\ No newline at end of file
diff --git a/public/css/lrr.css b/public/css/lrr.css
index 4ef206ca5..f3a85c920 100644
--- a/public/css/lrr.css
+++ b/public/css/lrr.css
@@ -153,7 +153,7 @@ table thead .sorting_disabled a:after {
}
.index-carousel-container {
- margin-top: -20px;
+ margin-top: -10px;
}
.carousel-prev {
@@ -320,7 +320,6 @@ p#nb {
}
.collapsible-right {
- float: right;
padding: 1.2rem 1.2rem 0 0;
}
@@ -499,12 +498,47 @@ li {
}
/* Overrides */
-.jq-toast-single a {
- padding-bottom: 0 !important;
+.Toastify {
+ --toastify-font-family: "Open Sans", arial, sans-serif;
+}
+
+.Toastify__toast-body {
+ font-size: 13px;
+ line-height: 1.3;
+}
+
+.Toastify__toast-body h2 {
+ margin: 0;
+ padding: 4px 0 8px;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.Toastify__toast-body a {
+ opacity: 0.75;
+ text-decoration: underline;
+}
+
+.Toastify__toast-body a:hover {
+ opacity: 1;
+}
+
+.Toastify__toast-body .Toastify__toast-icon {
+ align-self: flex-start;
+ padding: 4px 4px 0 0;
+}
+
+.swal2-popup {
+ font-size: 9pt !important;
+}
+
+.swal2-styled.swal2-cancel {
+ margin-right: 8px;
}
-.jq-toast-single h2 {
- margin: 0 !important;
+body.swal2-shown>[aria-hidden="true"] {
+ transition: 0.1s filter;
+ filter: opacity(0.8);
}
.awesomplete>ul {
diff --git a/public/js/backup.js b/public/js/backup.js
new file mode 100644
index 000000000..c38c6bf9b
--- /dev/null
+++ b/public/js/backup.js
@@ -0,0 +1,36 @@
+/**
+ * Backup Operations.
+ */
+const Backup = {};
+
+Backup.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+ $(document).on("click.do-backup", "#do-backup", () => { window.open("./backup?dobackup=1", "_blank"); });
+
+ // Handler for file uploading.
+ $("#fileupload").fileupload({
+ dataType: "json",
+ done(e, data) {
+ $("#processing").attr("style", "display:none");
+
+ if (data.result.success === 1) $("#result").html("Backup restored!");
+ else $("#result").html(data.result.error);
+ },
+
+ fail() {
+ $("#processing").attr("style", "display:none");
+ $("#result").html("An error occured server-side. woops.
Maybe your JSON is badly formatted ?");
+ },
+
+ progressall() {
+ $("#result").html("");
+ $("#processing").attr("style", "");
+ },
+
+ });
+};
+
+jQuery(() => {
+ Backup.initializeAll();
+});
diff --git a/public/js/batch.js b/public/js/batch.js
index 349ee0149..6fc898981 100644
--- a/public/js/batch.js
+++ b/public/js/batch.js
@@ -15,9 +15,12 @@ Batch.initializeAll = function () {
$(document).on("change.plugin", "#plugin", Batch.showOverride);
$(document).on("click.override", "#override", Batch.showOverride);
$(document).on("click.check-uncheck", "#check-uncheck", Batch.checkAll);
- $(document).on("click.start-batch", "#start-batch", Batch.startBatch);
+ $(document).on("click.start-batch", "#start-batch", Batch.startBatchCheck);
$(document).on("click.restart-job", "#restart-job", Batch.restartBatchUI);
$(document).on("click.cancel-job", "#cancel-job", Batch.cancelBatch);
+ $(document).on("click.server-config", "#server-config", () => LRR.openInNewTab("./config"));
+ $(document).on("click.plugin-config", "#plugin-config", () => LRR.openInNewTab("./config/plugins"));
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
Batch.selectOperation();
Batch.showOverride();
@@ -35,7 +38,8 @@ Batch.initializeAll = function () {
});
Batch.checkUntagged();
- })
+ },
+ )
.finally(() => {
$("#arclist").show();
$("#loading-placeholder").hide();
@@ -83,7 +87,31 @@ Batch.checkUntagged = function () {
checkbox.parentElement.parentElement.prepend(checkbox.parentElement);
}
});
+ },
+ );
+};
+
+/**
+ * Pop up a confirm dialog if operation is destructive.
+ */
+Batch.startBatchCheck = function () {
+ if (Batch.currentOperation === "delete") {
+ LRR.showPopUp({
+ text: "Are you sure you want to delete the selected archives?",
+ icon: "warning",
+ showCancelButton: true,
+ focusConfirm: false,
+ confirmButtonText: "Yes, delete it!",
+ reverseButtons: true,
+ confirmButtonColor: "#d33",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ Batch.startBatch();
+ }
});
+ } else {
+ Batch.startBatch();
+ }
};
/**
@@ -91,10 +119,6 @@ Batch.checkUntagged = function () {
* This crafts a JSON list to send to the batch tagging websocket service.
*/
Batch.startBatch = function () {
- if (Batch.currentOperation === "delete" && !confirm("This is a destructive operation! Are you sure you want to delete the selected archives?")) {
- return;
- }
-
$(".tag-options").hide();
$("#log-container").html("Started Batch Operation...\n************\n");
@@ -105,12 +129,8 @@ Batch.startBatch = function () {
const checkeds = document.querySelectorAll("input[name=archive]:checked");
// Extract IDs from nodelist
- const arcs = [];
- const args = [];
-
- for (let i = 0, ref = arcs.length = checkeds.length; i < ref; i++) {
- arcs[i] = checkeds[i].id;
- }
+ const arcs = Array.from(checkeds).map((item) => item.id);
+ let args = [];
// Reset counts
Batch.treatedArchives = 0;
@@ -122,14 +142,14 @@ Batch.startBatch = function () {
// Only add values into the override argument array if the checkbox is on
const arginputs = $(`.${Batch.currentPlugin}-argvalue`);
if ($("#override")[0].checked) {
- for (let j = 0, ref = args.length = arginputs.length; j < ref; j++) {
+ args = Array.from(arginputs).map((item) => {
// Checkbox inputs are handled by looking at the checked prop instead of the value.
- if (arginputs[j].type !== "checkbox") {
- args[j] = arginputs[j].value;
+ if (item.type !== "checkbox") {
+ return item.value;
} else {
- args[j] = arginputs[j].checked ? 1 : 0;
+ return item.checked ? 1 : 0;
}
- }
+ });
}
// Initialize websocket connection
@@ -142,7 +162,7 @@ Batch.startBatch = function () {
// Close any existing connection
// eslint-disable-next-line no-empty
- try { Batch.socket.close(); } catch {}
+ try { Batch.socket.close(); } catch { }
let wsProto = "ws://";
if (document.location.protocol === "https:") wsProto = "wss://";
@@ -240,14 +260,11 @@ Batch.batchError = function () {
$("#log-container").append("************\nError! Terminating session.\n");
Batch.scrollLogs();
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
- hideAfter: false,
+ LRR.toast({
heading: "An error occured during batch tagging!",
text: "Please check application logs.",
icon: "error",
+ hideAfter: false,
});
};
@@ -263,12 +280,8 @@ Batch.endBatch = function (event) {
$("#log-container").append(`************\n${event.reason}(code ${event.code})\n`);
Batch.scrollLogs();
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Batch Operation complete!",
- text: "",
icon: status,
});
@@ -306,8 +319,6 @@ Batch.restartBatchUI = function () {
$(".job-status").hide();
};
-$(document).ready(() => {
+jQuery(() => {
Batch.initializeAll();
});
-
-window.Batch = Batch;
diff --git a/public/js/category.js b/public/js/category.js
index b5c675307..b8317b0cb 100644
--- a/public/js/category.js
+++ b/public/js/category.js
@@ -1,55 +1,85 @@
-let categories = [];
-
-function addNewCategory(isDynamic) {
-
- const catName = prompt("Enter a name for the new category:", "My Category");
- if (catName == null || catName == "") {
- return;
- }
-
- // Initialize dynamic collections with a bogus search
- const searchtag = isDynamic ? "language:english" : "";
-
- // Make an API request to create the category, if search is empty -> static, otherwise dynamic
- Server.callAPI(`/api/categories?name=${catName}&search=${searchtag}`, "PUT", `Category "${catName}" created!`, "Error creating category:",
- function (data) {
- // Reload categories and select the newly created ID
- loadCategories(data.category_id);
- });
-
-}
-
-function loadCategories(selectedID) {
+/**
+ * Category Operations.
+ */
+const Category = {};
+
+Category.categories = [];
+
+Category.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("change.category", "#category", Category.updateCategoryDetails);
+ $(document).on("change.catname", "#catname", Category.saveCurrentCategoryDetails);
+ $(document).on("change.catsearch", "#catsearch", Category.saveCurrentCategoryDetails);
+ $(document).on("change.pinned", "#pinned", Category.saveCurrentCategoryDetails);
+ $(document).on("click.new-static", "#new-static", () => Category.addNewCategory(false));
+ $(document).on("click.new-dynamic", "#new-dynamic", () => Category.addNewCategory(true));
+ $(document).on("click.predicate-help", "#predicate-help", Category.predicateHelp);
+ $(document).on("click.delete", "#delete", Category.deleteSelectedCategory);
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+
+ Category.loadCategories();
+};
+
+Category.addNewCategory = function (isDynamic) {
+ LRR.showPopUp({
+ title: "Enter a name for the new category",
+ input: "text",
+ inputPlaceholder: "My Category",
+ inputAttributes: {
+ autocapitalize: "off",
+ },
+ showCancelButton: true,
+ reverseButtons: true,
+ inputValidator: (value) => {
+ if (!value) {
+ return "Please enter a category name.";
+ }
+ return undefined;
+ },
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // Initialize dynamic collections with a bogus search
+ const searchtag = isDynamic ? "language:english" : "";
+
+ // Make an API request to create category, search is empty -> static, otherwise dynamic
+ Server.callAPI(`/api/categories?name=${result.value}&search=${searchtag}`, "PUT", `Category "${result.value}" created!`, "Error creating category:",
+ (data) => {
+ // Reload categories and select the newly created ID
+ Category.loadCategories(data.category_id);
+ },
+ );
+ }
+ });
+};
+Category.loadCategories = function (selectedID) {
fetch("/api/categories")
- .then(response => response.json())
+ .then((response) => response.json())
.then((data) => {
-
// Save data clientside for reference in later functions
- categories = data;
+ Category.categories = data;
// Clear combobox and fill it again with categories from the API
- const catCombobox = document.getElementById('category');
+ const catCombobox = document.getElementById("category");
catCombobox.options.length = 0;
// Add default
catCombobox.options[catCombobox.options.length] = new Option("-- No Category --", "", true, false);
// Add categories, select if the ID matches the optional argument
- data.forEach(c => {
- catCombobox.options[catCombobox.options.length] = new Option(c.name, c.id, false, c.id === selectedID);
+ data.forEach((c) => {
+ const newOption = new Option(c.name, c.id, false, c.id === selectedID);
+ catCombobox.options[catCombobox.options.length] = newOption;
});
// Update form with selected category details
- updateCategoryDetails();
+ Category.updateCategoryDetails();
})
- .catch(error => LRR.showErrorToast("Error getting categories from server", error));
-
-}
-
-function updateCategoryDetails() {
+ .catch((error) => LRR.showErrorToast("Error getting categories from server", error));
+};
+Category.updateCategoryDetails = function () {
// Get selected category ID and find it in the reference array
- const categoryID = document.getElementById('category').value;
- const category = categories.find(x => x.id === categoryID);
+ const categoryID = document.getElementById("category").value;
+ const category = Category.categories.find((x) => x.id === categoryID);
$("#archivelist").hide();
$("#dynamicplaceholder").show();
@@ -58,9 +88,9 @@ function updateCategoryDetails() {
if (!category) return;
$(".tag-options").show();
- document.getElementById('catname').value = category.name;
- document.getElementById('catsearch').value = category.search;
- document.getElementById('pinned').checked = category.pinned === "1";
+ document.getElementById("catname").value = category.name;
+ document.getElementById("catsearch").value = category.search;
+ document.getElementById("pinned").checked = category.pinned === "1";
if (category.search === "") {
// Show archives if static and check the matching IDs
@@ -70,15 +100,21 @@ function updateCategoryDetails() {
// Sort archive list alphabetically
const arclist = $("#archivelist");
- arclist.find('li').sort(function (a, b) {
- var upA = $(a).find('label').text().toUpperCase();
- var upB = $(b).find('label').text().toUpperCase();
- return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;
+ arclist.find("li").sort((a, b) => {
+ const upA = $(a).find("label").text().toUpperCase();
+ const upB = $(b).find("label").text().toUpperCase();
+ if (upA < upB) {
+ return -1;
+ } else if (upA > upB) {
+ return 1;
+ } else {
+ return 0;
+ }
}).appendTo("#archivelist");
// Uncheck all
$(".checklist > * > input:checkbox").prop("checked", false);
- category.archives.forEach(id => {
+ category.archives.forEach((id) => {
const checkbox = document.getElementById(id);
if (checkbox != null) {
@@ -87,73 +123,84 @@ function updateCategoryDetails() {
checkbox.parentElement.parentElement.prepend(checkbox.parentElement);
}
});
-
} else {
// Show predicate field if dynamic
$("#predicatefield").show();
}
+};
-}
-
-function saveCurrentCategoryDetails() {
-
+Category.saveCurrentCategoryDetails = function () {
// Get selected category ID
- const categoryID = document.getElementById('category').value;
- const catName = document.getElementById('catname').value;
- const searchtag = document.getElementById('catsearch').value;
- const pinned = document.getElementById('pinned').checked ? "1" : "0";
+ const categoryID = document.getElementById("category").value;
+ const catName = document.getElementById("catname").value;
+ const searchtag = document.getElementById("catsearch").value;
+ const pinned = document.getElementById("pinned").checked ? "1" : "0";
- indicateSaving();
+ Category.indicateSaving();
// PUT update with name and search (search is empty if this is a static category)
- Server.callAPI(`/api/categories/${categoryID}?name=${catName}&search=${searchtag}&pinned=${pinned}`,
- "PUT", null, "Error updating category:",
- function (data) {
+ Server.callAPI(`/api/categories/${categoryID}?name=${catName}&search=${searchtag}&pinned=${pinned}`, "PUT", null, "Error updating category:",
+ (data) => {
// Reload categories and select the newly created ID
- indicateSaved();
- loadCategories(data.category_id);
- });
-}
-
-function updateArchiveInCategory(id, checked) {
-
- const categoryID = document.getElementById('category').value;
- indicateSaving();
+ Category.indicateSaved();
+ Category.loadCategories(data.category_id);
+ },
+ );
+};
+
+Category.updateArchiveInCategory = function (id, checked) {
+ const categoryID = document.getElementById("category").value;
+ Category.indicateSaving();
// PUT/DELETE api/categories/catID/archiveID
- Server.callAPI(`/api/categories/${categoryID}/${id}`, checked ? 'PUT' : 'DELETE', null, "Error adding/removing archive to category",
- function (data) {
+ Server.callAPI(`/api/categories/${categoryID}/${id}`, checked ? "PUT" : "DELETE", null, "Error adding/removing archive to category",
+ () => {
// Reload categories and select the archive list properly
- indicateSaved();
- loadCategories(categoryID);
- });
-}
-
-function deleteSelectedCategory() {
- const categoryID = document.getElementById('category').value;
- if (confirm("Are you sure? The category will be deleted permanently!")) {
-
- Server.callAPI(`/api/categories/${categoryID}`, "DELETE", "Category deleted!", "Error deleting category",
- function (data) {
+ Category.indicateSaved();
+ Category.loadCategories(categoryID);
+ },
+ );
+};
+
+Category.deleteSelectedCategory = function () {
+ const categoryID = document.getElementById("category").value;
+ LRR.showPopUp({
+ text: "The category will be deleted permanently.",
+ icon: "warning",
+ showCancelButton: true,
+ focusConfirm: false,
+ confirmButtonText: "Yes, delete it!",
+ reverseButtons: true,
+ confirmButtonColor: "#d33",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ Server.callAPI(`/api/categories/${categoryID}`, "DELETE", "Category deleted!", "Error deleting category",
+ () => {
// Reload categories to show the archive list properly
- loadCategories();
- });
- }
-}
-
-function indicateSaving() {
- document.getElementById("status").innerHTML = ` Saving your modifications...`;
-}
-
-function indicateSaved() {
- document.getElementById("status").innerHTML = ` Saved!`;
-}
-
-function predicateHelp() {
- $.toast({
- heading: 'Writing a Predicate',
- text: 'Predicates follow the same syntax as searches in the Archive Index. Check the Documentation for more information.',
- hideAfter: false,
- position: 'top-left',
- icon: 'info'
+ Category.loadCategories();
+ },
+ );
+ }
+ });
+};
+
+Category.indicateSaving = function () {
+ document.getElementById("status").innerHTML = " Saving your modifications...";
+};
+
+Category.indicateSaved = function () {
+ document.getElementById("status").innerHTML = " Saved!";
+};
+
+Category.predicateHelp = function () {
+ LRR.toast({
+ toastId: "predicateHelp",
+ heading: "Writing a Predicate",
+ text: "Predicates follow the same syntax as searches in the Archive Index. Check the Documentation for more information.",
+ icon: "info",
+ hideAfter: 20000,
});
-}
\ No newline at end of file
+};
+
+jQuery(() => {
+ Category.initializeAll();
+});
diff --git a/public/js/common.js b/public/js/common.js
index bdb3cf497..e30b8261e 100644
--- a/public/js/common.js
+++ b/public/js/common.js
@@ -4,14 +4,26 @@
const LRR = {};
/**
- * Quick 'n dirty HTML encoding function.
+ * Quick HTML encoding function.
* @param {*} r The HTML to encode
* @returns Encoded string
*/
LRR.encodeHTML = function (r) {
if (r === undefined) return r;
- if (Array.isArray(r)) return r[0].replace(/[\x26\x0A\<>'"]/g, (r2) => `${r2.charCodeAt(0)};`);
- else return r.replace(/[\x26\x0A\<>'"]/g, (r2) => `${r2.charCodeAt(0)};`);
+ if (Array.isArray(r)) {
+ return r[0].replace(/[\n&<>'"]/g, (r2) => `${r2.charCodeAt(0)};`);
+ } else {
+ return r.replace(/[\n&<>'"]/g, (r2) => `${r2.charCodeAt(0)};`);
+ }
+};
+
+/**
+ * Unix timestamp converting function.
+ * @param {number} r The timestamp to convert
+ * @returns Converted string
+ */
+LRR.convertTimestamp = function (r) {
+ return (new Date(r * 1000)).toLocaleDateString();
};
/**
@@ -38,7 +50,7 @@ LRR.isNullOrWhitespace = function (input) {
*/
LRR.getTagSearchURL = function (namespace, tag) {
const namespacedTag = this.buildNamespacedTag(namespace, tag);
- if (namespace !== "source"){
+ if (namespace !== "source") {
return `/?q=${encodeURIComponent(namespacedTag)}`;
} else if (/https?:\/\//.test(tag)) {
return `${tag}`;
@@ -99,12 +111,20 @@ LRR.colorCodeTags = function (tags) {
if (tags === "") return line;
const tagsByNamespace = LRR.splitTagsByNamespace(tags);
- Object.keys(tagsByNamespace).sort().forEach((key) => {
- tagsByNamespace[key].forEach((tag) => {
- if (key === "date_added" || key === "timestamp") return;
+ const filteredTags = Object.keys(tagsByNamespace).filter((tag) => tag !== "date_added" && tag !== "timestamp");
+ let tagsToEncode;
+
+ if (filteredTags.length) {
+ tagsToEncode = filteredTags.sort();
+ } else {
+ tagsToEncode = Object.keys(tagsByNamespace).sort();
+ }
+ tagsToEncode.sort().forEach((key) => {
+ tagsByNamespace[key].forEach((tag) => {
const encodedK = LRR.encodeHTML(key.toLowerCase());
- line += `${LRR.encodeHTML(tag)}, `;
+ const encodedVal = LRR.encodeHTML(key === "date_added" || key === "timestamp" ? LRR.convertTimestamp(tag) : tag);
+ line += `${encodedVal}, `;
});
});
// Remove last comma
@@ -170,11 +190,7 @@ LRR.buildTagsDiv = function (tags) {
const url = LRR.getTagSearchURL(key, tag);
const searchTag = LRR.buildNamespacedTag(key, tag);
- let tagText = LRR.encodeHTML(tag);
- if (key === "date_added" || key === "timestamp") {
- const date = new Date(tag * 1000);
- tagText = date.toLocaleDateString();
- }
+ const tagText = LRR.encodeHTML(key === "date_added" || key === "timestamp" ? LRR.convertTimestamp(tag) : tag);
line += `
-
${LRR.colorCodeTags(data.tags)}
-
${LRR.buildTagsDiv(data.tags)}
+
${LRR.colorCodeTags(data.tags)}
+ ${tagTooltip === true ? `
${LRR.buildTagsDiv(data.tags)}
` : ""}
`;
};
@@ -255,17 +272,32 @@ LRR.buildProgressDiv = function (arcdata) {
* @param {*} error Error message
*/
LRR.showErrorToast = function (header, error) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: header,
text: error,
- hideAfter: false,
icon: "error",
+ hideAfter: false,
});
};
+/**
+ * Show a pop-up window to request user input.
+ * @param {*} c Pop-up body
+ */
+LRR.showPopUp = function (c) {
+ if (!c.customClass) {
+ c.customClass = {
+ cancelButton: "stdbtn",
+ confirmButton: "stdbtn",
+ };
+ }
+
+ if (c.icon === "warning" && !c.title) {
+ c.title = "This is a destructive operation!";
+ }
+ return window.Swal.fire(c);
+};
+
/**
* Fires a HEAD request to get filesize of a given URL.
* return target img size.
@@ -283,3 +315,49 @@ LRR.getImgSize = function (target) {
});
return imgSize;
};
+
+/**
+ * Show a generic toast with a given header and message.
+ * This is a compatibility layer to migrate jquery-toast-plugin to react-toastify.
+ * @param {*} c Toast body
+ */
+LRR.toast = function (c) {
+ return window.reactToastify.toast(
+ window.React.createElement("div", { dangerouslySetInnerHTML: { __html: `${c.heading ? `${c.heading}
` : ""}${c.text ?? ""}` } }), (() => {
+ const toastType = c.icon || c.typel;
+ const isWarningOrError = (toastType === "warning") || (toastType === "error");
+ const autoCloseTime = {
+ info: 5000,
+ success: 5000,
+ warning: 10000,
+ error: false,
+ };
+ return {
+ toastId: c.toastId,
+ type: toastType || "info",
+ position: c.position || "top-left",
+ onOpen: c.onOpen,
+ onClose: c.onClose,
+ autoClose: c.hideAfter ?? c.autoClose ?? autoCloseTime[toastType] ?? 7000,
+ closeButton: c.allowToastClose ?? c.closeButton ?? true,
+ hideProgressBar: (typeof (c.loader) === "boolean" && !c.loader) ?? c.hideProgressBar ?? false,
+ pauseOnHover: c.pauseOnHover ?? true,
+ pauseOnFocusLoss: c.pauseOnFocusLoss ?? true,
+ closeOnClick: c.closeOnClick ?? (!isWarningOrError),
+ draggable: c.draggable ?? (!isWarningOrError),
+ };
+ })());
+};
+
+jQuery(() => {
+ // Initialize toast.
+ const toastDiv = document.createElement("div");
+ document.body.appendChild(toastDiv);
+ toastDiv.style.textAlign = "initial";
+ window.React.render(
+ window.React.createElement(window.reactToastify.ToastContainer, {
+ style: {},
+ limit: 7,
+ theme: "light",
+ }, undefined), toastDiv);
+});
diff --git a/public/js/config.js b/public/js/config.js
new file mode 100644
index 000000000..26c8f83c5
--- /dev/null
+++ b/public/js/config.js
@@ -0,0 +1,118 @@
+/**
+ * Config Operations.
+ */
+const Config = {};
+
+Config.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.save", "#save", () => { Server.saveFormData("#editConfigForm"); });
+ $(document).on("click.plugin-config", "#plugin-config", () => { window.location.href = "/config/plugins"; });
+ $(document).on("click.backup", "#backup", () => { window.location.href = "/backup"; });
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+ $(document).on("click.enablepass", "#enablepass", Config.enable_pass);
+ $(document).on("click.enableresize", "#enableresize", Config.enable_resize);
+ $(document).on("click.usedateadded", "#usedateadded", Config.enable_timemodified);
+
+ $(document).on("click.rescan-button", "#rescan-button", Config.rescanContentFolder);
+ $(document).on("click.clean-temp", "#clean-temp", Server.cleanTemporaryFolder);
+ $(document).on("click.reset-search-cache", "#reset-search-cache", Server.invalidateCache);
+ $(document).on("click.clear-new-tags", "#clear-new-tags", Server.clearAllNewFlags);
+
+ $(document).on("click.clean-db", "#clean-db", Server.cleanDatabase);
+ $(document).on("click.drop-db", "#drop-db", Server.dropDatabase);
+
+ $(document).on("click.restart-button", "#restart-button", Config.rebootShinobu);
+ $(document).on("click.open-minion", "#open-minion", () => LRR.openInNewTab("/minion"));
+
+ $(document).on("click.genthumb-button", "#genthumb-button", () => Server.regenerateThumbnails(false));
+ $(document).on("click.forcethumb-button", "#forcethumb-button", () => Server.regenerateThumbnails(true));
+
+ $(document).on("click.modern-div", "#modern-div", () => Config.switch_style("Hachikuji"));
+ $(document).on("click.modern-clear-div", "#modern-clear-div", () => Config.switch_style("Yotsugi"));
+ $(document).on("click.modern-red-div", "#modern-red-div", () => Config.switch_style("Nadeko"));
+ $(document).on("click.ex-div", "#ex-div", () => Config.switch_style("Sad Panda"));
+ $(document).on("click.g-div", "#g-div", () => Config.switch_style("H-Verse"));
+
+ Config.enable_pass();
+ Config.enable_resize();
+ Config.enable_timemodified();
+ Config.shinobuStatus();
+ setInterval(Config.shinobuStatus, 5000);
+};
+
+Config.rebootShinobu = function () {
+ $("#restart-button").prop("disabled", true);
+ Server.callAPI("/api/shinobu/restart", "POST", "Background Worker restarted!", "Error while restarting Worker:",
+ () => {
+ $("#restart-button").prop("disabled", false);
+ Config.shinobuStatus();
+ },
+ );
+};
+
+Config.rescanContentFolder = function () {
+ $("#rescan-button").prop("disabled", true);
+ Server.callAPI("/api/shinobu/rescan", "POST", "Content folder rescan started!", "Error while restarting Worker:",
+ () => {
+ $("#rescan-button").prop("disabled", false);
+ Config.shinobuStatus();
+ },
+ );
+};
+
+// Update the status of the background worker.
+Config.shinobuStatus = function () {
+ Server.callAPI("/api/shinobu", "GET", null, "Error while querying Shinobu status:",
+ (data) => {
+ if (data.is_alive) {
+ $("#shinobu-ok").show();
+ $("#shinobu-ko").hide();
+ } else {
+ $("#shinobu-ko").show();
+ $("#shinobu-ok").hide();
+ }
+ $("#pid").html(data.pid);
+ },
+ );
+};
+
+Config.switch_style = function (cssTitle) {
+ let i, linkTag, correctStyle, defaultStyle, newStyle;
+ correctStyle = 0;
+
+ for (i = 0, linkTag = document.getElementsByTagName("link"); i < linkTag.length; i++) {
+ if ((linkTag[i].rel.indexOf("stylesheet") !== -1) && linkTag[i].title) {
+ if ((linkTag[i].rel.indexOf("alternate stylesheet") !== -1)) linkTag[i].disabled = true;
+ else defaultStyle = linkTag[i];
+
+ if (linkTag[i].title === cssTitle) {
+ newStyle = linkTag[i];
+ correctStyle = 1;
+ }
+ }
+ }
+
+ if (correctStyle === 1) { // if the style that was switched to exists
+ defaultStyle.disabled = true; // we disable the default style
+ newStyle.disabled = false; // we enable the new style
+ }
+};
+
+Config.enable_pass = function () {
+ if ($("#enablepass").prop("checked")) $(".passwordfields").show();
+ else $(".passwordfields").hide();
+};
+
+Config.enable_resize = function () {
+ if ($("#enableresize").prop("checked")) $(".resizefields").show();
+ else $(".resizefields").hide();
+};
+
+Config.enable_timemodified = function () {
+ if ($("#usedateadded").prop("checked")) $(".datemodified").show();
+ else $(".datemodified").hide();
+};
+
+jQuery(() => {
+ Config.initializeAll();
+});
diff --git a/public/js/edit.js b/public/js/edit.js
index 50fbf392c..fadfb711f 100644
--- a/public/js/edit.js
+++ b/public/js/edit.js
@@ -7,33 +7,15 @@ const Edit = {};
Edit.tagInput = {};
Edit.suggestions = [];
-Edit.hideTags = function () {
- $("#tag-spinner").css("display", "block");
- $("#tagText").css("opacity", "0.5");
- $("#tagText").prop("disabled", true);
- $("#plugin-table").hide();
-};
-
-Edit.showTags = function () {
- $("#tag-spinner").css("display", "none");
- $("#tagText").prop("disabled", false);
- $("#tagText").css("opacity", "1");
- $("#plugin-table").show();
-};
-
-Edit.focusTagInput = function () {
- // Focus child of tagger-new
- $(".tagger-new").children()[0].focus();
-};
-
Edit.initializeAll = function () {
// bind events to DOM
+ $(document).on("change.plugin", "#plugin", Edit.updateOneShotArg);
$(document).on("click.show-help", "#show-help", Edit.showHelp);
$(document).on("click.run-plugin", "#run-plugin", Edit.runPlugin);
$(document).on("click.save-metadata", "#save-metadata", Edit.saveMetadata);
$(document).on("click.delete-archive", "#delete-archive", Edit.deleteArchive);
- $(document).on("change.plugin", "#plugin", Edit.updateOneShotArg);
$(document).on("click.tagger", ".tagger", Edit.focusTagInput);
+ $(document).on("click.goback", "#goback", () => { window.location.href = "/"; });
Edit.updateOneShotArg();
@@ -48,7 +30,8 @@ Edit.initializeAll = function () {
res.push(label);
return res;
}, []);
- })
+ },
+ )
.finally(() => {
const input = $("#tagText")[0];
@@ -69,13 +52,32 @@ Edit.initializeAll = function () {
});
};
+Edit.hideTags = function () {
+ $("#tag-spinner").css("display", "block");
+ $("#tagText").css("opacity", "0.5");
+ $("#tagText").prop("disabled", true);
+ $("#plugin-table").hide();
+};
+
+Edit.showTags = function () {
+ $("#tag-spinner").css("display", "none");
+ $("#tagText").prop("disabled", false);
+ $("#tagText").css("opacity", "1");
+ $("#plugin-table").show();
+};
+
+Edit.focusTagInput = function () {
+ // Focus child of tagger-new
+ $(".tagger-new").children()[0].focus();
+};
+
Edit.showHelp = function () {
- $.toast({
+ LRR.toast({
+ toastId: "pluginHelp",
heading: "About Plugins",
text: "You can use plugins to automatically fetch metadata for this archive.
Just select a plugin from the dropdown and hit Go!
Some plugins might provide an optional argument for you to specify. If that's the case, a textbox will be available to input said argument.",
- hideAfter: false,
- position: "top-left",
icon: "info",
+ hideAfter: 33000,
});
};
@@ -107,10 +109,7 @@ Edit.saveMetadata = function () {
.then((response) => (response.ok ? response.json() : { success: 0, error: "Response was not OK" }))
.then((data) => {
if (data.success) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Metadata saved!",
icon: "success",
});
@@ -125,9 +124,19 @@ Edit.saveMetadata = function () {
};
Edit.deleteArchive = function () {
- if (confirm("Are you sure you want to delete this archive?")) {
- Server.deleteArchive($("#archiveID").val(), () => { document.location.href = "./"; });
- }
+ LRR.showPopUp({
+ text: "Are you sure you want to delete this archive?",
+ icon: "warning",
+ showCancelButton: true,
+ focusConfirm: false,
+ confirmButtonText: "Yes, delete it!",
+ reverseButtons: true,
+ confirmButtonColor: "#d33",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ Server.deleteArchive($("#archiveID").val(), () => { document.location.href = "./"; });
+ }
+ });
};
Edit.getTags = function () {
@@ -136,14 +145,11 @@ Edit.getTags = function () {
const pluginID = $("select#plugin option:checked").val();
const archivID = $("#archiveID").val();
const pluginArg = $("#arg").val();
- Server.callAPI(`../api/plugins/use?plugin=${pluginID}&id=${archivID}&arg=${pluginArg}`, "POST", null,
- "Error while fetching tags :", (result) => {
+ Server.callAPI(`../api/plugins/use?plugin=${pluginID}&id=${archivID}&arg=${pluginArg}`, "POST", null, "Error while fetching tags :",
+ (result) => {
if (result.data.title && result.data.title !== "") {
$("#title").val(result.data.title);
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Archive title changed to :",
text: result.data.title,
icon: "info",
@@ -152,28 +158,25 @@ Edit.getTags = function () {
if (result.data.new_tags !== "") {
result.data.new_tags.split(/,\s?/).forEach((tag) => {
- Edit.tagInput.add_tag(tag);
+ // Remove trailing/leading spaces from tag before adding it
+ Edit.tagInput.add_tag(tag.trim());
});
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Added the following tags :",
text: result.data.new_tags,
icon: "info",
+ hideAfter: 7000,
});
} else {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "No new tags added!",
text: result.data.new_tags,
icon: "info",
});
}
- }).finally(() => {
+ },
+ ).finally(() => {
Edit.showTags();
});
};
@@ -182,8 +185,6 @@ Edit.runPlugin = function () {
Edit.saveMetadata().then(() => Edit.getTags());
};
-$(document).ready(() => {
+jQuery(() => {
Edit.initializeAll();
});
-
-window.Edit = Edit;
diff --git a/public/js/index.js b/public/js/index.js
index 2d5f547e4..7168b192c 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -17,16 +17,16 @@ Index.pageSize = 100;
*/
Index.initializeAll = function () {
// Bind events to DOM
- $(document).on("click.edit-header-1", "#edit-header-1", () => { Index.promptCustomColumn(1); });
- $(document).on("click.edit-header-2", "#edit-header-2", () => { Index.promptCustomColumn(2); });
+ $(document).on("click.edit-header-1", "#edit-header-1", () => Index.promptCustomColumn(1));
+ $(document).on("click.edit-header-2", "#edit-header-2", () => Index.promptCustomColumn(2));
$(document).on("click.mode-toggle", ".mode-toggle", Index.toggleMode);
$(document).on("change.page-select", "#page-select", () => IndexTable.dataTable.page($("#page-select").val() - 1).draw("page"));
$(document).on("change.thumbnail-crop", "#thumbnail-crop", Index.toggleCrop);
$(document).on("change.namespace-sortby", "#namespace-sortby", Index.handleCustomSort);
$(document).on("click.order-sortby", "#order-sortby", Index.toggleOrder);
- $(document).on("click.open_carousel", ".collapsible-title", Index.toggleCarousel);
+ $(document).on("click.open-carousel", ".collapsible-title", Index.toggleCarousel);
$(document).on("click.reload-carousel", "#reload-carousel", Index.updateCarousel);
- $(document).on("click.close_overlay", "#overlay-shade", LRR.closeOverlay);
+ $(document).on("click.close-overlay", "#overlay-shade", LRR.closeOverlay);
// 0 = List view
// 1 = Thumbnail view
@@ -58,10 +58,11 @@ Index.initializeAll = function () {
// Force-open the collapsible if carouselOpen = true
if (localStorage.carouselOpen === "1") {
- localStorage.carouselOpen = "0"; // Bad hack since clicking collapsible-title will trigger toggleCarousel and modify this
- $(".collapsible-title").click();
+ $(".collapsible-title").trigger("click", [false]);
+ // Index.updateCarousel(); will be executed by toggleCarousel
+ } else {
+ Index.updateCarousel();
}
- Index.updateCarousel();
// Initialize carousel mode menu
$.contextMenu({
@@ -85,43 +86,41 @@ Index.initializeAll = function () {
if (localStorage.getItem("sawContextMenuToast") === null) {
localStorage.sawContextMenuToast = true;
- $.toast({
+ LRR.toast({
heading: `Welcome to LANraragi ${Index.serverVersion}!`,
text: "If you want to perform advanced operations on an archive, remember to just right-click its name. Happy reading!",
- hideAfter: false,
- position: "top-left",
icon: "info",
+ hideAfter: 13000,
});
}
// Get some info from the server: version, debug mode, local progress
- Server.callAPI("/api/info", "GET", null, "Error getting basic server info!", (data) => {
- Index.serverVersion = data.version;
- Index.debugMode = data.debug_mode === "1";
- Index.isProgressLocal = data.server_tracks_progress !== "1";
- Index.pageSize = data.archives_per_page;
-
- // Check version if not in debug mode
- if (!Index.debugMode) {
- Index.checkVersion();
- Index.fetchChangelog();
- } else {
- $.toast({
- heading: " You're running in Debug Mode!",
- text: "Advanced server statistics can be viewed here.",
- hideAfter: false,
- position: "top-left",
- icon: "warning",
- });
- }
+ Server.callAPI("/api/info", "GET", null, "Error getting basic server info!",
+ (data) => {
+ Index.serverVersion = data.version;
+ Index.debugMode = data.debug_mode === "1";
+ Index.isProgressLocal = data.server_tracks_progress !== "1";
+ Index.pageSize = data.archives_per_page;
+
+ // Check version if not in debug mode
+ if (!Index.debugMode) {
+ Index.checkVersion();
+ Index.fetchChangelog();
+ } else {
+ LRR.toast({
+ heading: " You're running in Debug Mode!",
+ text: "Advanced server statistics can be viewed here.",
+ icon: "warning",
+ });
+ }
- Index.migrateProgress();
- Index.loadTagSuggestions();
- Index.loadCategories();
+ Index.migrateProgress();
+ Index.loadTagSuggestions();
+ Index.loadCategories();
- // Initialize DataTables
- IndexTable.initializeAll();
- });
+ // Initialize DataTables
+ IndexTable.initializeAll();
+ });
Index.updateTableHeaders();
};
@@ -131,22 +130,52 @@ Index.toggleMode = function () {
IndexTable.dataTable.draw();
};
-Index.toggleCarousel = function () {
- localStorage.carouselOpen = (localStorage.carouselOpen === "1") ? "0" : "1";
+Index.toggleCarousel = function (e, updateLocalStorage = true) {
+ if (updateLocalStorage) localStorage.carouselOpen = (localStorage.carouselOpen === "1") ? "0" : "1";
if (!Index.carouselInitialized) {
Index.carouselInitialized = true;
$("#reload-carousel").show();
Index.swiper = new Swiper(".index-carousel-container", {
- slidesPerView: "auto",
- spaceBetween: 8,
+ breakpoints: (() => {
+ const breakpoints = {
+ 0: { // ensure every device have at least 1 slide
+ slidesPerView: 1,
+ },
+ };
+ // virtual Slides doesn't work with slidesPerView: 'auto'
+ // the following loops are meant to implement same functionality by doing mathworks
+ // it also helps avoid writing a billion slidesPerView combos for window widths
+ // when the screen width <= 560px, every thumbnails have a different width
+ // from 169px, when the width is 17px bigger, we display 0.1 more slide
+ for (let width = 169, sides = 1; width <= 424; width += 17, sides += 0.1) {
+ breakpoints[width] = {
+ slidesPerView: sides,
+ };
+ }
+ // from 427px, when the width is 46px bigger, we display 0.2 more slide
+ // the width support up to 4K resolution
+ for (let width = 427, sides = 1.8; width <= 3840; width += 46, sides += 0.2) {
+ breakpoints[width] = {
+ slidesPerView: sides,
+ };
+ }
+ return breakpoints;
+ })(),
+ breakpointsBase: "container",
+ centerInsufficientSlides: true,
+ mousewheel: true,
navigation: {
nextEl: ".carousel-next",
prevEl: ".carousel-prev",
},
- mousewheel: true,
- freeMode: true,
+ slidesPerView: 7,
+ virtual: {
+ enabled: true,
+ addSlidesAfter: 2,
+ addSlidesBefore: 2,
+ },
});
Index.updateCarousel();
@@ -191,20 +220,35 @@ Index.toggleCategory = function (button) {
* @param {*} column Index of the column to modify, either 1 or 2
*/
Index.promptCustomColumn = function (column) {
- const promptText = "Enter the namespace of the tags you want to show in this column. \n\n"
- + "Enter a full namespace without the colon, e.g \"artist\".\n"
- + "If you have multiple tags with the same namespace, only the last one will be shown in the column.";
-
- const defaultText = localStorage.getItem(`customColumn${column}`);
- const input = prompt(promptText, defaultText);
-
- if (!LRR.isNullOrWhitespace(input)) {
- localStorage.setItem(`customColumn${column}`, input.trim());
-
- // Absolutely disgusting
- IndexTable.dataTable.settings()[0].aoColumns[column].sName = input.trim();
- Index.updateTableHeaders();
- }
+ LRR.showPopUp({
+ title: "Enter a tag namespace for this column",
+ text: "Enter a full namespace without the colon, e.g \"artist\".\nIf you have multiple tags with the same namespace, only the last one will be shown in the column.",
+ input: "text",
+ inputValue: localStorage.getItem(`customColumn${column}`),
+ inputPlaceholder: "Tag namespace",
+ inputAttributes: {
+ autocapitalize: "off",
+ },
+ showCancelButton: true,
+ reverseButtons: true,
+ inputValidator: (value) => {
+ if (!value) {
+ return "Please enter a namespace.";
+ }
+ return undefined;
+ },
+ }).then((result) => {
+ if (result.isConfirmed) {
+ if (!LRR.isNullOrWhitespace(result.value)) {
+ localStorage.setItem(`customColumn${column}`, result.value.trim());
+
+ // Absolutely disgusting
+ IndexTable.dataTable.settings()[0].aoColumns[column].sName = result.value.trim();
+ Index.updateTableHeaders();
+ IndexTable.doSearch();
+ }
+ }
+ });
};
/**
@@ -268,9 +312,9 @@ Index.handleCustomSort = function () {
Index.updateCarousel = function (e) {
e?.preventDefault();
- const carousel = $(".swiper-wrapper");
- carousel.empty();
$("#carousel-loading").show();
+ $(".swiper-wrapper").hide();
+ $("#reload-carousel").addClass("fa-spin");
// Hit a different API endpoint depending on the requested localStorage carousel type
let endpoint;
@@ -298,16 +342,19 @@ Index.updateCarousel = function (e) {
}
if (Index.carouselInitialized) {
- Server.callAPI(endpoint,
- "GET", null, "Error getting carousel data!",
+ Server.callAPI(endpoint, "GET", null, "Error getting carousel data!",
(results) => {
- results.data.forEach((archive) => {
- carousel.append(LRR.buildThumbnailDiv(archive));
- });
+ Index.swiper.virtual.removeAllSlides();
+ const slides = results.data
+ .map((archive) => LRR.buildThumbnailDiv(archive, false));
+ Index.swiper.virtual.appendSlide(slides);
+ Index.swiper.virtual.update();
- Index.swiper.update();
$("#carousel-loading").hide();
- });
+ $(".swiper-wrapper").show();
+ $("#reload-carousel").removeClass("fa-spin");
+ },
+ );
}
};
@@ -327,44 +374,49 @@ Index.updateTableHeaders = function () {
};
/**
- * Check the Github API to see if an update was released.
+ * Check the GitHub API to see if an update was released.
* If so, flash another friendly notification inviting the user to check it out
*/
Index.checkVersion = function () {
const githubAPI = "https://api.github.com/repos/difegue/lanraragi/releases/latest";
- $.getJSON(githubAPI).done((data) => {
- const expr = /(\d+)/g;
- const latestVersionArr = Array.from(data.tag_name.match(expr));
- let latestVersion = "";
- const currentVersionArr = Array.from(Index.serverVersion.match(expr));
- let currentVersion = "";
+ fetch(githubAPI)
+ .then((response) => response.json())
+ .then((data) => {
+ const expr = /(\d+)/g;
+ const latestVersionArr = Array.from(data.tag_name.match(expr));
+ let latestVersion = "";
+ const currentVersionArr = Array.from(Index.serverVersion.match(expr));
+ let currentVersion = "";
+
+ latestVersionArr.forEach((element, index) => {
+ if (index + 1 < latestVersionArr.length) {
+ latestVersion = `${latestVersion}${element}`;
+ } else {
+ latestVersion = `${latestVersion}.${element}`;
+ }
+ });
+ currentVersionArr.forEach((element, index) => {
+ if (index + 1 < currentVersionArr.length) {
+ currentVersion = `${currentVersion}${element}`;
+ } else {
+ currentVersion = `${currentVersion}.${element}`;
+ }
+ });
- latestVersionArr.forEach((element, index) => {
- if (index + 1 < latestVersionArr.length) {
- latestVersion = `${latestVersion}${element}`;
- } else {
- latestVersion = `${latestVersion}.${element}`;
- }
- });
- currentVersionArr.forEach((element, index) => {
- if (index + 1 < currentVersionArr.length) {
- currentVersion = `${currentVersion}${element}`;
- } else {
- currentVersion = `${currentVersion}.${element}`;
+ if (latestVersion > currentVersion) {
+ LRR.toast({
+ heading: `A new version of LANraragi (${data.tag_name}) is available !`,
+ text: `Click here to check it out.`,
+ icon: "info",
+ closeOnClick: false,
+ draggable: false,
+ hideAfter: 7000,
+ });
}
- });
-
- if (latestVersion > currentVersion) {
- $.toast({
- heading: `A new version of LANraragi (${data.tag_name}) is available !`,
- text: `Click here to check it out.`,
- hideAfter: false,
- position: "top-left",
- icon: "info",
- });
- }
- });
+ })
+ // eslint-disable-next-line no-console
+ .catch((error) => console.log("Error checking latest version.", error));
};
/**
@@ -420,7 +472,8 @@ Index.loadContextMenuCategories = function (id) {
}
return items;
- });
+ },
+ );
};
/**
@@ -447,9 +500,19 @@ Index.handleContextMenu = function (option, id) {
LRR.openInNewTab(`./edit?id=${id}`);
break;
case "delete":
- if (confirm("Are you sure you want to delete this archive?")) {
- Server.deleteArchive(id, () => { document.location.reload(true); });
- }
+ LRR.showPopUp({
+ text: "Are you sure you want to delete this archive?",
+ icon: "warning",
+ showCancelButton: true,
+ focusConfirm: false,
+ confirmButtonText: "Yes, delete it!",
+ reverseButtons: true,
+ confirmButtonColor: "#d33",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ Server.deleteArchive(id, () => { document.location.reload(true); });
+ }
+ });
break;
case "read":
LRR.openInNewTab(`./reader?id=${id}`);
@@ -502,7 +565,8 @@ Index.loadTagSuggestions = function () {
this.input.value = `${before + text}, `;
},
});
- });
+ },
+ );
};
/**
@@ -554,7 +618,8 @@ Index.loadCategories = function () {
// Add a listener on dropdown selection
$("#catdropdown").on("change", () => Index.toggleCategory($("#catdropdown")[0].selectedOptions[0]));
- });
+ },
+ );
};
/**
@@ -568,12 +633,11 @@ Index.migrateProgress = function () {
const localProgressKeys = Object.keys(localStorage).filter((x) => x.endsWith("-reader")).map((x) => x.slice(0, -7));
if (localProgressKeys.length > 0) {
- $.toast({
+ LRR.toast({
heading: "Your Reading Progression is now saved on the server!",
text: "You seem to have some local progression hanging around -- Please wait warmly while we migrate it to the server for you. β",
- hideAfter: false,
- position: "top-left",
icon: "info",
+ hideAfter: 23000,
});
const promises = [];
@@ -584,7 +648,10 @@ Index.migrateProgress = function () {
.then((response) => response.json())
.then((data) => {
// Don't migrate if the server progress is already further
- if (progress !== null && data !== undefined && data !== null && progress > data.progress) {
+ if (progress !== null
+ && data !== undefined
+ && data !== null
+ && progress > data.progress) {
Server.callAPI(`api/archives/${id}/progress/${progress}?force=1`, "PUT", null, "Error updating reading progress!", null);
}
@@ -594,16 +661,18 @@ Index.migrateProgress = function () {
}));
});
- Promise.all(promises).then(() => $.toast({
+ Promise.all(promises).then(() => LRR.toast({
heading: "Reading Progression has been fully migrated! π",
text: "You'll have to reopen archives in the Reader to see the migrated progression values.",
- hideAfter: false,
- position: "top-left",
icon: "success",
+ hideAfter: 13000,
}));
} else {
+ // eslint-disable-next-line no-console
console.log("No local reading progression to migrate");
}
};
-window.Index = Index;
+jQuery(() => {
+ Index.initializeAll();
+});
diff --git a/public/js/index_datatables.js b/public/js/index_datatables.js
index 921c71be3..fcd7e070d 100644
--- a/public/js/index_datatables.js
+++ b/public/js/index_datatables.js
@@ -2,6 +2,7 @@
* All the Archive Index functions related to DataTables.
*/
const IndexTable = {};
+
IndexTable.dataTable = {};
IndexTable.originalTitle = document.title;
IndexTable.isComingFromPopstate = false;
@@ -251,6 +252,12 @@ IndexTable.drawCallback = function () {
}
let currentSort = IndexTable.dataTable.order()[0][0];
+ const currentOrder = IndexTable.dataTable.order()[0][1];
+
+ // Save sort/order/page to localStorage
+ localStorage.indexSort = currentSort;
+ localStorage.indexOrder = currentOrder;
+
// Using double equals here since the sort column can be either a string or an int
// eslint-disable-next-line eqeqeq
if (currentSort == 1) {
@@ -262,7 +269,6 @@ IndexTable.drawCallback = function () {
currentSort = "title";
}
- const currentOrder = IndexTable.dataTable.order()[0][1];
Index.updateTableControls(currentSort, currentOrder, pageInfo.pages, pageInfo.page + 1);
// Clear potential leftover tooltips
@@ -297,9 +303,20 @@ IndexTable.consumeURLParameters = function () {
if (params.has("q")) { IndexTable.currentSearch = decodeURIComponent(params.get("q")); }
+ // Get order from URL, fallback to localstorage if available
const order = [[0, "asc"]];
- if (params.has("sort")) order[0][0] = params.get("sort");
- if (params.has("sortdir")) order[0][1] = params.get("sortdir");
+
+ if (params.has("sort")) {
+ order[0][0] = params.get("sort");
+ } else if (localStorage.indexSort) {
+ order[0][0] = localStorage.indexSort;
+ }
+
+ if (params.has("sortdir")) {
+ order[0][1] = params.get("sortdir");
+ } else if (localStorage.indexOrder) {
+ order[0][1] = localStorage.indexOrder;
+ }
IndexTable.dataTable.order(order);
diff --git a/public/js/logs.js b/public/js/logs.js
new file mode 100644
index 000000000..92d71a58a
--- /dev/null
+++ b/public/js/logs.js
@@ -0,0 +1,40 @@
+/**
+ * Logs Operations.
+ */
+const Logs = {};
+
+Logs.lastType = "";
+
+Logs.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.refresh", "#refresh", Logs.refreshLog);
+ $(document).on("click.loglines", "#loglines", Logs.refreshLog);
+ $(document).on("click.show-general", "#show-general", () => Logs.showLog("general"));
+ $(document).on("click.show-shinobu", "#show-shinobu", () => Logs.showLog("shinobu"));
+ $(document).on("click.show-plugins", "#show-plugins", () => Logs.showLog("plugins"));
+ $(document).on("click.show-mojo", "#show-mojo", () => Logs.showLog("mojo"));
+ $(document).on("click.show-redis", "#show-redis", () => Logs.showLog("redis"));
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+
+ Logs.showLog("general");
+};
+
+Logs.showLog = function (type) {
+ fetch(`/logs/${type}?lines=${$("#loglines").val()}`)
+ .then((response) => response.text())
+ .then((data) => {
+ $("#log-container").html(LRR.encodeHTML(data));
+ $("#indicator").html(type);
+ $("#log-container").scrollTop($("#log-container").prop("scrollHeight"));
+ Logs.lastType = type;
+ })
+ .catch((error) => LRR.showErrorToast("Error getting logs from server", error));
+};
+
+Logs.refreshLog = function () {
+ Logs.showLog(Logs.lastType);
+};
+
+jQuery(() => {
+ Logs.initializeAll();
+});
diff --git a/public/js/plugins.js b/public/js/plugins.js
new file mode 100644
index 000000000..a438b4359
--- /dev/null
+++ b/public/js/plugins.js
@@ -0,0 +1,38 @@
+/**
+ * Plugins Operations.
+ */
+const Plugins = {};
+
+Plugins.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.save", "#save", () => Server.saveFormData("#editPluginForm"));
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+
+ // Handler for file uploading.
+ $("#fileupload").fileupload({
+ url: "/config/plugins/upload",
+ dataType: "json",
+ done(e, data) {
+ if (data.result.success) {
+ LRR.toast({
+ heading: "Plugin successfully uploaded!",
+ text: `The plugin "${data.result.name}" has been successfully added. Refresh the page to see it.`,
+ icon: "info",
+ hideAfter: 10000,
+ });
+ } else {
+ LRR.toast({
+ heading: "Error uploading plugin",
+ text: data.result.error,
+ icon: "error",
+ hideAfter: false,
+ });
+ }
+ },
+
+ });
+};
+
+jQuery(() => {
+ Plugins.initializeAll();
+});
diff --git a/public/js/reader.js b/public/js/reader.js
index bcbcf4695..f1283beda 100644
--- a/public/js/reader.js
+++ b/public/js/reader.js
@@ -9,7 +9,6 @@ Reader.force = false;
Reader.previousPage = -1;
Reader.currentPage = -1;
Reader.showingSinglePage = true;
-Reader.isFullscreen = false;
Reader.preloadedImg = {};
Reader.preloadedSizes = {};
@@ -22,30 +21,30 @@ Reader.initializeAll = function () {
$(document).on("keyup", Reader.handleShortcuts);
$(document).on("wheel", Reader.handleWheel);
- $(document).on("click.toggle_fit_mode", "#fit-mode input", Reader.toggleFitMode);
- $(document).on("click.toggle_double_mode", "#toggle-double-mode input", Reader.toggleDoublePageMode);
- $(document).on("click.toggle_manga_mode", "#toggle-manga-mode input, .reading-direction", Reader.toggleMangaMode);
- $(document).on("click.toggle_header", "#toggle-header input", Reader.toggleHeader);
- $(document).on("click.toggle_progress", "#toggle-progress input", Reader.toggleProgressTracking);
- $(document).on("click.toggle_infinite_scroll", "#toggle-infinite-scroll input", Reader.toggleInfiniteScroll);
- $(document).on("click.toggle_overlay", "#toggle-overlay input", Reader.toggleOverlayByDefault);
- $(document).on("submit.container_width", "#container-width-input", Reader.registerContainerWidth);
- $(document).on("click.container_width", "#container-width-apply", Reader.registerContainerWidth);
+ $(document).on("click.toggle-fit-mode", "#fit-mode input", Reader.toggleFitMode);
+ $(document).on("click.toggle-double-mode", "#toggle-double-mode input", Reader.toggleDoublePageMode);
+ $(document).on("click.toggle-manga-mode", "#toggle-manga-mode input, .reading-direction", Reader.toggleMangaMode);
+ $(document).on("click.toggle-header", "#toggle-header input", Reader.toggleHeader);
+ $(document).on("click.toggle-progress", "#toggle-progress input", Reader.toggleProgressTracking);
+ $(document).on("click.toggle-infinite-scroll", "#toggle-infinite-scroll input", Reader.toggleInfiniteScroll);
+ $(document).on("click.toggle-overlay", "#toggle-overlay input", Reader.toggleOverlayByDefault);
+ $(document).on("submit.container-width", "#container-width-input", Reader.registerContainerWidth);
+ $(document).on("click.container-width", "#container-width-apply", Reader.registerContainerWidth);
$(document).on("submit.preload", "#preload-input", Reader.registerPreload);
$(document).on("click.preload", "#preload-apply", Reader.registerPreload);
- $(document).on("click.pagination_change_pages", ".page-link", Reader.handlePaginator);
-
- $(document).on("click.close_overlay", "#overlay-shade", LRR.closeOverlay);
- $(document).on("click.toggle_full_screen", "#toggle-full-screen", Reader.toggleFullScreen);
- $(document).on("click.toggle_archive_overlay", "#toggle-archive-overlay", Reader.toggleArchiveOverlay);
- $(document).on("click.toggle_settings_overlay", "#toggle-settings-overlay", Reader.toggleSettingsOverlay);
- $(document).on("click.toggle_help", "#toggle-help", Reader.toggleHelp);
- $(document).on("click.regenerate_archive_cache", "#regenerate-cache", () => {
+ $(document).on("click.pagination-change-pages", ".page-link", Reader.handlePaginator);
+
+ $(document).on("click.close-overlay", "#overlay-shade", LRR.closeOverlay);
+ $(document).on("click.toggle-full-screen", "#toggle-full-screen", () => Reader.handleFullScreen(true));
+ $(document).on("click.toggle-archive-overlay", "#toggle-archive-overlay", Reader.toggleArchiveOverlay);
+ $(document).on("click.toggle-settings-overlay", "#toggle-settings-overlay", Reader.toggleSettingsOverlay);
+ $(document).on("click.toggle-help", "#toggle-help", Reader.toggleHelp);
+ $(document).on("click.regenerate-archive-cache", "#regenerate-cache", () => {
window.location.href = `./reader?id=${Reader.id}&force_reload`;
});
- $(document).on("click.edit_metadata", "#edit-archive", () => LRR.openInNewTab(`./edit?id=${Reader.id}`));
- $(document).on("click.add_category", "#add-category", () => Server.addArchiveToCategory(Reader.id, $("#category").val()));
- $(document).on("click.set_thumbnail", "#set-thumbnail", () => Server.callAPI(`/api/archives/${Reader.id}/thumbnail?page=${Reader.currentPage + 1}`,
+ $(document).on("click.edit-metadata", "#edit-archive", () => LRR.openInNewTab(`./edit?id=${Reader.id}`));
+ $(document).on("click.add-category", "#add-category", () => Server.addArchiveToCategory(Reader.id, $("#category").val()));
+ $(document).on("click.set-thumbnail", "#set-thumbnail", () => Server.callAPI(`/api/archives/${Reader.id}/thumbnail?page=${Reader.currentPage + 1}`,
"PUT", `Successfully set page ${Reader.currentPage + 1} as the thumbnail!`, "Error updating thumbnail!", null));
$(document).on("click.thumbnail", ".quick-thumbnail", (e) => {
@@ -58,6 +57,16 @@ Reader.initializeAll = function () {
}
});
+ // Apply full-screen utility
+ // F11 Fullscreen is totally another "Fullscreen", so its support is beyong consideration.
+ if (!window.fscreen.fullscreenEnabled) {
+ // Fullscreen mode is unsupported
+ $("#toggle-full-screen").hide();
+ } else {
+ // Small override function, always returns boolean
+ window.fscreen.inFullscreen = () => !!window.fscreen.fullscreenElement;
+ }
+
// Infer initial information from the URL
const params = new URLSearchParams(window.location.search);
Reader.id = params.get("id");
@@ -97,7 +106,8 @@ Reader.initializeAll = function () {
// Load the actual reader pages now that we have basic info
Reader.loadImages();
- });
+ },
+ );
};
Reader.loadImages = function () {
@@ -144,10 +154,14 @@ Reader.loadImages = function () {
if (Reader.showOverlayByDefault) { Reader.toggleArchiveOverlay(); }
// Wait for the extraction job to conclude before getting thumbnails
- Server.checkJobStatus(data.job, false,
+ Server.checkJobStatus(
+ data.job,
+ false,
() => Reader.initializeArchiveOverlay(),
- () => LRR.showErrorToast("The extraction job didn't conclude properly. Your archive might be corrupted."));
- }).finally(() => {
+ () => LRR.showErrorToast("The extraction job didn't conclude properly. Your archive might be corrupted."),
+ );
+ },
+ ).finally(() => {
if (Reader.pages === undefined) {
$("#img").attr("src", "img/flubbed.gif");
$("#display").append("I flubbed it while trying to open the archive.
");
@@ -209,12 +223,13 @@ Reader.initInfiniteScrollView = function () {
Reader.pages.slice(1).forEach((source) => {
const img = new Image();
img.src = source;
+ img.loading = "lazy";
$(img).addClass("reader-image");
$("#display").append(img);
});
$("#i3").removeClass("loading");
- $(document).on("click.infinite_scroll_map", "#display .reader-image", () => Reader.changePage(1));
+ $(document).on("click.infinite-scroll-map", "#display .reader-image", () => Reader.changePage(1));
Reader.applyContainerWidth();
};
@@ -275,7 +290,7 @@ Reader.handleShortcuts = function (e) {
};
Reader.handleWheel = function (e) {
- if (Reader.isFullscreen && !Reader.infiniteScroll) {
+ if (window.fscreen.inFullscreen() && !Reader.infiniteScroll) {
let changePage = 1;
if (e.originalEvent.deltaY > 0) changePage = -1;
// In Manga mode, reverse the changePage variable
@@ -288,43 +303,32 @@ Reader.handleWheel = function (e) {
Reader.checkFiletypeSupport = function (extension) {
if ((extension === "rar" || extension === "cbr") && !localStorage.rarWarningShown) {
localStorage.rarWarningShown = true;
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "This archive seems to be in RAR format!",
text: "RAR archives might not work properly in LANraragi depending on how they were made. If you encounter errors while reading, consider converting your archive to zip.",
- hideAfter: false,
icon: "warning",
+ hideAfter: 23000,
});
} else if (extension === "epub" && !localStorage.epubWarningShown) {
localStorage.epubWarningShown = true;
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "EPUB support in LANraragi is minimal",
text: "EPUB books will only show images in the Web Reader. If you want text support, consider pairing LANraragi with an OPDS reader.",
- hideAfter: false,
icon: "warning",
+ hideAfter: 20000,
+ closeOnClick: false,
+ draggable: false,
});
}
};
Reader.toggleHelp = function () {
- const existingToast = $(".navigation-help-toast:visible");
- if (existingToast.length) {
- // ugly hack: this is an abandoned plugin, we should be using something like toastr
- existingToast.closest(".jq-toast-wrap").find(".close-jq-toast-single").click();
- return false;
- }
-
- $.toast({
+ LRR.toast({
+ toastId: "readerHelp",
heading: "Navigation Help",
text: $("#reader-help").children().first().html(),
- hideAfter: false,
- position: "top-left",
icon: "info",
+ hideAfter: 60000,
});
return false;
@@ -589,46 +593,29 @@ Reader.toggleArchiveOverlay = function () {
};
Reader.toggleFullScreen = function () {
- // if already full screen; exit
- // else go fullscreen
- if (
- document.fullscreenElement
- || document.webkitFullscreenElement
- || document.mozFullScreenElement
- || document.msFullscreenElement
- ) {
- if ($("body").hasClass("infinite-scroll")) {
- $("div#i3").removeClass("fullscreen-infinite");
- } else {
- $("div#i3").removeClass("fullscreen");
- }
- if (document.exitFullscreen) {
- document.exitFullscreen();
- } else if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- } else if (document.webkitExitFullscreen) {
- document.webkitExitFullscreen();
- } else if (document.msExitFullscreen) {
- document.msExitFullscreen();
- }
- Reader.isFullscreen = false;
+ if (window.fscreen.inFullscreen()) {
+ // if already full screen; exit
+ window.fscreen.exitFullscreen();
+ Reader.handleFullScreen();
} else {
+ // else go fullscreen
+ Reader.handleFullScreen(true);
+ }
+};
+
+Reader.handleFullScreen = function (enableFullscreen = false) {
+ if (window.fscreen.inFullscreen() || enableFullscreen === true) {
if ($("body").hasClass("infinite-scroll")) {
$("div#i3").addClass("fullscreen-infinite");
} else {
$("div#i3").addClass("fullscreen");
}
- const element = $("div#i3").get(0);
- if (element.requestFullscreen) {
- element.requestFullscreen();
- } else if (element.mozRequestFullScreen) {
- element.mozRequestFullScreen();
- } else if (element.webkitRequestFullscreen) {
- element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- } else if (element.msRequestFullscreen) {
- element.msRequestFullscreen();
- }
- Reader.isFullscreen = true;
+ // ensure in every case, the correct fullscreen element is binded.
+ window.fscreen.requestFullscreen($("div#i3").get(0));
+ } else if ($("body").hasClass("infinite-scroll")) {
+ $("div#i3").removeClass("fullscreen-infinite");
+ } else {
+ $("div#i3").removeClass("fullscreen");
}
};
@@ -672,9 +659,12 @@ Reader.initializeArchiveOverlay = function () {
thumbSuccess();
} else if (response.status === 202) {
// Wait for Minion job to finish
- response.json().then((data) => Server.checkJobStatus(data.job, false,
+ response.json().then((data) => Server.checkJobStatus(
+ data.job,
+ false,
() => thumbSuccess(),
- () => thumbFail()));
+ () => thumbFail(),
+ ));
} else {
// We don't have a thumbnail for this page
thumbFail();
@@ -731,9 +721,3 @@ Reader.handlePaginator = function () {
break;
}
};
-
-$(document).ready(() => {
- Reader.initializeAll();
-});
-
-window.Reader = Reader;
diff --git a/public/js/server.js b/public/js/server.js
index efc9c09c6..e11834d8f 100644
--- a/public/js/server.js
+++ b/public/js/server.js
@@ -23,12 +23,10 @@ Server.callAPI = function (endpoint, method, successMessage, errorMessage, succe
throw new Error(data.error);
} else {
if (successMessage !== null) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: successMessage,
icon: "success",
+ hideAfter: 7000,
});
}
@@ -84,10 +82,7 @@ Server.saveFormData = function (formSelector) {
.then((response) => (response.ok ? response.json() : { success: 0, error: "Response was not OK" }))
.then((data) => {
if (data.success) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Saved Successfully!",
icon: "success",
});
@@ -115,21 +110,22 @@ Server.triggerScript = function (namespace) {
.then(Server.callAPI(`/api/plugins/queue?plugin=${namespace}&arg=${scriptArg}`, "POST", null, "Error while executing Script :",
(data) => {
// Check minion job state periodically while we're on this page
- Server.checkJobStatus(data.job, true,
+ Server.checkJobStatus(
+ data.job,
+ true,
(d) => {
Server.isScriptRunning = false;
$(".script-running").hide();
$(".stdbtn").show();
if (d.result.success === 1) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Script result",
text: `${JSON.stringify(d.result.data, null, 4)}
`,
- hideAfter: false,
icon: "info",
+ hideAfter: 10000,
+ closeOnClick: false,
+ draggable: false,
});
} else LRR.showErrorToast(`Script failed: ${d.result.error}`);
},
@@ -137,15 +133,18 @@ Server.triggerScript = function (namespace) {
Server.isScriptRunning = false;
$(".script-running").hide();
$(".stdbtn").show();
- });
- }));
+ },
+ );
+ },
+ ));
};
Server.cleanTemporaryFolder = function () {
Server.callAPI("/api/tempfolder", "DELETE", "Temporary Folder Cleaned!", "Error while cleaning Temporary Folder :",
(data) => {
$("#tempsize").html(data.newsize);
- });
+ },
+ );
};
Server.invalidateCache = function () {
@@ -157,68 +156,81 @@ Server.clearAllNewFlags = function () {
};
Server.dropDatabase = function () {
- if (confirm("Danger! Are you *sure* you want to do this?")) {
- Server.callAPI("/api/database/drop", "POST", "Sayonara! Redirecting you...", "Error while resetting the database? Check Logs.",
- () => {
- setTimeout(() => { document.location.href = "./"; }, 1500);
- });
- }
+ LRR.showPopUp({
+ title: "This is a (very) destructive operation! ",
+ text: "Are you sure you want to wipe the database?",
+ icon: "warning",
+ showCancelButton: true,
+ focusConfirm: false,
+ confirmButtonText: "Yes, do it!",
+ reverseButtons: true,
+ confirmButtonColor: "#d33",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ Server.callAPI("/api/database/drop", "POST", "Sayonara! Redirecting you...", "Error while resetting the database? Check Logs.",
+ () => {
+ setTimeout(() => { document.location.href = "./"; }, 1500);
+ },
+ );
+ }
+ });
};
Server.cleanDatabase = function () {
Server.callAPI("/api/database/clean", "POST", null, "Error while cleaning the database! Check Logs.",
(data) => {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: `Successfully cleaned the database and removed ${data.deleted} entries!`,
icon: "success",
+ hideAfter: 7000,
});
if (data.unlinked > 0) {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
- heading: `${data.unlinked} other entries have been unlinked from the database and will be deleted on the next cleanup!
Do a backup now if some files disappeared from your archive index.`,
- hideAfter: false,
+ LRR.toast({
+ heading: `${data.unlinked} other entries have been unlinked from the database and will be deleted on the next cleanup!`,
+ text: "Do a backup now if some files disappeared from your archive index.",
icon: "warning",
+ hideAfter: 16000,
});
}
- });
+ },
+ );
};
Server.regenerateThumbnails = function (force) {
const forceparam = force ? 1 : 0;
Server.callAPI(`/api/regen_thumbs?force=${forceparam}`, "POST",
- "Queued up a job to regenerate thumbnails! Stay tuned for updates or check the Minion console.", "Error while sending job to Minion:",
+ "Queued up a job to regenerate thumbnails! Stay tuned for updates or check the Minion console.",
+ "Error while sending job to Minion:",
(data) => {
// Disable the buttons to avoid accidental double-clicks.
$("#genthumb-button").prop("disabled", true);
$("#forcethumb-button").prop("disabled", true);
// Check minion job state periodically while we're on this page
- Server.checkJobStatus(data.job, true,
+ Server.checkJobStatus(
+ data.job,
+ true,
(d) => {
$("#genthumb-button").prop("disabled", false);
$("#forcethumb-button").prop("disabled", false);
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "All thumbnails generated! Encountered the following errors:",
text: d.result.errors,
- hideAfter: false,
icon: "success",
+ hideAfter: 15000,
+ closeOnClick: false,
+ draggable: false,
});
},
(error) => {
$("#genthumb-button").prop("disabled", false);
$("#forcethumb-button").prop("disabled", false);
LRR.showErrorToast("The thumbnail regen job failed!", error);
- });
- });
+ },
+ );
+ },
+ );
};
// Adds an archive to a category. Basic implementation to use everywhere.
@@ -242,25 +254,20 @@ Server.deleteArchive = function (arcId, callback) {
.then((response) => (response.ok ? response.json() : { success: 0, error: "Response was not OK" }))
.then((data) => {
if (data.success === "0") {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Couldn't delete archive file.
(Maybe it has already been deleted beforehand?)",
text: "Archive metadata has been deleted properly.
Please delete the file manually before returning to Library View.",
- hideAfter: false,
icon: "warning",
+ hideAfter: 20000,
});
$(".stdbtn").hide();
$("#goback").show();
} else {
- $.toast({
- showHideTransition: "slide",
- position: "top-left",
- loader: false,
+ LRR.toast({
heading: "Archive successfully deleted. Redirecting you ...",
text: `File name : ${data.filename}`,
icon: "success",
+ hideAfter: 7000,
});
setTimeout(callback, 1500);
}
diff --git a/public/js/stats.js b/public/js/stats.js
new file mode 100644
index 000000000..098e6baf0
--- /dev/null
+++ b/public/js/stats.js
@@ -0,0 +1,41 @@
+/**
+ * Stats Operations.
+ */
+const Stats = {};
+
+Stats.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.goback", "#goback", () => { window.location.replace("./"); });
+
+ Server.callAPI("/api/database/stats?minweight=2", "GET", null, "Couldn't load tag statistics",
+ (data) => {
+ $("#statsLoading").hide();
+ $("#tagcount").html(data.length);
+ $("#tagCloud").jQCloud(data, {
+ autoResize: true,
+ });
+
+ // Sort data by weight
+ data.sort((a, b) => b.weight - a.weight);
+
+ // Buildup detailed stats
+ const tagList = $("#tagList");
+ data.forEach((tag) => {
+ const namespacedTag = LRR.buildNamespacedTag(tag.namespace, tag.text);
+ const url = LRR.getTagSearchURL(tag.namespace, tag.text);
+
+ const ocss = "max-width: 95%; display: flex;";
+ const icss = "text-overflow: ellipsis; white-space: nowrap; overflow: hidden; min-width: 0; max-width: 100%;";
+
+ const html = `${namespacedTag} (${tag.weight})`;
+ tagList.append(html);
+ });
+
+ $("#detailedStats").show();
+ },
+ );
+};
+
+jQuery(() => {
+ Stats.initializeAll();
+});
diff --git a/public/js/upload.js b/public/js/upload.js
index 94ac2eefd..a1f35e38a 100644
--- a/public/js/upload.js
+++ b/public/js/upload.js
@@ -1,26 +1,96 @@
// Scripting for the Upload page.
+const Upload = {};
let processingArchives = 0;
let completedArchives = 0;
let failedArchives = 0;
let totalUploads = 0;
+// Set up jqueryfileupload.
+Upload.initializeAll = function () {
+ // bind events to DOM
+ $(document).on("click.download-url", "#download-url", Upload.downloadUrl);
+ $(document).on("click.return", "#return", () => { window.location.href = "/"; });
+
+ $("#fileupload").fileupload({
+ dataType: "json",
+ formData() {
+ const array = [{ name: "catid", value: document.getElementById("category").value }];
+ return array;
+ },
+ done(e, data) {
+ let result;
+ if (data.result.success === 0) {
+ result = `${data.result.name} |
+ ${data.result.error} |
+
`;
+ } else {
+ result = `
+ ${data.result.name}
+ |
+
+ Processing file... (Job #${data.result.job})
+ |
+
`;
+ }
+
+ $("#progress .bar").css("width", "0%");
+ $("#files").append(result);
+
+ totalUploads += 1;
+ processingArchives += 1;
+ Upload.updateUploadCounters();
+
+ // Check minion job state periodically to update the result
+ Server.checkJobStatus(
+ data.result.job,
+ true,
+ (d) => Upload.handleCompletedUpload(data.result.job, d),
+ (error) => Upload.handleFailedUpload(data.result.job, error),
+ );
+ },
+
+ fail(e, data) {
+ const result = `${data.result.name} |
+ ${data.errorThrown} |
+
`;
+ $("#progress .bar").css("width", "0%");
+ $("#files").append(result);
+
+ totalUploads += 1;
+ failedArchives += 1;
+ Upload.updateUploadCounters();
+ },
+
+ progressall(e, data) {
+ const progress = parseInt((data.loaded / data.total) * 100, 10);
+ $("#progress .bar").css("width", `${progress}%`);
+ },
+
+ });
+};
+
// Handle updating the upload counters.
-function updateUploadCounters() {
+Upload.updateUploadCounters = function () {
$("#progressCount").html(`π€ Processing: ${processingArchives} π Completed: ${completedArchives} πΉ Failed: ${failedArchives}`);
- const icon = (completedArchives == totalUploads) ? "fas fa-check-circle"
- : failedArchives > 0 ? "fas fa-exclamation-circle"
- : "fa fa-spinner fa-spin";
-
+ let icon;
+ if (completedArchives === totalUploads) {
+ icon = "fas fa-check-circle";
+ } else if (failedArchives > 0) {
+ icon = "fas fa-exclamation-circle";
+ } else {
+ icon = "fa fa-spinner fa-spin";
+ }
$("#progressTotal").html(` Total:${completedArchives + failedArchives}/${totalUploads}`);
// At the end of the upload job, dump the search cache!
if (processingArchives === 0) { Server.invalidateCache(); }
-}
+};
-// Handle a completed job from minion. Update the line in upload results with the title, ID, message.
-function handleCompletedUpload(jobID, d) {
+// Handle a completed job from minion.
+// Update the line in upload results with the title, ID, message.
+Upload.handleCompletedUpload = function (jobID, d) {
$(`#${jobID}-name`).html(d.result.title);
if (d.result.id) {
@@ -31,28 +101,28 @@ function handleCompletedUpload(jobID, d) {
if (d.result.success) {
$(`#${jobID}-link`).html(`Click here to edit metadata.
(${d.result.message})`);
$(`#${jobID}-icon`).attr("class", "fa fa-check-circle");
- completedArchives++;
+ completedArchives += 1;
} else {
$(`#${jobID}-link`).html(`Error while processing archive.
(${d.result.message})`);
$(`#${jobID}-icon`).attr("class", "fa fa-exclamation-circle");
- failedArchives++;
+ failedArchives += 1;
}
- processingArchives--;
- updateUploadCounters();
-}
+ processingArchives -= 1;
+ Upload.updateUploadCounters();
+};
-function handleFailedUpload(jobID, d) {
+Upload.handleFailedUpload = function (jobID, d) {
$(`#${jobID}-link`).html(`Error while processing file.
(${d})`);
$(`#${jobID}-icon`).attr("class", "fa fa-exclamation-circle");
- failedArchives++;
- processingArchives--;
- updateUploadCounters();
-}
+ failedArchives += 1;
+ processingArchives -= 1;
+ Upload.updateUploadCounters();
+};
// Send URLs to the Download API and add a Server.checkJobStatus to track its progress.
-function downloadUrl() {
+Upload.downloadUrl = function () {
const categoryID = document.getElementById("category").value;
// One fetch job per non-empty line of the form
@@ -73,7 +143,7 @@ function downloadUrl() {
.then((response) => response.json())
.then((data) => {
if (data.success) {
- result = `
+ const result = ` |
${data.url}
|
@@ -83,74 +153,25 @@ function downloadUrl() {
$("#files").append(result);
- totalUploads++;
- processingArchives++;
- updateUploadCounters();
+ totalUploads += 1;
+ processingArchives += 1;
+ Upload.updateUploadCounters();
// Check minion job state periodically to update the result
- Server.checkJobStatus(data.job, true,
- (d) => handleCompletedUpload(data.job, d),
- (error) => handleFailedUpload(data.job, error));
+ Server.checkJobStatus(
+ data.job,
+ true,
+ (d) => Upload.handleCompletedUpload(data.job, d),
+ (error) => Upload.handleFailedUpload(data.job, error),
+ );
} else {
throw new Error(data.message);
}
})
.catch((error) => LRR.showErrorToast("Error while adding download job", error));
});
-}
-
-// Set up jqueryfileupload.
-function initUpload() {
- $("#fileupload").fileupload({
- dataType: "json",
- formData(form) {
- const array = [{ name: "catid", value: document.getElementById("category").value }];
- return array;
- },
- done(e, data) {
- if (data.result.success == 0) {
- result = ` |
${data.result.name} |
- ${data.result.error} |
-
`;
- } else {
- result = `
- ${data.result.name}
- |
-
- Processing file... (Job #${data.result.job})
- |
-
`;
- }
-
- $("#progress .bar").css("width", "0%");
- $("#files").append(result);
-
- totalUploads++;
- processingArchives++;
- updateUploadCounters();
+};
- // Check minion job state periodically to update the result
- Server.checkJobStatus(data.result.job, true,
- (d) => handleCompletedUpload(data.result.job, d),
- (error) => handleFailedUpload(data.result.job, error));
- },
-
- fail(e, data) {
- result = `${data.result.name} |
- ${data.errorThrown} |
-
`;
- $("#progress .bar").css("width", "0%");
- $("#files").append(result);
-
- totalUploads++;
- failedArchives++;
- updateUploadCounters();
- },
-
- progressall(e, data) {
- const progress = parseInt(data.loaded / data.total * 100, 10);
- $("#progress .bar").css("width", `${progress}%`);
- },
-
- });
-}
+jQuery(() => {
+ Upload.initializeAll();
+});
diff --git a/public/themes/ex.css b/public/themes/ex.css
index d2268ad60..28f8bfdb4 100644
--- a/public/themes/ex.css
+++ b/public/themes/ex.css
@@ -1,7 +1,22 @@
/* misc */
-body{font-size:8pt;font-family:arial,helvetica,sans-serif;color:#f1f1f1;background:#34353b;padding:2px;margin:0px; text-align: center;}
-p{padding:3px 1px;margin:0}
-img{border:0}
+body {
+ font-size: 8pt;
+ font-family: arial, helvetica, sans-serif;
+ color: #f1f1f1;
+ background: #34353b;
+ padding: 2px;
+ margin: 0px;
+ text-align: center;
+}
+
+p {
+ padding: 3px 1px;
+ margin: 0
+}
+
+img {
+ border: 0
+}
.logo-container {
background-color: #DDDDDD;
@@ -11,24 +26,52 @@ a.fa {
text-decoration: none;
}
-a{color:#DDDDDD; }
-a:hover{color:#FFFBDB}
+a {
+ color: #DDDDDD;
+}
-input[type='checkbox']:checked::after, input[type='checkbox']:checked::before {
+a:hover {
+ color: #FFFBDB
+}
+
+input[type='checkbox']:checked::after,
+input[type='checkbox']:checked::before {
color: #FFFBDB;
-}
+}
-.sorting_asc>a, .sorting_desc>a {
+.sorting_asc>a,
+.sorting_desc>a {
color: #FFFBDB !important;
}
-.sorting_asc>a:hover, .sorting_desc>a:hover {
+
+.sorting_asc>a:hover,
+.sorting_desc>a:hover {
color: #DDDDDD !important;
}
-p.ip{margin:-3px auto;text-align:center;padding:0px 5px 5px 5px;clear:both}
+p.ip {
+ margin: -3px auto;
+ text-align: center;
+ padding: 0px 5px 5px 5px;
+ clear: both
+}
-img.mr{border:0;margin-left:10px;width:5px;height:7px}
-div.ido{background:#4f535b;border:1px solid #000000;width:99%;max-width:1200px;margin:10px auto 10px auto;padding:5px;position:relative}
+img.mr {
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px
+}
+
+div.ido {
+ background: #4f535b;
+ border: 1px solid #000000;
+ width: 99%;
+ max-width: 1200px;
+ margin: 10px auto 10px auto;
+ padding: 5px;
+ position: relative
+}
/* Tags */
div.gt {
@@ -48,21 +91,33 @@ div.gt {
text-overflow: ellipsis;
}
-.tagger > ul > li:not(.tagger-new) > a, .tagger li:not(.tagger-new) > span, .tagger .tagger-new ul {
+.tagger>ul>li:not(.tagger-new)>a,
+.tagger li:not(.tagger-new)>span,
+.tagger .tagger-new ul {
background: none repeat scroll 0 0 #4F535B;
border: 1px solid #989898;
border-radius: 5px 5px 5px 5px;
- }
-
- .tagger > ul > li:not(.tagger-new) a, .tagger > ul > li:not(.tagger-new) a:visited, .tagger-new ul a, .tagger-new ul a:visited {
+}
+
+.tagger>ul>li:not(.tagger-new) a,
+.tagger>ul>li:not(.tagger-new) a:visited,
+.tagger-new ul a,
+.tagger-new ul a:visited {
color: #DDDDDD;
- }
-
- div.gt:hover, .tagger > ul > li:not(.tagger-new) a:hover {
+}
+
+div.gt:hover,
+.tagger>ul>li:not(.tagger-new) a:hover {
color: #3b97ea;
}
-h1.ih{font-size:10pt;font-weight:bold;margin:2px auto;text-align:center;padding-bottom:6px}
+h1.ih {
+ font-size: 10pt;
+ font-weight: bold;
+ margin: 2px auto;
+ text-align: center;
+ padding-bottom: 6px
+}
.caption-tags {
padding: 5px;
@@ -74,26 +129,38 @@ h1.ih{font-size:10pt;font-weight:bold;margin:2px auto;text-align:center;padding-
}
/* navbar */
-p#nb{margin:2px auto;text-align:center}
-p#nb img{border:0;margin-left:10px;width:5px;height:7px}
-p#nb a{font-weight:bold}
+p#nb {
+ margin: 2px auto;
+ text-align: center
+}
+
+p#nb img {
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px
+}
+
+p#nb a {
+ font-weight: bold
+}
/* shared table stuff */
table.itg {
- width:99% !important;
- max-width:1175px;
- border-collapse:collapse;
- margin:0px auto;
- padding:3px;
- border:2px ridge #3c3c3c;
- font-size:9pt
-}
-
-table.itg th{
- font-weight:bold;
- text-align:left;
- padding:3px 4px 3px 4px;
- background:#40454b;
+ width: 99% !important;
+ max-width: 1175px;
+ border-collapse: collapse;
+ margin: 0px auto;
+ padding: 3px;
+ border: 2px ridge #3c3c3c;
+ font-size: 9pt
+}
+
+table.itg th {
+ font-weight: bold;
+ text-align: left;
+ padding: 3px 4px 3px 4px;
+ background: #40454b;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -108,55 +175,111 @@ table.itc {
min-width: 50px;
height: 25px;
border-radius: 3px;
- color:#f1f1f1;
- background:#4f5052;
- border:2px solid #000000;
- margin:4px 1px 0px 1px;
- padding:0px 4px 1px 4px;
+ color: #f1f1f1;
+ background: #4f5052;
+ border: 2px solid #000000;
+ margin: 4px 1px 0px 1px;
+ padding: 0px 4px 1px 4px;
cursor: pointer;
}
.favtag-btn:hover {
- background:#3b3435;
+ background: #3b3435;
}
.toggled {
- background:#3b3435 !important;
+ background: #3b3435 !important;
border: 2px solid #3b97ea !important;
}
/* options flyout menus */
.option-flyout {
border: 1px solid #363940;
- background:#40454b;
- padding:3px 4px 3px 4px;
+ background: #40454b;
+ padding: 3px 4px 3px 4px;
margin: 10px;
}
-.caret-right::after, .caret::before {
+.caret-right::after,
+.caret::before {
color: #DDDDDD !important;
}
/* gallery table */
-tr.gtr{background:#40454b}
-tr.gtr0{background:#4f535b}
-tr.gtr1{background:#363940}
+tr.gtr {
+ background: #40454b
+}
+
+tr.gtr0 {
+ background: #4f535b
+}
+
+tr.gtr1 {
+ background: #363940
+}
/* input */
-.stdbtn{font-size:8pt;color:#f1f1f1;background:#34353b;border:2px solid #000000;height:21px;margin:4px 1px 0px 1px;padding:0px 4px 1px 4px; min-width: 150px; cursor: pointer;}
-.stdbtn:hover{color:#f1f1f1;background:#43464e;border:2px solid #000000}
-.stdbtn:focus{color:#f1f1f1;background:#43464e}
+.stdbtn {
+ font-size: 8pt;
+ color: #f1f1f1;
+ background: #34353b;
+ border: 2px solid #000000;
+ height: 21px;
+ margin: 4px 1px 0px 1px;
+ padding: 0px 4px 1px 4px;
+ min-width: 150px;
+ cursor: pointer;
+}
-.stdinput{font-size:8pt;color:#f1f1f1;background:#34353b;border:1px solid #000000;margin:4px 1px 0px 1px;padding:2px 3px 2px 3px; width:80%; max-width: 450px;}
-.stdinput:hover{color:#f1f1f1;background:#43464e}
-.stdinput:focus{color:#f1f1f1;background:#43464e}
-.stdinput:disabled{color:#f1f1f1;background:#34353b}
+.swal2-actions>.stdbtn {
+ background: #34353b;
+ border: 2px solid #000000;
+ border-radius: 0;
+ height: 24px;
+}
+
+.stdbtn:hover {
+ color: #f1f1f1;
+ background: #43464e;
+ border: 2px solid #000000
+}
+
+.stdbtn:focus {
+ color: #f1f1f1;
+ background: #43464e
+}
+
+.stdinput {
+ font-size: 8pt;
+ color: #f1f1f1;
+ background: #34353b;
+ border: 1px solid #000000;
+ margin: 4px 1px 0px 1px;
+ padding: 2px 3px 2px 3px;
+ width: 80%;
+ max-width: 450px;
+}
+
+.stdinput:hover {
+ color: #f1f1f1;
+ background: #43464e
+}
+
+.stdinput:focus {
+ color: #f1f1f1;
+ background: #43464e
+}
+
+.stdinput:disabled {
+ color: #f1f1f1;
+ background: #34353b
+}
.tagger {
- font-size:8pt !important;
- color:#f1f1f1;
- background:#34353b;
- border:1px solid #000000;
+ font-size: 8pt !important;
+ color: #f1f1f1;
+ background: #34353b;
+ border: 1px solid #000000;
max-width: 450px;
width: 60%;
margin: 4px 1px 0;
@@ -164,26 +287,77 @@ tr.gtr1{background:#363940}
display: table-cell;
}
-.searchbtn{min-width: 100px !important;}
+.searchbtn {
+ min-width: 100px !important;
+}
/* gallery row */
-td.itd{text-align:left;padding:3px 4px;border-right:1px solid #40454b}
-td.itd a{text-decoration:none}
-td.itu{text-align:left;padding:3px 4px 3px 4px;border-right:1px solid #40454b}
-td.itu a{text-decoration:none}
-td.itdc{text-align:center;padding:0 1px 0 2px;margin:0;border-right:1px solid #40454b}
+td.itd {
+ text-align: left;
+ padding: 3px 4px;
+ border-right: 1px solid #40454b
+}
+
+td.itd a {
+ text-decoration: none
+}
+
+td.itu {
+ text-align: left;
+ padding: 3px 4px 3px 4px;
+ border-right: 1px solid #40454b
+}
+
+td.itu a {
+ text-decoration: none
+}
+
+td.itdc {
+ text-align: center;
+ padding: 0 1px 0 2px;
+ margin: 0;
+ border-right: 1px solid #40454b
+}
/* page selector */
-.paginate_button {display:inline-block; text-align:center;height:15px;width:31px;background:#34353b;cursor:pointer;border:1px solid #000000;margin-top:10px;margin-bottom:10px;}
-.paginate_button:hover{color:#000000;background:#43464e}
-.paginate_button.current{color:#C2A8A4;background:#43464e}
+.paginate_button {
+ display: inline-block;
+ text-align: center;
+ height: 15px;
+ width: 31px;
+ background: #34353b;
+ cursor: pointer;
+ border: 1px solid #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.paginate_button:hover {
+ color: #000000;
+ background: #43464e
+}
+
+.paginate_button.current {
+ color: #C2A8A4;
+ background: #43464e
+}
+
.ellipsis {
- display:inline-block; text-align:center;height:15px;width:31px;background:#34353b;cursor:pointer;border:1px solid #000000;margin-top:10px;margin-bottom:10px;
+ display: inline-block;
+ text-align: center;
+ height: 15px;
+ width: 31px;
+ background: #34353b;
+ cursor: pointer;
+ border: 1px solid #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
}
+
.table-options {
margin-right: 4px;
margin-left: 4px;
- margin-bottom:-38px;
+ margin-bottom: -38px;
}
.table-option {
@@ -196,65 +370,125 @@ td.itdc{text-align:center;padding:0 1px 0 2px;margin:0;border-right:1px solid #4
.collapsible-right {
padding: 0.2rem 0.5rem 0 0 !important;
- }
+}
.index-carousel {
margin-top: 0 !important;
margin-bottom: 12px !important;
}
-.index-carousel > .option-flyout {
+.index-carousel>.option-flyout {
margin: 0 !important;
}
.carousel-prev {
- padding:4px;
- border:1px solid #000000;
+ padding: 4px;
+ border: 1px solid #000000;
background-color: #40454b;
}
-
+
.carousel-next {
- padding:4px;
- border:1px solid #000000;
+ padding: 4px;
+ border: 1px solid #000000;
background-color: #40454b;
}
/* image pages */
-div.sni{background:#4f535b;border:1px solid #000000;text-align:center;margin:2px auto 6px;padding:0 10px 5px;position:relative;z-index:1}
-div.sni h1{font-size:12pt;font-weight:bold;text-align:center}
-div.sni img{border:0;vertical-align:middle;margin:1px;clear:both}
-div.if{margin:-5px auto 5px}
-div.sn{margin:1px auto;font-size:10pt;height:32px;z-index:1}
-div.sn div{
- margin:2px 25px 0px;display:inline;
+div.sni {
+ background: #4f535b;
+ border: 1px solid #000000;
+ text-align: center;
+ margin: 2px auto 6px;
+ padding: 0 10px 5px;
+ position: relative;
+ z-index: 1
+}
+
+div.sni h1 {
+ font-size: 12pt;
+ font-weight: bold;
+ text-align: center
+}
+
+div.sni img {
+ border: 0;
+ vertical-align: middle;
+ margin: 1px;
+ clear: both
+}
+
+div.if {
+ margin: -5px auto 5px
+}
+
+div.sn {
+ margin: 1px auto;
+ font-size: 10pt;
+ height: 32px;
+ z-index: 1
+}
+
+div.sn div {
+ margin: 2px 25px 0px;
+ display: inline;
padding-bottom: 8px;
vertical-align: middle;
}
-div.sn span{font-weight:bold}
-div.sn img{width:30px;height:30px;padding:0px 2px}
-div.sb{margin-top:-10px;position:relative;z-index:2}
-div.sb img{border:0}
+
+div.sn span {
+ font-weight: bold
+}
+
+div.sn img {
+ width: 30px;
+ height: 30px;
+ padding: 0px 2px
+}
+
+div.sb {
+ margin-top: -10px;
+ position: relative;
+ z-index: 2
+}
+
+div.sb img {
+ border: 0
+}
/* index */
-div.idi{margin:auto;border-collapse:collapse;margin:0px auto 8px auto;padding:5px;border:2px ridge #000000;text-align:center}
-div#toppane{margin:auto;width:99%}
+div.idi {
+ margin: auto;
+ border-collapse: collapse;
+ margin: 0px auto 8px auto;
+ padding: 5px;
+ border: 2px ridge #000000;
+ text-align: center
+}
+div#toppane {
+ margin: auto;
+ width: 99%
+}
-.caption, .context-menu-list {
-background-color: #4f535b;
-border:2px solid #000000 !important;
+
+.caption,
+.context-menu-list {
+ background-color: #4f535b;
+ border: 2px solid #000000 !important;
}
.context-menu-list {
padding: 6px 0;
box-shadow: none;
-}
+}
-.context-menu-item, .context-menu-icon.context-menu-icon--fa5 i {
+.context-menu-item,
+.context-menu-icon.context-menu-icon--fa5 i {
color: #DDDDDD;
}
-.context-menu-item.context-menu-hover, .context-menu-icon.context-menu-icon--fa5.context-menu-hover > i {
+.context-menu-item.context-menu-hover,
+.context-menu-icon.context-menu-icon--fa5.context-menu-hover>i {
background: #3b97ea !important;
color: #f1f1f1 !important;
}
@@ -270,33 +504,30 @@ border:2px solid #000000 !important;
}
.indeterminate {
- width:99%;
- max-width:1175px;
-}
-
-/* Toasts */
-.jq-toast-single {
- font-family: arial,helvetica,sans-serif !important;
-}
-
-.jq-toast-single h2 {
- font-family: arial,helvetica,sans-serif !important;
+ width: 99%;
+ max-width: 1175px;
}
/* Tag Cloud */
div.jqcloud {
- font-family: arial,helvetica,sans-serif !important;
+ font-family: arial, helvetica, sans-serif !important;
}
-div.jqcloud span.w10, div.jqcloud span.w8, div.jqcloud span.w9 {
+div.jqcloud span.w10,
+div.jqcloud span.w8,
+div.jqcloud span.w9 {
color: #f2f2f2 !important;
}
-div.jqcloud span.w7, div.jqcloud span.w6, div.jqcloud span.w5 {
+div.jqcloud span.w7,
+div.jqcloud span.w6,
+div.jqcloud span.w5 {
color: #DDDDDD !important;
}
-div.jqcloud span.w4, div.jqcloud span.w3, div.jqcloud span.w2 {
+div.jqcloud span.w4,
+div.jqcloud span.w3,
+div.jqcloud span.w2 {
color: #cccccc !important;
}
@@ -331,7 +562,7 @@ div.id2 a {
text-decoration: none;
}
-div.id3 {
+div.id3 {
margin: auto;
overflow: hidden;
position: relative;
@@ -352,22 +583,55 @@ div.id4 {
}
/* awesomplete */
-.awesomplete > ul {
+.awesomplete>ul {
background: #34353b;
border-radius: 0px;
border: 1px solid #000000;
box-shadow: none;
}
-.awesomplete > ul:before {
+
+.awesomplete>ul:before {
background: #34353b;
}
-.awesomplete > ul > li:hover {
+
+.awesomplete>ul>li:hover {
color: inherit;
background: #4f535b;
}
+
.awesomplete mark {
background: #3b97ea;
}
+
.awesomplete li:hover mark {
background: #3b97ea;
}
+
+/** Toasts **/
+.Toastify {
+ --toastify-text-color-light: #DDDDDD;
+ --toastify-color-light: #4F535B;
+}
+
+.Toastify__toast {
+ box-shadow: 0 0 0 0;
+ border: 1px solid #000000;
+ border-radius: 0;
+}
+
+.Toastify__close-button--light {
+ color: #fff;
+ opacity: 1;
+}
+
+.Toastify__toast-body a {
+ color: #fff;
+}
+
+/** Popups **/
+.swal2-popup {
+ background: #43464e;
+ color: #dddddd;
+ border-radius: 0;
+ border: 1px solid #000000;
+}
\ No newline at end of file
diff --git a/public/themes/g.css b/public/themes/g.css
index 2e711afa7..5afa3f952 100644
--- a/public/themes/g.css
+++ b/public/themes/g.css
@@ -1,17 +1,18 @@
-
body {
background: none repeat scroll 0 0 #E3E0D1;
color: #5C0D11;
- font-family: arial,helvetica,sans-serif;
+ font-family: arial, helvetica, sans-serif;
font-size: 8pt;
margin: 0;
padding: 2px;
text-align: center;
}
+
p {
margin: 0;
padding: 3px 1px;
}
+
img {
border: 0 none;
}
@@ -27,18 +28,23 @@ a.fa {
a {
color: #5C0D11;
}
+
a:hover {
color: #9B4E03;
}
-input[type='checkbox']:checked::after, input[type='checkbox']:checked::before {
+input[type='checkbox']:checked::after,
+input[type='checkbox']:checked::before {
color: #9B4E03;
-}
+}
-.sorting_asc>a, .sorting_desc>a {
+.sorting_asc>a,
+.sorting_desc>a {
color: #9B4E03 !important;
}
-.sorting_asc>a:hover, .sorting_desc>a:hover {
+
+.sorting_asc>a:hover,
+.sorting_desc>a:hover {
color: #5C0D11 !important;
}
@@ -48,12 +54,14 @@ p.ip {
padding: 0 5px 5px;
text-align: center;
}
+
img.mr {
border: 0 none;
height: 7px;
margin-left: 10px;
width: 5px;
}
+
div.ido {
background: none repeat scroll 0 0 #EDEBDF;
border: 1px solid #5C0D12;
@@ -82,17 +90,23 @@ div.gt {
text-overflow: ellipsis;
}
-.tagger > ul > li:not(.tagger-new) > a, .tagger li:not(.tagger-new) > span, .tagger .tagger-new ul {
+.tagger>ul>li:not(.tagger-new)>a,
+.tagger li:not(.tagger-new)>span,
+.tagger .tagger-new ul {
background: none repeat scroll 0 0 #F2EFDF;
border: 1px solid #806769;
border-radius: 5px 5px 5px 5px;
- }
-
- .tagger > ul > li:not(.tagger-new) a, .tagger > ul > li:not(.tagger-new) a:visited, .tagger-new ul a, .tagger-new ul a:visited {
+}
+
+.tagger>ul>li:not(.tagger-new) a,
+.tagger>ul>li:not(.tagger-new) a:visited,
+.tagger-new ul a,
+.tagger-new ul a:visited {
color: #5C0D11;
- }
-
- div.gt:hover, .tagger > ul > li:not(.tagger-new) a:hover {
+}
+
+div.gt:hover,
+.tagger>ul>li:not(.tagger-new) a:hover {
color: #9b4e03;
}
@@ -103,19 +117,23 @@ h1.ih {
padding-bottom: 6px;
text-align: center;
}
+
p#nb {
margin: 2px auto;
text-align: center;
}
+
p#nb img {
border: 0 none;
height: 7px;
margin-left: 10px;
width: 5px;
}
+
p#nb a {
font-weight: bold;
}
+
table.itg {
border: 2px ridge #5C0D12;
border-collapse: collapse;
@@ -125,6 +143,7 @@ table.itg {
padding: 3px;
width: 99% !important;
}
+
table.itg th {
background: none repeat scroll 0 0 #E0DED3;
font-weight: bold;
@@ -147,8 +166,8 @@ table.itc {
background: none repeat scroll 0 0 #EDEADA;
border: 2px outset #5C0D11;
color: #5C0D11;
- margin:4px 1px 0px 1px;
- padding:0px 4px 1px 4px;
+ margin: 4px 1px 0px 1px;
+ padding: 0px 4px 1px 4px;
cursor: pointer;
font-weight: bold;
}
@@ -168,20 +187,24 @@ table.itc {
/* options flyout menus */
.option-flyout {
border: 1px solid #363940;
- background:#E0DED3;
- padding:3px 4px 3px 4px;
+ background: #E0DED3;
+ padding: 3px 4px 3px 4px;
margin: 10px;
}
-.caret-right::after, .caret::before {
+.caret-right::after,
+.caret::before {
color: #5C0D11 !important;
}
+
tr.gtr0 {
background: none repeat scroll 0 0 #EDEBDF;
}
+
tr.gtr1 {
background: none repeat scroll 0 0 #F2F0E4;
}
+
.stdbtn {
background: none repeat scroll 0 0 #EDEADA;
border: 2px outset #5C0D11;
@@ -193,15 +216,26 @@ tr.gtr1 {
min-width: 150px;
cursor: pointer;
}
+
+.swal2-actions>.stdbtn {
+ background: none repeat scroll 0 0 #EDEADA;
+ border: 2px outset #5C0D11;
+ color: #5C0D11;
+ border-radius: 0;
+ height: 24px;
+}
+
.stdbtn:hover {
background: none repeat scroll 0 0 #F2EFDF;
border: 2px outset #9B4E03;
color: #9B4E03;
}
+
.stdbtn:focus {
background: none repeat scroll 0 0 #F2EFDF;
color: #9B4E03;
}
+
.stdinput {
background: none repeat scroll 0 0 #EDEADA;
border: 1px solid #5C0D11;
@@ -212,17 +246,19 @@ tr.gtr1 {
width: 80%;
max-width: 450px;
}
+
.stdinput:hover {
background: none repeat scroll 0 0 #F2EFDF;
color: #9B4E03;
}
+
.stdinput:focus {
background: none repeat scroll 0 0 #F2EFDF;
color: #9B4E03;
}
.tagger {
- font-size:8pt !important;
+ font-size: 8pt !important;
background: none repeat scroll 0 0 #EDEADA;
border: 1px solid #5C0D11;
color: #5C0D11;
@@ -236,25 +272,30 @@ tr.gtr1 {
.searchbtn {
min-width: 100px !important;
}
+
td.itd {
border-right: 1px solid #D9D7CC;
padding: 3px 4px;
text-align: left;
}
+
td.itd a {
text-decoration: none;
}
+
td.itu {
border-right: 1px solid #D9D7CC;
padding: 3px 4px;
text-align: left;
}
+
td.itdc {
border-right: 1px solid #D9D7CC;
margin: 0;
padding: 0 1px 0 2px;
text-align: center;
}
+
div.sni {
background: none repeat scroll 0 0 #EDEBDF;
border: 1px solid #5C0D12;
@@ -262,50 +303,61 @@ div.sni {
padding: 0 10px 5px;
position: relative;
text-align: center;
-
+
z-index: 1;
}
+
div.sni h1 {
font-size: 12pt;
font-weight: bold;
text-align: center;
}
+
div.sni img {
border: 0 none;
clear: both;
margin: 1px;
vertical-align: middle;
}
+
div.if {
margin: -5px auto 5px;
}
+
div.sn {
font-size: 10pt;
height: 32px;
margin: 1px auto;
z-index: 1;
}
-div.sn div{
- margin:2px 25px 0px;display:inline;
+
+div.sn div {
+ margin: 2px 25px 0px;
+ display: inline;
padding-bottom: 8px;
vertical-align: middle;
}
+
div.sn span {
font-weight: bold;
}
+
div.sn img {
height: 30px;
padding: 0 2px;
width: 30px;
}
+
div.sb {
margin-top: -10px;
position: relative;
z-index: 2;
}
+
div.sb img {
border: 0 none;
}
+
div.idi {
border: 2px ridge #5C0D12;
border-collapse: collapse;
@@ -313,6 +365,7 @@ div.idi {
padding: 5px;
text-align: center;
}
+
div#toppane {
margin: auto;
width: 99%;
@@ -320,15 +373,44 @@ div#toppane {
/* page selector */
-.paginate_button {display:inline-block; text-align:center;height:15px;width:31px;background:#EDEADA;cursor:pointer;border:1px solid #000000;margin-top:10px;margin-bottom:10px;}
-.paginate_button:hover{color:#9b4e03;background:#F2EFDF}
-.paginate_button.current{color:#5C0D11;background:#F2EFDF;}
-.ellipsis {display:inline-block; text-align:center;height:15px;width:31px;background:#EDEADA;cursor:pointer;border:1px solid #000000;margin-top:10px;margin-bottom:10px;}
+.paginate_button {
+ display: inline-block;
+ text-align: center;
+ height: 15px;
+ width: 31px;
+ background: #EDEADA;
+ cursor: pointer;
+ border: 1px solid #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.paginate_button:hover {
+ color: #9b4e03;
+ background: #F2EFDF
+}
+
+.paginate_button.current {
+ color: #5C0D11;
+ background: #F2EFDF;
+}
+
+.ellipsis {
+ display: inline-block;
+ text-align: center;
+ height: 15px;
+ width: 31px;
+ background: #EDEADA;
+ cursor: pointer;
+ border: 1px solid #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
.table-options {
margin-right: 4px;
margin-left: 4px;
- margin-bottom:-38px;
+ margin-bottom: -38px;
}
.table-option {
@@ -341,32 +423,33 @@ div#toppane {
.collapsible-right {
padding: 0.2rem 0.5rem 0 0 !important;
- }
+}
.index-carousel {
margin-top: 0 !important;
margin-bottom: 12px !important;
}
-.index-carousel > .option-flyout {
+.index-carousel>.option-flyout {
margin: 0 !important;
}
.carousel-prev {
- padding:4px;
+ padding: 4px;
border: 1px solid #5C0D12;
background-color: #e0ded3;
}
-
+
.carousel-next {
- padding:4px;
+ padding: 4px;
border: 1px solid #5C0D12;
background-color: #e0ded3;
}
-.caption, .context-menu-list {
-background-color: #EDEADA;
-border:2px outset #5C0D11 !important;
+.caption,
+.context-menu-list {
+ background-color: #EDEADA;
+ border: 2px outset #5C0D11 !important;
}
.caption-tags {
@@ -387,18 +470,20 @@ border:2px outset #5C0D11 !important;
.context-menu-list {
padding: 6px 0;
box-shadow: none;
-}
+}
-.context-menu-item, .context-menu-icon.context-menu-icon--fa5 i {
+.context-menu-item,
+.context-menu-icon.context-menu-icon--fa5 i {
color: #5C0D11;
font-size: 8pt;
}
-.context-menu-item.context-menu-hover, .context-menu-icon.context-menu-icon--fa5.context-menu-hover > i {
+.context-menu-item.context-menu-hover,
+.context-menu-icon.context-menu-icon--fa5.context-menu-hover>i {
font-weight: bold;
background: #F2EFDF !important;
color: #5C0D11 !important;
-
+
}
.context-menu-submenu::after {
@@ -412,33 +497,39 @@ border:2px outset #5C0D11 !important;
}
.indeterminate {
- width:99%;
- max-width:1175px;
+ width: 99%;
+ max-width: 1175px;
}
/* Toasts */
-.jq-toast-single {
- font-family: arial,helvetica,sans-serif !important;
+.jq-toast-single {
+ font-family: arial, helvetica, sans-serif !important;
}
-.jq-toast-single h2 {
- font-family: arial,helvetica,sans-serif !important;
+.jq-toast-single h2 {
+ font-family: arial, helvetica, sans-serif !important;
}
/* Tag Cloud */
div.jqcloud {
- font-family: arial,helvetica,sans-serif !important;
+ font-family: arial, helvetica, sans-serif !important;
}
-div.jqcloud span.w10, div.jqcloud span.w8, div.jqcloud span.w9 {
+div.jqcloud span.w10,
+div.jqcloud span.w8,
+div.jqcloud span.w9 {
color: #5C0D11 !important;
}
-div.jqcloud span.w7, div.jqcloud span.w6, div.jqcloud span.w5 {
+div.jqcloud span.w7,
+div.jqcloud span.w6,
+div.jqcloud span.w5 {
color: #861319 !important;
}
-div.jqcloud span.w4, div.jqcloud span.w3, div.jqcloud span.w2 {
+div.jqcloud span.w4,
+div.jqcloud span.w3,
+div.jqcloud span.w2 {
color: #b31921 !important;
}
@@ -472,7 +563,7 @@ div.id2 a {
text-decoration: none;
}
-div.id3 {
+div.id3 {
margin: auto;
overflow: hidden;
position: relative;
@@ -493,23 +584,55 @@ div.id4 {
}
/* awesomplete */
-.awesomplete > ul {
+.awesomplete>ul {
background: #EDEADA;
border-radius: 0px;
- border:1px outset #5C0D11;
+ border: 1px outset #5C0D11;
box-shadow: none;
}
-.awesomplete > ul:before {
+
+.awesomplete>ul:before {
background: #EDEADA;
}
-.awesomplete > ul > li:hover, .awesomplete > ul > li[aria-selected="true"] {
+
+.awesomplete>ul>li:hover,
+.awesomplete>ul>li[aria-selected="true"] {
color: inherit;
background: #E0DED3;
}
+
.awesomplete mark {
background: hsl(39, 74%, 60%);
}
-.awesomplete li:hover mark, .awesomplete li[aria-selected="true"] mark{
+
+.awesomplete li:hover mark,
+.awesomplete li[aria-selected="true"] mark {
background: hsl(39, 74%, 60%);
}
+/** Toasts **/
+.Toastify {
+ --toastify-text-color-light: #5c0d11;
+ --toastify-color-light: #EDEBDF;
+
+}
+
+.Toastify__toast {
+ box-shadow: 0 0 0 0;
+ border: 1px solid #5C0D12;
+}
+
+.Toastify__toast-body a {
+ color: #5C0D11;
+}
+
+.Toastify__toast-body a:hover {
+ color: #9B4E03;
+}
+
+/** handle the popup color and the text color based on the theme **/
+.swal2-popup {
+ border: 1px solid #5C0D12;
+ background: #EDEBDF;
+ color: #5c0d11;
+}
\ No newline at end of file
diff --git a/public/themes/modern.css b/public/themes/modern.css
index 85015d359..930fb5b55 100644
--- a/public/themes/modern.css
+++ b/public/themes/modern.css
@@ -1,31 +1,32 @@
/* misc */
p {
- padding:3px 1px;margin:0
+ padding: 3px 1px;
+ margin: 0
}
img {
- border:0
+ border: 0
}
@font-face {
- font-family: "Open Sans";
- font-style: normal;
- font-weight: 400;
- src: local("Open Sans"), local("OpenSans"), url(../css/webfonts/OpenSans-Regular.woff) format("woff");
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 400;
+ src: local("Open Sans"), local("OpenSans"), url(../css/webfonts/OpenSans-Regular.woff) format("woff");
}
@font-face {
- font-family: "Open Sans";
- font-style: normal;
- font-weight: 700;
- src: local("Open Sans Bold"), local("OpenSans-Bold"), url(../css/webfonts/OpenSans-Bold.woff) format("woff");
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 700;
+ src: local("Open Sans Bold"), local("OpenSans-Bold"), url(../css/webfonts/OpenSans-Bold.woff) format("woff");
}
body {
background-color: #34353B;
color: #DDDDDD;
- font-family: "Open Sans", arial,sans-serif;
+ font-family: "Open Sans", arial, sans-serif;
height: 100%;
text-align: center;
padding: 2px;
@@ -41,42 +42,47 @@ a {
color: #DDDDDD;
text-decoration: none;
}
+
a:hover {
color: #3b97ea;
text-decoration: none;
}
-input[type='checkbox']:checked::after, input[type='checkbox']:checked::before {
+input[type='checkbox']:checked::after,
+input[type='checkbox']:checked::before {
color: #3b97ea;
}
-.sorting_asc>a, .sorting_desc>a {
+.sorting_asc>a,
+.sorting_desc>a {
color: #3b97ea !important;
}
-.sorting_asc>a:hover, .sorting_desc>a:hover {
+
+.sorting_asc>a:hover,
+.sorting_desc>a:hover {
color: #DDDDDD !important;
}
p.ip {
- margin: -3px auto;
- text-align: center;
- padding: 10px 5px 5px 5px;
- clear: both;
+ margin: -3px auto;
+ text-align: center;
+ padding: 10px 5px 5px 5px;
+ clear: both;
}
img.mr {
- border: 0;
- margin-left: 10px;
- width: 5px;
- height: 7px;
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px;
}
h1.ih {
- font-size: 10pt;
- font-weight: bold;
- margin: 2px auto;
- text-align: center;
- padding-bottom: 6px;
+ font-size: 10pt;
+ font-weight: bold;
+ margin: 2px auto;
+ text-align: center;
+ padding-bottom: 6px;
}
div.ido {
@@ -110,45 +116,54 @@ div.gt {
text-overflow: ellipsis;
}
-.tagger > ul > li:not(.tagger-new) > a, .tagger li:not(.tagger-new) > span, .tagger .tagger-new ul {
+.tagger>ul>li:not(.tagger-new)>a,
+.tagger li:not(.tagger-new)>span,
+.tagger .tagger-new ul {
background: none repeat scroll 0 0 #4F535B;
border-radius: 3px;
}
-.tagger > ul > li:not(.tagger-new) a, .tagger > ul > li:not(.tagger-new) a:visited, .tagger-new ul a, .tagger-new ul a:visited {
+.tagger>ul>li:not(.tagger-new) a,
+.tagger>ul>li:not(.tagger-new) a:visited,
+.tagger-new ul a,
+.tagger-new ul a:visited {
color: #DDDDDD;
}
-div.gt:hover, .tagger > ul > li:not(.tagger-new) a:hover {
+div.gt:hover,
+.tagger>ul>li:not(.tagger-new) a:hover {
color: #3b97ea;
}
/* navbar */
p#nb {
- margin: 2px auto;
- text-align: center;
+ margin: 2px auto;
+ text-align: center;
}
+
p#nb img {
- border: 0;
- margin-left: 10px;
- width: 5px;
- height: 7px
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px
}
+
p#nb a {
- font-weight: bold
+ font-weight: bold
}
/* shared table stuff */
table.itg {
- width: 99% !important;
+ width: 99% !important;
max-width: 90%;
border-collapse: collapse;
margin: 0 auto;
padding: 3px;
font-size: 9pt;
- border: 1px solid #363940;
- border-radius: 9px;
+ border: 1px solid #363940;
+ border-radius: 9px;
}
+
table.itg th {
font-weight: bold;
text-align: left;
@@ -168,7 +183,7 @@ table.itg th {
color: #363940;
}
-.caption-tags > table.itg {
+.caption-tags>table.itg {
color: #dddddd !important;
}
@@ -207,6 +222,7 @@ table.itc {
padding: 3px 4px;
margin: 10px;
}
+
.caret-right::after,
.caret::before {
color: #DDDDDD !important;
@@ -214,13 +230,15 @@ table.itc {
/* gallery table */
tr.gtr {
- background: #40454b
+ background: #40454b
}
+
tr.gtr0 {
- background: #4F535B
+ background: #4F535B
}
+
tr.gtr1 {
- background: #363940
+ background: #363940
}
/* input */
@@ -238,23 +256,28 @@ tr.gtr1 {
min-width: 150px;
cursor: pointer;
}
+
+.swal2-actions>.stdbtn {
+ background-color: #34353B;
+}
+
.stdbtn:hover {
- background-color: #43464E
+ background-color: #43464E
}
-.option-td > .stdbtn,
-.caption-tags > * > * > .stdbtn {
+.option-td>.stdbtn,
+.caption-tags>*>*>.stdbtn {
background-color: #4F535B;
}
-.option-td > .stdbtn:hover,
-.caption-tags > * > * > .stdbtn:hover {
- background-color: #43464E;
+.option-td>.stdbtn:hover,
+.caption-tags>*>*>.stdbtn:hover {
+ background-color: #43464E;
}
.stdinput {
background: none repeat scroll 0 0 #ECF0F1;
- font-family: "Open Sans", arial,sans-serif;
+ font-family: "Open Sans", arial, sans-serif;
border: medium none;
color: #34353B;
font-size: 9pt;
@@ -266,7 +289,7 @@ tr.gtr1 {
.tagger {
background: none repeat scroll 0 0 #ECF0F1;
- font-family: "Open Sans", arial,sans-serif;
+ font-family: "Open Sans", arial, sans-serif;
border: medium none;
color: #34353B;
max-width: 768px;
@@ -282,35 +305,38 @@ tr.gtr1 {
/* gallery row */
td.itd,
td.itu {
- text-align: left;
- padding: 3px 4px;
- border-right: 1px solid #40454b;
+ text-align: left;
+ padding: 3px 4px;
+ border-right: 1px solid #40454b;
}
+
td.itd a,
td.itu a {
- text-decoration: none;
+ text-decoration: none;
}
+
td.itdc {
- text-align: center;
- padding: 0 1px 0 2px;
- margin: 0;
- border-right: 1px solid #40454b;
+ text-align: center;
+ padding: 0 1px 0 2px;
+ margin: 0;
+ border-right: 1px solid #40454b;
}
/* page selector */
.paginate_button {
- display: inline-block;
- text-align: center;
- height: 30px;
- width: 31px;
- cursor: pointer;
- font-size: 2em;
- margin-top: 10px;
+ display: inline-block;
+ text-align: center;
+ height: 30px;
+ width: 31px;
+ cursor: pointer;
+ font-size: 2em;
+ margin-top: 10px;
margin-bottom: 10px;
margin-left: 0.5vw;
}
+
.paginate_button:hover {
- background-color: #F5F7F7;
+ background-color: #F5F7F7;
border-radius: 100%;
}
@@ -336,26 +362,26 @@ td.itdc {
}
.carousel-prev {
- padding:4px;
+ padding: 4px;
background-color: #363940;
border-radius: 0px 9px 9px 0px;
}
.carousel-next {
- padding:4px;
+ padding: 4px;
background-color: #363940;
border-radius: 9px 0px 0px 9px;
}
/* image pages */
div.sni {
- text-align: center;
- margin: 2px auto 6px;
- padding: 0 10px 5px;
- /*min-width: 800px;*/
- position: relative;
- z-index: 1;
- background-color: #4F535B;
+ text-align: center;
+ margin: 2px auto 6px;
+ padding: 0 10px 5px;
+ /*min-width: 800px;*/
+ position: relative;
+ z-index: 1;
+ background-color: #4F535B;
border-radius: 9px;
clear: both;
display: block;
@@ -363,29 +389,34 @@ div.sni {
padding-bottom: 10px;
width: 98%;
}
+
div.sni h1 {
- font-size: 12pt;
- font-weight: bold;
- text-align: center;
+ font-size: 12pt;
+ font-weight: bold;
+ text-align: center;
}
+
div.sni img {
- border: 0;
- vertical-align: middle;
- margin: 1px;
- clear: both;
+ border: 0;
+ vertical-align: middle;
+ margin: 1px;
+ clear: both;
}
+
div.if {
- margin: -5px auto 5px;
+ margin: -5px auto 5px;
}
+
div.sn {
- margin: 10px auto;
- font-size: 15pt;
- height: 32px;
- z-index: 1;
+ margin: 10px auto;
+ font-size: 15pt;
+ height: 32px;
+ z-index: 1;
}
+
div.sn div {
margin: 2px 25px 0;
- display: inline;
+ display: inline;
padding-bottom: 15px;
vertical-align: middle;
}
@@ -395,21 +426,23 @@ div.pagecount {
}
div.sn span {
- font-weight: bold;
+ font-weight: bold;
}
+
div.sn img {
- width: 30px;
- height: 30px;
- padding: 0 2px;
+ width: 30px;
+ height: 30px;
+ padding: 0 2px;
}
div.sb {
- margin-top: -10px;
- position: relative;
- z-index: 2
+ margin-top: -10px;
+ position: relative;
+ z-index: 2
}
+
div.sb img {
- border: 0;
+ border: 0;
}
div.idi {
@@ -421,15 +454,16 @@ div.idi {
text-align: center;
/*width: 598px;*/
}
+
div#toppane {
- margin: auto;
- width: 99%
+ margin: auto;
+ width: 99%
}
.caption,
.context-menu-list {
- background-color: #34353B;
- box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
+ background-color: #34353B;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.context-menu-item {
@@ -445,7 +479,7 @@ div#toppane {
}
.context-menu-item.context-menu-hover,
-.context-menu-icon.context-menu-icon--fa5.context-menu-hover > i {
+.context-menu-icon.context-menu-icon--fa5.context-menu-hover>i {
background-color: #43464E !important;
color: #3b97ea;
}
@@ -469,29 +503,27 @@ div#toppane {
width: 90%;
}
-/* Toasts */
-.jq-toast-single,
-.jq-toast-single h2 {
- font-family: "Open Sans", arial, sans-serif !important;
-}
-
/* Tag Cloud */
div.jqcloud {
font-family: "Open Sans", arial, sans-serif !important;
}
+
div.jqcloud span.w1 {
color: #b3b3b3 !important;
}
+
div.jqcloud span.w2,
div.jqcloud span.w3,
div.jqcloud span.w4 {
color: #cccccc !important;
}
+
div.jqcloud span.w5,
div.jqcloud span.w6,
div.jqcloud span.w7 {
color: #dddddd !important;
}
+
div.jqcloud span.w8,
div.jqcloud span.w9,
div.jqcloud span.w10 {
@@ -546,20 +578,44 @@ div.id4 {
}
/* awesomplete */
-.awesomplete > ul {
+.awesomplete>ul {
background: #363940;
border-radius: 3px;
border: none;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
-.awesomplete > ul:before {
+
+.awesomplete>ul:before {
background: #363940;
}
-.awesomplete > ul > li:hover {
+
+.awesomplete>ul>li:hover {
color: inherit;
background: #4F535B;
}
+
.awesomplete mark,
.awesomplete li:hover mark {
background: #3b97ea;
}
+
+/** Toasts **/
+.Toastify {
+ --toastify-text-color-light: #DDDDDD;
+ --toastify-color-light: #4F535B;
+}
+
+.Toastify__close-button--light {
+ color: #fff;
+ opacity: 1;
+}
+
+.Toastify__toast-body a {
+ color: #fff;
+}
+
+/** Popups **/
+.swal2-popup {
+ background: #43464e;
+ color: #dddddd;
+}
\ No newline at end of file
diff --git a/public/themes/modern_clear.css b/public/themes/modern_clear.css
index 8874a5315..eb34388e5 100644
--- a/public/themes/modern_clear.css
+++ b/public/themes/modern_clear.css
@@ -1,33 +1,38 @@
/* misc */
-p{padding:3px 1px;margin:0}
+p {
+ padding: 3px 1px;
+ margin: 0
+}
-img{border:0}
+img {
+ border: 0
+}
@font-face {
- font-family: 'Inter UI';
- font-style: normal;
- font-weight: 400;
- src: local('Inter'), local('Inter'), url(../css/webfonts/Inter-Regular.woff) format('woff');
+ font-family: 'Inter UI';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Inter'), local('Inter'), url(../css/webfonts/Inter-Regular.woff) format('woff');
}
@font-face {
- font-family: 'Inter UI';
- font-style: normal;
- font-weight: 700;
- src: local('Inter'), local('Inter-Bold'), url(../css/webfonts/Inter-Bold.woff) format('woff');
-}
+ font-family: 'Inter UI';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Inter'), local('Inter-Bold'), url(../css/webfonts/Inter-Bold.woff) format('woff');
+}
body {
background-color: #FCFCFC;
color: #34495E;
- font-family: 'Inter UI',arial,sans-serif;
+ font-family: 'Inter UI', arial, sans-serif;
height: 100%;
text-align: center;
- padding:2px;
- margin:0px;
+ padding: 2px;
+ margin: 0px;
font-size: 8pt;
-}
+}
.logo-container {
background-color: #34495E;
@@ -37,29 +42,50 @@ a {
color: #34495E;
text-decoration: none;
}
+
a:hover {
color: #ed2553;
text-decoration: none;
}
-input[type='checkbox']:checked::after, input[type='checkbox']:checked::before {
+input[type='checkbox']:checked::after,
+input[type='checkbox']:checked::before {
color: #ed2553;
-}
+}
-.sorting_asc>a, .sorting_desc>a {
+.sorting_asc>a,
+.sorting_desc>a {
color: #ed2553 !important;
}
-.sorting_asc>a:hover, .sorting_desc>a:hover {
+
+.sorting_asc>a:hover,
+.sorting_desc>a:hover {
color: #34495E !important;
}
-p.ip{margin:-3px auto;text-align:center;padding:10px 5px 5px 5px;clear:both}
+p.ip {
+ margin: -3px auto;
+ text-align: center;
+ padding: 10px 5px 5px 5px;
+ clear: both
+}
-img.mr{border:0;margin-left:10px;width:5px;height:7px}
+img.mr {
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px
+}
-h1.ih{font-size:10pt;font-weight:bold;margin:2px auto;text-align:center;padding-bottom:6px}
+h1.ih {
+ font-size: 10pt;
+ font-weight: bold;
+ margin: 2px auto;
+ text-align: center;
+ padding-bottom: 6px
+}
-div.ido{
+div.ido {
background-color: #E1E7E9;
box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
clear: both;
@@ -69,9 +95,9 @@ div.ido{
margin-top: 10px;
padding-top: 10px;
padding-bottom: 10px;
- width:99%;
- max-width:90%;
- position:relative;
+ width: 99%;
+ max-width: 90%;
+ position: relative;
}
/* Tags */
@@ -91,57 +117,87 @@ div.gt {
text-overflow: ellipsis;
}
-.tagger > ul > li:not(.tagger-new) > a, .tagger li:not(.tagger-new) > span, .tagger .tagger-new ul {
+.tagger>ul>li:not(.tagger-new)>a,
+.tagger li:not(.tagger-new)>span,
+.tagger .tagger-new ul {
background: none repeat scroll 0 0 #4F535B;
border-radius: 3px;
}
-
-.tagger > ul > li:not(.tagger-new) a, .tagger > ul > li:not(.tagger-new) a:visited, .tagger-new ul a, .tagger-new ul a:visited {
+
+.tagger>ul>li:not(.tagger-new) a,
+.tagger>ul>li:not(.tagger-new) a:visited,
+.tagger-new ul a,
+.tagger-new ul a:visited {
color: #DDDDDD;
}
-
-div.gt:hover, .tagger > ul > li:not(.tagger-new) a:hover {
+
+div.gt:hover,
+.tagger>ul>li:not(.tagger-new) a:hover {
color: #ed2553;
}
/* navbar */
-p#nb{margin:2px auto;text-align:center}
-p#nb img{border:0;margin-left:10px;width:5px;height:7px}
-p#nb a{font-weight:bold; color: #34495E;}
-p#nb a:hover{color: #ed2553;}
+p#nb {
+ margin: 2px auto;
+ text-align: center
+}
-p.ip a{color: #34495E;}
-p.ip a:hover{color: #ed2553;}
+p#nb img {
+ border: 0;
+ margin-left: 10px;
+ width: 5px;
+ height: 7px
+}
+
+p#nb a {
+ font-weight: bold;
+ color: #34495E;
+}
+
+p#nb a:hover {
+ color: #ed2553;
+}
+
+p.ip a {
+ color: #34495E;
+}
+
+p.ip a:hover {
+ color: #ed2553;
+}
/* shared table stuff */
-table.itg{
+table.itg {
width: 99% !important;
- max-width:90%;
- border-collapse:collapse;
- margin:0px auto;
- padding:3px;
- font-size:9pt;
- border: 0 none;
- box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
+ max-width: 90%;
+ border-collapse: collapse;
+ margin: 0px auto;
+ padding: 3px;
+ font-size: 9pt;
+ border: 0 none;
+ box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
color: #FCFCFC;
}
-table.itg th{font-weight:bold;
- text-align:left;
- padding:3px 4px 3px 4px;
- background:#34495E;
- color:#FCFCFC;
+
+table.itg th {
+ font-weight: bold;
+ text-align: left;
+ padding: 3px 4px 3px 4px;
+ background: #34495E;
+ color: #FCFCFC;
overflow: hidden;
- text-overflow: ellipsis;}
+ text-overflow: ellipsis;
+}
.tippy-content {
border: 1px solid #E1E7E9;
}
-.caption-tags > table.itg {
+.caption-tags>table.itg {
color: #34495E !important;
}
-.caption-tags > * > * > h2 {
+.caption-tags>*>*>h2 {
color: #34495E !important;
}
@@ -162,10 +218,10 @@ table.itc {
border: 0 none;
border-radius: 3px 3px 3px 3px;
color: #f1f1f1;
- font-family: 'Inter UI',arial,sans-serif;
+ font-family: 'Inter UI', arial, sans-serif;
margin: 1px;
outline: 0 none;
- padding: 0 4px 1px;
+ padding: 0 4px 1px;
cursor: pointer;
}
@@ -175,7 +231,7 @@ table.itc {
}
.toggled {
- background:#ed2553 !important;
+ background: #ed2553 !important;
color: #f1f1f1 !important;
}
@@ -192,13 +248,13 @@ table.itc {
.option-flyout {
border: 1px solid #FCFCFC;
border-radius: 9px 9px 9px 9px;
- background:#34495E;
- padding:3px 4px 3px 4px;
+ background: #34495E;
+ padding: 3px 4px 3px 4px;
margin: 10px;
color: #FCFCFC;
}
-.option-flyout > .collapsible-body > table {
+.option-flyout>.collapsible-body>table {
color: #FCFCFC
}
@@ -210,21 +266,36 @@ table.itc {
color: #34495E !important;
}
-table.itg a, .collapsible-title a{color:#FCFCFC;}
-table.itg a:hover, .collapsible-title a:hover{color:#ed2553;}
+table.itg a,
+.collapsible-title a {
+ color: #FCFCFC;
+}
+
+table.itg a:hover,
+.collapsible-title a:hover {
+ color: #ed2553;
+}
/* gallery table */
-tr.gtr{background:#40454b}
-tr.gtr0{background:#475D73}
-tr.gtr1{background:#34495E}
+tr.gtr {
+ background: #40454b
+}
+
+tr.gtr0 {
+ background: #475D73
+}
+
+tr.gtr1 {
+ background: #34495E
+}
/* input */
-.stdbtn{
+.stdbtn {
background-color: #34495E;
border: 0 none;
border-radius: 3px 3px 3px 3px;
color: #f1f1f1;
- font-family: 'Inter UI',arial,sans-serif;
+ font-family: 'Inter UI', arial, sans-serif;
font-size: 9pt;
height: 28px;
margin: 1px;
@@ -234,19 +305,22 @@ tr.gtr1{background:#34495E}
cursor: pointer;
}
-.id1 > * > .stdbtn, .id1 > .stdbtn, .collapsible-body .stdbtn {
+.id1>*>.stdbtn,
+.id1>.stdbtn,
+.collapsible-body .stdbtn,
+.swal2-actions>.stdbtn {
background-color: #e1e7e9;
color: #34495e;
}
.stdbtn:hover {
- background-color: #ed2553;
+ background-color: #ed2553;
color: #f1f1f1
}
-.stdinput{
+.stdinput {
background: none repeat scroll 0 0 #fcfcfc;
- font-family: 'Inter UI',arial,sans-serif;
+ font-family: 'Inter UI', arial, sans-serif;
border: medium none;
color: #34495E;
font-size: 9pt;
@@ -254,13 +328,13 @@ tr.gtr1{background:#34495E}
margin: 4px 1px 0;
padding: 2px 3px;
max-width: 450px;
- width:80%;
+ width: 80%;
border-radius: 3px;
}
.tagger {
background: none repeat scroll 0 0 #fcfcfc;
- font-family: 'Inter UI',arial,sans-serif;
+ font-family: 'Inter UI', arial, sans-serif;
border: medium none;
color: #34495E;
max-width: 768px;
@@ -274,23 +348,56 @@ tr.gtr1{background:#34495E}
}
/* gallery row */
-td.itd{text-align:left;padding:3px 4px;border-right:1px solid #566D75}
-td.itd a{text-decoration:none}
-td.itu{text-align:left;padding:3px 4px 3px 4px;border-right:1px solid #566D75}
-td.itu a{text-decoration:none}
-td.itdc{text-align:center;padding:0 1px 0 2px;margin:0;border-right:1px solid #566D75}
+td.itd {
+ text-align: left;
+ padding: 3px 4px;
+ border-right: 1px solid #566D75
+}
+
+td.itd a {
+ text-decoration: none
+}
+
+td.itu {
+ text-align: left;
+ padding: 3px 4px 3px 4px;
+ border-right: 1px solid #566D75
+}
+
+td.itu a {
+ text-decoration: none
+}
+
+td.itdc {
+ text-align: center;
+ padding: 0 1px 0 2px;
+ margin: 0;
+ border-right: 1px solid #566D75
+}
/* page selector */
-.paginate_button {display:inline-block; text-align:center;height:30px;width:31px;cursor:pointer;font-size: 2em;margin-top:10px;margin-bottom:10px;margin-left: 0.5vw;}
-.paginate_button:hover{
- background-color: #df696e;
+.paginate_button {
+ display: inline-block;
+ text-align: center;
+ height: 30px;
+ width: 31px;
+ cursor: pointer;
+ font-size: 2em;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ margin-left: 0.5vw;
+}
+
+.paginate_button:hover {
+ background-color: #df696e;
border-radius: 100% 100% 100% 100%;
}
-.paginate_button.current{
+
+.paginate_button.current {
font-weight: bold;
background-color: #566D75;
border-radius: 100% 100% 100% 100%;
- color:#E1E7E9;
+ color: #E1E7E9;
}
.ellipsis {
@@ -306,18 +413,18 @@ td.itdc{text-align:center;padding:0 1px 0 2px;margin:0;border-right:1px solid #5
.table-option {
margin-bottom: 3px;
-}
+}
.carousel-prev {
- padding:4px;
+ padding: 4px;
background-color: #34495e;
color: #e1e7e9;
border-radius: 0px 9px 9px 0px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
-
+
.carousel-next {
- padding:4px;
+ padding: 4px;
background-color: #34495e;
color: #e1e7e9;
border-radius: 9px 0px 0px 9px;
@@ -327,56 +434,115 @@ td.itdc{text-align:center;padding:0 1px 0 2px;margin:0;border-right:1px solid #5
#reload-carousel {
color: #e1e7e9;
}
+
#reload-carousel:hover {
color: #ed2553;
}
+#carousel-mode-menu {
+ color: #e1e7e9;
+}
+
+#carousel-mode-menu:hover {
+ color: #ed2553;
+}
+
/* image pages */
-div.sni{
- text-align:center;
- margin:2px auto 6px;
- padding:0 10px 5px;
- position:relative;
- z-index:1;
- background-color: #E1E7E9;
+div.sni {
+ text-align: center;
+ margin: 2px auto 6px;
+ padding: 0 10px 5px;
+ position: relative;
+ z-index: 1;
+ background-color: #E1E7E9;
color: #34495E;
border-radius: 9px 9px 9px 9px;
clear: both;
display: block;
padding-top: 5px;
- padding-bottom: 10px;
+ padding-bottom: 10px;
width: 98%;
}
-div.sni h1{font-size:12pt;font-weight:bold;text-align:center;}
-div.sni img{border:0;vertical-align:middle;margin:1px;clear:both}
-div.if{margin:-5px auto 5px}
-div.sn{margin:10px auto;font-size:15pt;height:32px;z-index:1}
-div.sn div{
- margin:2px 25px 0px;display:inline;
+div.sni h1 {
+ font-size: 12pt;
+ font-weight: bold;
+ text-align: center;
+}
+
+div.sni img {
+ border: 0;
+ vertical-align: middle;
+ margin: 1px;
+ clear: both
+}
+
+div.if {
+ margin: -5px auto 5px
+}
+
+div.sn {
+ margin: 10px auto;
+ font-size: 15pt;
+ height: 32px;
+ z-index: 1
+}
+
+div.sn div {
+ margin: 2px 25px 0px;
+ display: inline;
padding-bottom: 15px;
vertical-align: middle;
}
-div.pagecount{
- position:relative;
+div.pagecount {
+ position: relative;
}
-div.sn span{font-weight:bold}
-div.sn img{width:30px;height:30px;padding:0px 2px}
+div.sn span {
+ font-weight: bold
+}
-div.sn a{color: #34495E;}
-div.sn a:hover{color: #ed2553;}
+div.sn img {
+ width: 30px;
+ height: 30px;
+ padding: 0px 2px
+}
-div.if a{color: #34495E;}
-div.if a:hover{color: #ed2553;}
+div.sn a {
+ color: #34495E;
+}
-div.sb a{color: #34495E;}
-div.sb a:hover{color: #ed2553;}
+div.sn a:hover {
+ color: #ed2553;
+}
+
+div.if a {
+ color: #34495E;
+}
+
+div.if a:hover {
+ color: #ed2553;
+}
+
+div.sb a {
+ color: #34495E;
+}
+
+div.sb a:hover {
+ color: #ed2553;
+}
-div.sb{margin-top:-10px;position:relative;z-index:2}
-div.sb img{border:0}
+div.sb {
+ margin-top: -10px;
+ position: relative;
+ z-index: 2
+}
+
+div.sb img {
+ border: 0
+}
div.idi {
border: medium none;
@@ -386,11 +552,16 @@ div.idi {
padding: 15px;
text-align: center;
}
-div#toppane{margin:auto;width:99%}
-.caption, .context-menu-list {
-background-color: #E1E7E9;
-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
+div#toppane {
+ margin: auto;
+ width: 99%
+}
+
+.caption,
+.context-menu-list {
+ background-color: #E1E7E9;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.context-menu-item {
@@ -406,7 +577,8 @@ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
border-bottom: 2px solid #34495E;
}
-.context-menu-item.context-menu-hover, .context-menu-icon.context-menu-icon--fa5.context-menu-hover > i {
+.context-menu-item.context-menu-hover,
+.context-menu-icon.context-menu-icon--fa5.context-menu-hover>i {
background-color: #34495E !important;
color: #ed2553;
}
@@ -432,28 +604,34 @@ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
/* Toasts */
-.jq-toast-single {
- font-family: 'Inter UI',arial,sans-serif !important;
+.jq-toast-single {
+ font-family: 'Inter UI', arial, sans-serif !important;
}
-.jq-toast-single h2 {
- font-family: 'Inter UI',arial,sans-serif !important;
+.jq-toast-single h2 {
+ font-family: 'Inter UI', arial, sans-serif !important;
}
/* Tag Cloud */
div.jqcloud {
- font-family: 'Inter UI',arial,sans-serif !important;
+ font-family: 'Inter UI', arial, sans-serif !important;
}
-div.jqcloud span.w10, div.jqcloud span.w8, div.jqcloud span.w9 {
+div.jqcloud span.w10,
+div.jqcloud span.w8,
+div.jqcloud span.w9 {
color: #34495E !important;
}
-div.jqcloud span.w7, div.jqcloud span.w6, div.jqcloud span.w5 {
+div.jqcloud span.w7,
+div.jqcloud span.w6,
+div.jqcloud span.w5 {
color: #3f5973 !important;
}
-div.jqcloud span.w4, div.jqcloud span.w3, div.jqcloud span.w2 {
+div.jqcloud span.w4,
+div.jqcloud span.w3,
+div.jqcloud span.w2 {
color: #517394 !important;
}
@@ -494,7 +672,7 @@ div.id2 a:hover {
}
-div.id3 {
+div.id3 {
margin: auto;
overflow: hidden;
position: relative;
@@ -524,23 +702,50 @@ div.id4 a:hover {
}
/* awesomplete */
-.awesomplete > ul {
+.awesomplete>ul {
background: #E1E7E9;
border: none;
border-radius: 0;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
-.awesomplete > ul:before {
+
+.awesomplete>ul:before {
display: none;
}
-.awesomplete > ul > li:hover{
+
+.awesomplete>ul>li:hover {
color: #FCFCFC;
background: #34495E;
}
+
.awesomplete mark {
- background:#ED2553;
+ background: #ED2553;
color: white;
}
-.awesomplete li:hover mark, .awesomplete li[aria-selected="true"] mark {
+
+.awesomplete li:hover mark,
+.awesomplete li[aria-selected="true"] mark {
background: #ED2553;
}
+
+/** Toasts **/
+.Toastify {
+ --toastify-font-family: "Inter UI";
+ --toastify-text-color-light: #FCFCFC;
+ --toastify-color-light: #34495e;
+}
+
+.Toastify__close-button--light {
+ color: #fff;
+ opacity: 1;
+}
+
+.Toastify__toast-body a {
+ color: #ED2553;
+}
+
+/** Popups **/
+.swal2-popup {
+ background: #34495e;
+ color: #FCFCFC;
+}
\ No newline at end of file
diff --git a/public/themes/modern_red.css b/public/themes/modern_red.css
index 5e4319b56..ce6256622 100644
--- a/public/themes/modern_red.css
+++ b/public/themes/modern_red.css
@@ -2,21 +2,25 @@ p {
margin: 0;
padding: 3px 1px;
}
+
img {
border: 0 none;
}
+
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"), url(../css/webfonts/Roboto-Regular.woff) format("woff");
}
+
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: local("Roboto Bold"), local("Roboto-Bold"), url(../css/webfonts/Roboto-Bold.woff) format("woff");
}
+
body {
background-color: #E9BBC5;
color: #EFEAEA;
@@ -36,15 +40,19 @@ a {
color: #EFEAEA;
text-decoration: none;
}
+
a:hover {
color: #D7D4C5;
text-decoration: none;
}
-.sorting_asc>a, .sorting_desc>a {
+.sorting_asc>a,
+.sorting_desc>a {
color: #D7D4C5 !important;
}
-.sorting_asc>a:hover, .sorting_desc>a:hover {
+
+.sorting_asc>a:hover,
+.sorting_desc>a:hover {
color: #EFEAEA !important;
}
@@ -55,12 +63,14 @@ p.ip {
text-align: center;
color: #414135
}
+
img.mr {
border: 0 none;
height: 7px;
margin-left: 10px;
width: 5px;
}
+
h1.ih {
font-size: 10pt;
font-weight: bold;
@@ -68,6 +78,7 @@ h1.ih {
padding-bottom: 6px;
text-align: center;
}
+
div.ido {
background-color: #D83B66;
box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
@@ -99,20 +110,26 @@ div.gt {
text-overflow: ellipsis;
}
-div.gt > a {
+div.gt>a {
color: #EFEAEA !important;
}
-.tagger > ul > li:not(.tagger-new) > a, .tagger li:not(.tagger-new) > span, .tagger .tagger-new ul {
+.tagger>ul>li:not(.tagger-new)>a,
+.tagger li:not(.tagger-new)>span,
+.tagger .tagger-new ul {
background: none repeat scroll 0 0 #4F535B;
border-radius: 3px;
}
-.tagger > ul > li:not(.tagger-new) a, .tagger > ul > li:not(.tagger-new) a:visited, .tagger-new ul a, .tagger-new ul a:visited {
+.tagger>ul>li:not(.tagger-new) a,
+.tagger>ul>li:not(.tagger-new) a:visited,
+.tagger-new ul a,
+.tagger-new ul a:visited {
color: #EFEAEA !important;
}
-div.gt > a:hover, .tagger > ul > li:not(.tagger-new) a:hover {
+div.gt>a:hover,
+.tagger>ul>li:not(.tagger-new) a:hover {
color: #D7D4C5 !important;
}
@@ -120,22 +137,27 @@ p#nb {
margin: 2px auto;
text-align: center;
}
+
p#nb img {
border: 0 none;
height: 7px;
margin-left: 10px;
width: 5px;
}
+
p#nb a {
color: #414135;
font-weight: bold;
}
+
p.ip a {
color: #34495E;
}
+
p.ip a:hover {
color: #00BCD4;
}
+
table.itg {
border: 0 none;
border-collapse: collapse;
@@ -148,6 +170,7 @@ table.itg {
padding: 3px;
width: 99% !important;
}
+
table.itg th {
background: none repeat scroll 0 0 #D7D4C5;
border-bottom: 2px solid #414135;
@@ -163,7 +186,7 @@ table.itg th {
}
.tippy-arrow {
- display:none !important;
+ display: none !important;
}
/* favtags */
@@ -179,7 +202,7 @@ table.itc {
border: 0 none;
border-radius: 3px 3px 3px 3px;
color: #F1F1F1;
- font-family: "Roboto",arial,sans-serif;
+ font-family: "Roboto", arial, sans-serif;
margin: 1px;
outline: 0 none;
padding: 0 4px 1px;
@@ -189,7 +212,7 @@ table.itc {
.favtag-btn:hover,
.toggled {
- background:#E9A53A !important;
+ background: #E9A53A !important;
}
.toggled:hover {
@@ -205,7 +228,7 @@ table.itc {
border-bottom: 2px solid #414135;
}
-.option-flyout > .collapsible-body > table {
+.option-flyout>.collapsible-body>table {
border-top: 2px dashed #414135;
color: #414135;
}
@@ -217,12 +240,15 @@ table.itc {
tr.gtr {
background: none repeat scroll 0 0 #40454B;
}
+
tr.gtr0 {
background: none repeat scroll 0 0 #DCDDCB;
}
+
tr.gtr1 {
background: none repeat scroll 0 0 #D7D4C5;
}
+
.stdbtn {
background-color: #E9A53A;
border: 0 none;
@@ -237,9 +263,15 @@ tr.gtr1 {
outline: 0 none;
padding: 0 4px 1px;
}
+
+.swal2-actions>.stdbtn {
+ background-color: #E9A53A;
+}
+
.stdbtn:hover {
background-color: #BE234B;
}
+
.stdinput {
background: none repeat scroll 0 0 #FCFCFC;
border: medium none;
@@ -272,17 +304,21 @@ td.itd {
padding: 3px 4px;
text-align: left;
}
+
td.itd a {
text-decoration: none;
}
+
td.itu {
border-right: 1px solid #B84B55;
padding: 3px 4px;
text-align: left;
}
+
td.itu a {
text-decoration: none;
}
+
td.itdc {
border-right: 2px dotted #414135;
margin: 0;
@@ -329,30 +365,41 @@ td.itdc {
}
.carousel-prev {
- padding:4px;
+ padding: 4px;
background-color: #d7d4c5;
color: #414135;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.carousel-next {
- padding:4px;
+ padding: 4px;
background-color: #d7d4c5;
color: #414135;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
-.carousel-prev:hover, .carousel-next:hover {
+.carousel-prev:hover,
+.carousel-next:hover {
color: #ed2553;
}
#reload-carousel {
color: #414135;
}
+
#reload-carousel:hover {
color: #ed2553;
}
+#carousel-mode-menu {
+ color: #414135;
+}
+
+#carousel-mode-menu:hover {
+ color: #ed2553;
+}
+
+
div.sni {
background-color: #D83B66;
box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
@@ -366,11 +413,13 @@ div.sni {
width: 98%;
z-index: 1;
}
+
div.sni h1 {
font-size: 12pt;
font-weight: bold;
text-align: center;
}
+
div.sni img {
border: 0 none;
box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
@@ -378,50 +427,61 @@ div.sni img {
margin: 1px;
vertical-align: middle;
}
+
div.if {
margin: -5px auto 5px;
}
+
div.sn {
font-size: 15pt;
height: 32px;
margin: 10px auto;
z-index: 1;
}
+
div.sn div {
display: inline;
margin: 2px 25px 0;
padding-bottom: 15px;
vertical-align: middle;
}
+
div.pagecount {
position: relative;
}
+
div.sn span {
font-weight: bold;
}
+
div.sn img {
height: 30px;
padding: 0 2px;
width: 30px;
}
+
div.sn a,
div.if a,
div.sb a {
color: #EFEAEA;
}
+
div.sn a:hover,
div.if a:hover,
div.sb a:hover {
color: #E9A53A;
}
+
div.sb {
margin-top: -10px;
position: relative;
z-index: 2;
}
+
div.sb img {
border: 0 none;
}
+
div.idi {
border: medium none;
border-collapse: collapse;
@@ -430,10 +490,12 @@ div.idi {
padding: 15px;
text-align: center;
}
+
div#toppane {
margin: auto;
width: 99%;
}
+
.caption,
.context-menu-list {
background-color: #DCDDCB;
@@ -441,7 +503,7 @@ div#toppane {
}
.caption-reader {
- box-shadow:none;
+ box-shadow: none;
border: 2px dashed #414135;
}
@@ -459,7 +521,7 @@ div#toppane {
}
.context-menu-item.context-menu-hover,
-.context-menu-icon.context-menu-icon--fa5.context-menu-hover > i {
+.context-menu-icon.context-menu-icon--fa5.context-menu-hover>i {
background-color: #E9A53A !important;
color: #F1F1F1;
}
@@ -484,7 +546,8 @@ p#nb a:hover {
color: #BE234B;
}
-input[type='checkbox']:checked::after, input[type='checkbox']:checked::before {
+input[type='checkbox']:checked::after,
+input[type='checkbox']:checked::before {
color: #BE234B;
}
@@ -513,16 +576,19 @@ div.jqcloud {
div.jqcloud span.w1 {
color: #b29999 !important;
}
+
div.jqcloud span.w2,
div.jqcloud span.w3,
div.jqcloud span.w4 {
color: #c8b6b6 !important;
}
+
div.jqcloud span.w5,
div.jqcloud span.w6,
div.jqcloud span.w7 {
color: #ded3d3 !important;
}
+
div.jqcloud span.w8,
div.jqcloud span.w9,
div.jqcloud span.w10 {
@@ -594,22 +660,53 @@ div.id4 a:hover {
}
/* awesomplete */
-.awesomplete > ul {
+.awesomplete>ul {
color: #414135;
background: #DCDDCB;
border: 2px dotted #414135;
}
-.awesomplete > ul:before {
+
+.awesomplete>ul:before {
display: none;
}
-.awesomplete > ul > li:hover,
-.awesomplete > ul > li[aria-selected="true"] {
+
+.awesomplete>ul>li:hover,
+.awesomplete>ul>li[aria-selected="true"] {
color: #EFEAEA;
background: #D83B66;
}
+
.awesomplete mark,
.awesomplete li:hover mark,
.awesomplete li[aria-selected="true"] mark {
background: #E9A53A;
color: #F1F1F1;
}
+
+/** Toasts **/
+.Toastify {
+ --toastify-font-family: "Roboto", arial, sans-serif;
+ --toastify-text-color-light: #414135;
+ --toastify-color-light: #d7d4c5;
+}
+
+.Toastify__toast {
+ box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.16), 0 2px 15px 0 rgba(0, 0, 0, 0.12);
+ border-radius: 0;
+}
+
+.Toastify__toast-body a {
+ color: #414135;
+}
+
+/** Popups **/
+.swal2-popup {
+ background: #D7D4C5;
+ border-radius: 0;
+ color: #414135;
+}
+
+.swal2-icon.swal2-warning {
+ border-color: #D83B66;
+ color: #BE234B;
+}
\ No newline at end of file
diff --git a/templates/backup.html.tt2 b/templates/backup.html.tt2
index 3062c4801..c85c7dae1 100644
--- a/templates/backup.html.tt2
+++ b/templates/backup.html.tt2
@@ -11,18 +11,28 @@
+
+
[% csshead %]
+
+
+
+
+
+
+
+
-
+
Database Backup/Restore
@@ -38,8 +48,7 @@
-
+
Backup Database
@@ -68,44 +77,13 @@
-
+
-
-
[% INCLUDE footer %]
diff --git a/templates/batch.html.tt2 b/templates/batch.html.tt2
index a75df9efb..2b053b4c8 100644
--- a/templates/batch.html.tt2
+++ b/templates/batch.html.tt2
@@ -11,11 +11,18 @@
-
+
+
[% csshead %]
-
+
+
+
+
+
+
+
@@ -99,7 +106,7 @@
Archives.
You can edit your Tag Rules in Server Configuration.
-
|
@@ -193,9 +200,8 @@
-
-
+
+
[% INCLUDE footer %]
diff --git a/templates/category.html.tt2 b/templates/category.html.tt2
index e2309830e..3285ebb9d 100644
--- a/templates/category.html.tt2
+++ b/templates/category.html.tt2
@@ -11,11 +11,18 @@
-
+
+
[% csshead %]
-
+
+
+
+
+
+
+
@@ -23,7 +30,7 @@
-
+
|
- |
@@ -71,28 +77,26 @@
Name: |
-
+
|
Predicate: |
-
-
+
+
|
|
-
+
|
|
-
+ |
|
@@ -122,7 +126,7 @@
-
+
[% INCLUDE footer %]
diff --git a/templates/config.html.tt2 b/templates/config.html.tt2
index 189a17af2..988c1fe1a 100644
--- a/templates/config.html.tt2
+++ b/templates/config.html.tt2
@@ -11,16 +11,24 @@
-
+
+
[% csshead %]
-
+
+
+
+
+
+
+
+
@@ -38,13 +46,10 @@
Select a category to show the matching settings.
-
-
-
-
+
+
+
+
-
- [% INCLUDE footer %]
+ [% INCLUDE footer %]