Compare commits
11 commits
develop
...
feature/fo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e63c942759 | ||
![]() |
2bf9adc7af | ||
![]() |
01185bdb0a | ||
![]() |
5f1ea3c306 | ||
![]() |
19be1f2bbb | ||
![]() |
58800c0b4e | ||
![]() |
9fae799c63 | ||
![]() |
85c8a13beb | ||
![]() |
a956bb18ec | ||
![]() |
1f2ea8f59d | ||
![]() |
d77090beff |
112 changed files with 4408 additions and 94 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -508,6 +508,7 @@ UI/Web/dist/
|
|||
/API/config/logs/
|
||||
/API/config/backups/
|
||||
/API/config/cache/
|
||||
/API/config/fonts/
|
||||
/API/config/temp/
|
||||
/API/config/themes/
|
||||
/API/config/stats/
|
||||
|
|
|
@ -191,6 +191,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="config\fonts\" />
|
||||
<Folder Include="config\themes" />
|
||||
<Content Include="EmailTemplates\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
|
138
API/Controllers/FontController.cs
Normal file
138
API/Controllers/FontController.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.DTOs.Font;
|
||||
using API.Entities.Enums.Font;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MimeTypes;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[Authorize]
|
||||
public class FontController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IFontService _fontService;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
private readonly Regex _fontFileExtensionRegex = new(Parser.FontFileExtensions, RegexOptions.IgnoreCase, Parser.RegexTimeout);
|
||||
|
||||
public FontController(IUnitOfWork unitOfWork, IDirectoryService directoryService,
|
||||
IFontService fontService, IMapper mapper, ILocalizationService localizationService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_directoryService = directoryService;
|
||||
_fontService = fontService;
|
||||
_mapper = mapper;
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List out the fonts
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.TenMinute)]
|
||||
[HttpGet("all")]
|
||||
public async Task<ActionResult<IEnumerable<EpubFontDto>>> GetFonts()
|
||||
{
|
||||
return Ok(await _unitOfWork.EpubFontRepository.GetFontDtosAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a font
|
||||
/// </summary>
|
||||
/// <param name="fontId"></param>
|
||||
/// <param name="apiKey"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetFont(int fontId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
if (userId == 0) return BadRequest();
|
||||
|
||||
var font = await _unitOfWork.EpubFontRepository.GetFontAsync(fontId);
|
||||
if (font == null) return NotFound();
|
||||
|
||||
if (font.Provider == FontProvider.System) return BadRequest("System provided fonts are not loaded by API");
|
||||
|
||||
|
||||
var contentType = MimeTypeMap.GetMimeType(Path.GetExtension(font.FileName));
|
||||
var path = Path.Join(_directoryService.EpubFontDirectory, font.FileName);
|
||||
|
||||
return PhysicalFile(path, contentType, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a font from the system
|
||||
/// </summary>
|
||||
/// <param name="fontId"></param>
|
||||
/// <param name="confirmed">If the font is in use by other users and an admin wants it deleted, they must confirm to force delete it</param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteFont(int fontId, bool confirmed = false)
|
||||
{
|
||||
// TODO: We need to check if this font is used by anyone else and if so, need to inform the user
|
||||
// Need to check if this is a system font as well
|
||||
var forceDelete = User.IsInRole(PolicyConstants.AdminRole) && confirmed;
|
||||
await _fontService.Delete(fontId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manual upload
|
||||
/// </summary>
|
||||
/// <param name="formFile"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("upload")]
|
||||
public async Task<ActionResult<EpubFontDto>> UploadFont(IFormFile formFile)
|
||||
{
|
||||
if (!_fontFileExtensionRegex.IsMatch(Path.GetExtension(formFile.FileName))) return BadRequest("Invalid file");
|
||||
|
||||
if (formFile.FileName.Contains("..")) return BadRequest("Invalid file");
|
||||
|
||||
|
||||
var tempFile = await UploadToTemp(formFile);
|
||||
var font = await _fontService.CreateFontFromFileAsync(tempFile);
|
||||
return Ok(_mapper.Map<EpubFontDto>(font));
|
||||
}
|
||||
|
||||
[HttpPost("upload-by-url")]
|
||||
public async Task<ActionResult> UploadFontByUrl([FromQuery]string url)
|
||||
{
|
||||
// Validate url
|
||||
try
|
||||
{
|
||||
await _fontService.CreateFontFromUrl(url);
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(_localizationService.Translate(User.GetUserId(), ex.Message));
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private async Task<string> UploadToTemp(IFormFile file)
|
||||
{
|
||||
var outputFile = Path.Join(_directoryService.TempDirectory, file.FileName);
|
||||
await using var stream = System.IO.File.Create(outputFile);
|
||||
await file.CopyToAsync(stream);
|
||||
stream.Close();
|
||||
return outputFile;
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ public class ThemeController : BaseApiController
|
|||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[ResponseCache(CacheProfileName = "10Minute")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.TenMinute)]
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<SiteThemeDto>>> GetThemes()
|
||||
|
|
13
API/DTOs/Font/EpubFontDto.cs
Normal file
13
API/DTOs/Font/EpubFontDto.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using API.Entities.Enums.Font;
|
||||
|
||||
namespace API.DTOs.Font;
|
||||
|
||||
public class EpubFontDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public FontProvider Provider { get; set; }
|
||||
public string FileName { get; set; }
|
||||
|
||||
}
|
|
@ -66,6 +66,7 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
|||
public DbSet<ManualMigrationHistory> ManualMigrationHistory { get; set; } = null!;
|
||||
public DbSet<SeriesBlacklist> SeriesBlacklist { get; set; } = null!;
|
||||
public DbSet<AppUserCollection> AppUserCollection { get; set; } = null!;
|
||||
public DbSet<EpubFont> EpubFont { get; set; } = null!;
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
|
|
3078
API/Data/Migrations/20240621211843_EpubFontInitial.Designer.cs
generated
Normal file
3078
API/Data/Migrations/20240621211843_EpubFontInitial.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
42
API/Data/Migrations/20240621211843_EpubFontInitial.cs
Normal file
42
API/Data/Migrations/20240621211843_EpubFontInitial.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EpubFontInitial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EpubFont",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
FileName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Provider = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
CreatedUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EpubFont", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EpubFont");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
@ -907,6 +907,41 @@ namespace API.Data.Migrations
|
|||
b.ToTable("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.EpubFont", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EpubFont");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
101
API/Data/Repositories/EpubFontRepository.cs
Normal file
101
API/Data/Repositories/EpubFontRepository.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Font;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IEpubFontRepository
|
||||
{
|
||||
void Add(EpubFont font);
|
||||
void Remove(EpubFont font);
|
||||
void Update(EpubFont font);
|
||||
Task<IEnumerable<EpubFontDto>> GetFontDtosAsync();
|
||||
Task<EpubFontDto?> GetFontDtoAsync(int fontId);
|
||||
Task<EpubFontDto?> GetFontDtoByNameAsync(string name);
|
||||
Task<IEnumerable<EpubFont>> GetFontsAsync();
|
||||
Task<EpubFont?> GetFontAsync(int fontId);
|
||||
Task<bool> IsFontInUseAsync(int fontId);
|
||||
}
|
||||
|
||||
public class EpubFontRepository: IEpubFontRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public EpubFontRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Add(EpubFont font)
|
||||
{
|
||||
_context.Add(font);
|
||||
}
|
||||
|
||||
public void Remove(EpubFont font)
|
||||
{
|
||||
_context.Remove(font);
|
||||
}
|
||||
|
||||
public void Update(EpubFont font)
|
||||
{
|
||||
_context.Entry(font).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EpubFontDto>> GetFontDtosAsync()
|
||||
{
|
||||
return await _context.EpubFont
|
||||
.OrderBy(s => s.Name == "Default" ? -1 : 0)
|
||||
.ThenBy(s => s)
|
||||
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<EpubFontDto?> GetFontDtoAsync(int fontId)
|
||||
{
|
||||
return await _context.EpubFont
|
||||
.Where(f => f.Id == fontId)
|
||||
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<EpubFontDto?> GetFontDtoByNameAsync(string name)
|
||||
{
|
||||
return await _context.EpubFont
|
||||
.Where(f => f.NormalizedName.Equals(name.ToNormalized()))
|
||||
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EpubFont>> GetFontsAsync()
|
||||
{
|
||||
return await _context.EpubFont
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<EpubFont?> GetFontAsync(int fontId)
|
||||
{
|
||||
return await _context.EpubFont
|
||||
.Where(f => f.Id == fontId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> IsFontInUseAsync(int fontId)
|
||||
{
|
||||
return await _context.AppUserPreferences
|
||||
.Join(_context.EpubFont,
|
||||
preference => preference.BookReaderFontFamily,
|
||||
font => font.Name,
|
||||
(preference, font) => new { preference, font })
|
||||
.AnyAsync(joined => joined.font.Id == fontId);
|
||||
}
|
||||
|
||||
}
|
|
@ -76,6 +76,7 @@ public interface IUserRepository
|
|||
Task<IList<AppUserBookmark>> GetAllBookmarksByIds(IList<int> bookmarkIds);
|
||||
Task<AppUser?> GetUserByEmailAsync(string email, AppUserIncludes includes = AppUserIncludes.None);
|
||||
Task<IEnumerable<AppUserPreferences>> GetAllPreferencesByThemeAsync(int themeId);
|
||||
Task<IEnumerable<AppUserPreferences>> GetAllPreferencesByFontAsync(string fontName);
|
||||
Task<bool> HasAccessToLibrary(int libraryId, int userId);
|
||||
Task<bool> HasAccessToSeries(int userId, int seriesId);
|
||||
Task<IEnumerable<AppUser>> GetAllUsersAsync(AppUserIncludes includeFlags = AppUserIncludes.None);
|
||||
|
@ -260,6 +261,14 @@ public class UserRepository : IUserRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUserPreferences>> GetAllPreferencesByFontAsync(string fontName)
|
||||
{
|
||||
return await _context.AppUserPreferences
|
||||
.Where(p => p.BookReaderFontFamily == fontName)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasAccessToLibrary(int libraryId, int userId)
|
||||
{
|
||||
return await _context.Library
|
||||
|
|
123
API/Data/Seed.cs
123
API/Data/Seed.cs
|
@ -9,9 +9,11 @@ using API.Constants;
|
|||
using API.Data.Repositories;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Enums.Font;
|
||||
using API.Entities.Enums.Theme;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
@ -26,6 +28,97 @@ public static class Seed
|
|||
/// </summary>
|
||||
public static ImmutableArray<ServerSetting> DefaultSettings;
|
||||
|
||||
public static readonly ImmutableArray<EpubFont> DefaultFonts =
|
||||
[
|
||||
..new List<EpubFont>
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Name = "Default",
|
||||
NormalizedName = Parser.Normalize("Default"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = string.Empty,
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Merriweather",
|
||||
NormalizedName = Parser.Normalize("Merriweather"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "Merriweather-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "EB Garamond",
|
||||
NormalizedName = Parser.Normalize("EB Garamond"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "EBGaramond-VariableFont_wght.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Fira Sans",
|
||||
NormalizedName = Parser.Normalize("Fira Sans"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "FiraSans-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Lato",
|
||||
NormalizedName = Parser.Normalize("Lato"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "Lato-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Libre Baskerville",
|
||||
NormalizedName = Parser.Normalize("Libre Baskerville"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "LibreBaskerville-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Libre Caslon",
|
||||
NormalizedName = Parser.Normalize("Libre Caslon"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "LibreCaslonText-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Nanum Gothic",
|
||||
NormalizedName = Parser.Normalize("Nanum Gothic"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "NanumGothic-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Open Dyslexic 2",
|
||||
NormalizedName = Parser.Normalize("Open Dyslexic 2"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "OpenDyslexic-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Oswald",
|
||||
NormalizedName = Parser.Normalize("Oswald"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "Oswald-VariableFont_wght.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "RocknRoll One",
|
||||
NormalizedName = Parser.Normalize("RocknRoll One"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "RocknRollOne-Regular.woff2",
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Name = "Spartan",
|
||||
NormalizedName = Parser.Normalize("Spartan"),
|
||||
Provider = FontProvider.System,
|
||||
FileName = "Spartan-VariableFont_wght.woff2",
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
public static readonly ImmutableArray<SiteTheme> DefaultThemes = [
|
||||
..new List<SiteTheme>
|
||||
{
|
||||
|
@ -143,7 +236,7 @@ public static class Seed
|
|||
|
||||
foreach (var theme in DefaultThemes)
|
||||
{
|
||||
var existing = context.SiteTheme.FirstOrDefault(s => s.Name.Equals(theme.Name));
|
||||
var existing = await context.SiteTheme.FirstOrDefaultAsync(s => s.Name.Equals(theme.Name));
|
||||
if (existing == null)
|
||||
{
|
||||
await context.SiteTheme.AddAsync(theme);
|
||||
|
@ -153,6 +246,22 @@ public static class Seed
|
|||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public static async Task SeedFonts(DataContext context)
|
||||
{
|
||||
await context.Database.EnsureCreatedAsync();
|
||||
|
||||
foreach (var font in DefaultFonts)
|
||||
{
|
||||
var existing = await context.EpubFont.FirstOrDefaultAsync(f => f.Name.Equals(font.Name));
|
||||
if (existing == null)
|
||||
{
|
||||
await context.EpubFont.AddAsync(font);
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public static async Task SeedDefaultStreams(IUnitOfWork unitOfWork)
|
||||
{
|
||||
var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.DashboardStreams);
|
||||
|
@ -259,7 +368,7 @@ public static class Seed
|
|||
|
||||
foreach (var defaultSetting in DefaultSettings)
|
||||
{
|
||||
var existing = context.ServerSetting.FirstOrDefault(s => s.Key == defaultSetting.Key);
|
||||
var existing = await context.ServerSetting.FirstOrDefaultAsync(s => s.Key == defaultSetting.Key);
|
||||
if (existing == null)
|
||||
{
|
||||
await context.ServerSetting.AddAsync(defaultSetting);
|
||||
|
@ -269,15 +378,15 @@ public static class Seed
|
|||
await context.SaveChangesAsync();
|
||||
|
||||
// Port, IpAddresses and LoggingLevel are managed in appSettings.json. Update the DB values to match
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.Port).Value =
|
||||
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.Port)).Value =
|
||||
Configuration.Port + string.Empty;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.IpAddresses).Value =
|
||||
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.IpAddresses)).Value =
|
||||
Configuration.IpAddresses;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.CacheDirectory).Value =
|
||||
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.CacheDirectory)).Value =
|
||||
directoryService.CacheDirectory + string.Empty;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.BackupDirectory).Value =
|
||||
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.BackupDirectory)).Value =
|
||||
DirectoryService.BackupDirectory + string.Empty;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.CacheSize).Value =
|
||||
(await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.CacheSize)).Value =
|
||||
Configuration.CacheSize + string.Empty;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ public interface IUnitOfWork
|
|||
IAppUserSmartFilterRepository AppUserSmartFilterRepository { get; }
|
||||
IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
|
||||
IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
|
||||
IEpubFontRepository EpubFontRepository { get; }
|
||||
bool Commit();
|
||||
Task<bool> CommitAsync();
|
||||
bool HasChanges();
|
||||
|
@ -74,6 +75,7 @@ public class UnitOfWork : IUnitOfWork
|
|||
public IAppUserSmartFilterRepository AppUserSmartFilterRepository => new AppUserSmartFilterRepository(_context, _mapper);
|
||||
public IAppUserExternalSourceRepository AppUserExternalSourceRepository => new AppUserExternalSourceRepository(_context, _mapper);
|
||||
public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository => new ExternalSeriesMetadataRepository(_context, _mapper);
|
||||
public IEpubFontRepository EpubFontRepository => new EpubFontRepository(_context, _mapper);
|
||||
|
||||
/// <summary>
|
||||
/// Commits changes to the DB. Completes the open transaction.
|
||||
|
|
13
API/Entities/Enums/Font/FontProvider.cs
Normal file
13
API/Entities/Enums/Font/FontProvider.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace API.Entities.Enums.Font;
|
||||
|
||||
public enum FontProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Font is provider by System, always avaible
|
||||
/// </summary>
|
||||
System = 1,
|
||||
/// <summary>
|
||||
/// Font provider by the User
|
||||
/// </summary>
|
||||
User = 2,
|
||||
}
|
37
API/Entities/EpubFont.cs
Normal file
37
API/Entities/EpubFont.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using API.Entities.Enums.Font;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Services;
|
||||
|
||||
namespace API.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a user provider font to be used in the epub reader
|
||||
/// </summary>
|
||||
public class EpubFont: IEntityDate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the font
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Normalized name for lookups
|
||||
/// </summary>
|
||||
public required string NormalizedName { get; set; }
|
||||
/// <summary>
|
||||
/// Filename of the font, stored under <see cref="DirectoryService.EpubFontDirectory"/>
|
||||
/// </summary>
|
||||
/// <remarks>System provided fonts use an alternative location as they are packaged with the app</remarks>
|
||||
public required string FileName { get; set; }
|
||||
/// <summary>
|
||||
/// Where the font came from
|
||||
/// </summary>
|
||||
public FontProvider Provider { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
}
|
|
@ -53,6 +53,7 @@ public static class ApplicationServiceExtensions
|
|||
services.AddScoped<IMediaConversionService, MediaConversionService>();
|
||||
services.AddScoped<IRecommendationService, RecommendationService>();
|
||||
services.AddScoped<IStreamService, StreamService>();
|
||||
services.AddScoped<IFontService, FontService>();
|
||||
|
||||
services.AddScoped<IScannerService, ScannerService>();
|
||||
services.AddScoped<IMetadataService, MetadataService>();
|
||||
|
|
|
@ -10,6 +10,7 @@ using API.DTOs.Dashboard;
|
|||
using API.DTOs.Device;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.DTOs.Font;
|
||||
using API.DTOs.MediaErrors;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Progress;
|
||||
|
@ -262,6 +263,8 @@ public class AutoMapperProfiles : Profile
|
|||
opt =>
|
||||
opt.MapFrom(src => src.BookReaderLayoutMode));
|
||||
|
||||
CreateMap<EpubFont, EpubFontDto>();
|
||||
|
||||
|
||||
CreateMap<AppUserBookmark, BookmarkDto>();
|
||||
|
||||
|
|
|
@ -218,6 +218,9 @@
|
|||
"scan-libraries": "Scan Libraries",
|
||||
"kavita+-data-refresh": "Kavita+ Data Refresh",
|
||||
"backup": "Backup",
|
||||
"update-yearly-stats": "Update Yearly Stats"
|
||||
"update-yearly-stats": "Update Yearly Stats",
|
||||
|
||||
|
||||
"font-url-not-allowed": "Uploading a Font by url is only allowed from Google Fonts"
|
||||
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ public class Program
|
|||
await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>());
|
||||
await Seed.SeedSettings(context, directoryService);
|
||||
await Seed.SeedThemes(context);
|
||||
await Seed.SeedFonts(context);
|
||||
await Seed.SeedDefaultStreams(unitOfWork);
|
||||
await Seed.SeedDefaultSideNavStreams(unitOfWork);
|
||||
await Seed.SeedUserApiKeys(context);
|
||||
|
|
|
@ -33,6 +33,7 @@ public interface IDirectoryService
|
|||
/// Original BookmarkDirectory. Only used for resetting directory. Use <see cref="ServerSettingKey.BackupDirectory"/> for actual path.
|
||||
/// </summary>
|
||||
string BookmarkDirectory { get; }
|
||||
string EpubFontDirectory { get; }
|
||||
/// <summary>
|
||||
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
|
||||
/// </summary>
|
||||
|
@ -88,6 +89,8 @@ public class DirectoryService : IDirectoryService
|
|||
public string LocalizationDirectory { get; }
|
||||
public string CustomizedTemplateDirectory { get; }
|
||||
public string TemplateDirectory { get; }
|
||||
public string EpubFontDirectory { get; }
|
||||
|
||||
private readonly ILogger<DirectoryService> _logger;
|
||||
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
|
||||
|
||||
|
@ -125,6 +128,8 @@ public class DirectoryService : IDirectoryService
|
|||
ExistOrCreate(CustomizedTemplateDirectory);
|
||||
TemplateDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "EmailTemplates");
|
||||
ExistOrCreate(TemplateDirectory);
|
||||
EpubFontDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "config", "fonts");
|
||||
ExistOrCreate(EpubFontDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
133
API/Services/Tasks/FontService.cs
Normal file
133
API/Services/Tasks/FontService.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums.Font;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Tasks;
|
||||
|
||||
public interface IFontService
|
||||
{
|
||||
Task<EpubFont> CreateFontFromFileAsync(string path);
|
||||
Task Delete(int fontId);
|
||||
Task CreateFontFromUrl(string url);
|
||||
}
|
||||
|
||||
public class FontService: IFontService
|
||||
{
|
||||
|
||||
public static readonly string DefaultFont = "default";
|
||||
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<FontService> _logger;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
private const string SupportedFontUrlPrefix = "https://fonts.google.com/specimen/";
|
||||
|
||||
public FontService(IDirectoryService directoryService, IUnitOfWork unitOfWork, ILogger<FontService> logger, IEventHub eventHub)
|
||||
{
|
||||
_directoryService = directoryService;
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
public async Task<EpubFont> CreateFontFromFileAsync(string path)
|
||||
{
|
||||
if (!_directoryService.FileSystem.File.Exists(path))
|
||||
{
|
||||
_logger.LogInformation("Unable to create font from manual upload as font not in temp");
|
||||
throw new KavitaException("errors.font-manual-upload");
|
||||
}
|
||||
|
||||
var fileName = _directoryService.FileSystem.FileInfo.New(path).Name;
|
||||
var nakedFileName = _directoryService.FileSystem.Path.GetFileNameWithoutExtension(fileName);
|
||||
var fontName = Parser.PrettifyFileName(nakedFileName);
|
||||
var normalizedName = Parser.Normalize(nakedFileName);
|
||||
|
||||
if (await _unitOfWork.EpubFontRepository.GetFontDtoByNameAsync(fontName) != null)
|
||||
{
|
||||
throw new KavitaException("errors.font-already-in-use");
|
||||
}
|
||||
|
||||
_directoryService.CopyFileToDirectory(path, _directoryService.EpubFontDirectory);
|
||||
var finalLocation = _directoryService.FileSystem.Path.Join(_directoryService.EpubFontDirectory, fileName);
|
||||
|
||||
var font = new EpubFont()
|
||||
{
|
||||
Name = fontName,
|
||||
NormalizedName = normalizedName,
|
||||
FileName = Path.GetFileName(finalLocation),
|
||||
Provider = FontProvider.User
|
||||
};
|
||||
_unitOfWork.EpubFontRepository.Add(font);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
// TODO: Send update to UI
|
||||
return font;
|
||||
}
|
||||
|
||||
public async Task Delete(int fontId)
|
||||
{
|
||||
if (await _unitOfWork.EpubFontRepository.IsFontInUseAsync(fontId))
|
||||
{
|
||||
throw new KavitaException("errors.delete-font-in-use");
|
||||
}
|
||||
|
||||
var font = await _unitOfWork.EpubFontRepository.GetFontAsync(fontId);
|
||||
if (font == null) return;
|
||||
|
||||
await RemoveFont(font);
|
||||
}
|
||||
|
||||
public Task CreateFontFromUrl(string url)
|
||||
{
|
||||
if (!url.StartsWith(SupportedFontUrlPrefix))
|
||||
{
|
||||
throw new KavitaException("font-url-not-allowed");
|
||||
}
|
||||
|
||||
// Extract Font name from url
|
||||
var fontFamily = url.Split(SupportedFontUrlPrefix)[1].Split("?")[0];
|
||||
_logger.LogInformation("Preparing to download {FontName} font", fontFamily);
|
||||
|
||||
// TODO: Send a font update event
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RemoveFont(EpubFont font)
|
||||
{
|
||||
if (font.Provider == FontProvider.System) return;
|
||||
|
||||
var prefs = await _unitOfWork.UserRepository.GetAllPreferencesByFontAsync(font.Name);
|
||||
foreach (var pref in prefs)
|
||||
{
|
||||
pref.BookReaderFontFamily = DefaultFont;
|
||||
_unitOfWork.UserRepository.Update(pref);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Copy the theme file to temp for nightly removal (to give user time to reclaim if made a mistake)
|
||||
var existingLocation =
|
||||
_directoryService.FileSystem.Path.Join(_directoryService.SiteThemeDirectory, font.FileName);
|
||||
var newLocation =
|
||||
_directoryService.FileSystem.Path.Join(_directoryService.TempDirectory, font.FileName);
|
||||
_directoryService.CopyFileToDirectory(existingLocation, newLocation);
|
||||
_directoryService.DeleteFiles([existingLocation]);
|
||||
}
|
||||
catch (Exception) { /* Swallow */ }
|
||||
|
||||
_unitOfWork.EpubFontRepository.Remove(font);
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ public static class Parser
|
|||
private const string BookFileExtensions = EpubFileExtension + "|" + PdfFileExtension;
|
||||
private const string XmlRegexExtensions = @"\.xml";
|
||||
public const string MacOsMetadataFileStartsWith = @"._";
|
||||
public const string FontFileExtensions = @"\.[woff2|tff|otf|woff]";
|
||||
|
||||
public const string SupportedExtensions =
|
||||
ArchiveFileExtensions + "|" + ImageFileExtensions + "|" + BookFileExtensions;
|
||||
|
@ -1259,4 +1260,12 @@ public static class Parser
|
|||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaced non-alphanumerical chars with a space
|
||||
*/
|
||||
public static string PrettifyFileName(string name)
|
||||
{
|
||||
return Regex.Replace(name, "[^a-zA-Z0-9]", " ");
|
||||
}
|
||||
}
|
||||
|
|
17
UI/Web/src/app/_models/preferences/epub-font.ts
Normal file
17
UI/Web/src/app/_models/preferences/epub-font.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Where does the font come from
|
||||
*/
|
||||
export enum FontProvider {
|
||||
System = 1,
|
||||
User = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Font used in the book reader
|
||||
*/
|
||||
export interface EpubFont {
|
||||
id: number;
|
||||
name: string;
|
||||
provider: FontProvider;
|
||||
fileName: string;
|
||||
}
|
66
UI/Web/src/app/_services/font.service.ts
Normal file
66
UI/Web/src/app/_services/font.service.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import {DestroyRef, inject, Injectable} from "@angular/core";
|
||||
import {map, ReplaySubject} from "rxjs";
|
||||
import {EpubFont, FontProvider} from "../_models/preferences/epub-font";
|
||||
import {environment} from 'src/environments/environment';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {MessageHubService} from "./message-hub.service";
|
||||
import {NgxFileDropEntry} from "ngx-file-drop";
|
||||
import {AccountService} from "./account.service";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FontService {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
public defaultEpubFont: string = 'default';
|
||||
|
||||
private fontsSource = new ReplaySubject<EpubFont[]>(1);
|
||||
public fonts$ = this.fontsSource.asObservable();
|
||||
|
||||
baseUrl: string = environment.apiUrl;
|
||||
apiKey: string = '';
|
||||
encodedKey: string = '';
|
||||
|
||||
constructor(private httpClient: HttpClient, messageHub: MessageHubService, private accountService: AccountService) {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
if (user) {
|
||||
this.apiKey = user.apiKey;
|
||||
this.encodedKey = encodeURIComponent(this.apiKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getFonts() {
|
||||
return this.httpClient.get<Array<EpubFont>>(this.baseUrl + 'font/all').pipe(map(fonts => {
|
||||
this.fontsSource.next(fonts);
|
||||
return fonts;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
getFontFace(font: EpubFont): FontFace {
|
||||
// TODO: We need to refactor this so that we loadFonts with an array, fonts have an id to remove them, and we don't keep populating the document
|
||||
if (font.provider === FontProvider.System) {
|
||||
return new FontFace(font.name, `url('/assets/fonts/${font.name}/${font.fileName}')`);
|
||||
}
|
||||
|
||||
return new FontFace(font.name, `url(${this.baseUrl}font?fontId=${font.id}&apiKey=${this.encodedKey})`);
|
||||
}
|
||||
|
||||
uploadFont(fontFile: File, fileEntry: NgxFileDropEntry) {
|
||||
const formData = new FormData();
|
||||
formData.append('formFile', fontFile, fileEntry.relativePath);
|
||||
return this.httpClient.post<EpubFont>(this.baseUrl + "font/upload", formData);
|
||||
}
|
||||
|
||||
uploadFromUrl(url: string) {
|
||||
return this.httpClient.post(this.baseUrl + "font/upload-by-url?url=" + encodeURIComponent(url), {});
|
||||
}
|
||||
|
||||
deleteFont(id: number) {
|
||||
return this.httpClient.delete(this.baseUrl + `font?fontId=${id}`);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +1,3 @@
|
|||
@font-face {
|
||||
font-family: "Fira_Sans";
|
||||
src: url(../../../../assets/fonts/Fira_Sans/FiraSans-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url(../../../../assets/fonts/Lato/Lato-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Libre_Baskerville";
|
||||
src: url(../../../../assets/fonts/Libre_Baskerville/LibreBaskerville-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Merriweather";
|
||||
src: url(../../../../assets/fonts/Merriweather/Merriweather-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Nanum_Gothic";
|
||||
src: url(../../../../assets/fonts/Nanum_Gothic/NanumGothic-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "RocknRoll_One";
|
||||
src: url(../../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "OpenDyslexic2";
|
||||
src: url(../../../../assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--br-actionbar-button-text-color: #6c757d;
|
||||
--accordion-body-bg-color: black;
|
||||
|
|
|
@ -52,6 +52,7 @@ import {
|
|||
PersonalToCEvent
|
||||
} from "../personal-table-of-contents/personal-table-of-contents.component";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {FontService} from "../../../_services/font.service";
|
||||
|
||||
|
||||
enum TabID {
|
||||
|
@ -124,6 +125,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private readonly libraryService = inject(LibraryService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly fontService = inject(FontService);
|
||||
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
|
@ -583,6 +585,15 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fontService.getFonts().subscribe(fonts => {
|
||||
fonts.forEach(font => {
|
||||
this.fontService.getFontFace(font).load().then(loadedFace => {
|
||||
console.log('loaded font: ', loadedFace);
|
||||
(this.document as any).fonts.add(loadedFace);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||
const chapterId = this.route.snapshot.paramMap.get('chapterId');
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">{{t('font-family-label')}}</label>
|
||||
<select class="form-select" id="library-type" formControlName="bookReaderFontFamily">
|
||||
<option [value]="opt" *ngFor="let opt of fontOptions; let i = index">{{opt | titlecase}}</option>
|
||||
@for(opt of fontFamilies; track opt) {
|
||||
<option [value]="opt.name">{{opt.name | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { ThemeProvider } from 'src/app/_models/preferences/site-theme';
|
|||
import { User } from 'src/app/_models/user';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { ThemeService } from 'src/app/_services/theme.service';
|
||||
import { FontFamily, BookService } from '../../_services/book.service';
|
||||
import {BookService} from '../../_services/book.service';
|
||||
import { BookBlackTheme } from '../../_models/book-black-theme';
|
||||
import { BookDarkTheme } from '../../_models/book-dark-theme';
|
||||
import { BookWhiteTheme } from '../../_models/book-white-theme';
|
||||
|
@ -27,6 +27,8 @@ import { BookPaperTheme } from '../../_models/book-paper-theme';
|
|||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import { NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {FontService} from "../../../_services/font.service";
|
||||
import {EpubFont} from "../../../_models/preferences/epub-font";
|
||||
|
||||
/**
|
||||
* Used for book reader. Do not use for other components
|
||||
|
@ -83,6 +85,7 @@ export const bookColorThemes = [
|
|||
];
|
||||
|
||||
const mobileBreakpointMarginOverride = 700;
|
||||
const defaultFontFamily = 'Default';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reader-settings',
|
||||
|
@ -130,8 +133,7 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
/**
|
||||
* List of all font families user can select from
|
||||
*/
|
||||
fontOptions: Array<string> = [];
|
||||
fontFamilies: Array<FontFamily> = [];
|
||||
fontFamilies: Array<EpubFont> = [];
|
||||
/**
|
||||
* Internal property used to capture all the different css properties to render on all elements
|
||||
*/
|
||||
|
@ -171,20 +173,20 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
constructor(private bookService: BookService, private accountService: AccountService,
|
||||
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService,
|
||||
private readonly cdRef: ChangeDetectorRef) {}
|
||||
private readonly cdRef: ChangeDetectorRef, private fontService: FontService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.fontFamilies = this.bookService.getFontFamilies();
|
||||
this.fontOptions = this.fontFamilies.map(f => f.title);
|
||||
this.fontService.getFonts().subscribe(fonts => {
|
||||
this.fontFamilies = fonts;
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
|
||||
if (this.user.preferences.bookReaderFontFamily === undefined) {
|
||||
this.user.preferences.bookReaderFontFamily = 'default';
|
||||
this.user.preferences.bookReaderFontFamily = defaultFontFamily;
|
||||
}
|
||||
if (this.user.preferences.bookReaderFontSize === undefined || this.user.preferences.bookReaderFontSize < 50) {
|
||||
this.user.preferences.bookReaderFontSize = 100;
|
||||
|
@ -208,11 +210,11 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, []));
|
||||
this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => {
|
||||
const familyName = this.fontFamilies.filter(f => f.title === fontName)[0].family;
|
||||
if (familyName === 'default') {
|
||||
console.log('updating font-family to ', fontName);
|
||||
if (fontName === defaultFontFamily) {
|
||||
this.pageStyles['font-family'] = 'inherit';
|
||||
} else {
|
||||
this.pageStyles['font-family'] = "'" + familyName + "'";
|
||||
this.pageStyles['font-family'] = "'" + fontName + "'";
|
||||
}
|
||||
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
|
|
|
@ -5,17 +5,6 @@ import { environment } from 'src/environments/environment';
|
|||
import { BookChapterItem } from '../_models/book-chapter-item';
|
||||
import { BookInfo } from '../_models/book-info';
|
||||
|
||||
export interface FontFamily {
|
||||
/**
|
||||
* What the user should see
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* The actual font face
|
||||
*/
|
||||
family: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
@ -25,12 +14,6 @@ export class BookService {
|
|||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getFontFamilies(): Array<FontFamily> {
|
||||
return [{title: 'default', family: 'default'}, {title: 'EBGaramond', family: 'EBGaramond'}, {title: 'Fira Sans', family: 'Fira_Sans'},
|
||||
{title: 'Lato', family: 'Lato'}, {title: 'Libre Baskerville', family: 'Libre_Baskerville'}, {title: 'Merriweather', family: 'Merriweather'},
|
||||
{title: 'Nanum Gothic', family: 'Nanum_Gothic'}, {title: 'RocknRoll One', family: 'RocknRoll_One'}, {title: 'Open Dyslexic', family: 'OpenDyslexic2'}];
|
||||
}
|
||||
|
||||
getBookChapters(chapterId: number) {
|
||||
return this.http.get<Array<BookChapterItem>>(this.baseUrl + 'book/' + chapterId + '/chapters');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<ng-container *transloco="let t; read:'font-manager'">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<h3>{{t('title')}}</h3>
|
||||
</div>
|
||||
|
||||
<p>{{t('description')}}</p>
|
||||
|
||||
|
||||
<div class="row g-0 theme-container">
|
||||
<div class="col-lg-3 col-md-5 col-sm-7 col-xs-7 scroller">
|
||||
<div class="pe-2">
|
||||
<ul style="height: 100%" class="list-group list-group-flush">
|
||||
|
||||
@for (font of fonts; track font.name) {
|
||||
<ng-container [ngTemplateOutlet]="fontOption" [ngTemplateOutletContext]="{ $implicit: font}"></ng-container>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-9 col-md-7 col-sm-4 col-xs-4 ps-3">
|
||||
<div class="card p-3">
|
||||
|
||||
@if (selectedFont === undefined) {
|
||||
|
||||
<div class="row pb-4">
|
||||
<div class="mx-auto">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-flex justify-content-evenly">
|
||||
{{t('preview-default')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@if (files && files.length > 0) {
|
||||
<app-loading [loading]="isUploadingFont"></app-loading>
|
||||
} @else {
|
||||
<ngx-file-drop (onFileDrop)="dropped($event)" [accept]="acceptableExtensions" [directory]="false"
|
||||
dropZoneClassName="file-upload" contentClassName="file-upload-zone">
|
||||
|
||||
<ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
|
||||
|
||||
@switch (mode) {
|
||||
@case ('all') {
|
||||
<div class="row g-0 mt-3 pb-3">
|
||||
<div class="mx-auto">
|
||||
<div class="row g-0 mb-3">
|
||||
<i class="fa fa-file-upload mx-auto" style="font-size: 24px; width: 20px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-flex justify-content-evenly">
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="changeMode('url')">
|
||||
<span class="phone-hidden">{{t('enter-an-url-pre-title', {url: ''})}}</span>{{t('url')}}
|
||||
</a>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<span class="pe-0" href="javascript:void(0)">{{t('drag-n-drop')}}</span>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="openFileSelector()">{{t('upload')}}<span class="phone-hidden"> {{t('upload-continued')}}</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@case ('url') {
|
||||
<form [formGroup]="form">
|
||||
<div class="row g-0 mt-3 pb-3 ms-md-2 me-md-2">
|
||||
<div class="input-group col-auto me-md-2" style="width: 83%">
|
||||
<label class="input-group-text" for="load-url">{{t('url-label')}}</label>
|
||||
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="fontUrl"
|
||||
placeholder="https://" id="load-url">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="uploadFromUrl(); changeMode('all');"
|
||||
[disabled]="(form.get('fontUrl')?.value).length === 0">
|
||||
{{t('load')}}
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-secondary col-auto" href="javascript:void(0)" (click)="changeMode('all')">
|
||||
<i class="fas fa-share" aria-hidden="true" style="transform: rotateY(180deg)"></i>
|
||||
<span class="phone-hidden">{{t('back')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
</ngx-file-drop>
|
||||
}
|
||||
|
||||
} @else if (selectedFont) {
|
||||
<h4>
|
||||
{{selectedFont.name | sentenceCase}}
|
||||
<div class="float-end">
|
||||
@if (selectedFont.provider !== FontProvider.System && selectedFont.name !== 'Default') {
|
||||
<button class="btn btn-danger me-1" (click)="deleteFont(selectedFont.id)">{{t('delete')}}</button>
|
||||
}
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<div>
|
||||
<ng-container [ngTemplateOutlet]="availableFont" [ngTemplateOutletContext]="{ $implicit: selectedFont}"></ng-container>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #fontOption let-item>
|
||||
@if (item !== undefined) {
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start {{selectedFont && selectedFont.name === item.name ? 'active' : ''}}" (click)="selectFont(item)">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">{{item.name | sentenceCase}}</div>
|
||||
</div>
|
||||
<div><span class="pill p-1 mx-1 provider">{{item.provider | siteThemeProvider}}</span></div>
|
||||
</li>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #availableFont let-item>
|
||||
@if (item) {
|
||||
<div class="d-flex justify-content-between w-100">
|
||||
@if (item.name === 'Default') {
|
||||
<div class="ms-2 me-auto fs-6">
|
||||
This font cannot be previewed. This will take the default style from the book.
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
@if (item.hasOwnProperty('provider') && item.provider === FontProvider.User && item.hasOwnProperty('id')) {
|
||||
<button class="btn btn-danger me-1" (click)="deleteFont(item.id)">{{t('delete')}}</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-1 me-1 preview mt-2 flex-grow-1 text-center w-100 fs-4 fs-lg-3 mt-2" [ngStyle]="{'font-family': item.name, 'word-break': 'keep-all'}">
|
||||
The quick brown fox jumps over the lazy dog
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</ng-container>
|
|
@ -0,0 +1,26 @@
|
|||
.pill {
|
||||
font-size: .8rem;
|
||||
background-color: var(--card-bg-color);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.list-group-item, .list-group-item.active {
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
ngx-file-drop ::ng-deep > div {
|
||||
// styling for the outer drop box
|
||||
width: 100%;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
margin: auto;
|
||||
|
||||
> div {
|
||||
// styling for the inner box (template)
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Inject, inject, OnInit} from '@angular/core';
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {FontService} from "src/app/_services/font.service";
|
||||
import {AccountService} from "../../../_services/account.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ConfirmService} from "../../../shared/confirm.service";
|
||||
import {EpubFont, FontProvider} from 'src/app/_models/preferences/epub-font';
|
||||
import {User} from "../../../_models/user";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {shareReplay} from "rxjs/operators";
|
||||
import {map} from "rxjs";
|
||||
import {NgxFileDropEntry, NgxFileDropModule} from "ngx-file-drop";
|
||||
import {AsyncPipe, DOCUMENT, NgIf, NgStyle, NgTemplateOutlet} from "@angular/common";
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
import {FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {SentenceCasePipe} from "../../../_pipes/sentence-case.pipe";
|
||||
import {SiteThemeProviderPipe} from "../../../_pipes/site-theme-provider.pipe";
|
||||
import {CarouselReelComponent} from "../../../carousel/_components/carousel-reel/carousel-reel.component";
|
||||
import {DefaultValuePipe} from "../../../_pipes/default-value.pipe";
|
||||
import {ImageComponent} from "../../../shared/image/image.component";
|
||||
import {SafeUrlPipe} from "../../../_pipes/safe-url.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-font-manager',
|
||||
imports: [
|
||||
TranslocoDirective,
|
||||
AsyncPipe,
|
||||
LoadingComponent,
|
||||
NgxFileDropModule,
|
||||
FormsModule,
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
SentenceCasePipe,
|
||||
SiteThemeProviderPipe,
|
||||
NgTemplateOutlet,
|
||||
NgStyle,
|
||||
CarouselReelComponent,
|
||||
DefaultValuePipe,
|
||||
ImageComponent,
|
||||
SafeUrlPipe
|
||||
],
|
||||
templateUrl: './font-manager.component.html',
|
||||
styleUrl: './font-manager.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
})
|
||||
export class FontManagerComponent implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
protected readonly fontService = inject(FontService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
public readonly fb = inject(FormBuilder);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
|
||||
protected readonly FontProvider = FontProvider;
|
||||
|
||||
user: User | undefined;
|
||||
fonts: Array<EpubFont> = [];
|
||||
hasAdmin$ = this.accountService.currentUser$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef), shareReplay({refCount: true, bufferSize: 1}),
|
||||
map(c => c && this.accountService.hasAdminRole(c))
|
||||
);
|
||||
|
||||
form: FormGroup = new FormGroup({
|
||||
fontUrl: new FormControl('', [])
|
||||
});
|
||||
|
||||
selectedFont: EpubFont | undefined = undefined;
|
||||
|
||||
files: NgxFileDropEntry[] = [];
|
||||
acceptableExtensions = ['.woff2', 'woff', 'tff', 'otf'].join(',');
|
||||
mode: 'file' | 'url' | 'all' = 'all';
|
||||
isUploadingFont: boolean = false;
|
||||
|
||||
|
||||
constructor(@Inject(DOCUMENT) private document: Document) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadFonts();
|
||||
}
|
||||
|
||||
loadFonts() {
|
||||
this.fontService.getFonts().subscribe(fonts => {
|
||||
this.fonts = fonts;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
selectFont(font: EpubFont) {
|
||||
this.fontService.getFontFace(font).load().then(loadedFace => {
|
||||
(this.document as any).fonts.add(loadedFace);
|
||||
});
|
||||
this.selectedFont = font;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
dropped(files: NgxFileDropEntry[]) {
|
||||
for (const droppedFile of files) {
|
||||
if (!droppedFile.fileEntry.isFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
|
||||
fileEntry.file((file: File) => {
|
||||
this.fontService.uploadFont(file, droppedFile).subscribe(f => {
|
||||
this.isUploadingFont = false;
|
||||
this.fonts = [...this.fonts, f];
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
});
|
||||
}
|
||||
this.isUploadingFont = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
uploadFromUrl() {
|
||||
const url = this.form.get('fontUrl')?.value.trim();
|
||||
if (!url || url === '') return;
|
||||
|
||||
this.fontService.uploadFromUrl(url).subscribe(() => {
|
||||
this.loadFonts();
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFont(id: number) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-font'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fontService.deleteFont(id).subscribe(() => {
|
||||
this.fonts = this.fonts.filter(f => f.id !== id);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
changeMode(mode: 'file' | 'url' | 'all') {
|
||||
this.mode = mode;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
}
|
|
@ -71,8 +71,7 @@
|
|||
</ngx-file-drop>
|
||||
}
|
||||
|
||||
}
|
||||
@else {
|
||||
} @else {
|
||||
<h4>
|
||||
{{selectedTheme.name | sentenceCase}}
|
||||
<div class="float-end">
|
||||
|
|
|
@ -619,6 +619,10 @@
|
|||
<app-theme-manager></app-theme-manager>
|
||||
}
|
||||
|
||||
@defer (when tab.fragment === FragmentID.Font; prefetch on idle) {
|
||||
<app-font-manager></app-font-manager>
|
||||
}
|
||||
|
||||
@defer (when tab.fragment === FragmentID.Devices; prefetch on idle) {
|
||||
<app-manage-devices></app-manage-devices>
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import {SettingsService} from 'src/app/admin/settings.service';
|
|||
import {BookPageLayoutMode} from 'src/app/_models/readers/book-page-layout-mode';
|
||||
import {forkJoin} from 'rxjs';
|
||||
import {bookColorThemes} from 'src/app/book-reader/_components/reader-settings/reader-settings.component';
|
||||
import {BookService} from 'src/app/book-reader/_services/book.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {SentenceCasePipe} from '../../_pipes/sentence-case.pipe';
|
||||
import {UserHoldsComponent} from '../user-holds/user-holds.component';
|
||||
|
@ -75,8 +74,9 @@ import {ManageScrobblingProvidersComponent} from "../manage-scrobbling-providers
|
|||
import {PdfLayoutModePipe} from "../../pdf-reader/_pipe/pdf-layout-mode.pipe";
|
||||
import {PdfTheme} from "../../_models/preferences/pdf-theme";
|
||||
import {PdfScrollMode} from "../../_models/preferences/pdf-scroll-mode";
|
||||
import {PdfLayoutMode} from "../../_models/preferences/pdf-layout-mode";
|
||||
import {PdfSpreadMode} from "../../_models/preferences/pdf-spread-mode";
|
||||
import {FontManagerComponent} from "../font-manager/font-manager/font-manager.component";
|
||||
import {FontService} from "../../_services/font.service";
|
||||
|
||||
enum AccordionPanelID {
|
||||
ImageReader = 'image-reader',
|
||||
|
@ -90,6 +90,7 @@ enum FragmentID {
|
|||
Preferences = '',
|
||||
Clients = 'clients',
|
||||
Theme = 'theme',
|
||||
Font = 'font',
|
||||
Devices = 'devices',
|
||||
Stats = 'stats',
|
||||
Scrobbling = 'scrobbling'
|
||||
|
@ -105,14 +106,14 @@ enum FragmentID {
|
|||
ChangePasswordComponent, ChangeAgeRestrictionComponent, ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader,
|
||||
NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgbTooltip, NgTemplateOutlet, ColorPickerModule, ApiKeyComponent,
|
||||
ThemeManagerComponent, ManageDevicesComponent, UserStatsComponent, UserScrobbleHistoryComponent, UserHoldsComponent, NgbNavOutlet, TitleCasePipe, SentenceCasePipe,
|
||||
TranslocoDirective, LoadingComponent, ManageScrobblingProvidersComponent, PdfLayoutModePipe],
|
||||
TranslocoDirective, LoadingComponent, ManageScrobblingProvidersComponent, PdfLayoutModePipe, FontManagerComponent],
|
||||
})
|
||||
export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly bookService = inject(BookService);
|
||||
private readonly fontService = inject(FontService);
|
||||
private readonly titleService = inject(Title);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
|
@ -153,6 +154,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
{title: 'preferences-tab', fragment: FragmentID.Preferences},
|
||||
{title: '3rd-party-clients-tab', fragment: FragmentID.Clients},
|
||||
{title: 'theme-tab', fragment: FragmentID.Theme},
|
||||
{title: 'font-tab', fragment: FragmentID.Font},
|
||||
{title: 'devices-tab', fragment: FragmentID.Devices},
|
||||
{title: 'stats-tab', fragment: FragmentID.Stats},
|
||||
];
|
||||
|
@ -166,7 +168,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
|
||||
|
||||
constructor() {
|
||||
this.fontFamilies = this.bookService.getFontFamilies().map(f => f.title);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.accountService.getOpdsUrl().subscribe(res => {
|
||||
|
@ -174,6 +175,11 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.fontService.getFonts().subscribe(res => {
|
||||
this.fontFamilies = res.map(f => f.name);
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
|
||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||
this.opdsEnabled = res;
|
||||
this.cdRef.markForCheck();
|
||||
|
@ -227,7 +233,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
this.user.preferences = results.pref;
|
||||
|
||||
if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) {
|
||||
this.user.preferences.bookReaderFontFamily = 'default';
|
||||
this.user.preferences.bookReaderFontFamily = 'Default';
|
||||
}
|
||||
|
||||
this.settingsForm.addControl('readingDirection', new FormControl(this.user.preferences.readingDirection, []));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue