diff --git a/API/API.csproj b/API/API.csproj index 4b10eba10..c1d79a8c8 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -7,6 +7,7 @@ Linux true true + ../favicon.ico @@ -20,7 +21,7 @@ - bin\$(Configuration)\$(AssemblyName).xml + bin\$(Configuration)\$(AssemblyName).xml 1701;1702;1591 @@ -116,6 +117,7 @@ + @@ -125,6 +127,7 @@ + @@ -139,6 +142,7 @@ + @@ -164,178 +168,12 @@ - - <_ContentIncludedByDefault Remove="logs\kavita.json" /> - <_ContentIncludedByDefault Remove="wwwroot\3rdpartylicenses.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js" /> - <_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js" /> - <_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js" /> - <_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-Italic-VariableFont_wght.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-VariableFont_wght.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Black.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BlackItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLight.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-LightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Medium.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-MediumItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Thin.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ThinItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Black.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BlackItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-LightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Thin.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-ThinItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Black.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BlackItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-LightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-ExtraBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\Oswald-VariableFont_wght.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\README.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-ExtraLight.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Medium.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-SemiBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\RocknRollOne-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\preset-light.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\themes\dark.scss" /> - <_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js" /> - <_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\EBGaramond-VariableFont_wght.2a1da2dbe7a28d63f8cb.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.0fea24969112a781acd2.eot" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.c967a94cfbe2b06627ff.woff2" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.dc2cbadd690e1d4b2c9c.woff" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.e33e2cf6e02cac2ccb77.svg" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.ec82f282c7f54b637098.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.06b9d19ced8d17f3d5cb.svg" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.08f9891a6f44d9546678.eot" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1008b5226941c24f4468.woff2" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1069ea55beaa01060302.woff" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1495f578452eb676f730.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.10ecefc282f2761808bf.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.371dbce0dd46bd4d2033.svg" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3a24a60e7f9c6574864a.eot" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3ceb50e7bcafb577367c.woff2" /> - <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.46fdbd2d897f8824e63c.woff" /> - <_ContentIncludedByDefault Remove="wwwroot\favicon.ico" /> - <_ContentIncludedByDefault Remove="wwwroot\FiraSans-Regular.1c0bf0728b51cb9f2ddc.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\index.html" /> - <_ContentIncludedByDefault Remove="wwwroot\Lato-Regular.9919edff6283018571ad.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\LibreBaskerville-Regular.a27f99ca45522bb3d56d.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js" /> - <_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\Merriweather-Regular.55c73e48e04ec926ebfe.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\NanumGothic-Regular.6c84540de7730f833d6c.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js" /> - <_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\RocknRollOne-Regular.c75da4712d1e65ed1f69.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js" /> - <_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css" /> - <_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css.map" /> - <_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js" /> - <_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\10.b727db78581442412e9a.js" /> - <_ContentIncludedByDefault Remove="wwwroot\10.b727db78581442412e9a.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\2.fcc031071e80d6837012.js" /> - <_ContentIncludedByDefault Remove="wwwroot\2.fcc031071e80d6837012.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\7.c30da7d2e809fa05d1e3.js" /> - <_ContentIncludedByDefault Remove="wwwroot\7.c30da7d2e809fa05d1e3.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\8.d4c77a90c95e9861656a.js" /> - <_ContentIncludedByDefault Remove="wwwroot\8.d4c77a90c95e9861656a.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\9.489b177dd1a6beeb35ad.js" /> - <_ContentIncludedByDefault Remove="wwwroot\9.489b177dd1a6beeb35ad.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Spartan\OFL.txt" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Spartan\Spartan-VariableFont_wght.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\android-chrome-192x192.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\android-chrome-256x256.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\apple-touch-icon.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\browserconfig.xml" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon-16x16.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon-32x32.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\favicon.ico" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\icons\mstile-150x150.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-reset-cover-min.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-reset-cover.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\kavita-book-cropped.png" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\login-bg.jpg" /> - <_ContentIncludedByDefault Remove="wwwroot\assets\images\logo.png" /> - <_ContentIncludedByDefault Remove="wwwroot\common.fbf71de364f5a1f37413.js" /> - <_ContentIncludedByDefault Remove="wwwroot\common.fbf71de364f5a1f37413.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\login-bg.8860e6ff9d2a3598539c.jpg" /> - <_ContentIncludedByDefault Remove="wwwroot\main.a3a1e647a39145accff3.js" /> - <_ContentIncludedByDefault Remove="wwwroot\main.a3a1e647a39145accff3.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\polyfills.3dda3bf3d087e5d131ba.js" /> - <_ContentIncludedByDefault Remove="wwwroot\polyfills.3dda3bf3d087e5d131ba.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\runtime.b9818dfc90f418b3f0a7.js" /> - <_ContentIncludedByDefault Remove="wwwroot\runtime.b9818dfc90f418b3f0a7.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\scripts.7d1c78b2763c483bb699.js" /> - <_ContentIncludedByDefault Remove="wwwroot\scripts.7d1c78b2763c483bb699.js.map" /> - <_ContentIncludedByDefault Remove="wwwroot\site.webmanifest" /> - <_ContentIncludedByDefault Remove="wwwroot\Spartan-VariableFont_wght.0427aac0d980a12ae8ba.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\styles.85a58cb3e4a4b1add864.css" /> - <_ContentIncludedByDefault Remove="wwwroot\styles.85a58cb3e4a4b1add864.css.map" /> - <_ContentIncludedByDefault Remove="wwwroot\vendor.54bf44a9aa720ff8881d.js" /> - <_ContentIncludedByDefault Remove="wwwroot\vendor.54bf44a9aa720ff8881d.js.map" /> - - - - - - + + <_DeploymentManifestIconFile Remove="favicon.ico" /> + + diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs index 1bdca14ea..eaec15b0f 100644 --- a/API/Controllers/CollectionController.cs +++ b/API/Controllers/CollectionController.cs @@ -54,7 +54,7 @@ public class CollectionController : BaseApiController [HttpGet("search")] public async Task> SearchTags(string queryString) { - queryString ??= ""; + queryString ??= string.Empty; queryString = queryString.Replace(@"%", string.Empty); if (queryString.Length == 0) return await GetAllTags(); diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index cdd13882a..a9a2efa97 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -37,7 +37,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } @@ -53,7 +53,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } @@ -69,7 +69,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } @@ -85,7 +85,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); Response.AddCacheHeader(path); @@ -103,7 +103,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } @@ -119,7 +119,7 @@ public class ImageController : BaseApiController { var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } @@ -143,7 +143,7 @@ public class ImageController : BaseApiController var bookmarkDirectory = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value; var file = new FileInfo(Path.Join(bookmarkDirectory, bookmark.FileName)); - var format = Path.GetExtension(file.FullName).Replace(".", ""); + var format = Path.GetExtension(file.FullName).Replace(".", string.Empty); return PhysicalFile(file.FullName, "image/" + format, Path.GetFileName(file.FullName)); } @@ -162,7 +162,7 @@ public class ImageController : BaseApiController var path = Path.Join(_directoryService.TempDirectory, filename); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist"); - var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", ""); + var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty); return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path)); } diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 9159e763c..e39825893 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -429,7 +429,7 @@ public class OpdsController : BaseApiController { return BadRequest("You must pass a query parameter"); } - query = query.Replace(@"%", ""); + query = query.Replace(@"%", string.Empty); // Get libraries user has access to var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).ToList(); @@ -828,7 +828,7 @@ public class OpdsController : BaseApiController if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}"); var content = await _directoryService.ReadFileAsync(path); - var format = Path.GetExtension(path).Replace(".", ""); + var format = Path.GetExtension(path).Replace(".", string.Empty); // Calculates SHA1 Hash for byte[] Response.AddCacheHeader(content); @@ -860,7 +860,7 @@ public class OpdsController : BaseApiController if (files.Length == 0) return BadRequest("Cannot find icon"); var path = files[0]; var content = await _directoryService.ReadFileAsync(path); - var format = Path.GetExtension(path).Replace(".", ""); + var format = Path.GetExtension(path).Replace(".", string.Empty); return File(content, "image/" + format); } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index 52948c87e..0d75ceb01 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -93,7 +93,7 @@ public class CacheService : ICacheService } catch (Exception ex) { - _logger.LogError("There was an error calculating image dimensions for {ChapterId}", chapterId); + _logger.LogError(ex, "There was an error calculating image dimensions for {ChapterId}", chapterId); } finally { @@ -164,7 +164,7 @@ public class CacheService : ICacheService { var removeNonImages = true; var fileCount = files.Count; - var extraPath = ""; + var extraPath = string.Empty; var extractDi = _directoryService.FileSystem.DirectoryInfo.FromDirectoryName(extractPath); if (files.Count > 0 && files[0].Format == MangaFormat.Image) diff --git a/API/Startup.cs b/API/Startup.cs index 14c361f0c..6989b13c9 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -273,7 +273,7 @@ public class Startup .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials() // For SignalR token query param - .WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200") + .WithOrigins("http://localhost:4200", $"http://{GetLocalIpAddress()}:4200", $"http://{GetLocalIpAddress()}:5000") .WithExposedHeaders("Content-Disposition", "Pagination")); } diff --git a/README.md b/README.md index b92725d04..acaaf9bcc 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The easiest way to get started is to visit our Wiki which has up-to-date informa install methods and platforms. [https://wiki.kavitareader.com/en/install](https://wiki.kavitareader.com/en/install) -**Note: Kavita is under heavy development and is being updated all the time, so the tag for current builds is `:nightly`. The `:latest` tag will be the latest stable release.** +**Note: Kavita is under heavy development and is being updated all the time, so the tag for bleeding edge builds is `:nightly`. The `:latest` tag will be the latest stable release.** ## Feature Requests Got a great idea? Throw it up on our [Feature Request site](https://feats.kavitareader.com/) or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features before you submit an idea. @@ -100,5 +100,5 @@ being paid to help secure Kavita, please give them a try. ### License * [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) -* Copyright 2020-2022 +* Copyright 2020-2023 diff --git a/UI/Web/README.md b/UI/Web/README.md index 3e2904700..f088c87cf 100644 --- a/UI/Web/README.md +++ b/UI/Web/README.md @@ -5,6 +5,7 @@ This project was generated with [Angular CLI](https://github.com/angular/angular ## Development server Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. +Your backend must be served on port 5000. ## Code scaffolding @@ -24,6 +25,7 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github. Run `npx playwright test --reporter=line` or `npx playwright test` to run e2e tests. -## Further help +## Connecting to your dev server via your phone -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +ng serve --host 0.0.0.0 +and update environment.ts to your local ip. diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index 11883b72b..d385a2cb1 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -6735,6 +6735,24 @@ "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, + "ag-swipe-core": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ag-swipe-core/-/ag-swipe-core-1.0.2.tgz", + "integrity": "sha512-NNONbrEbsmu6wsl7E07eGYVZw8Wx7hOok2TlhQLU/50EUhmI3Vpg8EDz0rWhV/HrfUAoEd4LxBvLAeT9DswQDw==", + "requires": { + "rxjs": "^7.5.5" + }, + "dependencies": { + "rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "requires": { + "tslib": "^2.1.0" + } + } + } + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -12788,6 +12806,15 @@ "tslib": "^2.0.0" } }, + "ng-swipe": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ng-swipe/-/ng-swipe-2.0.1.tgz", + "integrity": "sha512-y4w2d719VK1u6KUlNqhHVevzT+yR30bnTTLkFNEsVG3Gp5+oZhUnflVNWfzIw+O8GCjZqVLelwla/jOkqUclmQ==", + "requires": { + "ag-swipe-core": "^1.0.0", + "tslib": "^2.3.0" + } + }, "ngx-color-picker": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-12.0.0.tgz", diff --git a/UI/Web/package.json b/UI/Web/package.json index 7014d733e..9ad7ba78a 100644 --- a/UI/Web/package.json +++ b/UI/Web/package.json @@ -39,6 +39,7 @@ "file-saver": "^2.0.5", "lazysizes": "^5.3.2", "ng-circle-progress": "^1.6.0", + "ng-swipe": "^2.0.1", "ngx-color-picker": "^12.0.0", "ngx-extended-pdf-viewer": "^15.0.0", "ngx-file-drop": "^14.0.1", diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts index 876a4847a..37dca4c42 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts @@ -993,7 +993,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { getPageWidth() { if (this.readingSectionElemRef == null) return 0; - const margin = (this.readingSectionElemRef.nativeElement.clientWidth*(parseInt(this.pageStyles['margin-left'], 10) / 100))*2; + const margin = (this.readingSectionElemRef.nativeElement.clientWidth * (parseInt(this.pageStyles['margin-left'], 10) / 100)) * 2; const columnGap = 20; return this.readingSectionElemRef.nativeElement.clientWidth - margin + columnGap; } diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html index f0fc351eb..6a0f703f6 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html @@ -47,7 +47,7 @@ - +
diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html index d3a45b4bf..9ed9c7137 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html @@ -28,7 +28,7 @@
@@ -43,14 +43,14 @@
-
-
diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 52c1b438a..edc3a1ad4 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -1,7 +1,7 @@ -import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; -import { BehaviorSubject, combineLatest, debounceTime, distinctUntilChanged, filter, forkJoin, fromEvent, map, merge, Observable, of, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs'; +import { BehaviorSubject, debounceTime, distinctUntilChanged, forkJoin, fromEvent, map, merge, Observable, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs'; import { LabelType, ChangeContext, Options } from '@angular-slider/ngx-slider'; import { trigger, state, style, transition, animate } from '@angular/animations'; import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; @@ -31,6 +31,7 @@ import { DoubleRendererComponent } from '../double-renderer/double-renderer.comp import { DoubleReverseRendererComponent } from '../double-reverse-renderer/double-reverse-renderer.component'; import { SingleRendererComponent } from '../single-renderer/single-renderer.component'; import { ChapterInfo } from '../../_models/chapter-info'; +import { SwipeEvent } from 'ng-swipe'; const PREFETCH_PAGES = 10; @@ -298,6 +299,43 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { */ rightPaginationOffset = 0; + /** + * Previous amount of scroll left. Used for swipe to paginate functionaliy. + */ + prevScrollLeft = 0; + /** + * Previous amount of scroll top. Used for swipe to paginate functionaliy. + */ + prevScrollTop = 0; + + prevIsHorizontalScrollLeft = true; + prevIsVerticalScrollLeft = true; + + /** + * Has the user scrolled to the far right side. This is used for swipe to next page and must ensure user is at end of scroll then on next swipe, will move pages. + */ + hasHitRightScroll = false; + /** + * Has the user scrolled once for the current page + */ + hasScrolledX: boolean = false; + /** + * Has the user scrolled once in the Y axis for the current page + */ + hasScrolledY: boolean = false; + /** + * Has the user scrolled to far left size. This doesn't include starting from no scroll + */ + hasHitZeroScroll: boolean = false; + /** + * Has the user scrolled to the far top of the screen + */ + hasHitZeroTopScroll: boolean = false; + /** + * Has the user scrolled to the far bottom of the screen + */ + hasHitBottomTopScroll: boolean = false; + // Renderer interaction readerSettings$!: Observable; private currentImage: Subject = new ReplaySubject(1); @@ -352,6 +390,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } + get KeyDirection() { return KeyDirection; } get ReaderMode() { return ReaderMode; } get LayoutMode() { return LayoutMode; } get ReadingDirection() { return ReadingDirection; } @@ -359,6 +398,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { get Breakpoint() { return Breakpoint; } get FITTING_OPTION() { return FITTING_OPTION; } get FittingOption() { return this.generalSettingsForm.get('fittingOption')?.value || FITTING_OPTION.HEIGHT; } + get ReadingAreaWidth() { + return this.readingArea?.nativeElement.scrollWidth - this.readingArea?.nativeElement.clientWidth; + } + + get ReadingAreaHeight() { + return this.readingArea?.nativeElement.scrollHeight - this.readingArea?.nativeElement.clientHeight; + } constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, public readerService: ReaderService, private formBuilder: FormBuilder, private navService: NavService, @@ -515,10 +561,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.cdRef.markForCheck(); }); - fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200)).subscribe((event: MouseEvent | any) => { + fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event: MouseEvent | any) => { if (event.detail > 1) return; this.toggleMenu(); }); + + fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event: MouseEvent | any) => { + this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0; + this.prevScrollTop = this.readingArea?.nativeElement?.scrollTop || 0; + this.hasScrolledX = true; + this.hasScrolledY = true; + }); } ngOnDestroy() { @@ -531,6 +584,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { if (this.goToPageEvent !== undefined) this.goToPageEvent.complete(); } + @HostListener('window:resize', ['$event']) @HostListener('window:orientationchange', ['$event']) onResize() { @@ -647,9 +701,28 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { return img; } - // if there is scroll room and on original, then don't paginate + + + isHorizontalScrollLeft() { + const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0; + // if scrollLeft is 0 and this.ReadingAreaWidth is 0, then there is no scroll needed + // if they equal each other, it means we are at the end of the scroll area + if (scrollLeft === 0 && this.ReadingAreaWidth === 0) return false; + if (scrollLeft === this.ReadingAreaWidth) return false; + return scrollLeft < this.ReadingAreaWidth; + } + + isVerticalScrollLeft() { + const scrollTop = this.readingArea?.nativeElement?.scrollTop || 0; + return scrollTop < this.ReadingAreaHeight; + } + + /** + * Is there any room to scroll in the direction we are giving? If so, return false. Otherwise return true. + * @param direction + * @returns + */ checkIfPaginationAllowed(direction: KeyDirection) { - // This is not used atm due to the complexity it adds with keyboard. if (this.readingArea === undefined || this.readingArea.nativeElement === undefined) return true; const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0; @@ -657,22 +730,30 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { switch (direction) { case KeyDirection.Right: - if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft < this.readingArea?.nativeElement.scrollWidth - this.readingArea?.nativeElement.clientWidth) { + if (this.prevIsHorizontalScrollLeft && !this.isHorizontalScrollLeft()) { return true; } + this.prevIsHorizontalScrollLeft = this.isHorizontalScrollLeft(); + + if (this.isHorizontalScrollLeft()) { return false; } break; case KeyDirection.Left: - if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft > 0) { + this.prevIsHorizontalScrollLeft = this.isHorizontalScrollLeft(); + if (scrollLeft > 0 || this.prevScrollLeft > 0) { return false; } break; case KeyDirection.Up: - if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop > 0) { + this.prevIsVerticalScrollLeft = this.isVerticalScrollLeft(); + if (scrollTop > 0|| this.prevScrollTop > 0) { return false; } break; case KeyDirection.Down: - if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop < this.readingArea?.nativeElement.scrollHeight - this.readingArea?.nativeElement.clientHeight) { + if (this.prevIsVerticalScrollLeft && !this.isVerticalScrollLeft()) { return true; } + this.prevIsVerticalScrollLeft = this.isVerticalScrollLeft(); + + if (this.isVerticalScrollLeft()) { return false; } break; @@ -681,17 +762,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { return true; } - // This is menu code - clickOverlayClass(side: 'right' | 'left') { - if (!this.showClickOverlay) { - return ''; - } - - if (this.readingDirection === ReadingDirection.LeftToRight) { - return side === 'right' ? 'highlight' : 'highlight-2'; - } - return side === 'right' ? 'highlight-2' : 'highlight'; - } + init() { this.nextChapterId = CHAPTER_ID_NOT_FETCHED; @@ -878,21 +949,169 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - onSwipeEvent(event: any) { + resetSwipeModifiers() { + this.prevScrollLeft = 0; + this.prevScrollTop = 0; + this.hasScrolledX = false; + this.hasScrolledY = false; + this.hasHitRightScroll = false; + this.hasHitZeroScroll = false; + this.hasHitBottomTopScroll = false; + this.hasHitZeroTopScroll = false; + } + + /** + * This executes BEFORE fromEvent('scroll') + * @param event + * @returns + */ + onSwipeMove(_: SwipeEvent) { + this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0; + this.prevScrollTop = this.readingArea?.nativeElement?.scrollTop || 0 } - handlePageChange(event: any, direction: string) { + triggerSwipePagination(direction: KeyDirection) { + + if (this.readingDirection === ReadingDirection.LeftToRight) { + if (direction === KeyDirection.Right) + this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage(); + } + switch(direction) { + case KeyDirection.Down: + this.nextPage(); + break; + case KeyDirection.Right: + this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage(); + break; + case KeyDirection.Up: + this.prevPage(); + break; + case KeyDirection.Left: + this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage() : this.nextPage(); + break; + } + + } + + onSwipeEnd(event: SwipeEvent) { + const threshold = .12; + + // Positive number means swiping right/down, negative means left + switch (this.readerMode) { + case ReaderMode.Webtoon: break; + case ReaderMode.LeftRight: + { + if (event.direction !== 'x') return; + const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0; + const direction = event.distance < 0 ? KeyDirection.Right : KeyDirection.Left; + if (!this.checkIfPaginationAllowed(direction)) { + return; + } + + + // We just came from a swipe where pagination was required and we are now at the end of the swipe, so make the user do it once more + if (direction === KeyDirection.Right) { + this.hasHitZeroScroll = false; + if (!this.hasHitRightScroll && this.checkIfPaginationAllowed(direction)) { + this.hasHitRightScroll = true; + return; + } + } else if (direction === KeyDirection.Left) { + this.hasHitRightScroll = false; + + // If we have not scrolled then let the user page back + if (scrollLeft === 0 && this.prevScrollLeft === 0) { + if (!this.hasScrolledX || this.hasHitZeroScroll) { + this.triggerSwipePagination(direction); + return; + } + this.hasHitZeroScroll = true; + return; + } + } + + if (!this.hasHitRightScroll) { + return; + } + + console.log('Next page triggered'); + this.triggerSwipePagination(direction); + break; + } + case ReaderMode.UpDown: + { + if (event.direction !== 'y') return; + const direction = event.distance < 0 ? KeyDirection.Down : KeyDirection.Up; + const scrollTop = this.readingArea?.nativeElement?.scrollTop || 0; + if (!this.checkIfPaginationAllowed(direction)) return; + + + if (direction === KeyDirection.Down) { + this.hasHitZeroTopScroll = false; + if (!this.hasHitBottomTopScroll && this.checkIfPaginationAllowed(direction)) { + this.hasHitBottomTopScroll = true; + return; + } + } else if (direction === KeyDirection.Up) { + this.hasHitBottomTopScroll = false; + + // If we have not scrolled then let the user page back + if (scrollTop === 0 && this.prevScrollTop === 0) { + if (!this.hasScrolledY || this.hasHitZeroTopScroll) { + this.triggerSwipePagination(direction); + return; + } + this.hasHitZeroTopScroll = true; + return; + } + } + + if (!this.hasHitBottomTopScroll) { + return; + } + + console.log('Next page triggered'); + this.triggerSwipePagination(direction); + break; + + + + + + + const height = (this.readingArea?.nativeElement.scrollHeight === this.readingArea?.nativeElement.clientHeight) + ? this.readingArea?.nativeElement.clientHeight : this.ReadingAreaHeight; + + if (direction === KeyDirection.Down && this.readingArea?.nativeElement?.scrollTop === height && this.prevScrollTop != 0) { + this.prevScrollTop = 0; + return; + } + + if (direction === KeyDirection.Up && this.readingArea?.nativeElement?.scrollTop === 0 && this.prevScrollTop != 0) { + this.prevScrollTop = 0; + return; + } + + const thresholdMet = Math.abs(event.distance) >= height * threshold; + if (!thresholdMet) return; + + this.triggerSwipePagination(direction); + } + } + } + + handlePageChange(event: any, direction: KeyDirection) { if (this.readerMode === ReaderMode.Webtoon) { - if (direction === 'right') { + if (direction === KeyDirection.Right) { this.nextPage(event); } else { this.prevPage(event); } return; } - if (direction === 'right') { + if (direction === KeyDirection.Right) { this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage(event) : this.prevPage(event); - } else if (direction === 'left') { + } else if (direction === KeyDirection.Left) { this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage(event) : this.nextPage(event); } } @@ -903,6 +1122,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { event.preventDefault(); } + this.resetSwipeModifiers(); + this.pagingDirectionSubject.next(PAGING_DIRECTION.FORWARD); const pageAmount = Math.max(this.canvasRenderer.getPageAmount(PAGING_DIRECTION.FORWARD), this.singleRenderer.getPageAmount(PAGING_DIRECTION.FORWARD), @@ -925,6 +1146,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { event.stopPropagation(); event.preventDefault(); } + + this.resetSwipeModifiers(); + this.pagingDirectionSubject.next(PAGING_DIRECTION.BACKWARDS); @@ -1030,16 +1254,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { renderPage() { const page = [this.canvasImage]; - // After switching from webtoon mode, these are all undefined - this.canvasRenderer?.renderPage(page); this.singleRenderer?.renderPage(page); this.doubleRenderer?.renderPage(page); this.doubleReverseRenderer?.renderPage(page); - if (this.FittingOption !== FITTING_OPTION.HEIGHT) { - this.readingArea.nativeElement.scroll(0,0); - } + // Originally this was only for fit to height, but when swiping was introduced, it made more sense to do it always to reset to the same view + this.readingArea.nativeElement.scroll(0,0); this.cdRef.markForCheck(); } @@ -1244,6 +1465,18 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.render(); } + // This is menu code + clickOverlayClass(side: 'right' | 'left') { + if (!this.showClickOverlay) { + return ''; + } + + if (this.readingDirection === ReadingDirection.LeftToRight) { + return side === 'right' ? 'highlight' : 'highlight-2'; + } + return side === 'right' ? 'highlight-2' : 'highlight'; + } + // This is menu only code promptForPage() { const goToPageNum = window.prompt('There are ' + this.maxPages + ' pages. What page would you like to go to?', ''); diff --git a/UI/Web/src/app/manga-reader/manga-reader.module.ts b/UI/Web/src/app/manga-reader/manga-reader.module.ts index 3e6f25eea..e2934eac9 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.module.ts +++ b/UI/Web/src/app/manga-reader/manga-reader.module.ts @@ -11,13 +11,13 @@ import { PipeModule } from '../pipe/pipe.module'; import { FullscreenIconPipe } from './_pipes/fullscreen-icon.pipe'; import { LayoutModeIconPipe } from './_pipes/layout-mode-icon.pipe'; import { ReaderModeIconPipe } from './_pipes/reader-mode-icon.pipe'; -import { SwipeDirective } from './swipe.directive'; import { CanvasRendererComponent } from './_components/canvas-renderer/canvas-renderer.component'; import { SingleRendererComponent } from './_components/single-renderer/single-renderer.component'; import { DoubleRendererComponent } from './_components/double-renderer/double-renderer.component'; import { DoubleReverseRendererComponent } from './_components/double-reverse-renderer/double-reverse-renderer.component'; import { MangaReaderComponent } from './_components/manga-reader/manga-reader.component'; import { FittingIconPipe } from './_pipes/fitting-icon.pipe'; +import { SwipeModule } from 'ng-swipe'; @NgModule({ declarations: [ @@ -26,7 +26,6 @@ import { FittingIconPipe } from './_pipes/fitting-icon.pipe'; FullscreenIconPipe, ReaderModeIconPipe, LayoutModeIconPipe, - SwipeDirective, CanvasRendererComponent, SingleRendererComponent, DoubleRendererComponent, @@ -43,6 +42,8 @@ import { FittingIconPipe } from './_pipes/fitting-icon.pipe'; NgxSliderModule, SharedModule, ReaderSharedModule, + + SwipeModule ], exports: [ MangaReaderComponent diff --git a/UI/Web/src/app/manga-reader/swipe.directive.ts b/UI/Web/src/app/manga-reader/swipe.directive.ts deleted file mode 100644 index 379077486..000000000 --- a/UI/Web/src/app/manga-reader/swipe.directive.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core'; -import { fromEvent, map, Observable } from 'rxjs'; - -/** - * Repsonsible for triggering a swipe event - */ -@Directive({ - selector: '[appSwipe]' -}) -export class SwipeDirective { - - @Input() threshold: number = 10; - @Output() swipeEvent: EventEmitter = new EventEmitter(); - - touchStarts$!: Observable; - touchMoves$!: Observable; - touchEnds$!: Observable; - touchCancels$!: Observable; - - @HostListener('touchstart') onTouchStart(event: TouchEvent) { - //console.log('Touch Start: ', event); - } - - @HostListener('touchend') onTouchEnd(event: TouchEvent) { - //console.log('Touch End: ', event); - } - - constructor(private el: ElementRef) { - this.touchStarts$ = fromEvent(el.nativeElement, 'touchstart').pipe(map(this.getTouchCoordinates)); - this.touchMoves$ = fromEvent(el.nativeElement, 'touchmove').pipe(map(this.getTouchCoordinates)); - this.touchEnds$ = fromEvent(el.nativeElement, 'touchend').pipe(map(this.getTouchCoordinates)); - this.touchCancels$ = fromEvent(el.nativeElement, 'touchcancel'); - } - - getTouchCoordinates(event: TouchEvent) { - - } - -} diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts index 1d6041a59..bd6255945 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts @@ -37,11 +37,10 @@ export class ReadingListDetailComponent implements OnInit { downloadInProgress: boolean = false; readingListSummary: string = ''; + readingListImage: string = ''; libraryTypes: {[key: number]: LibraryType} = {}; - readingListImage: string = ''; - get MangaFormat(): typeof MangaFormat { return MangaFormat; } @@ -60,6 +59,7 @@ export class ReadingListDetailComponent implements OnInit { return; } this.listId = parseInt(listId, 10); + this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId)); forkJoin([ this.libraryService.getLibraries(), @@ -134,6 +134,7 @@ export class ReadingListDetailComponent implements OnInit { // Reload information around list this.readingList = readingList; this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '
'); + this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId)); this.cdRef.markForCheck(); }); break; diff --git a/openapi.json b/openapi.json index 9e1c431ec..e08a99e25 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.6.1.22" + "version": "0.6.1.24" }, "servers": [ {