diff --git a/API.Tests/Comparers/NaturalSortComparerTest.cs b/API.Tests/Comparers/NaturalSortComparerTest.cs
index 39bad2003..d7c58d45a 100644
--- a/API.Tests/Comparers/NaturalSortComparerTest.cs
+++ b/API.Tests/Comparers/NaturalSortComparerTest.cs
@@ -42,6 +42,10 @@ namespace API.Tests.Comparers
new[] {"3and4.cbz", "The World God Only Knows - Oneshot.cbz", "5.cbz", "1and2.cbz"},
new[] {"1and2.cbz", "3and4.cbz", "5.cbz", "The World God Only Knows - Oneshot.cbz"}
)]
+ [InlineData(
+ new[] {"Solo Leveling - c000 (v01) - p000 [Cover] [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p001 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p002 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p003 [dig] [Yen Press] [LuCaZ].jpg"},
+ new[] {"Solo Leveling - c000 (v01) - p000 [Cover] [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p001 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p002 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p003 [dig] [Yen Press] [LuCaZ].jpg"}
+ )]
public void TestNaturalSortComparer(string[] input, string[] expected)
{
Array.Sort(input, _nc);
diff --git a/API/API.csproj b/API/API.csproj
index 458830ca1..50a464d0b 100644
--- a/API/API.csproj
+++ b/API/API.csproj
@@ -64,23 +64,147 @@
+
+
+
+
<_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" />
diff --git a/API/Comparators/StringLogicalComparer.cs b/API/Comparators/StringLogicalComparer.cs
index fe930c45c..67aa72225 100644
--- a/API/Comparators/StringLogicalComparer.cs
+++ b/API/Comparators/StringLogicalComparer.cs
@@ -2,7 +2,7 @@
// Version 2
// Taken from: https://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C
-using System;
+using static System.Char;
namespace API.Comparators
{
@@ -20,26 +20,26 @@ namespace API.Comparators
if (string.IsNullOrEmpty(s2)) return -1;
//WE style, special case
- var sp1 = Char.IsLetterOrDigit(s1, 0);
- var sp2 = Char.IsLetterOrDigit(s2, 0);
+ var sp1 = IsLetterOrDigit(s1, 0);
+ var sp2 = IsLetterOrDigit(s2, 0);
if(sp1 && !sp2) return 1;
if(!sp1 && sp2) return -1;
int i1 = 0, i2 = 0; //current index
while(true)
{
- var c1 = Char.IsDigit(s1, i1);
- var c2 = Char.IsDigit(s2, i2);
+ var c1 = IsDigit(s1, i1);
+ var c2 = IsDigit(s2, i2);
int r; // temp result
if(!c1 && !c2)
{
- bool letter1 = Char.IsLetter(s1, i1);
- bool letter2 = Char.IsLetter(s2, i2);
+ bool letter1 = IsLetter(s1, i1);
+ bool letter2 = IsLetter(s2, i2);
if((letter1 && letter2) || (!letter1 && !letter2))
{
if(letter1 && letter2)
{
- r = Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2]));
+ r = ToLower(s1[i1]).CompareTo(ToLower(s2[i2]));
}
else
{
@@ -114,8 +114,8 @@ namespace API.Comparators
{
nzStart = start;
end = start;
- bool countZeros = true;
- while(Char.IsDigit(s, end))
+ var countZeros = true;
+ while(IsDigit(s, end))
{
if(countZeros && s[end].Equals('0'))
{
diff --git a/API/Configurations/CustomOptions/StatsOptions.cs b/API/Configurations/CustomOptions/StatsOptions.cs
new file mode 100644
index 000000000..ac0cd0ac5
--- /dev/null
+++ b/API/Configurations/CustomOptions/StatsOptions.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace API.Configurations.CustomOptions
+{
+ public class StatsOptions
+ {
+ public string ServerUrl { get; set; }
+ public string ServerSecret { get; set; }
+ public string SendDataAt { get; set; }
+
+ private const char Separator = ':';
+
+ public short SendDataHour => GetValueFromSendAt(0);
+ public short SendDataMinute => GetValueFromSendAt(1);
+
+ // The expected SendDataAt format is: Hour:Minute. Ex: 19:45
+ private short GetValueFromSendAt(int index)
+ {
+ var key = $"{nameof(StatsOptions)}:{nameof(SendDataAt)}";
+
+ if (string.IsNullOrEmpty(SendDataAt))
+ throw new InvalidOperationException($"{key} is invalid. Check the app settings file");
+
+ if (short.TryParse(SendDataAt.Split(Separator)[index], out var parsedValue))
+ return parsedValue;
+
+ throw new InvalidOperationException($"Could not parse {key}. Check the app settings file");
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs
index 6ad5fdbaf..e09f8592a 100644
--- a/API/Controllers/CollectionController.cs
+++ b/API/Controllers/CollectionController.cs
@@ -10,7 +10,6 @@ using API.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
namespace API.Controllers
{
diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs
index 2ac3d51fe..b9bc15fb7 100644
--- a/API/Controllers/ReaderController.cs
+++ b/API/Controllers/ReaderController.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.DTOs;
+using API.DTOs.Reader;
using API.Entities;
using API.Extensions;
using API.Interfaces;
@@ -49,15 +50,27 @@ namespace API.Controllers
return File(content, "image/" + format);
}
-
- [HttpGet("chapter-path")]
- public async Task> GetImagePath(int chapterId)
+
+ [HttpGet("chapter-info")]
+ public async Task> GetChapterInfo(int chapterId)
{
var chapter = await _cacheService.Ensure(chapterId);
- if (chapter == null) return BadRequest("There was an issue finding image file for reading");
-
+ if (chapter == null) return BadRequest("Could not find Chapter");
+ var volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(chapter.VolumeId);
+ if (volume == null) return BadRequest("Could not find Volume");
var (_, mangaFile) = await _cacheService.GetCachedPagePath(chapter, 0);
- return Ok(mangaFile.FilePath);
+ var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId);
+
+ return Ok(new ChapterInfoDto()
+ {
+ ChapterNumber = chapter.Range,
+ VolumeNumber = volume.Number + string.Empty,
+ VolumeId = volume.Id,
+ FileName = Path.GetFileName(mangaFile.FilePath),
+ SeriesName = series?.Name,
+ IsSpecial = chapter.IsSpecial,
+ Pages = chapter.Pages,
+ });
}
[HttpGet("get-bookmark")]
diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs
index 7bedceb3f..398de3efc 100644
--- a/API/Controllers/ServerController.cs
+++ b/API/Controllers/ServerController.cs
@@ -1,10 +1,8 @@
using System;
using System.IO;
-using System.IO.Compression;
using System.Threading.Tasks;
using API.Extensions;
using API.Interfaces.Services;
-using API.Services;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs
index 3a9a44d6c..ee4c9ac66 100644
--- a/API/Controllers/UsersController.cs
+++ b/API/Controllers/UsersController.cs
@@ -61,6 +61,8 @@ namespace API.Controllers
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
existingPreferences.PageSplitOption = preferencesDto.PageSplitOption;
+ existingPreferences.AutoCloseMenu = preferencesDto.AutoCloseMenu;
+ existingPreferences.ReaderMode = preferencesDto.ReaderMode;
existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin;
existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing;
existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily;
diff --git a/API/DTOs/CollectionTagDto.cs b/API/DTOs/CollectionTagDto.cs
index 72027e84a..26f256562 100644
--- a/API/DTOs/CollectionTagDto.cs
+++ b/API/DTOs/CollectionTagDto.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-
-namespace API.DTOs
+namespace API.DTOs
{
public class CollectionTagDto
{
diff --git a/API/DTOs/Reader/ChapterInfoDto.cs b/API/DTOs/Reader/ChapterInfoDto.cs
new file mode 100644
index 000000000..850149016
--- /dev/null
+++ b/API/DTOs/Reader/ChapterInfoDto.cs
@@ -0,0 +1,16 @@
+namespace API.DTOs.Reader
+{
+ public class ChapterInfoDto
+ {
+
+ public string ChapterNumber { get; set; }
+ public string VolumeNumber { get; set; }
+ public int VolumeId { get; set; }
+ public string SeriesName { get; set; }
+ public string ChapterTitle { get; set; } = "";
+ public int Pages { get; set; }
+ public string FileName { get; set; }
+ public bool IsSpecial { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/API/DTOs/UpdateSeriesMetadataDto.cs b/API/DTOs/UpdateSeriesMetadataDto.cs
index fd71526b7..a9c852632 100644
--- a/API/DTOs/UpdateSeriesMetadataDto.cs
+++ b/API/DTOs/UpdateSeriesMetadataDto.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using API.Entities;
namespace API.DTOs
{
diff --git a/API/DTOs/UserPreferencesDto.cs b/API/DTOs/UserPreferencesDto.cs
index 0d8f3ae68..03dbeaa5e 100644
--- a/API/DTOs/UserPreferencesDto.cs
+++ b/API/DTOs/UserPreferencesDto.cs
@@ -7,6 +7,8 @@ namespace API.DTOs
public ReadingDirection ReadingDirection { get; set; }
public ScalingOption ScalingOption { get; set; }
public PageSplitOption PageSplitOption { get; set; }
+ public ReaderMode ReaderMode { get; set; }
+ public bool AutoCloseMenu { get; set; }
public bool BookReaderDarkMode { get; set; } = false;
public int BookReaderMargin { get; set; }
public int BookReaderLineSpacing { get; set; }
diff --git a/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs b/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs
new file mode 100644
index 000000000..2797f05ab
--- /dev/null
+++ b/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs
@@ -0,0 +1,869 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20210622164318_NewUserPreferences")]
+ partial class NewUserPreferences
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.4");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderDarkMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("SiteDarkMode")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId")
+ .IsUnique();
+
+ b.ToTable("AppUserPreferences");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserProgress", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookScrollId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("PagesRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserProgresses");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRating", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("INTEGER");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("BLOB");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSpecial")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Number")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("Range")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("VolumeId");
+
+ b.ToTable("Chapter");
+ });
+
+ modelBuilder.Entity("API.Entities.CollectionTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("BLOB");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Id", "Promoted")
+ .IsUnique();
+
+ b.ToTable("CollectionTag");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LastScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.MangaFile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("FilePath")
+ .HasColumnType("TEXT");
+
+ b.Property("Format")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.ToTable("MangaFile");
+ });
+
+ modelBuilder.Entity("API.Entities.Series", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("BLOB");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LocalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("OriginalName")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortName")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
+ .IsUnique();
+
+ b.ToTable("Series");
+ });
+
+ modelBuilder.Entity("API.Entities.SeriesMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId")
+ .IsUnique();
+
+ b.HasIndex("Id", "SeriesId")
+ .IsUnique();
+
+ b.ToTable("SeriesMetadata");
+ });
+
+ modelBuilder.Entity("API.Entities.ServerSetting", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("ServerSetting");
+ });
+
+ modelBuilder.Entity("API.Entities.Volume", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("BLOB");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Number")
+ .HasColumnType("INTEGER");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("Volume");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.Property("AppUsersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibrariesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AppUsersId", "LibrariesId");
+
+ b.HasIndex("LibrariesId");
+
+ b.ToTable("AppUserLibrary");
+ });
+
+ modelBuilder.Entity("CollectionTagSeriesMetadata", b =>
+ {
+ b.Property("CollectionTagsId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesMetadatasId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("CollectionTagsId", "SeriesMetadatasId");
+
+ b.HasIndex("SeriesMetadatasId");
+
+ b.ToTable("CollectionTagSeriesMetadata");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
+ {
+ b.HasOne("API.Entities.AppUser", "AppUser")
+ .WithOne("UserPreferences")
+ .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AppUser");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserProgress", b =>
+ {
+ b.HasOne("API.Entities.AppUser", "AppUser")
+ .WithMany("Progresses")
+ .HasForeignKey("AppUserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AppUser");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRating", b =>
+ {
+ b.HasOne("API.Entities.AppUser", "AppUser")
+ .WithMany("Ratings")
+ .HasForeignKey("AppUserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AppUser");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.HasOne("API.Entities.AppRole", "Role")
+ .WithMany("UserRoles")
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.AppUser", "User")
+ .WithMany("UserRoles")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Role");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.HasOne("API.Entities.Volume", "Volume")
+ .WithMany("Chapters")
+ .HasForeignKey("VolumeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Volume");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.HasOne("API.Entities.Library", "Library")
+ .WithMany("Folders")
+ .HasForeignKey("LibraryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.MangaFile", b =>
+ {
+ b.HasOne("API.Entities.Chapter", "Chapter")
+ .WithMany("Files")
+ .HasForeignKey("ChapterId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Chapter");
+ });
+
+ modelBuilder.Entity("API.Entities.Series", b =>
+ {
+ b.HasOne("API.Entities.Library", "Library")
+ .WithMany("Series")
+ .HasForeignKey("LibraryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.SeriesMetadata", b =>
+ {
+ b.HasOne("API.Entities.Series", "Series")
+ .WithOne("Metadata")
+ .HasForeignKey("API.Entities.SeriesMetadata", "SeriesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Series");
+ });
+
+ modelBuilder.Entity("API.Entities.Volume", b =>
+ {
+ b.HasOne("API.Entities.Series", "Series")
+ .WithMany("Volumes")
+ .HasForeignKey("SeriesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Series");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("AppUsersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.Library", null)
+ .WithMany()
+ .HasForeignKey("LibrariesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("CollectionTagSeriesMetadata", b =>
+ {
+ b.HasOne("API.Entities.CollectionTag", null)
+ .WithMany()
+ .HasForeignKey("CollectionTagsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.SeriesMetadata", null)
+ .WithMany()
+ .HasForeignKey("SeriesMetadatasId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("API.Entities.AppRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Navigation("Progresses");
+
+ b.Navigation("Ratings");
+
+ b.Navigation("UserPreferences");
+
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Navigation("Files");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Navigation("Folders");
+
+ b.Navigation("Series");
+ });
+
+ modelBuilder.Entity("API.Entities.Series", b =>
+ {
+ b.Navigation("Metadata");
+
+ b.Navigation("Volumes");
+ });
+
+ modelBuilder.Entity("API.Entities.Volume", b =>
+ {
+ b.Navigation("Chapters");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Data/Migrations/20210622164318_NewUserPreferences.cs b/API/Data/Migrations/20210622164318_NewUserPreferences.cs
new file mode 100644
index 000000000..bd75d5b2c
--- /dev/null
+++ b/API/Data/Migrations/20210622164318_NewUserPreferences.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Data.Migrations
+{
+ public partial class NewUserPreferences : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "AutoCloseMenu",
+ table: "AppUserPreferences",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "ReaderMode",
+ table: "AppUserPreferences",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "AutoCloseMenu",
+ table: "AppUserPreferences");
+
+ migrationBuilder.DropColumn(
+ name: "ReaderMode",
+ table: "AppUserPreferences");
+ }
+ }
+}
diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs
index f14402ece..c6d49fc2a 100644
--- a/API/Data/Migrations/DataContextModelSnapshot.cs
+++ b/API/Data/Migrations/DataContextModelSnapshot.cs
@@ -127,6 +127,9 @@ namespace API.Data.Migrations
b.Property("AppUserId")
.HasColumnType("INTEGER");
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
b.Property("BookReaderDarkMode")
.HasColumnType("INTEGER");
@@ -151,6 +154,9 @@ namespace API.Data.Migrations
b.Property("PageSplitOption")
.HasColumnType("INTEGER");
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
b.Property("ReadingDirection")
.HasColumnType("INTEGER");
diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs
index 0f725444b..07d7102e1 100644
--- a/API/Data/SeriesRepository.cs
+++ b/API/Data/SeriesRepository.cs
@@ -289,7 +289,7 @@ namespace API.Data
///
///
/// Library to restrict to, if 0, will apply to all libraries
- /// How many series to pick.
+ /// Contains pagination information
///
public async Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams)
{
diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs
index ba89d0612..394e6fed1 100644
--- a/API/Data/UnitOfWork.cs
+++ b/API/Data/UnitOfWork.cs
@@ -29,7 +29,8 @@ namespace API.Data
public IAppUserProgressRepository AppUserProgressRepository => new AppUserProgressRepository(_context);
public ICollectionTagRepository CollectionTagRepository => new CollectionTagRepository(_context, _mapper);
-
+ public IFileRepository FileRepository => new FileRepository(_context);
+
public bool Commit()
{
return _context.SaveChanges() > 0;
diff --git a/API/Entities/AppUserPreferences.cs b/API/Entities/AppUserPreferences.cs
index fb5fe9bc2..149512e00 100644
--- a/API/Entities/AppUserPreferences.cs
+++ b/API/Entities/AppUserPreferences.cs
@@ -17,7 +17,18 @@ namespace API.Entities
/// Manga Reader Option: Which side of a split image should we show first
///
public PageSplitOption PageSplitOption { get; set; } = PageSplitOption.SplitRightToLeft;
-
+ ///
+ /// Manga Reader Option: How the manga reader should perform paging or reading of the file
+ ///
+ /// Webtoon uses scrolling to page, MANGA_LR uses paging by clicking left/right side of reader, MANGA_UD uses paging
+ /// by clicking top/bottom sides of reader.
+ ///
+ ///
+ public ReaderMode ReaderMode { get; set; }
+ ///
+ /// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
+ ///
+ public bool AutoCloseMenu { get; set; }
///
/// Book Reader Option: Should the background color be dark
///
diff --git a/API/Entities/Enums/ReaderMode.cs b/API/Entities/Enums/ReaderMode.cs
new file mode 100644
index 000000000..04156df24
--- /dev/null
+++ b/API/Entities/Enums/ReaderMode.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+
+namespace API.Entities.Enums
+{
+ public enum ReaderMode
+ {
+ [Description("Left and Right")]
+ MANGA_LR = 0,
+ [Description("Up and Down")]
+ MANGA_UP = 1,
+ [Description("Webtoon")]
+ WEBTOON = 2
+ }
+}
\ No newline at end of file
diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs
index 4ea8f1cf4..4d8a48be4 100644
--- a/API/Entities/Series.cs
+++ b/API/Entities/Series.cs
@@ -32,7 +32,7 @@ namespace API.Entities
///
/// Summary information related to the Series
///
- public string Summary { get; set; } // TODO: Migrate into SeriesMetdata
+ public string Summary { get; set; } // TODO: Migrate into SeriesMetdata (with Metadata update)
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
public byte[] CoverImage { get; set; }
diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs
index c3db5c08a..e713acbe1 100644
--- a/API/Extensions/ApplicationServiceExtensions.cs
+++ b/API/Extensions/ApplicationServiceExtensions.cs
@@ -1,5 +1,4 @@
-using System;
-using API.Data;
+using API.Data;
using API.Helpers;
using API.Interfaces;
using API.Interfaces.Services;
@@ -34,7 +33,6 @@ namespace API.Extensions
services.AddScoped();
services.AddSqLite(config, env);
- services.ConfigRepositories();
services.AddLogging(loggingBuilder =>
{
@@ -56,17 +54,5 @@ namespace API.Extensions
return services;
}
-
- private static IServiceCollection ConfigRepositories(this IServiceCollection services)
- {
- services.AddScoped();
- services.AddScoped();
-
- return services;
- }
-
- public static IServiceCollection AddStartupTask(this IServiceCollection services)
- where T : class, IStartupTask
- => services.AddTransient();
}
}
\ No newline at end of file
diff --git a/API/Extensions/ServiceCollectionExtensions.cs b/API/Extensions/ServiceCollectionExtensions.cs
index a9d12b471..1b752431c 100644
--- a/API/Extensions/ServiceCollectionExtensions.cs
+++ b/API/Extensions/ServiceCollectionExtensions.cs
@@ -16,7 +16,7 @@ namespace API.Extensions
{
services.AddHttpClient(client =>
{
- client.BaseAddress = new Uri("http://stats.kavitareader.com");
+ client.BaseAddress = new Uri("https://kavitastats.majora2007.duckdns.org");
client.DefaultRequestHeaders.Add("api-key", "MsnvA2DfQqxSK5jh");
});
diff --git a/API/Interfaces/IUnitOfWork.cs b/API/Interfaces/IUnitOfWork.cs
index df326c3e2..63051d2e3 100644
--- a/API/Interfaces/IUnitOfWork.cs
+++ b/API/Interfaces/IUnitOfWork.cs
@@ -11,6 +11,7 @@ namespace API.Interfaces
ISettingsRepository SettingsRepository { get; }
IAppUserProgressRepository AppUserProgressRepository { get; }
ICollectionTagRepository CollectionTagRepository { get; }
+ IFileRepository FileRepository { get; }
bool Commit();
Task CommitAsync();
bool HasChanges();
diff --git a/API/Interfaces/Services/IArchiveService.cs b/API/Interfaces/Services/IArchiveService.cs
index f77784878..18869b7cd 100644
--- a/API/Interfaces/Services/IArchiveService.cs
+++ b/API/Interfaces/Services/IArchiveService.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO.Compression;
using System.Threading.Tasks;
using API.Archive;
-using API.Entities;
namespace API.Interfaces.Services
{
diff --git a/API/Program.cs b/API/Program.cs
index b084c2ef3..fc906cca1 100644
--- a/API/Program.cs
+++ b/API/Program.cs
@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using API.Data;
using API.Entities;
+using API.Services.HostedServices;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Hosting;
@@ -20,7 +21,7 @@ namespace API
{
public class Program
{
- private static int HttpPort;
+ private static int _httpPort;
protected Program()
{
@@ -48,7 +49,7 @@ namespace API
}
// Get HttpPort from Config
- HttpPort = Configuration.GetPort(GetAppSettingFilename());
+ _httpPort = Configuration.GetPort(GetAppSettingFilename());
var host = CreateHostBuilder(args).Build();
@@ -64,7 +65,6 @@ namespace API
await context.Database.MigrateAsync();
await Seed.SeedRoles(roleManager);
await Seed.SeedSettings(context);
-
}
catch (Exception ex)
{
@@ -81,7 +81,7 @@ namespace API
{
webBuilder.UseKestrel((opts) =>
{
- opts.ListenAnyIP(HttpPort, options =>
+ opts.ListenAnyIP(_httpPort, options =>
{
options.Protocols = HttpProtocols.Http1AndHttp2;
});
diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs
index 2ce9b375b..73c279657 100644
--- a/API/Services/CacheService.cs
+++ b/API/Services/CacheService.cs
@@ -63,10 +63,6 @@ namespace API.Services
}
new DirectoryInfo(extractPath).Flatten();
- // if (fileCount > 1)
- // {
- // new DirectoryInfo(extractPath).Flatten();
- // }
return chapter;
}
diff --git a/API/Services/Clients/StatsApiClient.cs b/API/Services/Clients/StatsApiClient.cs
index 10b7ba543..00dddfad3 100644
--- a/API/Services/Clients/StatsApiClient.cs
+++ b/API/Services/Clients/StatsApiClient.cs
@@ -2,20 +2,25 @@
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
+
+using API.Configurations.CustomOptions;
using API.DTOs;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace API.Services.Clients
{
public class StatsApiClient
{
private readonly HttpClient _client;
+ private readonly StatsOptions _options;
private readonly ILogger _logger;
- public StatsApiClient(HttpClient client, ILogger logger)
+ public StatsApiClient(HttpClient client, IOptions options, ILogger logger)
{
_client = client;
_logger = logger;
+ _options = options.Value ?? throw new ArgumentNullException(nameof(options));
}
public async Task SendDataToStatsServer(UsageStatisticsDto data)
diff --git a/API/Services/ComicInfo.cs b/API/Services/ComicInfo.cs
index 8277cfb35..55e823ee4 100644
--- a/API/Services/ComicInfo.cs
+++ b/API/Services/ComicInfo.cs
@@ -9,6 +9,7 @@
public string Publisher { get; set; }
public string Genre { get; set; }
public int PageCount { get; set; }
+ // ReSharper disable once InconsistentNaming
public string LanguageISO { get; set; }
public string Web { get; set; }
}
diff --git a/API/Services/HostedServices/StartupTasksHostedService.cs b/API/Services/HostedServices/StartupTasksHostedService.cs
index dcdb22cca..95f87006e 100644
--- a/API/Services/HostedServices/StartupTasksHostedService.cs
+++ b/API/Services/HostedServices/StartupTasksHostedService.cs
@@ -28,7 +28,7 @@ namespace API.Services.HostedServices
{
await ManageStartupStatsTasks(scope, taskScheduler);
}
- catch (Exception e)
+ catch (Exception)
{
//If stats startup fail the user can keep using the app
}
@@ -36,9 +36,9 @@ namespace API.Services.HostedServices
private async Task ManageStartupStatsTasks(IServiceScope serviceScope, ITaskScheduler taskScheduler)
{
- var settingsRepository = serviceScope.ServiceProvider.GetRequiredService();
+ var unitOfWork = serviceScope.ServiceProvider.GetRequiredService();
- var settingsDto = await settingsRepository.GetSettingsDtoAsync();
+ var settingsDto = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
if (!settingsDto.AllowStatCollection) return;
diff --git a/API/Services/StatsService.cs b/API/Services/StatsService.cs
index 4d5e3a315..2c315c99d 100644
--- a/API/Services/StatsService.cs
+++ b/API/Services/StatsService.cs
@@ -25,15 +25,15 @@ namespace API.Services
private readonly StatsApiClient _client;
private readonly DataContext _dbContext;
private readonly ILogger _logger;
- private readonly IFileRepository _fileRepository;
+ private readonly IUnitOfWork _unitOfWork;
public StatsService(StatsApiClient client, DataContext dbContext, ILogger logger,
- IFileRepository fileRepository)
+ IUnitOfWork unitOfWork)
{
_client = client;
_dbContext = dbContext;
_logger = logger;
- _fileRepository = fileRepository;
+ _unitOfWork = unitOfWork;
}
private static string FinalPath => Path.Combine(Directory.GetCurrentDirectory(), TempFilePath, TempFileName);
@@ -77,9 +77,9 @@ namespace API.Services
_logger.LogInformation("Deleting the file from disk");
if (FileExists) File.Delete(FinalPath);
}
- catch (Exception e)
+ catch (Exception ex)
{
- _logger.LogError("Error Finalizing Stats collection flow", e);
+ _logger.LogError(ex, "Error Finalizing Stats collection flow");
throw;
}
}
@@ -121,7 +121,7 @@ namespace API.Services
.Select(x => new LibInfo {Type = x.Key, Count = x.Count()})
.ToArrayAsync();
- var uniqueFileTypes = await _fileRepository.GetFileExtensions();
+ var uniqueFileTypes = await _unitOfWork.FileRepository.GetFileExtensions();
var usageInfo = new UsageInfoDto
{
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..ab4a8a30e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# How to Contribute #
+
+We're always looking for people to help make Kavita even better, there are a number of ways to contribute.
+
+## Documentation ##
+Setup guides, FAQ, the more information we have on the [wiki](https://github.com/Kareadita/Kavita/wiki) the better.
+
+## Development ##
+
+### Tools required ###
+- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works fine. [Download it here](https://www.visualstudio.com/downloads/).
+- Rider (optional to Visual Studio) (https://www.jetbrains.com/rider/)
+- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
+- [Git](https://git-scm.com/downloads)
+- [NodeJS](https://nodejs.org/en/download/) (Node 14.X.X or higher)
+- .NET 5.0+
+
+### Getting started ###
+
+1. Fork Kavita
+2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
+ - Kavita as of v0.4.2 requires Kavita-webui to be cloned next to the Kavita. Fork and clone this as well.
+3. Install the required Node Packages
+ - cd kavita-webui
+ - `npm install`
+ - `npm install -g @angular/cli`
+4. Start webui server `ng serve`
+5. Build the project in Visual Studio/Rider, Setting startup project to `API`
+6. Debug the project in Visual Studio/Rider
+7. Open http://localhost:4200
+8. (Deployment only) Run build.sh and pass the Runtime Identifier for your OS or just build.sh for all supported RIDs.
+
+
+### Contributing Code ###
+- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Kareadita/Kavita/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
+- Rebase from Kavita's develop branch, don't merge
+- Make meaningful commits, or squash them
+- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
+- Reach out to us on the discord if you have any questions
+- Add tests (unit/integration)
+- Commit with *nix line endings for consistency (We checkout Windows and commit *nix)
+- One feature/bug fix per pull request to keep things clean and easy to understand
+- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge)
+ - Use 2 spaces for Kavita-webui files
+
+### Pull Requesting ###
+- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
+- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
+- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
+- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
+ - new-feature (Good)
+ - fix-bug (Good)
+ - patch (Bad)
+ - develop (Bad)
+
+If you have any questions about any of this, please let us know.
diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj
index 673974db1..3b92d09f8 100644
--- a/Kavita.Common/Kavita.Common.csproj
+++ b/Kavita.Common/Kavita.Common.csproj
@@ -4,7 +4,7 @@
net5.0kareadita.github.ioKavita
- 0.4.1.1
+ 0.4.2.0en
diff --git a/Logo/dottrace.svg b/Logo/dottrace.svg
new file mode 100644
index 000000000..b879517cd
--- /dev/null
+++ b/Logo/dottrace.svg
@@ -0,0 +1,33 @@
+
+
+
diff --git a/Logo/jetbrains.svg b/Logo/jetbrains.svg
new file mode 100644
index 000000000..75d4d2177
--- /dev/null
+++ b/Logo/jetbrains.svg
@@ -0,0 +1,66 @@
+
+
+
diff --git a/Logo/kavita.svg b/Logo/kavita.svg
new file mode 100644
index 000000000..f56f8a7c5
--- /dev/null
+++ b/Logo/kavita.svg
@@ -0,0 +1,124 @@
+
+
+
+
diff --git a/Logo/resharper.svg b/Logo/resharper.svg
new file mode 100644
index 000000000..24c987a78
--- /dev/null
+++ b/Logo/resharper.svg
@@ -0,0 +1,50 @@
+
+
+
diff --git a/Logo/rider.svg b/Logo/rider.svg
new file mode 100644
index 000000000..82da35b0b
--- /dev/null
+++ b/Logo/rider.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/Logo/sentry.svg b/Logo/sentry.svg
new file mode 100644
index 000000000..40bd18594
--- /dev/null
+++ b/Logo/sentry.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index a3fd09193..2dda67528 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Kavita
+# []() Kavita

@@ -9,44 +9,41 @@ your reading collection with your friends and family!
[](https://github.com/Kareadita/Kavita/releases)
[](https://github.com/Kareadita/Kavita/blob/master/LICENSE)
-[](https://discord.gg/eczRp9eeem)
[](https://github.com/Kareadita/Kavita/releases)
[](https://hub.docker.com/r/kizaing/kavita/)
-[](https://sonarcloud.io/dashboard?id=Kareadita_Kavita)
[](https://sonarcloud.io/dashboard?id=Kareadita_Kavita)
[](https://sonarcloud.io/dashboard?id=Kareadita_Kavita)
[](https://paypal.me/majora2007?locale.x=en_US)
+[](#backers)
+[](#sponsors)
-## Goals:
+## Goals
- [x] Serve up Manga/Webtoons/Comics (cbr, cbz, zip/rar, 7zip, raw images) and Books (epub, mobi, azw, djvu, pdf)
-- [x] First class responsive readers that work great on any device
+- [x] First class responsive readers that work great on any device (phone, tablet, desktop)
- [x] Provide a dark theme for web app
- [ ] Provide hooks into metadata providers to fetch metadata for Comics, Manga, and Books
- [ ] Metadata should allow for collections, want to read integration from 3rd party services, genres.
- [x] Ability to manage users, access, and ratings
- [ ] Ability to sync ratings and reviews to external services
-- [x] Fully Accessible
+- [x] Fully Accessible with active accessibility audits
+- [x] Dedicated webtoon reader
- [ ] And so much [more...](https://github.com/Kareadita/Kavita/projects)
+## Support
+[](https://www.reddit.com/r/KavitaManga/)
+[](https://discord.gg/eczRp9eeem)
+[](https://github.com/Kareadita/Kavita/issues)
-# How to contribute
-- Ensure you've cloned Kavita-webui. You should have Projects/Kavita and Projects/Kavita-webui
-- In Kavita-webui, run ng serve. This will start the webserver on localhost:4200
-- Run API project in Kavita, this will start the backend on localhost:5000
-
-
-## Deploy local build
-- Run build.sh and pass the Runtime Identifier for your OS or just build.sh for all supported RIDs.
-
-## How to install
+## Setup
+### Non-Docker
- Unzip the archive for your target OS
- Place in a directory that is writable. If on windows, do not place in Program Files
- Linux users must ensure the directory & kavita.db is writable by Kavita (might require starting server once)
- Run Kavita
- If you are updating, do not copy appsettings.json from the new version over. It will override your TokenKey and you will have to reauthenticate on your devices.
-## Docker
+### Docker
Running your Kavita server in docker is super easy! Barely an inconvenience. You can run it with this command:
```
@@ -72,17 +69,51 @@ services:
restart: unless-stopped
```
-**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. There is also the :alpine tag if you want a smaller image, but it is only available for x64 systems.**
+**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. There is also the `:alpine` tag if you want a smaller image, but it is only available for x64 systems.**
-## Got an Idea?
-Got a great idea? Throw it up on the FeatHub or vote on another persons. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features.
+## Feature Requests
+Got a great idea? Throw it up on the FeatHub or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features.
[](https://feathub.com/Kareadita/Kavita)
-## Want to help?
-I am looking for developers with a passion for building the next Plex for Reading. Developers with C#/ASP.NET, Angular 11 please reach out on [Discord](https://discord.gg/eczRp9eeem).
+
+## Contributors
+
+This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
+
+
## Donate
-If you like Kavita, have gotten good use out of it or feel like you want to say thanks with a few bucks, feel free to donate. Money will
-likely go towards beer or hosting.
+If you like Kavita, have gotten good use out of it or feel like you want to say thanks with a few bucks, feel free to donate. Money will go towards
+expenses related to Kavita. You can back us through OpenCollective.
+
[](https://paypal.me/majora2007?locale.x=en_US)
+
+## Backers
+
+Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Kavita#backer)
+
+
+
+## Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Kavita#sponsor)
+
+
+
+## Mega Sponsors
+
+
+## JetBrains
+Thank you to [ JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
+
+* [ Rider](http://www.jetbrains.com/rider/)
+* [ dotTrace](http://www.jetbrains.com/dottrace/)
+
+## Sentry
+Thank you to [ Sentry](https://sentry.io/welcome/) for providing us with free license to their software.
+
+### License
+
+* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
+* Copyright 2010-2021
\ No newline at end of file
diff --git a/build.sh b/build.sh
index d32da32dc..adb753827 100644
--- a/build.sh
+++ b/build.sh
@@ -48,9 +48,15 @@ Build()
BuildUI()
{
ProgressStart 'Building UI'
+ echo 'Removing old wwwroot'
+ rm -rf API/wwwroot/*
cd ../Kavita-webui/ || exit
+ echo 'Installing web dependencies'
npm install
+ echo 'Building UI'
npm run prod
+ echo 'Copying back to Kavita wwwroot'
+ cp -r dist/* ../Kavita/API/wwwroot
cd ../Kavita/ || exit
ProgressEnd 'Building UI'
}
@@ -68,6 +74,9 @@ Package()
cd API
echo dotnet publish -c Release --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework
dotnet publish -c Release --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework
+
+ echo "Recopying wwwroot due to bug"
+ cp -r ./wwwroot/* $lOutputFolder/wwwroot
echo "Copying Install information"
cp ../INSTALL.txt "$lOutputFolder"/README.txt