First go
will add comments in draft pull request
This commit is contained in:
parent
2fb72ab0d4
commit
d77090beff
23 changed files with 3570 additions and 67 deletions
|
@ -190,6 +190,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="config\fonts\" />
|
||||||
<Folder Include="config\themes" />
|
<Folder Include="config\themes" />
|
||||||
<Content Include="EmailTemplates\**">
|
<Content Include="EmailTemplates\**">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
|
72
API/Controllers/FontController.cs
Normal file
72
API/Controllers/FontController.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Data;
|
||||||
|
using API.DTOs.Font;
|
||||||
|
using API.Services;
|
||||||
|
using API.Services.Tasks;
|
||||||
|
using Kavita.Common;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
public class FontController : BaseApiController
|
||||||
|
{
|
||||||
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IFontService _fontService;
|
||||||
|
private readonly ITaskScheduler _taskScheduler;
|
||||||
|
|
||||||
|
public FontController(IUnitOfWork unitOfWork, IFontService fontService, ITaskScheduler taskScheduler)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_fontService = fontService;
|
||||||
|
_taskScheduler = taskScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ResponseCache(CacheProfileName = "10Minute")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("GetFonts")]
|
||||||
|
public async Task<ActionResult<IEnumerable<EpubFontDto>>> GetFonts()
|
||||||
|
{
|
||||||
|
return Ok(await _unitOfWork.EpubFontRepository.GetFontDtos());
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("download-font")]
|
||||||
|
public async Task<IActionResult> GetFont(int fontId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
|
||||||
|
if (font == null) return NotFound();
|
||||||
|
var contentType = GetContentType(font.FileName);
|
||||||
|
return File(await _fontService.GetContent(fontId), contentType, font.FileName);
|
||||||
|
}
|
||||||
|
catch (KavitaException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("scan")]
|
||||||
|
public IActionResult Scan()
|
||||||
|
{
|
||||||
|
_taskScheduler.ScanEpubFonts();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetContentType(string fileName)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(fileName).ToLowerInvariant();
|
||||||
|
return extension switch
|
||||||
|
{
|
||||||
|
".ttf" => "application/font-tff",
|
||||||
|
".otf" => "application/font-otf",
|
||||||
|
".woff" => "application/font-woff",
|
||||||
|
".woff2" => "application/font-woff2",
|
||||||
|
_ => "application/octet-stream",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
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 DateTime Created { get; set; }
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
}
|
|
@ -66,6 +66,7 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||||
public DbSet<ManualMigrationHistory> ManualMigrationHistory { get; set; } = null!;
|
public DbSet<ManualMigrationHistory> ManualMigrationHistory { get; set; } = null!;
|
||||||
public DbSet<SeriesBlacklist> SeriesBlacklist { get; set; } = null!;
|
public DbSet<SeriesBlacklist> SeriesBlacklist { get; set; } = null!;
|
||||||
public DbSet<AppUserCollection> AppUserCollection { get; set; } = null!;
|
public DbSet<AppUserCollection> AppUserCollection { get; set; } = null!;
|
||||||
|
public DbSet<EpubFont> EpubFont { get; set; } = null!;
|
||||||
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
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)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||||
{
|
{
|
||||||
|
@ -907,6 +907,41 @@ namespace API.Data.Migrations
|
||||||
b.ToTable("Device");
|
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 =>
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
77
API/Data/Repositories/EpubFontRepository.cs
Normal file
77
API/Data/Repositories/EpubFontRepository.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.DTOs.Font;
|
||||||
|
using API.Entities;
|
||||||
|
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>> GetFontDtos();
|
||||||
|
Task<EpubFontDto?> GetFontDto(int fontId);
|
||||||
|
Task<IEnumerable<EpubFont>> GetFonts();
|
||||||
|
Task<EpubFont?> GetFont(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>> GetFontDtos()
|
||||||
|
{
|
||||||
|
return await _context.EpubFont
|
||||||
|
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EpubFontDto?> GetFontDto(int fontId)
|
||||||
|
{
|
||||||
|
return await _context.EpubFont
|
||||||
|
.Where(f => f.Id == fontId)
|
||||||
|
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EpubFont>> GetFonts()
|
||||||
|
{
|
||||||
|
return await _context.EpubFont
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EpubFont?> GetFont(int fontId)
|
||||||
|
{
|
||||||
|
return await _context.EpubFont
|
||||||
|
.Where(f => f.Id == fontId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,9 +9,11 @@ using API.Constants;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Entities.Enums.Font;
|
||||||
using API.Entities.Enums.Theme;
|
using API.Entities.Enums.Theme;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Kavita.Common.EnvironmentInfo;
|
using Kavita.Common.EnvironmentInfo;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
@ -26,6 +28,20 @@ public static class Seed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ImmutableArray<ServerSetting> DefaultSettings;
|
public static ImmutableArray<ServerSetting> DefaultSettings;
|
||||||
|
|
||||||
|
public static readonly ImmutableArray<EpubFont> DefaultFonts =
|
||||||
|
[
|
||||||
|
..new List<EpubFont>
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Name = "Merriweather",
|
||||||
|
NormalizedName = Parser.Normalize("Merriweather"),
|
||||||
|
Provider = FontProvider.System,
|
||||||
|
FileName = "Merriweather-Regular.woff2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly ImmutableArray<SiteTheme> DefaultThemes = [
|
public static readonly ImmutableArray<SiteTheme> DefaultThemes = [
|
||||||
..new List<SiteTheme>
|
..new List<SiteTheme>
|
||||||
{
|
{
|
||||||
|
@ -153,6 +169,21 @@ public static class Seed
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task SeedFonts(DataContext context)
|
||||||
|
{
|
||||||
|
await context.Database.EnsureCreatedAsync();
|
||||||
|
foreach (var font in DefaultFonts)
|
||||||
|
{
|
||||||
|
var existing = context.SiteTheme.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)
|
public static async Task SeedDefaultStreams(IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.DashboardStreams);
|
var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.DashboardStreams);
|
||||||
|
|
|
@ -31,6 +31,7 @@ public interface IUnitOfWork
|
||||||
IAppUserSmartFilterRepository AppUserSmartFilterRepository { get; }
|
IAppUserSmartFilterRepository AppUserSmartFilterRepository { get; }
|
||||||
IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
|
IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
|
||||||
IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
|
IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
|
||||||
|
IEpubFontRepository EpubFontRepository { get; }
|
||||||
bool Commit();
|
bool Commit();
|
||||||
Task<bool> CommitAsync();
|
Task<bool> CommitAsync();
|
||||||
bool HasChanges();
|
bool HasChanges();
|
||||||
|
@ -74,6 +75,7 @@ public class UnitOfWork : IUnitOfWork
|
||||||
public IAppUserSmartFilterRepository AppUserSmartFilterRepository => new AppUserSmartFilterRepository(_context, _mapper);
|
public IAppUserSmartFilterRepository AppUserSmartFilterRepository => new AppUserSmartFilterRepository(_context, _mapper);
|
||||||
public IAppUserExternalSourceRepository AppUserExternalSourceRepository => new AppUserExternalSourceRepository(_context, _mapper);
|
public IAppUserExternalSourceRepository AppUserExternalSourceRepository => new AppUserExternalSourceRepository(_context, _mapper);
|
||||||
public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository => new ExternalSeriesMetadataRepository(_context, _mapper);
|
public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository => new ExternalSeriesMetadataRepository(_context, _mapper);
|
||||||
|
public IEpubFontRepository EpubFontRepository => new EpubFontRepository(_context, _mapper);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Commits changes to the DB. Completes the open transaction.
|
/// 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<IMediaConversionService, MediaConversionService>();
|
||||||
services.AddScoped<IRecommendationService, RecommendationService>();
|
services.AddScoped<IRecommendationService, RecommendationService>();
|
||||||
services.AddScoped<IStreamService, StreamService>();
|
services.AddScoped<IStreamService, StreamService>();
|
||||||
|
services.AddScoped<IFontService, FontService>();
|
||||||
|
|
||||||
services.AddScoped<IScannerService, ScannerService>();
|
services.AddScoped<IScannerService, ScannerService>();
|
||||||
services.AddScoped<IMetadataService, MetadataService>();
|
services.AddScoped<IMetadataService, MetadataService>();
|
||||||
|
|
|
@ -10,6 +10,7 @@ using API.DTOs.Dashboard;
|
||||||
using API.DTOs.Device;
|
using API.DTOs.Device;
|
||||||
using API.DTOs.Filtering;
|
using API.DTOs.Filtering;
|
||||||
using API.DTOs.Filtering.v2;
|
using API.DTOs.Filtering.v2;
|
||||||
|
using API.DTOs.Font;
|
||||||
using API.DTOs.MediaErrors;
|
using API.DTOs.MediaErrors;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.DTOs.Progress;
|
using API.DTOs.Progress;
|
||||||
|
@ -257,6 +258,8 @@ public class AutoMapperProfiles : Profile
|
||||||
opt =>
|
opt =>
|
||||||
opt.MapFrom(src => src.BookReaderLayoutMode));
|
opt.MapFrom(src => src.BookReaderLayoutMode));
|
||||||
|
|
||||||
|
CreateMap<EpubFont, EpubFontDto>();
|
||||||
|
|
||||||
|
|
||||||
CreateMap<AppUserBookmark, BookmarkDto>();
|
CreateMap<AppUserBookmark, BookmarkDto>();
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,7 @@ public class Program
|
||||||
await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>());
|
await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>());
|
||||||
await Seed.SeedSettings(context, directoryService);
|
await Seed.SeedSettings(context, directoryService);
|
||||||
await Seed.SeedThemes(context);
|
await Seed.SeedThemes(context);
|
||||||
|
await Seed.SeedFonts(context);
|
||||||
await Seed.SeedDefaultStreams(unitOfWork);
|
await Seed.SeedDefaultStreams(unitOfWork);
|
||||||
await Seed.SeedDefaultSideNavStreams(unitOfWork);
|
await Seed.SeedDefaultSideNavStreams(unitOfWork);
|
||||||
await Seed.SeedUserApiKeys(context);
|
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.
|
/// Original BookmarkDirectory. Only used for resetting directory. Use <see cref="ServerSettingKey.BackupDirectory"/> for actual path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string BookmarkDirectory { get; }
|
string BookmarkDirectory { get; }
|
||||||
|
string EpubFontDirectory { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
|
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -88,6 +89,8 @@ public class DirectoryService : IDirectoryService
|
||||||
public string LocalizationDirectory { get; }
|
public string LocalizationDirectory { get; }
|
||||||
public string CustomizedTemplateDirectory { get; }
|
public string CustomizedTemplateDirectory { get; }
|
||||||
public string TemplateDirectory { get; }
|
public string TemplateDirectory { get; }
|
||||||
|
public string EpubFontDirectory { get; }
|
||||||
|
|
||||||
private readonly ILogger<DirectoryService> _logger;
|
private readonly ILogger<DirectoryService> _logger;
|
||||||
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
|
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
|
||||||
|
|
||||||
|
@ -125,6 +128,8 @@ public class DirectoryService : IDirectoryService
|
||||||
ExistOrCreate(CustomizedTemplateDirectory);
|
ExistOrCreate(CustomizedTemplateDirectory);
|
||||||
TemplateDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "EmailTemplates");
|
TemplateDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "EmailTemplates");
|
||||||
ExistOrCreate(TemplateDirectory);
|
ExistOrCreate(TemplateDirectory);
|
||||||
|
EpubFontDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "config", "fonts");
|
||||||
|
ExistOrCreate(EpubFontDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -35,6 +35,7 @@ public interface ITaskScheduler
|
||||||
void CovertAllCoversToEncoding();
|
void CovertAllCoversToEncoding();
|
||||||
Task CleanupDbEntries();
|
Task CleanupDbEntries();
|
||||||
Task CheckForUpdate();
|
Task CheckForUpdate();
|
||||||
|
void ScanEpubFonts();
|
||||||
|
|
||||||
}
|
}
|
||||||
public class TaskScheduler : ITaskScheduler
|
public class TaskScheduler : ITaskScheduler
|
||||||
|
@ -57,6 +58,7 @@ public class TaskScheduler : ITaskScheduler
|
||||||
private readonly ILicenseService _licenseService;
|
private readonly ILicenseService _licenseService;
|
||||||
private readonly IExternalMetadataService _externalMetadataService;
|
private readonly IExternalMetadataService _externalMetadataService;
|
||||||
private readonly ISmartCollectionSyncService _smartCollectionSyncService;
|
private readonly ISmartCollectionSyncService _smartCollectionSyncService;
|
||||||
|
private readonly IFontService _fontService;
|
||||||
|
|
||||||
public static BackgroundJobServer Client => new ();
|
public static BackgroundJobServer Client => new ();
|
||||||
public const string ScanQueue = "scan";
|
public const string ScanQueue = "scan";
|
||||||
|
@ -93,7 +95,8 @@ public class TaskScheduler : ITaskScheduler
|
||||||
ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService,
|
ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService,
|
||||||
IThemeService themeService, IWordCountAnalyzerService wordCountAnalyzerService, IStatisticService statisticService,
|
IThemeService themeService, IWordCountAnalyzerService wordCountAnalyzerService, IStatisticService statisticService,
|
||||||
IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService,
|
IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService,
|
||||||
IExternalMetadataService externalMetadataService, ISmartCollectionSyncService smartCollectionSyncService)
|
IExternalMetadataService externalMetadataService, ISmartCollectionSyncService smartCollectionSyncService,
|
||||||
|
IFontService fontService)
|
||||||
{
|
{
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -112,6 +115,7 @@ public class TaskScheduler : ITaskScheduler
|
||||||
_licenseService = licenseService;
|
_licenseService = licenseService;
|
||||||
_externalMetadataService = externalMetadataService;
|
_externalMetadataService = externalMetadataService;
|
||||||
_smartCollectionSyncService = smartCollectionSyncService;
|
_smartCollectionSyncService = smartCollectionSyncService;
|
||||||
|
_fontService = fontService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ScheduleTasks()
|
public async Task ScheduleTasks()
|
||||||
|
@ -431,6 +435,13 @@ public class TaskScheduler : ITaskScheduler
|
||||||
await _versionUpdaterService.PushUpdate(update);
|
await _versionUpdaterService.PushUpdate(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this auto scan from time to time?
|
||||||
|
public void ScanEpubFonts()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting Epub Font scam");
|
||||||
|
BackgroundJob.Enqueue(() => _fontService.Scan());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If there is an enqueued or scheduled task for <see cref="ScannerService.ScanLibrary"/> method
|
/// If there is an enqueued or scheduled task for <see cref="ScannerService.ScanLibrary"/> method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
108
API/Services/Tasks/FontService.cs
Normal file
108
API/Services/Tasks/FontService.cs
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
using System;
|
||||||
|
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<byte[]> GetContent(int fontId);
|
||||||
|
Task Scan();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FontService: IFontService
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly IDirectoryService _directoryService;
|
||||||
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IHubContext<MessageHub> _messageHub;
|
||||||
|
private readonly ILogger<FontService> _logger;
|
||||||
|
|
||||||
|
public FontService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IHubContext<MessageHub> messageHub,
|
||||||
|
ILogger<FontService> logger)
|
||||||
|
{
|
||||||
|
_directoryService = directoryService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_messageHub = messageHub;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GetContent(int fontId)
|
||||||
|
{
|
||||||
|
// TODO: Differentiate between Provider.User & Provider.System
|
||||||
|
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
|
||||||
|
if (font == null) throw new KavitaException("Font file missing or invalid");
|
||||||
|
var fontFile = _directoryService.FileSystem.Path.Join(_directoryService.EpubFontDirectory, font.FileName);
|
||||||
|
if (string.IsNullOrEmpty(fontFile) || !_directoryService.FileSystem.File.Exists(fontFile))
|
||||||
|
throw new KavitaException("Font file missing or invalid");
|
||||||
|
return await _directoryService.FileSystem.File.ReadAllBytesAsync(fontFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Scan()
|
||||||
|
{
|
||||||
|
_directoryService.Exists(_directoryService.EpubFontDirectory);
|
||||||
|
var reservedNames = Seed.DefaultFonts.Select(f => f.NormalizedName).ToList();
|
||||||
|
var fontFiles =
|
||||||
|
_directoryService.GetFilesWithExtension(Parser.NormalizePath(_directoryService.EpubFontDirectory), @"\.[woff2|tff|otf|woff]")
|
||||||
|
.Where(name => !reservedNames.Contains(Parser.Normalize(name))).ToList();
|
||||||
|
|
||||||
|
var allFonts = (await _unitOfWork.EpubFontRepository.GetFonts()).ToList();
|
||||||
|
var userFonts = allFonts.Where(f => f.Provider == FontProvider.User).ToList();
|
||||||
|
|
||||||
|
foreach (var userFont in userFonts)
|
||||||
|
{
|
||||||
|
var filePath = Parser.NormalizePath(
|
||||||
|
_directoryService.FileSystem.Path.Join(_directoryService.EpubFontDirectory, userFont.FileName));
|
||||||
|
if (_directoryService.FileSystem.File.Exists(filePath)) continue;
|
||||||
|
allFonts.Remove(userFont);
|
||||||
|
await RemoveFont(userFont);
|
||||||
|
|
||||||
|
// TODO: Send update to UI
|
||||||
|
_logger.LogInformation("Removed a font because it didn't exist on disk {FilePath}", filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var allFontNames = allFonts.Select(f => f.NormalizedName).ToList();
|
||||||
|
foreach (var fontFile in fontFiles)
|
||||||
|
{
|
||||||
|
var nakedFileName = _directoryService.FileSystem.Path.GetFileNameWithoutExtension(fontFile);
|
||||||
|
// TODO: discuss this, using this to "prettyfy" the file name, to display in the UI
|
||||||
|
var fontName = Regex.Replace(nakedFileName, "[^a-zA-Z0-9]", " ");
|
||||||
|
var normalizedName = Parser.Normalize(nakedFileName);
|
||||||
|
if (allFontNames.Contains(normalizedName)) continue;
|
||||||
|
|
||||||
|
_unitOfWork.EpubFontRepository.Add(new EpubFont()
|
||||||
|
{
|
||||||
|
Name = fontName,
|
||||||
|
NormalizedName = normalizedName,
|
||||||
|
FileName = _directoryService.FileSystem.Path.GetFileName(fontFile),
|
||||||
|
Provider = FontProvider.User,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Send update to UI
|
||||||
|
_logger.LogInformation("Added a new font from disk {FontFile}", fontFile);
|
||||||
|
}
|
||||||
|
if (_unitOfWork.HasChanges())
|
||||||
|
{
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Send update to UI
|
||||||
|
_logger.LogInformation("Finished FontService#Scan");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveFont(EpubFont font)
|
||||||
|
{
|
||||||
|
// TODO: Default font? Ask in kavita discord if needed, as we can always fallback to the browsers default font.
|
||||||
|
_unitOfWork.EpubFontRepository.Remove(font);
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
:root {
|
||||||
--br-actionbar-button-text-color: #6c757d;
|
--br-actionbar-button-text-color: #6c757d;
|
||||||
--accordion-body-bg-color: black;
|
--accordion-body-bg-color: black;
|
||||||
|
|
|
@ -583,6 +583,15 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.bookService.getEpubFonts().subscribe(fonts => {
|
||||||
|
fonts.forEach(font => {
|
||||||
|
const fontFace = new FontFace(font.name, `url(${this.bookService.baseUrl}Font/download-font?fontId=${font.id})`);
|
||||||
|
fontFace.load().then(loadedFace => {
|
||||||
|
(document as any).fonts.add(loadedFace);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||||
const chapterId = this.route.snapshot.paramMap.get('chapterId');
|
const chapterId = this.route.snapshot.paramMap.get('chapterId');
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { ThemeProvider } from 'src/app/_models/preferences/site-theme';
|
||||||
import { User } from 'src/app/_models/user';
|
import { User } from 'src/app/_models/user';
|
||||||
import { AccountService } from 'src/app/_services/account.service';
|
import { AccountService } from 'src/app/_services/account.service';
|
||||||
import { ThemeService } from 'src/app/_services/theme.service';
|
import { ThemeService } from 'src/app/_services/theme.service';
|
||||||
import { FontFamily, BookService } from '../../_services/book.service';
|
import {BookService, EpubFont} from '../../_services/book.service';
|
||||||
import { BookBlackTheme } from '../../_models/book-black-theme';
|
import { BookBlackTheme } from '../../_models/book-black-theme';
|
||||||
import { BookDarkTheme } from '../../_models/book-dark-theme';
|
import { BookDarkTheme } from '../../_models/book-dark-theme';
|
||||||
import { BookWhiteTheme } from '../../_models/book-white-theme';
|
import { BookWhiteTheme } from '../../_models/book-white-theme';
|
||||||
|
@ -131,7 +131,7 @@ export class ReaderSettingsComponent implements OnInit {
|
||||||
* List of all font families user can select from
|
* List of all font families user can select from
|
||||||
*/
|
*/
|
||||||
fontOptions: Array<string> = [];
|
fontOptions: Array<string> = [];
|
||||||
fontFamilies: Array<FontFamily> = [];
|
fontFamilies: Array<EpubFont> = [];
|
||||||
/**
|
/**
|
||||||
* Internal property used to capture all the different css properties to render on all elements
|
* Internal property used to capture all the different css properties to render on all elements
|
||||||
*/
|
*/
|
||||||
|
@ -174,10 +174,11 @@ export class ReaderSettingsComponent implements OnInit {
|
||||||
private readonly cdRef: ChangeDetectorRef) {}
|
private readonly cdRef: ChangeDetectorRef) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.bookService.getEpubFonts().subscribe(fonts => {
|
||||||
this.fontFamilies = this.bookService.getFontFamilies();
|
this.fontFamilies = fonts;
|
||||||
this.fontOptions = this.fontFamilies.map(f => f.title);
|
this.fontOptions = fonts.map(f => f.name);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
})
|
||||||
|
|
||||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -208,11 +209,10 @@ export class ReaderSettingsComponent implements OnInit {
|
||||||
|
|
||||||
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, []));
|
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, []));
|
||||||
this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => {
|
this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => {
|
||||||
const familyName = this.fontFamilies.filter(f => f.title === fontName)[0].family;
|
if (fontName === 'default') {
|
||||||
if (familyName === 'default') {
|
|
||||||
this.pageStyles['font-family'] = 'inherit';
|
this.pageStyles['font-family'] = 'inherit';
|
||||||
} else {
|
} else {
|
||||||
this.pageStyles['font-family'] = "'" + familyName + "'";
|
this.pageStyles['font-family'] = "'" + fontName + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.styleUpdate.emit(this.pageStyles);
|
this.styleUpdate.emit(this.pageStyles);
|
||||||
|
|
|
@ -4,16 +4,19 @@ import { TextResonse } from 'src/app/_types/text-response';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { BookChapterItem } from '../_models/book-chapter-item';
|
import { BookChapterItem } from '../_models/book-chapter-item';
|
||||||
import { BookInfo } from '../_models/book-info';
|
import { BookInfo } from '../_models/book-info';
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
export interface FontFamily {
|
export enum FontProvider {
|
||||||
/**
|
System = 1,
|
||||||
* What the user should see
|
User = 2,
|
||||||
*/
|
}
|
||||||
title: string;
|
|
||||||
/**
|
export interface EpubFont {
|
||||||
* The actual font face
|
id: number;
|
||||||
*/
|
name: string;
|
||||||
family: string;
|
provider: FontProvider;
|
||||||
|
created: Date;
|
||||||
|
lastModified: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -25,10 +28,8 @@ export class BookService {
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
getFontFamilies(): Array<FontFamily> {
|
getEpubFonts(): Observable<EpubFont[]> {
|
||||||
return [{title: 'default', family: 'default'}, {title: 'EBGaramond', family: 'EBGaramond'}, {title: 'Fira Sans', family: 'Fira_Sans'},
|
return this.http.get<Array<EpubFont>>(this.baseUrl + 'Font/GetFonts')
|
||||||
{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) {
|
getBookChapters(chapterId: number) {
|
||||||
|
|
|
@ -166,7 +166,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.fontFamilies = this.bookService.getFontFamilies().map(f => f.title);
|
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
||||||
this.accountService.getOpdsUrl().subscribe(res => {
|
this.accountService.getOpdsUrl().subscribe(res => {
|
||||||
|
@ -174,6 +173,11 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.bookService.getEpubFonts().subscribe(res => {
|
||||||
|
this.fontFamilies = res.map(f => f.name);
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
})
|
||||||
|
|
||||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||||
this.opdsEnabled = res;
|
this.opdsEnabled = res;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue