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 += `
    @@ -193,9 +209,10 @@ LRR.buildTagsDiv = function (tags) { /** * Build a thumbnail div for the given archive data. * @param {*} data The archive data + * @param {boolean} tagTooltip Option to build TagTooltip on mouseover * @returns HTML component string */ -LRR.buildThumbnailDiv = function (data) { +LRR.buildThumbnailDiv = function (data, tagTooltip = true) { const thumbCss = (localStorage.cropthumbs === "true") ? "id3" : "id3 nocrop"; // The ID can be in a different field depending on the archive object... const id = data.arcid || data.id; @@ -215,8 +232,8 @@ LRR.buildThumbnailDiv = function (data) {
    - ${LRR.colorCodeTags(data.tags)} - + ${LRR.colorCodeTags(data.tags)} + ${tagTooltip === true ? `` : ""}
    `; }; @@ -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 @@ - +

    Categories

    @@ -48,8 +55,8 @@ You can create new categories here or edit existing ones.

    - - + +

    Select a category in the combobox below to edit its name, the archives it contains, or its predicate. @@ -62,8 +69,7 @@

    Category:

    - @@ -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 %] \ No newline at end of file diff --git a/templates/edit.html.tt2 b/templates/edit.html.tt2 index dac5cd996..ced2e4100 100644 --- a/templates/edit.html.tt2 +++ b/templates/edit.html.tt2 @@ -11,13 +11,20 @@ - + + [% csshead %] - + + + + + + + @@ -111,8 +118,7 @@
    - +
    diff --git a/templates/index.html.tt2 b/templates/index.html.tt2 index a06473b2c..274c34174 100644 --- a/templates/index.html.tt2 +++ b/templates/index.html.tt2 @@ -10,17 +10,23 @@ - + + [% csshead %] - + + + + + + @@ -29,6 +35,7 @@ + @@ -82,10 +89,12 @@
    - - - + - - - [% INCLUDE footer %] diff --git a/templates/templates_config/config_files.html.tt2 b/templates/templates_config/config_files.html.tt2 index 35ac9c51e..59d03b5e5 100644 --- a/templates/templates_config/config_files.html.tt2 +++ b/templates/templates_config/config_files.html.tt2 @@ -11,12 +11,25 @@ + + +

    Synology eCryptFS Compatibility Mode

    + + + [% IF enablecryptofs %] + [% ELSE %] + [% END %] + + + + - + Click this button to trigger a rescan of the Archive Directory in case you're missing files,
    @@ -39,7 +52,7 @@ - + Current Size: @@ -52,7 +65,7 @@ - + The last searches done in the archive index are cached for faster loads.
    @@ -65,7 +78,7 @@ - + Newly uploaded archives are marked as "new" in the index until you've opened them.
    diff --git a/templates/templates_config/config_global.html.tt2 b/templates/templates_config/config_global.html.tt2 index 0bcbfe69b..ada385e41 100644 --- a/templates/templates_config/config_global.html.tt2 +++ b/templates/templates_config/config_global.html.tt2 @@ -103,7 +103,7 @@ - + Cleaning the database will remove entries that aren't on your filesystem. @@ -112,7 +112,7 @@ - + Danger zone!
    diff --git a/templates/templates_config/config_shinobu.html.tt2 b/templates/templates_config/config_shinobu.html.tt2 index f139749aa..c38c3eacd 100644 --- a/templates/templates_config/config_shinobu.html.tt2 +++ b/templates/templates_config/config_shinobu.html.tt2 @@ -18,8 +18,7 @@ - + If Shinobu is dead or unresponsive, you can reboot her by clicking this button. @@ -28,7 +27,7 @@ - + The Minion Worker handles spare tasks that are too long to execute within the request/response lifecycle of web diff --git a/templates/templates_config/config_tags.html.tt2 b/templates/templates_config/config_tags.html.tt2 index cfa99e106..4c03d2746 100644 --- a/templates/templates_config/config_tags.html.tt2 +++ b/templates/templates_config/config_tags.html.tt2 @@ -13,8 +13,23 @@ - +

    Use high-quality thumbnails for pages

    + + + [% IF hqthumbpages %] + [% ELSE %] + [% END %] + + + + + + + Generate Thumbnails for all archives that don't have one yet. @@ -23,8 +38,7 @@ - + Regenerate all thumbnails. This might take a while! diff --git a/templates/templates_config/config_theme.html.tt2 b/templates/templates_config/config_theme.html.tt2 index af9e4185b..fddddad46 100644 --- a/templates/templates_config/config_theme.html.tt2 +++ b/templates/templates_config/config_theme.html.tt2 @@ -1,8 +1,9 @@ - +
    The selected theme will apply to the entire application and be shown to all users.
    - If you're using a browser that supports "theme-color", the theme's primary color will also be applied there.

    + If you're using a browser that supports "theme-color", the theme's primary color will also be applied + there.

    Click on a theme to preview it before saving! @@ -10,63 +11,61 @@ - - -
    - + + +
    +
    -
    - +
    +
    -
    - +
    +
    -
    - +
    +
    -
    - +
    +
    + - + \ No newline at end of file diff --git a/templates/upload.html.tt2 b/templates/upload.html.tt2 index 0a40c7637..cb9a662da 100644 --- a/templates/upload.html.tt2 +++ b/templates/upload.html.tt2 @@ -10,21 +10,28 @@ - + + [% csshead %] - + + + + + + + - +

    Adding Archives to the Library

    @@ -70,7 +77,7 @@

    - +
    Add from URL(s)
    @@ -98,7 +105,7 @@


    - +
    [% INCLUDE footer %] diff --git a/tests/LANraragi/Plugin/Metadata/Hitomi.t b/tests/LANraragi/Plugin/Metadata/Hitomi.t new file mode 100644 index 000000000..4d3f73315 --- /dev/null +++ b/tests/LANraragi/Plugin/Metadata/Hitomi.t @@ -0,0 +1,60 @@ +use strict; +use warnings; +use utf8; +use Data::Dumper; + +use Cwd qw( getcwd ); +use Mojo::JSON qw(decode_json encode_json); +use Mojo::File; + +use Test::More; +use Test::Deep; + +my $cwd = getcwd(); +my $SAMPLES = "$cwd/tests/samples"; +require "$cwd/tests/mocks.pl"; +setup_redis_mock(); + +my @all_tags = ( + 'female:big breasts', 'female:big clit', 'female:blindfold', 'female:clit stimulation', + 'female:collar', 'female:cunnilingus', 'female:elf', 'female:exhibitionism', + 'female:females only', 'female:fff threesome', 'female:fingering', 'female:group', + 'female:masturbation', 'female:nun', 'female:pixie cut', 'female:ponytail', + 'female:slime', 'female:small breasts', 'female:squirting', 'female:stockings', + 'female:tentacles', 'female:tomboy', 'female:tribadism', 'female:unusual pupils', + 'female:yuri', 'story arc', 'parody:original', 'artist:yukataro', + 'group:sonotaozey', 'type:doujinshi', 'language:english' +); + +use_ok('LANraragi::Plugin::Metadata::Hitomi'); + +note('testing getting tags from JSON ...'); + +{ + no warnings 'once', 'redefine'; + local *LANraragi::Plugin::Metadata::Hitomi::get_plugin_logger = sub { return get_logger_mock(); }; + + my $json = decode_json( Mojo::File->new("$SAMPLES/hitomi/2261881.js")->slurp ); + my @tags = LANraragi::Plugin::Metadata::Hitomi::get_tags_from_taglist($json); + + cmp_bag( \@tags, \@all_tags, 'tag list' ); +} + +note('testing getting title from JSON ...'); + +{ + no warnings 'once', 'redefine'; + local *LANraragi::Plugin::Metadata::Hitomi::get_plugin_logger = sub { return get_logger_mock(); }; + + my $json = decode_json( Mojo::File->new("$SAMPLES/hitomi/2261881.js")->slurp ); + my @tags = LANraragi::Plugin::Metadata::Hitomi::get_tags_from_taglist($json); + + my $title = LANraragi::Plugin::Metadata::Hitomi::get_title_from_json($json); + + is( $title, + 'Nakayoshi Onna Boukensha wa Yoru ni Naru to Yadoya de Mechakucha Ecchi Suru | Party of Female Adventurers Fuck a lot at the Inn Once Nighttime Comes.', + 'title' + ); +} + +done_testing(); diff --git a/tests/modules.t b/tests/modules.t index 1c660671f..6807da35b 100644 --- a/tests/modules.t +++ b/tests/modules.t @@ -54,7 +54,8 @@ my @modules = ( "LANraragi::Plugin::Login::EHentai", "LANraragi::Plugin::Login::Fakku", "LANraragi::Plugin::Scripts::SourceFinder", "LANraragi::Plugin::Scripts::FolderToCat", "LANraragi::Plugin::Download::EHentai", "LANraragi::Plugin::Download::Chaika", - "LANraragi::Plugin::Scripts::nHentaiSourceConverter", "LANraragi::Plugin::Scripts::BlacklistMigrate" + "LANraragi::Plugin::Scripts::nHentaiSourceConverter", "LANraragi::Plugin::Scripts::BlacklistMigrate", + "LANraragi::Plugin::Metadata::Hitomi" ); # Test all modules load properly diff --git a/tests/plugins.t b/tests/plugins.t index 6e5f7c99f..0373dd6f2 100644 --- a/tests/plugins.t +++ b/tests/plugins.t @@ -19,6 +19,7 @@ use LANraragi::Plugin::Metadata::nHentai; use LANraragi::Plugin::Metadata::Chaika; use LANraragi::Plugin::Metadata::Eze; use LANraragi::Plugin::Metadata::Fakku; +use LANraragi::Plugin::Metadata::Hitomi; # Mock Redis my $cwd = getcwd; @@ -47,19 +48,19 @@ note("E-Hentai Tests"); isa_ok( $test_eH_json->{gmetadata}[0]{tags}, 'ARRAY', 'type of tags' ); } -note("nHentai Tests"); +note("nHentai Tests : Disabled due to cloudflare being used on nH"); -{ - my $nH_gID = "52249"; - my $test_nH_gID = trap { LANraragi::Plugin::Metadata::nHentai::get_gallery_id_from_title("\"Pieces 1\" shirow"); }; - - is( $test_nH_gID, $nH_gID, 'nHentai search test' ); +# { +# my $nH_gID = "52249"; +# my $test_nH_gID = LANraragi::Plugin::Metadata::nHentai::get_gallery_id_from_title("\"Pieces 1\" shirow"); +# +# is( $test_nH_gID, $nH_gID, 'nHentai search test' ); - my %nH_hashdata = trap { LANraragi::Plugin::Metadata::nHentai::get_tags_from_NH( $nH_gID, 1 ) }; +# my %nH_hashdata = trap { LANraragi::Plugin::Metadata::nHentai::get_tags_from_NH( $nH_gID, 1 ) }; - ok( length $nH_hashdata{tags} > 0, 'nHentai API Tag retrieval test' ); - ok( length $nH_hashdata{title} > 0, 'nHentai title test' ); -} +# ok( length $nH_hashdata{tags} > 0, 'nHentai API Tag retrieval test' ); +# ok( length $nH_hashdata{title} > 0, 'nHentai title test' ); +# } note("Chaika Tests"); @@ -94,4 +95,17 @@ note("FAKKU Tests : Disabled due to cloudflare being used on FAKKU"); # is( $f_result_title, $f_title, 'FAKKU title parsing test' ); # } +note("Hitomi Tests"); + +{ + my $hi_gID = "2261881"; + my %hi_hashdata = trap { LANraragi::Plugin::Metadata::Hitomi::get_tags_from_Hitomi( $hi_gID, 1 ); }; + + ok( length $hi_hashdata{tags} > 0, 'Hitomi API Tag retrieval test' ); + is( $hi_hashdata{title}, + "Nakayoshi Onna Boukensha wa Yoru ni Naru to Yadoya de Mechakucha Ecchi Suru | Party of Female Adventurers Fuck a lot at the Inn Once Nighttime Comes.", + 'Hitomi title test' + ); +} + done_testing(); diff --git a/tests/samples/hitomi/2261881.js b/tests/samples/hitomi/2261881.js new file mode 100644 index 000000000..2e14aeea8 --- /dev/null +++ b/tests/samples/hitomi/2261881.js @@ -0,0 +1,963 @@ +{ + "title": "Nakayoshi Onna Boukensha wa Yoru ni Naru to Yadoya de Mechakucha Ecchi Suru | Party of Female Adventurers Fuck a lot at the Inn Once Nighttime Comes.", + "scene_indexes": [], + "artists": [{ + "url": "/artist/yukataro-all.html", + "artist": "yukataro" + }], + "languages": [], + "language_localname": "English", + "tags": [{ + "url": "/tag/female%3Abig%20breasts-all.html", + "female": "1", + "tag": "big breasts", + "male": "" + }, { + "tag": "big clit", + "male": "", + "url": "/tag/female%3Abig%20clit-all.html", + "female": "1" + }, { + "url": "/tag/female%3Ablindfold-all.html", + "female": "1", + "tag": "blindfold", + "male": "" + }, { + "female": "1", + "url": "/tag/female%3Aclit%20stimulation-all.html", + "tag": "clit stimulation", + "male": "" + }, { + "male": "", + "tag": "collar", + "female": "1", + "url": "/tag/female%3Acollar-all.html" + }, { + "tag": "cunnilingus", + "male": "", + "url": "/tag/female%3Acunnilingus-all.html", + "female": "1" + }, { + "male": "", + "tag": "elf", + "url": "/tag/female%3Aelf-all.html", + "female": "1" + }, { + "female": "1", + "url": "/tag/female%3Aexhibitionism-all.html", + "tag": "exhibitionism", + "male": "" + }, { + "tag": "females only", + "male": "", + "url": "/tag/female%3Afemales%20only-all.html", + "female": "1" + }, { + "female": "1", + "url": "/tag/female%3Afff%20threesome-all.html", + "tag": "fff threesome", + "male": "" + }, { + "url": "/tag/female%3Afingering-all.html", + "female": "1", + "male": "", + "tag": "fingering" + }, { + "male": "", + "tag": "group", + "url": "/tag/female%3Agroup-all.html", + "female": "1" + }, { + "url": "/tag/female%3Amasturbation-all.html", + "female": "1", + "tag": "masturbation", + "male": "" + }, { + "tag": "nun", + "male": "", + "url": "/tag/female%3Anun-all.html", + "female": "1" + }, { + "tag": "pixie cut", + "male": "", + "female": "1", + "url": "/tag/female%3Apixie%20cut-all.html" + }, { + "tag": "ponytail", + "male": "", + "female": "1", + "url": "/tag/female%3Aponytail-all.html" + }, { + "tag": "slime", + "male": "", + "female": "1", + "url": "/tag/female%3Aslime-all.html" + }, { + "url": "/tag/female%3Asmall%20breasts-all.html", + "female": "1", + "male": "", + "tag": "small breasts" + }, { + "male": "", + "tag": "squirting", + "url": "/tag/female%3Asquirting-all.html", + "female": "1" + }, { + "tag": "stockings", + "male": "", + "female": "1", + "url": "/tag/female%3Astockings-all.html" + }, { + "male": "", + "tag": "tentacles", + "url": "/tag/female%3Atentacles-all.html", + "female": "1" + }, { + "female": "1", + "url": "/tag/female%3Atomboy-all.html", + "tag": "tomboy", + "male": "" + }, { + "female": "1", + "url": "/tag/female%3Atribadism-all.html", + "tag": "tribadism", + "male": "" + }, { + "tag": "unusual pupils", + "male": "", + "female": "1", + "url": "/tag/female%3Aunusual%20pupils-all.html" + }, { + "tag": "yuri", + "male": "", + "female": "1", + "url": "/tag/female%3Ayuri-all.html" + }, { + "url": "/tag/story%20arc-all.html", + "tag": "story arc" + }], + "videofilename": null, + "language_url": "/index-english.html", + "type": "doujinshi", + "parodys": [{ + "url": "/series/original-all.html", + "parody": "original" + }], + "related": [2223506, 2234669, 1706867, 1639461, 1390017], + "language": "english", + "japanese_title": null, + "files": [{ + "name": "001.jpg", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "f6870f2b2950690b093a1ac841327ef052f1bf056580a5cec3fc942a973622f0", + "hasavif": 1 + }, { + "name": "002.jpg", + "width": 2150, + "height": 3036, + "hash": "a7de8e12469081836da87657487c15881351d7dd2adf5c247bfae321bf9d0a57", + "haswebp": 1, + "hasavif": 1 + }, { + "height": 3036, + "hash": "6b623dab365e7eb79e50f5147281b60b98d9b4ac053ac6d2e1d73f612e7084ab", + "haswebp": 1, + "hasavif": 1, + "width": 2150, + "name": "003.png" + }, { + "name": "004.png", + "width": 2150, + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "7b633a59d71f966e64ae6fc741b620dcd5e5a845eaeb4563a5039dd63fbb5145" + }, { + "name": "005.png", + "width": 2150, + "height": 3036, + "hash": "8ef60405e76f2b7ff1eee8cc38732678017c1a1c4dcead4e5585917c3b57ae66", + "haswebp": 1, + "hasavif": 1 + }, { + "name": "006.png", + "width": 2150, + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "600d634f7771e0fbf2fbfe972a3ebcf56d0d63b617d475d03edfc85e516d1240" + }, { + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "065b691fd96bf989b7810497c97245fda35e9b65c66e40ce98a5c288ffdbb135", + "width": 2150, + "name": "007.png" + }, { + "width": 2150, + "name": "008.png", + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "6993101b0552268dd5a7b14b49e48325851f0254ec2d56a9403c516c8954a829" + }, { + "height": 2150, + "haswebp": 1, + "hash": "fd595effd70093616f1b8d29b3f1859145b8163eeaf296a2a3c43d2129f11f31", + "hasavif": 1, + "name": "009.png", + "width": 3074 + }, { + "width": 3074, + "name": "010.png", + "hasavif": 1, + "haswebp": 1, + "height": 2150, + "hash": "db3b60371a3ba486fc8e62917bdb69ad83687152610846eebb2e969c8cc267ab" + }, { + "name": "011.png", + "width": 3074, + "height": 2150, + "haswebp": 1, + "hash": "e9dabb033865fcdcd74c25d565c199a28781d0fc9ada76b9e0901fc937868cf7", + "hasavif": 1 + }, { + "hasavif": 1, + "height": 2150, + "hash": "49130dbca1a951e2af51bc45489f5cdd15e001220d598ce9fe4bb965f33b2e9d", + "haswebp": 1, + "name": "012.png", + "width": 3074 + }, { + "name": "013.png", + "width": 3074, + "hash": "a13811a4313a8d3d707de65eeb3aeda2a56db71e27808a6f7312fe55b6da3ecd", + "height": 2150, + "haswebp": 1, + "hasavif": 1 + }, { + "hasavif": 1, + "hash": "05f1f12e4e9f9893331676dc00face907085a77475ca079482f53ea4347796d1", + "height": 3036, + "haswebp": 1, + "width": 2150, + "name": "014.png" + }, { + "name": "015.png", + "width": 2150, + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "353b1c73895e2f52ad5527590a6112993a50ebc98fceec3901e17fdae3899d41" + }, { + "hasavif": 1, + "height": 3036, + "hash": "b6a4ce8071e5e1f7a34700df3c9abe13891e384e98e4bd344ca0f7f9dd54244e", + "haswebp": 1, + "width": 2150, + "name": "016.png" + }, { + "hasavif": 1, + "height": 2170, + "hash": "328ea6df9790452285d4b58d41052c4b7b6b2e0cde40d2b064d39f4b6785e83d", + "haswebp": 1, + "name": "017.png", + "width": 3035 + }, { + "name": "018.png", + "width": 3035, + "hash": "45d1522a9406afcbd4e3a113d5e789749813abf7db0030ff069233db181c16b0", + "height": 2169, + "haswebp": 1, + "hasavif": 1 + }, { + "hasavif": 1, + "height": 2169, + "hash": "76fbfacf9fa2caa7616237bdfb2d55d662259e03f87513a2b3f978e261660ea6", + "haswebp": 1, + "name": "019.png", + "width": 3035 + }, { + "hasavif": 1, + "hash": "0212aaa91e0938be7b7b0e28e04006d02103bc11887bed4214085066cfe779de", + "height": 2169, + "haswebp": 1, + "width": 3035, + "name": "020.png" + }, { + "hasavif": 1, + "haswebp": 1, + "height": 2169, + "hash": "99ba5b790e09ab26e330e48915af3daa5f4bc59aa7332404c0020190aeb82d96", + "width": 3035, + "name": "021.png" + }, { + "width": 3035, + "name": "022.png", + "hash": "c16f78f2694029fab8f1619a3a3c18275628d95ff50b39e33de26997b31dad52", + "height": 2169, + "haswebp": 1, + "hasavif": 1 + }, { + "name": "023.png", + "width": 2150, + "hash": "cbb2dd50099fc764f458a8223018c695c437d8acddd0438812fbb82806e9f778", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "height": 3036, + "haswebp": 1, + "hash": "61d61ab576f73aca35a79ce3d6d11dafeddcac685b0b63d099b9336ce0fda44c", + "hasavif": 1, + "name": "024.png", + "width": 2150 + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "ef811ee9900f474ae449c5d9ec6df416d81bf69f0c32fa81f93cd2c14faa72c2", + "width": 2150, + "name": "025.png" + }, { + "name": "026.png", + "width": 2150, + "hasavif": 1, + "hash": "41b9d6367f7964759109ef25941e04271cbd866e03ee58596298bfa21f6aed10", + "height": 3036, + "haswebp": 1 + }, { + "width": 2150, + "name": "027.png", + "haswebp": 1, + "height": 3036, + "hash": "891e9b022e0ffa303706b7ddd598ae5d77e32084e51d57c87c92cab134883ab0", + "hasavif": 1 + }, { + "width": 2150, + "name": "028.png", + "height": 3036, + "hash": "7062e036feaf5122c2a098b7b852293a0bc96b207988900830f31e7b235f62cd", + "haswebp": 1, + "hasavif": 1 + }, { + "hasavif": 1, + "height": 3780, + "haswebp": 1, + "hash": "6181d610e5cb1086e359f3cc100a909126fcdcb95b5d4713717aef8c2f67accf", + "width": 2221, + "name": "029.png" + }, { + "haswebp": 1, + "height": 3036, + "hash": "44271cc8906899a3241cfce194e83aaf666c829e1f340a003895149fe2a56324", + "hasavif": 1, + "width": 2150, + "name": "030.png" + }, { + "width": 2150, + "name": "031.png", + "height": 3036, + "haswebp": 1, + "hash": "e2baac0884c36fcc90f577e1ca78bb2441ec4669ecf398b81e8e116aefc6983f", + "hasavif": 1 + }, { + "width": 2221, + "name": "032.png", + "hash": "3d437a1085ae8f77f4dde7978910b8e098263592798bd76621827597693025c8", + "height": 3662, + "haswebp": 1, + "hasavif": 1 + }, { + "haswebp": 1, + "height": 3662, + "hash": "5fa7931d5a589f97b837f24aeaa77d0b62ef9800d29dc79310947152e9619d2a", + "hasavif": 1, + "name": "033.png", + "width": 2221 + }, { + "haswebp": 1, + "height": 3036, + "hash": "1c9ace54e089d984375e56ae19c71e5c67eac74dcfa2662ae49ef00485ba50be", + "hasavif": 1, + "name": "034.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "hash": "defb028766d337ac5970e9f57ec82a7201844cbbf1eb63729bbdfc081c15942e", + "haswebp": 1, + "name": "035.png", + "width": 2150 + }, { + "hasavif": 1, + "hash": "cbe1522d90d3c4c9f1cc186d4df80004efd4037c345868d4e2c4c8fb33286d30", + "height": 3036, + "haswebp": 1, + "width": 2150, + "name": "036.png" + }, { + "height": 3036, + "hash": "a4223ac54bc336e7e67a30bc6c45136f8689f2fd0a1cce6ffc25164319270de9", + "haswebp": 1, + "hasavif": 1, + "name": "037.png", + "width": 2150 + }, { + "name": "038.png", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "41b86d5ea3607afb76f64613cabebac660683a9eb117b4b493e9e628923cd449", + "hasavif": 1 + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "33abafefa437b868dbac1d8287fc9bc2c137c636e83cb68aa4e0bb394ee65c96", + "name": "039.png", + "width": 2150 + }, { + "hasavif": 1, + "hash": "dd029bc4f24daf2f950ee9362e61f30beb2631602f1ded54533d41efe93c0a98", + "height": 3822, + "haswebp": 1, + "name": "040.png", + "width": 2150 + }, { + "height": 3702, + "haswebp": 1, + "hash": "65f14c773939f2757f5ab368f3d37586a8061a2bbef5e75865aa8c7782dabe44", + "hasavif": 1, + "name": "041.png", + "width": 2150 + }, { + "height": 3036, + "hash": "64812264736217f9a398dc315f4eff59abc1176cf361cecb1558ab58f6fdd664", + "haswebp": 1, + "hasavif": 1, + "width": 2150, + "name": "042.png" + }, { + "hasavif": 1, + "height": 3036, + "hash": "f8ba7f9af1dd58b8b779ad615b9ccb6d0d40736b1a9a6614c8d7425c797fc9b0", + "haswebp": 1, + "name": "043.png", + "width": 2150 + }, { + "name": "044.png", + "width": 2150, + "hasavif": 1, + "hash": "7b6a03b7a50059e3ee0394c5a9da46333bfcc5b46d6dea3fe517ae8ab563eb14", + "height": 3036, + "haswebp": 1 + }, { + "width": 2150, + "name": "045.png", + "hasavif": 1, + "hash": "249d5dd1e629cf3575d506a1009c17ab9dfb46fc1ec3f8f82392014a64e8b0fe", + "height": 3036, + "haswebp": 1 + }, { + "height": 2150, + "haswebp": 1, + "hash": "b8a018902d8fe1c5883f037d31c94eb643c04c70e283c8b86767e61051043511", + "hasavif": 1, + "width": 3008, + "name": "046.png" + }, { + "name": "047.png", + "width": 3008, + "hasavif": 1, + "hash": "d67d54087aa8ac1a9dc5d833ef8028c62eb541cfa26d796130245cef0fcb2147", + "height": 2150, + "haswebp": 1 + }, { + "width": 3008, + "name": "048.png", + "hasavif": 1, + "height": 2150, + "hash": "1217534fbf5efd3690935e910b2310060801d66f46bcec39c296c098d28f6ae3", + "haswebp": 1 + }, { + "name": "049.png", + "width": 2150, + "hasavif": 1, + "height": 3036, + "hash": "c9384d1a2cb5bb823567e260d8e3c7dabc65c628553580e8cd5c81c2b035f780", + "haswebp": 1 + }, { + "height": 3036, + "haswebp": 1, + "hash": "2a857b07a1e07c270141f4be214098f870a954ba3be05e4cc66b3f06993b06a3", + "hasavif": 1, + "width": 2150, + "name": "050.png" + }, { + "width": 2150, + "name": "051.png", + "height": 3036, + "haswebp": 1, + "hash": "fc6657888c30957526d54377027903805e0fde2a79bce37b3390caf1d3cc412f", + "hasavif": 1 + }, { + "width": 2150, + "name": "052.png", + "height": 3822, + "hash": "7ff606dc30f8dfa5bd2ef98ad4c49fac7775ea51a9fd8e655b13aea6bd4b3823", + "haswebp": 1, + "hasavif": 1 + }, { + "height": 1765, + "haswebp": 1, + "hash": "c436225fae07a6e2e038a8720289f83734db71793e4e7fb4a79614b8a3d1df2a", + "hasavif": 1, + "name": "053.png", + "width": 2150 + }, { + "width": 2150, + "name": "054.png", + "hasavif": 1, + "hash": "2669c0e976f6fe60f59a0bfca4a63b2e4ff0616c44c3fca0bdcd6bc07c828985", + "height": 3036, + "haswebp": 1 + }, { + "width": 2150, + "name": "055.png", + "hash": "6fdae6c57873b1b3b38e09be9dc65f178f42a36204d9a721f1d4d2f6be8606d2", + "height": 1765, + "haswebp": 1, + "hasavif": 1 + }, { + "name": "056.png", + "width": 2150, + "hasavif": 1, + "hash": "704e79e45d6d73c22ad6e3a6bfff0fa707481032b99a583a7dc145c479ec7725", + "height": 3036, + "haswebp": 1 + }, { + "name": "057.png", + "width": 2150, + "height": 3036, + "haswebp": 1, + "hash": "e3d177e643a1ab8de9ef3ae19221f74ed2a69836a4232f37a848e08e86454dee", + "hasavif": 1 + }, { + "name": "058.png", + "width": 2150, + "hash": "cc340230e24f8b9a7227c5a4c8b5f748aafede8cb14955329468e5538b66a0ae", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "haswebp": 1, + "height": 3036, + "hash": "e2d690ce8161af1dcc70009ee3b1fc49c14972e3fefe856f98bd92b369f582d4", + "hasavif": 1, + "width": 2150, + "name": "059.png" + }, { + "width": 2150, + "name": "060.png", + "hash": "badc287a19fe23dd52621a383adfd07cf946c414b5adf32146d90d0347113867", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "hasavif": 1, + "hash": "8a0c1fff56f2fbab9367cc68aa15e3b1a7810fc14bf30a400c81200bec5f45cc", + "height": 3036, + "haswebp": 1, + "name": "061.png", + "width": 2150 + }, { + "width": 2150, + "name": "062.png", + "hash": "04fb2959c0cfc2d9ebb4a2fee882f01c4f7fb7d1650dc80c975fa8b8ff44cccc", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "name": "063.png", + "width": 2150, + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "343a95223db6951a1607d3f779491a709be1ff9c6d13a035c80704aac7c29931" + }, { + "name": "064.png", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "3ecbbd40620728c33e00b0b5c91efbdbc0615bddc51c81d3e49b09e904144a77", + "hasavif": 1 + }, { + "haswebp": 1, + "height": 3036, + "hash": "fff00df0d5cb8d344a3fab76e41af4e34fa9948a9b03ca1e34af32a462bbad1c", + "hasavif": 1, + "name": "065.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "hash": "a7304dce41592bf8c26f2095994ff9ec65d05be42d282c8d68a0be36f7a8a14f", + "haswebp": 1, + "name": "066.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "hash": "f47e2419d9a0730a79ae10de462c4e941af71fa35eec9a118fcaccb281e4cc20", + "haswebp": 1, + "width": 2150, + "name": "067.png" + }, { + "name": "068.png", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "f3d83ed5c7b383be740a48c1149f5ded88405eede4b7fc11ed8c44eb7927b8fd", + "hasavif": 1 + }, { + "name": "069.png", + "width": 2150, + "height": 3036, + "hash": "1776d7ca157ced792414fb1dc28616e1a63f9d07e2e858d3086bc73a0d657f27", + "haswebp": 1, + "hasavif": 1 + }, { + "haswebp": 1, + "height": 3036, + "hash": "ddb2ae2a30342843ff806c82d0df8bba7b193e5c9d9fac2771aa14c96aa81eac", + "hasavif": 1, + "name": "070.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "00b4bc36d67b395578f115f9e80a65eca6e6f0c3b321e682b7fb6ed4e26671d1", + "width": 2150, + "name": "071.png" + }, { + "width": 2150, + "name": "072.png", + "height": 3036, + "haswebp": 1, + "hash": "f4737d2e5d4b0845ef598f92a45d9a35399e94fc0a8be52424a79e1afc9cae0f", + "hasavif": 1 + }, { + "name": "073.png", + "width": 2150, + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "f41b7d750aff4a863d82cdafb988b02a27386e340ee3a4632c71ad36a2f9fb0d" + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "2a561469b5f714c1f1654cc4f56b0519670f3aa31ab70e6272260ae5daa2de96", + "width": 2150, + "name": "074.png" + }, { + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "6d6197a7f93bbd2fd4927c03fe1c7bfe992749106ceb36706530e63329644f04", + "name": "075.png", + "width": 2150 + }, { + "name": "076.png", + "width": 2150, + "hasavif": 1, + "height": 3036, + "hash": "2979d2695e4668e4e396d59f6b6f54d88ca7a944a9bf5f71e87d77e3fdcfd64d", + "haswebp": 1 + }, { + "name": "077.png", + "width": 2150, + "hasavif": 1, + "hash": "ce7ca9ee83939e9c3c2188f7b367141b7b9d0c770dc7f609c4f8269fcda08f9b", + "height": 3036, + "haswebp": 1 + }, { + "hasavif": 1, + "hash": "968515d3f1dfd051b833d1496aebe58953d6fb6fdfc4b9bf90e3822cf4008811", + "height": 3036, + "haswebp": 1, + "width": 2150, + "name": "078.png" + }, { + "haswebp": 1, + "height": 3036, + "hash": "f9dc4ab8fb87cf576270ad424c29df52bde0629da7b2786032e49908ffeb4ac9", + "hasavif": 1, + "name": "079.png", + "width": 2150 + }, { + "width": 2150, + "name": "080.png", + "hasavif": 1, + "height": 3036, + "hash": "81e5130d63e9a923aa01bc2071161401550e2de136d729581055271148fb42f0", + "haswebp": 1 + }, { + "height": 3036, + "hash": "eb07ab657a805e22745fc2ee7c859f0cf903df6215db48e8937e680c48054a77", + "haswebp": 1, + "hasavif": 1, + "name": "081.png", + "width": 2150 + }, { + "name": "082.png", + "width": 2150, + "hasavif": 1, + "hash": "769cef1e0f5f49716fbf71588b87de5b12f9711981f7a3a203bae236eb136704", + "height": 3036, + "haswebp": 1 + }, { + "name": "083.png", + "width": 2150, + "hasavif": 1, + "height": 3036, + "hash": "9ee906efa0f38dec528231125f4fa0852d32de77492df4afeb89e1abf4fbea3c", + "haswebp": 1 + }, { + "width": 2150, + "name": "084.png", + "hash": "b0203645bb21dc8d1338e71d085d05a194a635fec30761b517f7c9f6a4c8ab01", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "width": 2150, + "name": "085.png", + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "3cffc285171e18125ed69064cb53bd9db68eb5aa1dc64993e464ff3487d051b5" + }, { + "width": 2150, + "name": "086.png", + "hash": "a4a1dae9bbd2d00970cbc9a6ee0d768edc116267d0b4db438ac805c0d0f49848", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "hash": "58890d9821b959acc4ffc2fa0c125a949a4359602262097fc90e74dddd467e57", + "height": 3036, + "haswebp": 1, + "hasavif": 1, + "name": "087.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "6b3f3a81be3a9c1aba5fe6f7cce79ef544dc8dacf0a97c3ca33fc2ab56c4222e", + "name": "088.jpg", + "width": 2150 + }, { + "width": 2150, + "name": "089.jpg", + "haswebp": 1, + "height": 3036, + "hash": "900f5a762507208ada463c3e0b0e9371497b1c0e4f588910383f9155219722bb", + "hasavif": 1 + }, { + "hash": "3af9894be0b7a54900d268103b6571ad1d5b63fd1ca3c07e8766143b3c4f80d4", + "height": 3036, + "haswebp": 1, + "hasavif": 1, + "name": "090.png", + "width": 2150 + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "03eba4a7b4a54fc819792e306c54e3e1f5d4ece06cc08e7b51e976706631203e", + "width": 2150, + "name": "091.jpg" + }, { + "width": 2150, + "name": "092.jpg", + "hasavif": 1, + "height": 3036, + "hash": "544928078274878f8fd0495c2e1b522c8a12a978a45a4012e9f126293298beb9", + "haswebp": 1 + }, { + "width": 2150, + "name": "093.jpg", + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "cf21634fe7a58e369d5da625d59af88197bb31f25e91b3f8f18c655962d37cc2" + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "0e98d97d4b5fb9ae7c00fcb43c1181bb02fd81396aaa564a76196a454cba4592", + "width": 2150, + "name": "094.png" + }, { + "width": 2150, + "name": "095.png", + "height": 3036, + "haswebp": 1, + "hash": "07316b39154752f432d8d14bac13098ae9320821488a62c12dd74786f8fb15e3", + "hasavif": 1 + }, { + "width": 2150, + "name": "096.png", + "hasavif": 1, + "hash": "60a17366015db46df61a576329b3d10b850291c9fb87bb9e2c5cc003fa65cf43", + "height": 3036, + "haswebp": 1 + }, { + "haswebp": 1, + "height": 3036, + "hash": "6d25841b167b536dabbfafc89a402a9f831aa5ce9d9b422eadd72d750009e2ff", + "hasavif": 1, + "name": "097.png", + "width": 2150 + }, { + "name": "098.png", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "9b83e9f980ec96837d42e69c0764e115fa9a4c88e99be8e412989090659ab3d0", + "hasavif": 1 + }, { + "height": 3036, + "hash": "3dfa8605981dccdece3a5ff5b4ce36bc68102eccdca706287e18c060ed614866", + "haswebp": 1, + "hasavif": 1, + "name": "099.png", + "width": 2150 + }, { + "width": 2150, + "name": "100.png", + "hasavif": 1, + "hash": "e4fb3d05f2845004bad799e01e0b8b5b9326f3cd39064db42ad2314935fca848", + "height": 3036, + "haswebp": 1 + }, { + "name": "101.png", + "width": 2150, + "height": 3036, + "hash": "911bc25ee3e519a3529e3a58d1882d4d5fece0fc1199e930613e73e1b46f2573", + "haswebp": 1, + "hasavif": 1 + }, { + "name": "102.png", + "width": 2150, + "haswebp": 1, + "height": 3036, + "hash": "c88200e0446eed889b59f7bce329e15dbffd245678f94a305bd511a4f58b6331", + "hasavif": 1 + }, { + "hasavif": 1, + "haswebp": 1, + "height": 3036, + "hash": "6def42f7713cb6996482138ee2758c966d9a64d4856b6e69f594aaf7002b7217", + "name": "103.png", + "width": 2150 + }, { + "hash": "79ff73b1bd58a2d7252773ba63e0124d8e041c7c6c179c4ddf13802244bcac09", + "height": 3036, + "haswebp": 1, + "hasavif": 1, + "name": "104.png", + "width": 2150 + }, { + "name": "105.png", + "width": 2150, + "height": 3036, + "haswebp": 1, + "hash": "4b8b8f14d12bfbb80190efb87fa68f93a387442665b80416459e1ee2602aa7b4", + "hasavif": 1 + }, { + "hasavif": 1, + "height": 3036, + "hash": "ce653193c376443532a0b6e5fa29ab562673e9b549391a53815493ce29e89e7c", + "haswebp": 1, + "width": 2150, + "name": "106.png" + }, { + "hasavif": 1, + "height": 3036, + "hash": "27df010717acb8edf2d4494941e76881514f5438a11a1e3a4235f1ab3dd9be28", + "haswebp": 1, + "name": "107.png", + "width": 2150 + }, { + "name": "108.png", + "width": 2150, + "hasavif": 1, + "hash": "59e3c85ef50aa889626b4be08288c31d50f034a9489ed72784552aeb007fe406", + "height": 3036, + "haswebp": 1 + }, { + "hash": "28c5910fdc823873e449dd7f1bde4d48b4bb219cba0a94c0188bbf4405970483", + "height": 3036, + "haswebp": 1, + "hasavif": 1, + "width": 2150, + "name": "109.png" + }, { + "width": 2150, + "name": "110.png", + "height": 3036, + "haswebp": 1, + "hash": "ad3af88238a8b4471ff20569256ae1553612af9db5e88b8ebd9abd45fe044f47", + "hasavif": 1 + }, { + "name": "111.png", + "width": 2150, + "hash": "51c1b882d0fe4756cc7c2277a5057d25269eb0c4ecc5c16d1406160b72626bc4", + "height": 3036, + "haswebp": 1, + "hasavif": 1 + }, { + "height": 3036, + "hash": "f02a87e3fe12ce4b00d6488479b74a3b012230fe13fad1f7f5431324913b4953", + "haswebp": 1, + "hasavif": 1, + "width": 2150, + "name": "112.jpg" + }, { + "hasavif": 1, + "height": 3036, + "hash": "b4221d03baae3b565e3a0957f867f30388293702794cecb42ae733d72a2b7a90", + "haswebp": 1, + "name": "113.png", + "width": 2150 + }, { + "hasavif": 1, + "height": 3036, + "haswebp": 1, + "hash": "77379584adb5fb4601153344b7ee5875c4c159d162fffe5bacaf49dbaa4d1677", + "width": 2150, + "name": "114.jpg" + }, { + "hasavif": 1, + "height": 1765, + "haswebp": 1, + "hash": "e4dde784f1d4364788c0c36c8a6d58aea8e4cc52fc270e4924d290d05c78fa67", + "width": 2150, + "name": "115.png" + }], + "date": "2022-07-02 00:04:00-05", + "characters": null, + "groups": [{ + "group": "sonotaozey", + "url": "/group/sonotaozey-all.html" + }], + "id": "2261881", + "video": null +} diff --git a/tools/Documentation/SUMMARY.md b/tools/Documentation/SUMMARY.md index f10d7a9cd..3f82f9128 100644 --- a/tools/Documentation/SUMMARY.md +++ b/tools/Documentation/SUMMARY.md @@ -11,7 +11,6 @@ * [πŸ›  Source Code (Linux/macOS)](installing-lanraragi/source.md) * [🐧 Community (Linux)](installing-lanraragi/community.md) * [πŸ‘Ώ Jail (FreeBSD)](installing-lanraragi/jail.md) -* [πŸ•Έ Vagrant (Deprecated)](installing-lanraragi/vagrant.md) ## Basic Operations @@ -30,6 +29,7 @@ * [πŸ’Ύ Backup and Restore](advanced-usage/backup-and-restore.md) * [πŸ“± Using External Readers](advanced-usage/external-readers.md) * [🌐 Network Interface Setup](advanced-usage/network-interfaces.md) +* [πŸ•΅οΈ Proxy Setup](advanced-usage/proxy-setup.md) * [πŸ“ Tag Rules](advanced-usage/tag-rules.md) ## Developer Guide diff --git a/tools/Documentation/advanced-usage/downloading.md b/tools/Documentation/advanced-usage/downloading.md index 60142a371..7983afeb4 100644 --- a/tools/Documentation/advanced-usage/downloading.md +++ b/tools/Documentation/advanced-usage/downloading.md @@ -12,6 +12,11 @@ This allows you to seamlessly add archives from the Internet to your LRR instanc By default, we will try to download any URL you chuck at us! This will mostly work for simple URLs that point directly to a file we support. (For example, something like this very nice Quake booklet: `https://archive.org/download/quake-essays-sep-15-fin-4-graco-l-cl/QUAKE_essays_SEP15_FIN4_GRACoL_CL.pdf` will download without a fuss.) +{% hint style="info" %} +Downloaded archives will automatically get a `source:` tag with the URL they were downloaded from. +Said source tags can often be used with compatible Metadata plugins to fetch metadata precisely. (Supported by E-H and nH) +{% endhint %} + For non-direct links, you will need to have a matching **Downloader Plugin** configured. LANraragi currently ships with Downloaders handling E-H and Chaika links. diff --git a/tools/Documentation/advanced-usage/proxy-setup.md b/tools/Documentation/advanced-usage/proxy-setup.md new file mode 100644 index 000000000..8f885548c --- /dev/null +++ b/tools/Documentation/advanced-usage/proxy-setup.md @@ -0,0 +1,67 @@ +# πŸ•΅οΈ Proxy Setup + +## Setting up LANraragi behind a proxy (reverse proxy setup) + +A common post-install setup is to make requests to the app transit through a gateway server such as Apache or nginx. +If you do so, please note that archive uploads through LRR will likely **not work out of the box** due to maximum sizes on uploads those servers can enforce. The example below is for nginx: + +``` +http { + client_max_body_size 0; <----------------------- This line here +} + +server { + listen 80; + + server_name lanraragi.[REDACTED].net; + + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + index index.php index.html index.htm; + server_name lanraragi.[REDACTED].net; + + client_max_body_size 0; <----------------------- And this line here + + # Cert Stuff Omitted + + location / { + proxy_pass http://0.0.0.0:3000; + proxy_http_version 1.1; + <----- The two following lines are needed for batch tagger support with SSL -----> + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} +``` + +## Setting up LANraragi to use a proxy for outbound network requests + +This is a less common scenario, but you might want to have downloads or metadata requests to external services go through a proxy, in case said external services are blocked by your friendly local totalitarian regime. + +LANraragi runs on top of the Mojolicious web server, which has [built-in](https://docs.mojolicious.org/Mojo/UserAgent/Proxy#detect) support for proxifying external requests. + +To enable automatic proxy detection, the `MOJO_PROXY` environment variable must be set to 1 on your machine: This is enabled by default on Docker builds. +Once said detection enabled, environment variables `HTTP_PROXY, http_proxy, HTTPS_PROXY, https_proxy, NO_PROXY` and `no_proxy` will be checked for proxy information. + +Here's an example for a Docker-compose setup: + +``` +--- +version: "2.1" +services: + lanraragi: + image: difegue/lanraragi:latest + container_name: lanraragi + environment: + - http_proxy=http://192.168.10.186:1082 + - https_proxy=http://192.168.10.186:1082 + volumes: + - [database]:/home/koyomi/lanraragi/database + - [content]:/home/koyomi/lanraragi/content + ports: + - 7070:3000 + restart: unless-stopped +``` diff --git a/tools/Documentation/basic-operations/metadata.md b/tools/Documentation/basic-operations/metadata.md index 4cf89a639..d6835276e 100644 --- a/tools/Documentation/basic-operations/metadata.md +++ b/tools/Documentation/basic-operations/metadata.md @@ -40,3 +40,9 @@ Plugins have as much control over your system as the main LANraragi application When installing Plugins from unknown sources, do a little research first. {% endhint %} + +## About source: tags + +If your archive has a `source:` tag (likely from the use of the [built-in downloading feature](../advanced-usage/downloading.md)), many plugins will use said tag to directly fetch metadata from it without having to use heuristics of any kind to guess what your archive is. + +If you have the URL on hand directly, you can either add it as a `source:` tag to your archive, or use it as a one-shot parameter on most downloader plugins. \ No newline at end of file diff --git a/tools/Documentation/extending-lanraragi/architecture.md b/tools/Documentation/extending-lanraragi/architecture.md index da73ba6ae..97dbed062 100644 --- a/tools/Documentation/extending-lanraragi/architecture.md +++ b/tools/Documentation/extending-lanraragi/architecture.md @@ -24,7 +24,7 @@ Those variables were introduced for the Homebrew package, but they can be declar While Perl's mantra is "There's more than one way to do it", I try to make LRR follow the PBP, aka Perl Best Practices. This is done by the use of the [Perl::Critic](https://metacpan.org/pod/Perl::Critic) module, which reports PBP violations. If installed, you can run the critic on the entire LRR source tree through the `npm run critic` shortcut command. -Critic is automatically run on every commit made to LRR at the level 5 thanks to [Github Actions](../../../.github/main.workflow). +Critic is automatically run on every commit made to LRR at the level 5 thanks to [GitHub Actions](../../../.github/main.workflow). I also run [perltidy](https://en.wikipedia.org/wiki/PerlTidy) on the source tree every now and then for consistency. The rules used in perltidy passes are stored in the .perltidyrc file at the source root. @@ -47,14 +47,14 @@ I recommend trying to only use exported functions in your code, and consider the ``` root/ |- .devcontainer <- VSCode setup files for Codespaces -|- .github <- Github-specific files +|- .github <- GitHub-specific files | |- action-run-tests <- Run the LRR Test Suite | |- ISSUE_TEMPLATE <- Template for bug reports -| |- workflows <- Github Actions workflows +| |- workflows <- GitHub Actions workflows | |- CD <- Continuous Delivery, Nightly builds | |- CI <- Tests | +- Release <- Build latest and upload .zip to release post on GH -| +- FUNDING.yml <- Github Sponsors file +| +- FUNDING.yml <- GitHub Sponsors file | |- content <- Default content folder | @@ -117,7 +117,6 @@ root/ | |- windows <- Windows build script and submodule link to the Karen WPF Bootstrapper | |- docker <- Dockerfile and configuration files for LRR Docker Container | |- homebrew <- Script and configuration files for the LRR Homebrew cask -| |- vagrant <- Vagrantfile for LRR Vagrant Machine | |- cpanfile <- Perl dependencies description | |- install.pl <- LANraragi Installer | +- lanraragi-systemd.service <- Example SystemD service diff --git a/tools/Documentation/installing-lanraragi/docker.md b/tools/Documentation/installing-lanraragi/docker.md index 0afc01921..188d4e9ac 100644 --- a/tools/Documentation/installing-lanraragi/docker.md +++ b/tools/Documentation/installing-lanraragi/docker.md @@ -12,43 +12,24 @@ Docker is the best way to install the software on remote servers. I don't recomm ## Cloning the base LRR image -Download [the Docker setup](https://www.docker.com/products/docker) and install it. Once you're done, execute: - -```bash -docker run --name=lanraragi -p 3000:3000 \ ---mount type=bind,source=[YOUR_CONTENT_DIRECTORY],target=/home/koyomi/lanraragi/content \ ---mount type=bind,source=[YOUR_DATABASE_DIRECTORY],target=/home/koyomi/lanraragi/database \ -difegue/lanraragi -``` +Download [the Docker setup](https://www.docker.com/products/docker) and install it. {% hint style="warning" %} -If your Docker version is [_below 17.06_](https://docs.docker.com/storage/bind-mounts/) and you use the --mount option as listed above, you will get the following error: - -```bash -unknown flag: --mount -See 'docker run --help'. -``` - -You can bypass this issue by using the --volume option for bind-mounting like so: +The LRR Docker container uses a fairly recent ([3.14](https://alpinelinux.org/posts/Alpine-3.14.0-released.html)) version of Alpine Linux as its base. I recommend you use at least Docker version **20.10.0** to avoid issues with the `faccessat2` syscall. +You can check your Docker version by executing `docker version`. +{% endhint %} +Once you're done, execute: ```bash docker run --name=lanraragi -p 3000:3000 \ ---volume [YOUR_CONTENT_DIRECTORY]:/home/koyomi/lanraragi/content \ ---volume [YOUR_CONTENT_DIRECTORY]:/home/koyomi/lanraragi/database \ +--mount type=bind,source=[YOUR_CONTENT_DIRECTORY],target=/home/koyomi/lanraragi/content \ +--mount type=bind,source=[YOUR_DATABASE_DIRECTORY],target=/home/koyomi/lanraragi/database \ difegue/lanraragi ``` -{% endhint %} - {% hint style="info" %} You can tell Docker to auto-restart the LRR container on boot by adding the `--restart always` flag to this command. {% endhint %} -{% hint style="warning" %} -If you're running on Windows, please check the syntax for mapping your content directory [here](https://docs.docker.com/docker-for-windows/#shared-drives). - -Windows 7/8 users running the Legacy Docker toolbox will have to explicitly forward port 127.0.0.1:3000 from the host to the container in order to be able to access the app. -{% endhint %} - The content directory you have to specify in the command above will contain archives you either upload through the software or directly drop in, alongside generated thumbnails. The database directory houses the LANraragi database(As database.rdb), allowing you to hotswap containers without losing any data. @@ -84,6 +65,24 @@ If you're feeling **extra dangerous**, you can run the last files directly from `docker run [zoinks] difegue/lanraragi:nightly` {% endhint %} +## Platform-specific caveats + +### Windows +If you're running on Windows, please check the syntax for mapping your content directory [here](https://docs.docker.com/docker-for-windows/#shared-drives). + +Windows 7/8 users running the Legacy Docker toolbox will have to explicitly forward port 127.0.0.1:3000 from the host to the container in order to be able to access the app. +### Raspbian + +If you're using **Raspbian**, it's likely you'll encounter installation issues like `s6-svscan: warning: unable to iopause: Operation not permitted` due to their outdated version of `libseccomp`. +You can fix this by either adding `--security-opt seccomp=unconfined` to your Docker arguments(discouraged, allows LRR wider access to underlying OS), or by installing an up-to-date version of `libseccomp`: + +```bash +wget http://ftp.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1~bpo10+1_armhf.deb +sudo dpkg -i libseccomp2_2.5.1-1~bpo10+1_armhf.deb +``` + +Regular versions of Debian shouldn't have this issue. + ## Changing the port Since Docker allows for port mapping, you can most of times map the default port of 3000 to another port on your host quickly. @@ -116,10 +115,13 @@ As Docker containers are immutable, you need to destroy your existing container docker pull difegue/lanraragi docker stop lanraragi docker rm lanraragi -docker run --name=lanraragi -p 3000:3000 --mount type=bind,source=[YOUR_CONTENT_DIRECTORY],target=/home/koyomi/lanraragi/content difegue/lanraragi +docker run --name=lanraragi -p 3000:3000 \ + --mount type=bind,source=[YOUR_CONTENT_DIRECTORY],target=/home/koyomi/lanraragi/content \ + --mount type=bind,source=[YOUR_DATABASE_DIRECTORY],target=/home/koyomi/lanraragi/database \ + difegue/lanraragi ``` -As long as you use the same content directory as the mount source, your data will still be there. +As long as you use the same content/database directories as before, your data will still be there. {% hint style="info" %} If you update often, you might want to consider using docker-compose or [Portainer](https://portainer.io) to redeploy containers without entering the entire configuration every time. diff --git a/tools/Documentation/installing-lanraragi/macos.md b/tools/Documentation/installing-lanraragi/macos.md index 2867c9b09..bdf2192a7 100644 --- a/tools/Documentation/installing-lanraragi/macos.md +++ b/tools/Documentation/installing-lanraragi/macos.md @@ -1,4 +1,4 @@ -# 🍎 Homebrew (macOS) +# 🍎 Homebrew (macOS/Linux) ## Migration @@ -34,8 +34,9 @@ brew install lanraragi ## Configuration -Your content folder is stored by default in `${HOME}/Library/Application Support/LANraragi`. -The Redis database is stored in `${HOME}/Library/Application Support/LANraragi/database`. The content folder can be moved to any folder you want through the in-app settings page. +Your content folder is stored by default in `${HOME}/Library/Application Support/LANraragi`. (`${HOME}/LANraragi/content` on Linux.) +The Redis database is stored in `${HOME}/Library/Application Support/LANraragi/database`. (`${HOME}/LANraragi/database` on Linux.) +The content folder can be moved to any folder you want through the in-app settings page. ## Usage @@ -64,4 +65,4 @@ The same warning as in the Installation step applies. ## Uninstallation Run `brew remove lanraragi` to uninstall the app. -Data in the `${HOME}/Library/Application Support/LANraragi` folder is not deleted. +Data in the `${HOME}/Library/Application Support/LANraragi`/`${HOME}/LANraragi/` folder is not deleted. diff --git a/tools/Documentation/installing-lanraragi/methods.md b/tools/Documentation/installing-lanraragi/methods.md index 4942e9508..99cc11da0 100644 --- a/tools/Documentation/installing-lanraragi/methods.md +++ b/tools/Documentation/installing-lanraragi/methods.md @@ -9,9 +9,9 @@ However, a lot of work as been done behind the scenes to make it easy! Look at the methods below for something that fits your OS and usage. -## macOS: _Homebrew_ +## Linux/macOS: _Homebrew_ -[Homebrew](https://brew.sh) allows you to quickly setup LRR on macOS without relying on containers or modifying your preinstalled system libaries. +[Homebrew](https://brew.sh) allows you to quickly setup LRR on macOS and Linux without relying on containers or modifying your preinstalled system libaries. ![brew](<../.screenshots/brew.jpg>) @@ -23,9 +23,6 @@ Look at the methods below for something that fits your OS and usage. {% hint style="warning" %} This method works on **64-bit** editions of Windows 10 only. - -Since LRR 0.8.0, you need Windows 10 version _1903_ at least. 0.7.9 will work with version _1809_ if you're on an LTSC channel. -If you still want to use further server versions on 1809, a step-by-step workaround is available on the Windows documentation page below. {% endhint %} ![win10](../.screenshots/karen.jpg) @@ -69,51 +66,8 @@ Similar to installing from source with an altered process for FreeBSD compatabil [jail.md](jail.md) {% endcontent-ref %} -## Older Windows: _Legacy Docker Toolbox or Vagrant_ +## Windows 7 or 8: don't ![I really hope you guys don't do this](../.screenshots/shiggy.png) -At this point the only solutions I have to give you are basically glorified VMs. - -You can either download the [Legacy Docker Toolbox](https://docs.docker.com/toolbox/toolbox\_install\_windows/) and follow the Docker tutorial linked above, or try Vagrant. I provide **no support** for either of these methods. - -{% content-ref url="vagrant.md" %} -[vagrant.md](vagrant.md) -{% endcontent-ref %} - -## WARNING: Reverse Proxies - -A common post-install setup is to make requests to the app transit through a gateway server such as Apache or nginx. -If you do so, please note that archive uploads through LRR will likely **not work out of the box** due to maximum sizes on uploads those servers can enforce. The example below is for nginx: - -``` -http { - client_max_body_size 0; <----------------------- This line here -} - -server { - listen 80; - - server_name lanraragi.[REDACTED].net; - - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl; - index index.php index.html index.htm; - server_name lanraragi.[REDACTED].net; - - client_max_body_size 0; <----------------------- And this line here - - # Cert Stuff Omitted - - location / { - proxy_pass http://0.0.0.0:3000; - proxy_http_version 1.1; - <----- The two following lines are needed for batch tagger support with SSL -----> - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - } -} -``` +Switch to 10 or Linux. diff --git a/tools/Documentation/installing-lanraragi/vagrant.md b/tools/Documentation/installing-lanraragi/vagrant.md deleted file mode 100644 index 8ff94bf00..000000000 --- a/tools/Documentation/installing-lanraragi/vagrant.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -description: >- - For computers that are unable to easily use Docker or WSL(Basically just - Windows 7 and 8), Vagrant allows you to quickly get started nonetheless. ---- - -# πŸ•Έ Vagrant (Deprecated) - -{% hint style="danger" %} -Vagrant installs are **deprecated** as of 0.6.0. They'll work, but come with enough potential issues and slowdowns that I don't recommend you use them at all! -{% endhint %} - -## Using the Vagrantfile - -You can use the available Vagrantfile with [Vagrant](https://www.vagrantup.com/downloads.html) to deploy a virtual machine on your computer with LANraragi preinstalled. - -{% hint style="info" %} -This method requires [VirtualBox](https://www.virtualbox.org) to be installed on your machine! -{% endhint %} - -Download the [Vagrantfile](https://github.com/Difegue/LANraragi/tree/dev/tools/build/vagrant) that's relevant to the version of LANraragi that you want to install, then move it to your future LANraragi folder. If you downloaded the nightly Vagrantfile, be sure to remove `_nightly` from the end of the filename. Once you've done that, open a terminal in that folder and enter the following commands: - -``` -vagrant plugin install vagrant-vbguest -vagrant up -``` - -Once the Vagrant machine is up and provisioned, you can access LANraragi at [http://localhost:3000](http://localhost:3000). -Archives you upload will be placed in the directory of the Vagrantfile. - -The Vagrant machine is a simple Docker wrapper, so the database will also be stored in this directory. (As database.rdb) - -You can use `vagrant halt` to stop the VM when you're done. -To start it up again, use the following commands: - -``` -vagrant up -vagrant provision -``` - -Keep in mind that the Vagrant setup, just like Docker, will always use the latest release. - -## Updating - -From the directory where the Vagrantfile is located: - -```bash -vagrant up -vagrant provision -``` - -Those two commands will update the wrapped Docker image to the latest one(basically automatically doing the commands written up there on the Docker section). No other operations are needed. diff --git a/tools/Documentation/installing-lanraragi/windows.md b/tools/Documentation/installing-lanraragi/windows.md index 920490af5..b53c529ba 100644 --- a/tools/Documentation/installing-lanraragi/windows.md +++ b/tools/Documentation/installing-lanraragi/windows.md @@ -13,7 +13,8 @@ winget install lanraragi {% hint style="warning" %} The installer will tell you about this anyways, but LRR for Windows **requires** the Windows Subsystem for Linux to function properly. -Read the tutorial [here](https://docs.microsoft.com/en-us/windows/wsl/install) to see how to enable WSL on your Windows 10 machine. +Read the tutorial [here](https://docs.microsoft.com/en-us/windows/wsl/install) to see how to enable WSL on your Windows 10 machine. +WSL defaults to WSL2, so if the installer doesn't work properly make sure you have virtualization enabled as well, or switch to WSL1. (`wsl --set-default-version 1`) You don't need to install a distribution through the Windows Store, as that is handled by the LRR installer package. {% endhint %} @@ -39,28 +40,6 @@ Once the install completes properly, you'll be able to launch the GUI from the s ![](../.screenshots/karen-startmenu.png) -## Installation on Windows 10 1809 (LTSC) - -Recent MSI packages don't install on 1809 anymore due to underlying changes to make the installer lighter, but you can still sideload the latest server version on top of an old 0.7.9 install. - -{% hint style="warning" %} -This method shouldn't break in the foreseeable future, but as the Win32 bootstrapper will still be the 0.7.9 version, you might lose out on future functionalities later on. -You might want to consider switching to a [source install](./source.md) on top of a Debian WSL distro you'd maintain yourself. -{% endhint %} - -1. Install 0.7.9 like normal, this is mostly done to get the Win32 UI application installed on to your taskbar, we'll install the updated Linux image next. -2. If you started the service and the Windows application, make sure to close BOTH. -3. Download the [MSI installer for the latest version](https://github.com/Difegue/LANraragi/releases/latest) -4. Open the MSI file in 7zip, and extract the "package.tar" file, which is the underlying Linux image -5. Download [LxRunOffline](https://github.com/DDoSolitary/LxRunOffline/releases) and put it in the same directory as the "package.tar" file you just extracted -6. Uninstall the old Linux image from 0.7.9 with the following command, make sure to have your command window opened as administrator: - `lxrunoffline ui -n lanraragi` -7. install the new image: - `lxrunoffline i -n lanraragi -d "C:\Users\*your user name*\AppData\Roaming\LANraragi\Distro\rootfs" -f LANraragi.tar` -Note: the name of the install HAS to be "lanraragi", do not change this on the -n argument -8. Start the application again, and you should see that it now shows the newest version of the server - - ## Configuration Starting the GUI for the first time will prompt you to setup your content folder and the port you want the server to listen on. The main GUI is always available from your Taskbar. diff --git a/tools/Documentation/plugin-docs/index.md b/tools/Documentation/plugin-docs/index.md index d5fc23e31..f3375f28f 100644 --- a/tools/Documentation/plugin-docs/index.md +++ b/tools/Documentation/plugin-docs/index.md @@ -24,7 +24,7 @@ Basically, _as long as it can run, it will run_. {% hint style="danger" %} As you might've guessed, Plugins run with the same permissions as the main application. This means they can modify the application database at will, delete files, and execute system commands. -None of this is obviously an issue if the application is installed in a proper fashion.(Docker/Vagrant, or non-root user on Linux _I seriously hope you guys don't run this as root_) +None of this is obviously an issue if the application is installed in a proper fashion.(Docker/VM, or non-root user on Linux _I seriously hope you guys don't run this as root_) Still, as said in the User Documentation, be careful of what you do with Plugins. {% endhint %} diff --git a/tools/Documentation/plugin-docs/metadata.md b/tools/Documentation/plugin-docs/metadata.md index d66ecbddf..af1e126e1 100644 --- a/tools/Documentation/plugin-docs/metadata.md +++ b/tools/Documentation/plugin-docs/metadata.md @@ -24,6 +24,7 @@ The variables match the parameters you've entered in the `plugin_info` subroutin The `$lrr_info` hash contains various variables you can use in your plugin: +* _$lrr\_info->{archive\_id}_: The internal ID of the archive. * _$lrr\_info->{archive\_title}_: The title of the archive, as entered by the User. * _$lrr\_info->{existing\_tags}_: The tags that are already in LRR for this archive, if there are any. * _$lrr\_info->{thumbnail\_hash}_: A SHA-1 hash of the first image of the archive. diff --git a/tools/build/docker/Dockerfile b/tools/build/docker/Dockerfile index 2d4d652fc..22b134a96 100644 --- a/tools/build/docker/Dockerfile +++ b/tools/build/docker/Dockerfile @@ -22,14 +22,18 @@ HEALTHCHECK --interval=1m --timeout=10s --retries=3 \ #Default mojo server port EXPOSE 3000 -#Enable UTF-8 (might not do anything extra on alpine tho) +# Enable UTF-8 (might not do anything extra on alpine tho) ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 \ - #rootless user id + # rootless user id LRR_UID=9001 LRR_GID=9001 \ - #Environment variables overridable by the user on container deployment + # Environment variables overridable by the user on container deployment LRR_NETWORK=http://*:3000 \ # extra variables - EV_EXTRA_DEFS=-DEV_NO_ATFORK + EV_EXTRA_DEFS=-DEV_NO_ATFORK \ + # Enable automatic http proxy detection for mojo + MOJO_PROXY=1 \ + # Allow Mojo to automatically pick up the X-Forwarded-For and X-Forwarded-Proto headers + MOJO_REVERSE_PROXY=1 RUN \ if [ $(getent group ${LRR_GID}) ]; then \ diff --git a/tools/build/docker/Dockerfile-legacy b/tools/build/docker/Dockerfile-legacy new file mode 100644 index 000000000..f9fc05b8c --- /dev/null +++ b/tools/build/docker/Dockerfile-legacy @@ -0,0 +1,75 @@ +# DOCKER-VERSION 0.3.4 +FROM alpine:3.12.12 +LABEL git="https://github.com/Difegue/LANraragi" + +ENV S6_KEEP_ENV 1 + +# warn if we can't run stage2 (fix-attrs/cont-init) +ENV S6_BEHAVIOUR_IF_STAGE2_FAILS 1 + +# wait 10s before KILLing +ENV S6_KILL_GRACETIME 10000 + +# s6 +ENTRYPOINT ["/init"] + +# Check application health +HEALTHCHECK --interval=1m --timeout=10s --retries=3 \ + CMD wget --quiet --tries=1 --no-check-certificate --spider \ + http://localhost:3000 || exit 1 + +#Default mojo server port +EXPOSE 3000 + +# Enable UTF-8 (might not do anything extra on alpine tho) +ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 \ + # rootless user id + LRR_UID=9001 LRR_GID=9001 \ + # Environment variables overridable by the user on container deployment + LRR_NETWORK=http://*:3000 \ + # extra variables + EV_EXTRA_DEFS=-DEV_NO_ATFORK \ + # Enable automatic http proxy detection for mojo + MOJO_PROXY=1 \ + # Allow Mojo to automatically pick up the X-Forwarded-For and X-Forwarded-Proto headers + MOJO_REVERSE_PROXY=1 + +RUN \ + if [ $(getent group ${LRR_GID}) ]; then \ + adduser -D -u ${LRR_UID} koyomi; \ + else \ + addgroup -g ${LRR_GID} koyomi && \ + adduser -D -u ${LRR_UID} -G koyomi koyomi; \ +fi + +# we use s6-overlay-nobin to just pull in the s6-overlay arch agnostic (shell) +# components, since we apk install the binaries of s6 later which are arch specific +ADD https://github.com/just-containers/s6-overlay/releases/download/v2.0.0.1/s6-overlay-nobin.tar.gz /tmp/s6-overlay-nobin.tar.gz +RUN tar -C / -xzf /tmp/s6-overlay-nobin.tar.gz && rm -f /tmp/s6-overlay-nobin.tar.gz + +WORKDIR /home/koyomi/lanraragi + +#Copy cpanfile and install script before copying the entire context +#This allows for Docker cache to preserve cpan dependencies +COPY --chown=koyomi:koyomi /tools/cpanfile /tools/install.pl /tools/build/docker/install-everything.sh tools/ +COPY --chown=koyomi:koyomi /package.json package.json + +# Run the install script as root +RUN sh ./tools/install-everything.sh + +#Copy remaining LRR files from context +COPY --chown=koyomi:koyomi /lib lib +COPY --chown=koyomi:koyomi /public public +COPY --chown=koyomi:koyomi /script script +COPY --chown=koyomi:koyomi /templates templates +COPY --chown=koyomi:koyomi /tests tests +COPY --chown=koyomi:koyomi /lrr.conf lrr.conf +COPY --chown=koyomi:koyomi /tools/build/docker/redis.conf tools/build/docker/ +COPY /tools/build/docker/wsl.conf /etc/wsl.conf +COPY /tools/build/docker/s6/cont-init.d/ /etc/cont-init.d/ +COPY /tools/build/docker/s6/services.d/ /etc/services.d/ +#COPY /tools/build/docker/s6/fix-attrs.d/ /etc/fix-attrs.d/ + +# Persistent volumes +VOLUME [ "/home/koyomi/lanraragi/content" ] +VOLUME [ "/home/koyomi/lanraragi/database"] diff --git a/tools/build/docker/install-everything.sh b/tools/build/docker/install-everything.sh index 597a77faf..07b582fa5 100755 --- a/tools/build/docker/install-everything.sh +++ b/tools/build/docker/install-everything.sh @@ -2,10 +2,21 @@ #Just do everything apk update +apk add tzdata apk add perl perl-io-socket-ssl perl-dev redis libarchive-dev libbz2 openssl-dev zlib-dev apk add imagemagick imagemagick-perlmagick libwebp-tools libheif -apk add g++ make pkgconf gnupg wget curl nodejs npm file -apk add shadow s6 s6-portable-utils s6-overlay s6-overlay-preinit +apk add g++ make pkgconf gnupg wget curl file +apk add shadow s6 s6-portable-utils + +# Check for alpine version +if [ -f /etc/alpine-release ]; then + alpine_version=$(cat /etc/alpine-release) + if [ "$alpine_version" = "3.12.12" ]; then + apk add nodejs-npm + else # Those packages don't exist on 3.12 + apk add nodejs npm s6-overlay s6-overlay-preinit + fi +fi #Hey it's cpanm curl -L https://cpanmin.us | perl - App::cpanminus @@ -29,4 +40,5 @@ npm run lanraragi-installer install-full #Cleanup to lighten the image apk del perl-dev g++ make gnupg wget curl nodejs npm openssl-dev file +rm -rf public/js/vendor/*.map public/css/vendor/*.map rm -rf /root/.cpanm/* /root/.npm/ /usr/local/share/man/* node_modules /var/cache/apk/* diff --git a/tools/build/homebrew/Lanraragi.rb b/tools/build/homebrew/Lanraragi.rb index a03884c4b..c11ef54fa 100644 --- a/tools/build/homebrew/Lanraragi.rb +++ b/tools/build/homebrew/Lanraragi.rb @@ -28,8 +28,11 @@ class Lanraragi < Formula uses_from_macos "libarchive" - on_linux do - depends_on "libarchive" + on_macos do + resource "libarchive-headers" do + url "https://opensource.apple.com/tarballs/libarchive/libarchive-83.100.2.tar.gz" + sha256 "e54049be1b1d4f674f33488fdbcf5bb9f9390db5cc17a5b34cbeeb5f752b207a" + end end resource "Image::Magick" do @@ -37,20 +40,21 @@ class Lanraragi < Formula sha256 "1d5272d71b5cb44c30cd84b09b4dc5735b850de164a192ba191a9b35568305f4" end - resource "libarchive-headers" do - url "https://opensource.apple.com/tarballs/libarchive/libarchive-83.40.4.tar.gz" - sha256 "20ad61b1301138bc7445e204dd9e9e49145987b6655bbac39f6cad3c75b10369" - end - def install ENV.prepend_create_path "PERL5LIB", "#{libexec}/lib/perl5" ENV.prepend_path "PERL5LIB", "#{libexec}/lib" - ENV["CFLAGS"] = "-I#{libexec}/include" + + # On Linux, use the headers provided by the libarchive formula rather than the ones provided by Apple. + ENV["CFLAGS"] = if OS.mac? + "-I#{libexec}/include" + else + "-I#{Formula["libarchive"].opt_include}" + end # https://stackoverflow.com/questions/60521205/how-can-i-install-netssleay-with-perlbrew-in-macos-catalina ENV["OPENSSL_PREFIX"] = Formula["openssl@1.1"].opt_prefix # for Net::SSLeay system "cpanm", "-v", "Net::SSLeay", "-l", libexec - + imagemagick = Formula["imagemagick"] resource("Image::Magick").stage do inreplace "Makefile.PL" do |s| @@ -63,13 +67,15 @@ def install system "make", "install" end - resource("libarchive-headers").stage do - cd "libarchive/libarchive" do - (libexec/"include").install "archive.h", "archive_entry.h" + if OS.mac? + resource("libarchive-headers").stage do + cd "libarchive/libarchive" do + (libexec/"include").install "archive.h", "archive_entry.h" + end end end - system "cpanm", "Config::AutoConf", "--notest", "-l", libexec + system "cpanm", "-v", "Config::AutoConf", "-l", libexec system "npm", "install", *Language::Node.local_npm_install_args system "perl", "./tools/install.pl", "install-full" @@ -98,12 +104,24 @@ def caveats system "npm", "--prefix", libexec, "test" # but while we're at it, we can also check for the table flip! it's free real estate + # Make sure lanraragi writes files to a path allowed by the sandbox + ENV["LRR_LOG_DIRECTORY"] = ENV["LRR_TEMP_DIRECTORY"] = testpath + %w[server.pid shinobu.pid minion.pid].each { |file| touch file } + + # Set PERL5LIB as we're not calling the launcher script + ENV["PERL5LIB"] = libexec/"lib/perl5" + + # This can't have its _user-facing_ functionality tested in the `brew test` + # environment because it needs Redis. It fails spectacularly tho with some + # table flip emoji. So let's use those to confirm _some_ functionality. output = <<~EOS キタ━━━━━━(οΎŸβˆ€οΎŸ)━━━━━━!!!!! (╯・_>・)╯︡ ┻━┻ It appears your Redis database is currently not running. The program will cease functioning now. EOS - assert_match output, shell_output("npm start --prefix #{libexec}", 61) + # Execute through npm to avoid starting a redis-server + return_value = OS.mac? ? 61 : 111 + assert_match output, shell_output("npm start --prefix #{libexec}", return_value) end end diff --git a/tools/build/vagrant/.gitignore b/tools/build/vagrant/.gitignore deleted file mode 100644 index 34c277cca..000000000 --- a/tools/build/vagrant/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.vagrant -database.rdb \ No newline at end of file diff --git a/tools/build/vagrant/Vagrantfile b/tools/build/vagrant/Vagrantfile deleted file mode 100644 index 596b0f173..000000000 --- a/tools/build/vagrant/Vagrantfile +++ /dev/null @@ -1,22 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. -Vagrant.configure(2) do |config| - - config.vm.box = "debian/jessie64" - config.vm.network :forwarded_port, guest: 3000, host: 3000 - config.vm.synced_folder ".", "/vagrant", type: "virtualbox" - config.vm.provision "shell", - inline: "docker stop lanraragi || true && docker rm lanraragi || true" - config.vm.provision "docker" do |d| - d.pull_images "difegue/lanraragi:latest" - d.run "difegue/lanraragi:latest", - args: "--name=lanraragi -e LRR_UID=1000 -p 3000:3000 --restart=always --mount type=bind,source=/vagrant,target=/home/koyomi/lanraragi/content " - end - config.vm.post_up_message = "LANraragi Vagrant Machine Started, App should be available at http://localhost:3000." - -end diff --git a/tools/build/vagrant/Vagrantfile_nightly b/tools/build/vagrant/Vagrantfile_nightly deleted file mode 100644 index b3f72be23..000000000 --- a/tools/build/vagrant/Vagrantfile_nightly +++ /dev/null @@ -1,22 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. -Vagrant.configure(2) do |config| - - config.vm.box = "debian/jessie64" - config.vm.network :forwarded_port, guest: 3000, host: 3000 - config.vm.synced_folder ".", "/vagrant", type: "virtualbox" - config.vm.provision "shell", - inline: "docker stop lanraragi || true && docker rm lanraragi || true" - config.vm.provision "docker" do |d| - d.pull_images "difegue/lanraragi" - d.run "difegue/lanraragi:nightly", - args: "--name=lanraragi -e LRR_UID=1000 -p 3000:3000 --restart=always --mount type=bind,source=/vagrant,target=/home/koyomi/lanraragi/content " - end - config.vm.post_up_message = "LANraragi Vagrant Machine Started, App should be available at http://localhost:3000." - -end diff --git a/tools/build/windows/Karen b/tools/build/windows/Karen index 3d50b496d..c6b8c6b80 160000 --- a/tools/build/windows/Karen +++ b/tools/build/windows/Karen @@ -1 +1 @@ -Subproject commit 3d50b496decd9ff95b59690103837d77f393c3e2 +Subproject commit c6b8c6b80d01d486cba64d1730530f8584419387 diff --git a/tools/cpanfile b/tools/cpanfile index 68f27c294..f6eadc8f3 100755 --- a/tools/cpanfile +++ b/tools/cpanfile @@ -12,6 +12,9 @@ requires 'Digest::SHA', 6.02; # Not required by LRR itself but needs this version for Alpine support requires 'Crypt::Rijndael', 1.14; +# Specifically use native DNS resolver to fix issues on WSL1+Alpine +requires 'Net::DNS::Native', 0.22; + # Web UI requires 'Sort::Naturally', 1.03; requires 'Authen::Passphrase', 0.008; @@ -54,4 +57,4 @@ requires 'File::ChangeNotify', 0.31; requires 'Module::Pluggable', 5.2; # Eze plugin -requires 'Time::Local', 1.30; \ No newline at end of file +requires 'Time::Local', 1.30; diff --git a/tools/install.pl b/tools/install.pl index b42d92a38..76aff8d32 100755 --- a/tools/install.pl +++ b/tools/install.pl @@ -13,21 +13,25 @@ #Vendor dependencies my @vendor_css = ( "/blueimp-file-upload/css/jquery.fileupload.css", "/\@fortawesome/fontawesome-free/css/all.min.css", - "/jqcloud2/dist/jqcloud.min.css", "/jquery-toast-plugin/dist/jquery.toast.min.css", + "/jqcloud2/dist/jqcloud.min.css", "/react-toastify/dist/ReactToastify.min.css", "/jquery-contextmenu/dist/jquery.contextMenu.min.css", "/tippy.js/dist/tippy.css", "/allcollapsible/dist/css/allcollapsible.min.css", "/awesomplete/awesomplete.css", - "/\@jcubic/tagger/tagger.css", "/swiper/swiper-bundle.min.css" + "/\@jcubic/tagger/tagger.css", "/swiper/swiper-bundle.min.css", + "/sweetalert2/dist/sweetalert2.min.css", ); my @vendor_js = ( "/blueimp-file-upload/js/jquery.fileupload.js", "/blueimp-file-upload/js/vendor/jquery.ui.widget.js", "/datatables.net/js/jquery.dataTables.min.js", "/jqcloud2/dist/jqcloud.min.js", - "/jquery/dist/jquery.min.js", "/jquery-toast-plugin/dist/jquery.toast.min.js", + "/jquery/dist/jquery.min.js", "/react-toastify/dist/react-toastify.umd.js", "/jquery-contextmenu/dist/jquery.ui.position.min.js", "/jquery-contextmenu/dist/jquery.contextMenu.min.js", "/tippy.js/dist/tippy-bundle.umd.min.js", "/\@popperjs/core/dist/umd/popper.min.js", "/allcollapsible/dist/js/allcollapsible.min.js", "/awesomplete/awesomplete.min.js", "/\@jcubic/tagger/tagger.js", "/marked/marked.min.js", - "/swiper/swiper-bundle.min.js" + "/swiper/swiper-bundle.min.js", "/preact/dist/preact.umd.js", + "/clsx/dist/clsx.min.js", "/preact/compat/dist/compat.umd.js", + "/preact/hooks/dist/hooks.umd.js", "/sweetalert2/dist/sweetalert2.min.js", + "/fscreen/dist/fscreen.esm.js" ); my @vendor_woff = ( @@ -40,7 +44,7 @@ "/roboto-fontface/fonts/roboto/Roboto-Regular.woff", "/roboto-fontface/fonts/roboto/Roboto-Bold.woff", "/inter-ui/Inter (web)/Inter-Regular.woff", - "/inter-ui/Inter (web)/Inter-Bold.woff" + "/inter-ui/Inter (web)/Inter-Bold.woff", ); say("β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β’€β£ β£΄β£Άβ£Ώβ Ώβ Ÿβ ›β “β ’β €"); @@ -83,9 +87,15 @@ say( "Working Directory: " . getcwd ); say(""); +# Provide cpanm with the correct module installation dir when using Homebrew +my $cpanopt = ""; +if ( $ENV{HOMEBREW_FORMULA_PREFIX} ) { + $cpanopt = " -l " . $ENV{HOMEBREW_FORMULA_PREFIX} . "/libexec"; +} + #Load IPC::Cmd -install_package( "IPC::Cmd", "" ); -install_package( "Config::AutoConf", "" ); +install_package( "IPC::Cmd", $cpanopt ); +install_package( "Config::AutoConf", $cpanopt ); IPC::Cmd->import('can_run'); require Config::AutoConf; @@ -132,12 +142,6 @@ if ( $back || $full ) { say("\r\nInstalling Perl modules... This might take a while.\r\n"); - # Provide cpanm with the correct module installation dir when using Homebrew - my $cpanopt = ""; - if ( $ENV{HOMEBREW_FORMULA_PREFIX} ) { - $cpanopt = " -l " . $ENV{HOMEBREW_FORMULA_PREFIX} . "/libexec"; - } - if ( $Config{"osname"} ne "darwin" ) { say("Installing Linux::Inotify2 (2.2) for non-macOS systems..."); @@ -200,11 +204,15 @@ sub cp_node_module { my $nodename = getcwd . "/node_modules" . $item; $item =~ /([^\/]+$)/; - my $newname = getcwd . $newpath . $&; + my $newname = getcwd . $newpath . $&; + my $nodemapname = $nodename . ".map"; + my $newmapname = $newname . ".map"; - say("Copying $nodename \r\n to $newname \r\n"); + say("\r\nCopying $nodename \r\n to $newname"); copy( $nodename, $newname ) or die "The copy operation failed: $!"; + my $mapresult = copy( $nodemapname, $newmapname ) and say("Copied sourcemap file.\r\n"); + } sub install_package {