mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Nightly Issues (#2618)
This commit is contained in:
parent
0ff6d4a6fc
commit
d145dca0e7
@ -11,7 +11,7 @@
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.4" />
|
||||
<PackageReference Include="xunit" Version="2.6.5" />
|
||||
<PackageReference Include="xunit" Version="2.6.6" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -64,7 +64,7 @@
|
||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.7" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.7.0" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.0" />
|
||||
@ -81,7 +81,7 @@
|
||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
|
||||
<PackageReference Include="NetVips" Version="2.4.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.15.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.15.1" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.2.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
@ -92,15 +92,15 @@
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.35.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.2.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
||||
|
@ -31,12 +31,7 @@ public class LicenseController(
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
||||
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
||||
{
|
||||
var ret = await licenseService.HasActiveLicense(forceCheck);
|
||||
if (ret)
|
||||
{
|
||||
await taskScheduler.ScheduleKavitaPlusTasks();
|
||||
}
|
||||
return Ok(ret);
|
||||
return Ok(await licenseService.HasActiveLicense(forceCheck));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -190,23 +190,12 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = ["seriesId"])]
|
||||
public async Task<ActionResult<SeriesDetailPlusDto>> GetKavitaPlusSeriesDetailData(int seriesId)
|
||||
{
|
||||
var seriesDetail = new SeriesDetailPlusDto();
|
||||
if (!await licenseService.HasActiveLicense())
|
||||
{
|
||||
seriesDetail.Recommendations = null;
|
||||
seriesDetail.Ratings = Enumerable.Empty<RatingDto>();
|
||||
return Ok(seriesDetail);
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
seriesDetail = await metadataService.GetSeriesDetail(User.GetUserId(), seriesId);
|
||||
|
||||
// Temp solution, needs to be updated with new API
|
||||
// seriesDetail.Ratings = await ratingService.GetRatings(seriesId);
|
||||
// seriesDetail.Reviews = await reviewService.GetReviewsForSeries(User.GetUserId(), seriesId);
|
||||
// seriesDetail.Recommendations =
|
||||
// await recommendationService.GetRecommendationsForSeries(User.GetUserId(), seriesId);
|
||||
|
||||
return Ok(seriesDetail);
|
||||
return Ok(await metadataService.GetSeriesDetail(User.GetUserId(), seriesId));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Includes(includes)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data.ManualMigrations;
|
||||
using API.Data.Misc;
|
||||
using API.Data.Scanner;
|
||||
@ -31,6 +32,7 @@ using API.Services.Tasks;
|
||||
using API.Services.Tasks.Scanner;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SQLite;
|
||||
|
||||
@ -152,14 +154,16 @@ public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
|
||||
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled,
|
||||
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
|
||||
public SeriesRepository(DataContext context, IMapper mapper)
|
||||
public SeriesRepository(DataContext context, IMapper mapper, UserManager<AppUser> userManager)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public void Add(Series series)
|
||||
@ -462,14 +466,18 @@ public class SeriesRepository : ISeriesRepository
|
||||
.SelectMany(v => v.Chapters)
|
||||
.SelectMany(c => c.Files.Select(f => f.Id));
|
||||
|
||||
result.Files = await _context.MangaFile
|
||||
.Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id))
|
||||
.AsSplitQuery()
|
||||
.Take(maxRecords)
|
||||
.OrderBy(f => f.FilePath)
|
||||
.ProjectTo<MangaFileDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
// Need to check if an admin
|
||||
var user = await _context.AppUser.FirstAsync(u => u.Id == userId);
|
||||
if (await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole))
|
||||
{
|
||||
result.Files = await _context.MangaFile
|
||||
.Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id))
|
||||
.AsSplitQuery()
|
||||
.Take(maxRecords)
|
||||
.OrderBy(f => f.FilePath)
|
||||
.ProjectTo<MangaFileDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
result.Chapters = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
|
@ -48,7 +48,7 @@ public class UnitOfWork : IUnitOfWork
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper);
|
||||
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper, _userManager);
|
||||
public IUserRepository UserRepository => new UserRepository(_context, _userManager, _mapper);
|
||||
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
||||
|
||||
|
@ -114,11 +114,19 @@ public class ExternalMetadataService : IExternalMetadataService
|
||||
Reviews = result.Reviews
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (FlurlHttpException ex)
|
||||
{
|
||||
_logger.LogError(e, "An error happened during the request to Kavita+ API");
|
||||
return null;
|
||||
if (ex.StatusCode == 404)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error happened during the request to Kavita+ API");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<RecommendationDto> ProcessRecommendations(Series series, AppUser user, IEnumerable<MediaRecommendationDto> recs)
|
||||
|
@ -151,9 +151,8 @@ public class TaskScheduler : ITaskScheduler
|
||||
{
|
||||
// KavitaPlus based (needs license check)
|
||||
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
|
||||
if (!await _licenseService.HasActiveSubscription(license))
|
||||
if (string.IsNullOrEmpty(license) || !await _licenseService.HasActiveSubscription(license))
|
||||
{
|
||||
|
||||
return;
|
||||
}
|
||||
RecurringJob.AddOrUpdate(CheckScrobblingTokens, () => _scrobblingService.CheckExternalAccessTokens(), Cron.Daily, RecurringJobOptions);
|
||||
|
@ -122,10 +122,16 @@ public class ProcessSeries : IProcessSeries
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception finding existing series for {SeriesName} with Localized name of {LocalizedName} for library {LibraryId}. This indicates you have duplicate series with same name or localized name in the library. Correct this and rescan", firstInfo.Series, firstInfo.LocalizedSeries, library.Id);
|
||||
var series2 = await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(firstInfo.LocalizedSeries, string.Empty, library.Id, firstInfo.Format, false);
|
||||
var details = $"Series 1: {firstInfo.Series} Series 2: {series2.Name}" + "\n" +
|
||||
$"Localized: {firstInfo.LocalizedSeries} Localized: {series2.LocalizedName}" + "\n" +
|
||||
$"Filename: {_directoryService.FileSystem.FileInfo.New(firstInfo.FullFilePath).Directory} Filename: {series2.FolderPath}";
|
||||
_logger.LogError(ex, "Scanner found a Series {SeriesName} which matched another Series {LocalizedName} in a different folder parallel to Library {LibraryName} root folder. This is not allowed. Please correct",
|
||||
firstInfo.Series, firstInfo.LocalizedSeries, library.Name);
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||
MessageFactory.ErrorEvent($"There was an exception finding existing series for {firstInfo.Series} with Localized name of {firstInfo.LocalizedSeries} for library {library.Id}",
|
||||
"This indicates you have duplicate series with same name or localized name in the library. Correct this and rescan."));
|
||||
MessageFactory.ErrorEvent($"Scanner found a Series {firstInfo.Series} which matched another Series {firstInfo.LocalizedSeries} in a different folder parallel to Library {library.Name} root folder. This is not allowed. Please correct",
|
||||
details));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -349,16 +349,26 @@ public class Startup
|
||||
opts.IncludeQueryInRequestPath = true;
|
||||
});
|
||||
|
||||
var allowIframing = Configuration.AllowIFraming;
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
context.Response.Headers[HeaderNames.Vary] =
|
||||
new[] { "Accept-Encoding" };
|
||||
|
||||
// Don't let the site be iframed outside the same origin (clickjacking)
|
||||
context.Response.Headers.XFrameOptions = Configuration.XFrameOptions;
|
||||
|
||||
// Setup CSP to ensure we load assets only from these origins
|
||||
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
if (!allowIframing)
|
||||
{
|
||||
// Don't let the site be iframed outside the same origin (clickjacking)
|
||||
context.Response.Headers.XFrameOptions = "SAMEORIGIN";
|
||||
|
||||
// Setup CSP to ensure we load assets only from these origins
|
||||
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none';");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogCritical("appsetting.json has allow iframing on! This may allow for clickjacking on the server. User beware");
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
@ -3,6 +3,5 @@
|
||||
"Port": 5000,
|
||||
"IpAddresses": "",
|
||||
"BaseUrl": "/test/",
|
||||
"Cache": 90,
|
||||
"XFrameOrigins": "SAMEORIGIN"
|
||||
}
|
||||
"Cache": 90
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ public static class Configuration
|
||||
public const string DefaultBaseUrl = "/";
|
||||
public const int DefaultHttpPort = 5000;
|
||||
public const int DefaultTimeOutSecs = 90;
|
||||
public const string DefaultXFrameOptions = "SAMEORIGIN";
|
||||
public const long DefaultCacheMemory = 75;
|
||||
private static readonly string AppSettingsFilename = Path.Join("config", GetAppSettingFilename());
|
||||
|
||||
@ -49,7 +48,7 @@ public static class Configuration
|
||||
set => SetCacheSize(GetAppSettingFilename(), value);
|
||||
}
|
||||
|
||||
public static string XFrameOptions => GetXFrameOptions(GetAppSettingFilename());
|
||||
public static bool AllowIFraming => GetAllowIFraming(GetAppSettingFilename());
|
||||
|
||||
private static string GetAppSettingFilename()
|
||||
{
|
||||
@ -293,26 +292,21 @@ public static class Configuration
|
||||
|
||||
#endregion
|
||||
|
||||
#region XFrameOrigins
|
||||
private static string GetXFrameOptions(string filePath)
|
||||
#region AllowIFraming
|
||||
private static bool GetAllowIFraming(string filePath)
|
||||
{
|
||||
if (OsInfo.IsDocker)
|
||||
{
|
||||
return DefaultBaseUrl;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var jsonObj = JsonSerializer.Deserialize<AppSettings>(json);
|
||||
return !string.IsNullOrEmpty(jsonObj.XFrameOrigins) ? jsonObj.XFrameOrigins : DefaultXFrameOptions;
|
||||
return jsonObj.AllowIFraming;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error reading app settings: " + ex.Message);
|
||||
}
|
||||
|
||||
return DefaultXFrameOptions;
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -328,6 +322,6 @@ public static class Configuration
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public long Cache { get; set; } = DefaultCacheMemory;
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public string XFrameOrigins { get; set; } = DefaultXFrameOptions;
|
||||
public bool AllowIFraming { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.16.0.82469">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
24
UI/Web/hash-localization.js
Normal file
24
UI/Web/hash-localization.js
Normal file
@ -0,0 +1,24 @@
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
|
||||
const jsonFilesDir = 'dist/browser/assets/langs/'; // Adjust the path to your JSON files
|
||||
const outputDir = 'dist/browser/assets/langs'; // Directory to store minified files
|
||||
|
||||
function generateChecksum(str, algorithm, encoding) {
|
||||
return crypto
|
||||
.createHash(algorithm || 'md5')
|
||||
.update(str, 'utf8')
|
||||
.digest(encoding || 'hex');
|
||||
}
|
||||
|
||||
const result = {};
|
||||
|
||||
glob.sync(`${jsonFilesDir}**/*.json`).forEach(path => {
|
||||
const [_, lang] = path.split('dist\\browser\\assets\\langs\\');
|
||||
const content = fs.readFileSync(path, { encoding: 'utf-8' });
|
||||
result[lang.replace('.json', '')] = generateChecksum(content);
|
||||
});
|
||||
|
||||
fs.writeFileSync('./i18n-cache-busting.json', JSON.stringify(result));
|
||||
fs.writeFileSync(`dist/browser/i18n-cache-busting.json`, JSON.stringify(result));
|
1
UI/Web/i18n-cache-busting.json
Normal file
1
UI/Web/i18n-cache-busting.json
Normal file
@ -0,0 +1 @@
|
||||
{"zh_Hant":"05191aaae25a26a8597559e8318f97db","zh_Hans":"ca663a190b259b41ac365b6b5537558e","uk":"ccf59f571821ab842882378395ccf48c","tr":"5d6427179210cc370400b816c9d1116d","th":"1e27a1e1cadb2b9f92d85952bffaab95","sk":"24de417448b577b4899e917b70a43263","ru":"c547f0995c167817dd2408e4e9279de2","pt_BR":"44c7cd3da6baad38887fb03ac4ec5581","pt":"af4162a48f01c5260d6436e7e000c5ef","pl":"c6488fdb9a1ecfe5cde6bd1c264902aa","nl":"3ff322f7b24442bd6bceb5c692146d4f","nb_NO":"99914b932bd37a50b983c5e7c90ae93b","ms":"9fdfcc11a2e8a58a4baa691b93d93ff7","ko":"447e24f9f60e1b9f36bc0b087d059dbd","ja":"2f46a5ad1364a71255dd76c0094a9264","it":"4ef0a0ef56bab4650eda37e0dd841982","id":"0beab79883a28035c393768fb8c8ecbd","hi":"d850bb49ec6b5a5ccf9986823f095ab8","fr":"b41b7065960b431a9833140e9014189e","es":"151b6c17ef7382da9f0f22b87a346b7d","en":"d07463979db4cc7ab6e0089889cfc730","de":"f8e3ec31790044be07e222703ed0575a","cs":"0f0c433b9fd641977e89a42e92e4b884"}
|
@ -3,10 +3,11 @@
|
||||
"version": "0.7.12.1",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"start": "npm run cache-langs && ng serve",
|
||||
"build": "npm run cache-langs && ng build",
|
||||
"minify-langs": "node minify-json.js",
|
||||
"prod": "ng build --configuration production && npm run minify-langs",
|
||||
"cache-langs": "node hash-localization.js",
|
||||
"prod": "ng build --configuration production && npm run minify-langs && npm run cache-langs",
|
||||
"explore": "ng build --stats-json && webpack-bundle-analyzer dist/stats.json",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
|
@ -93,6 +93,8 @@ export class AppComponent implements OnInit {
|
||||
// Bootstrap anything that's needed
|
||||
this.themeService.getThemes().subscribe();
|
||||
this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe();
|
||||
// On load, make an initial call for valid license
|
||||
this.accountService.hasValidLicense().subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +277,6 @@ export class CardItemComponent implements OnInit {
|
||||
});
|
||||
|
||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||
console.log('Card Item download obv called for entity: ', this.entity);
|
||||
return this.downloadService.mapToEntityType(events, this.entity);
|
||||
}));
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
||||
|
||||
activeEvents: number = 0;
|
||||
|
||||
debugMode: boolean = true;
|
||||
debugMode: boolean = false;
|
||||
|
||||
protected readonly EVENTS = EVENTS;
|
||||
|
||||
|
@ -692,16 +692,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||
}
|
||||
}
|
||||
|
||||
// loadRecommendations() {
|
||||
// this.seriesService.getRecommendationsForSeries(this.seriesId).subscribe(rec => {
|
||||
// rec.ownedSeries.map(r => {
|
||||
// this.seriesService.getMetadata(r.id).subscribe(m => r.summary = m.summary);
|
||||
// });
|
||||
// this.combinedRecs = [...rec.ownedSeries, ...rec.externalSeries];
|
||||
// this.hasRecommendations = this.combinedRecs.length > 0;
|
||||
// this.cdRef.markForCheck();
|
||||
// });
|
||||
// }
|
||||
|
||||
loadPlusMetadata(seriesId: number) {
|
||||
this.metadataService.getSeriesMetadataFromPlus(seriesId).subscribe(data => {
|
||||
|
@ -9,8 +9,8 @@
|
||||
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('token-valid')"></i>
|
||||
<span class="visually-hidden">{{t('token-valid')}}</span>
|
||||
} @else {
|
||||
<i class="fa-solid fa-circle ms-1 confirm-icon error" aria-hidden="true" [ngbTooltip]="t('token-not-valid')"></i>
|
||||
<span class="visually-hidden">{{t('token-not-valid')}}</span>
|
||||
<i class="fa-solid fa-circle ms-1 confirm-icon error" aria-hidden="true" [ngbTooltip]="t('token-expired')"></i>
|
||||
<span class="visually-hidden">{{t('token-expired')}}</span>
|
||||
}
|
||||
</h4>
|
||||
|
||||
|
@ -436,8 +436,10 @@
|
||||
}
|
||||
|
||||
@defer (when tab.fragment === FragmentID.Scrobbling; prefetch on idle) {
|
||||
<app-user-scrobble-history></app-user-scrobble-history>
|
||||
<app-user-holds></app-user-holds>
|
||||
@if(hasActiveLicense) {
|
||||
<app-user-scrobble-history></app-user-scrobble-history>
|
||||
<app-user-holds></app-user-holds>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
@ -81,6 +81,19 @@ enum FragmentID {
|
||||
})
|
||||
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 titleService = inject(Title);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly localizationService = inject(LocalizationService);
|
||||
protected readonly AccordionPanelID = AccordionPanelID;
|
||||
protected readonly FragmentID = FragmentID;
|
||||
|
||||
readingDirectionsTranslated = readingDirections.map(this.translatePrefOptions);
|
||||
scalingOptionsTranslated = scalingOptions.map(this.translatePrefOptions);
|
||||
pageSplitOptionsTranslated = pageSplitOptions.map(this.translatePrefOptions);
|
||||
@ -115,19 +128,9 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
opdsEnabled: boolean = false;
|
||||
opdsUrl: string = '';
|
||||
makeUrl: (val: string) => string = (val: string) => { return this.opdsUrl; };
|
||||
hasActiveLicense = false;
|
||||
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly bookService = inject(BookService);
|
||||
private readonly titleService = inject(Title);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly localizationService = inject(LocalizationService);
|
||||
protected readonly AccordionPanelID = AccordionPanelID;
|
||||
protected readonly FragmentID = FragmentID;
|
||||
|
||||
|
||||
constructor() {
|
||||
@ -144,9 +147,12 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.accountService.hasValidLicense().subscribe(res => {
|
||||
|
||||
|
||||
this.accountService.hasValidLicense$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||
if (res) {
|
||||
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
||||
this.hasActiveLicense = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@ -159,7 +165,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||
|
@ -284,6 +284,7 @@
|
||||
"scrobbling-providers": {
|
||||
"title": "Scrobbling Providers",
|
||||
"requires": "This feature requires an active {{product}} license",
|
||||
"token-valid": "Token Valid",
|
||||
"token-expired": "Token Expired",
|
||||
"no-token-set": "No Token Set",
|
||||
"token-set": "Token Set",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Translation, TranslocoLoader} from "@ngneat/transloco";
|
||||
|
||||
import cacheBusting from 'i18n-cache-busting.json'; // allowSyntheticDefaultImports must be true
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HttpLoader implements TranslocoLoader {
|
||||
@ -9,6 +9,7 @@ export class HttpLoader implements TranslocoLoader {
|
||||
|
||||
getTranslation(langPath: string) {
|
||||
const tokens = langPath.split('/');
|
||||
return this.http.get<Translation>(`assets/langs/${tokens[tokens.length - 1]}.json`);
|
||||
const langCode = tokens[tokens.length - 1];
|
||||
return this.http.get<Translation>(`assets/langs/${langCode}.json?v=${(cacheBusting as { [key: string]: string })[langCode]}`);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
@ -31,9 +32,10 @@
|
||||
"strictTemplates": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"extendedDiagnostics": {
|
||||
"nullishCoalescingNotNullable": "warning",
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.12.4"
|
||||
"version": "0.7.12.5"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user