mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-23 15:30:34 -04:00
Various bug fixes (#3667)
This commit is contained in:
parent
a1d3aef39b
commit
1ad8a360cb
@ -158,6 +158,7 @@ public class DownloadController : BaseApiController
|
|||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
MessageFactory.DownloadProgressEvent(username,
|
MessageFactory.DownloadProgressEvent(username,
|
||||||
filename, $"Downloading {filename}", 0F, "started"));
|
filename, $"Downloading {filename}", 0F, "started"));
|
||||||
|
|
||||||
if (files.Count == 1 && files.First().Format != MangaFormat.Image)
|
if (files.Count == 1 && files.First().Format != MangaFormat.Image)
|
||||||
{
|
{
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
@ -167,15 +168,17 @@ public class DownloadController : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var filePath = _archiveService.CreateZipFromFoldersForDownload(files.Select(c => c.FilePath).ToList(), tempFolder, ProgressCallback);
|
var filePath = _archiveService.CreateZipFromFoldersForDownload(files.Select(c => c.FilePath).ToList(), tempFolder, ProgressCallback);
|
||||||
|
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
MessageFactory.DownloadProgressEvent(username,
|
MessageFactory.DownloadProgressEvent(username,
|
||||||
filename, "Download Complete", 1F, "ended"));
|
filename, "Download Complete", 1F, "ended"));
|
||||||
|
|
||||||
return PhysicalFile(filePath, DefaultContentType, Uri.EscapeDataString(downloadName), true);
|
return PhysicalFile(filePath, DefaultContentType, Uri.EscapeDataString(downloadName), true);
|
||||||
|
|
||||||
async Task ProgressCallback(Tuple<string, float> progressInfo)
|
async Task ProgressCallback(Tuple<string, float> progressInfo)
|
||||||
{
|
{
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||||
MessageFactory.DownloadProgressEvent(username, filename, $"Extracting {Path.GetFileNameWithoutExtension(progressInfo.Item1)}",
|
MessageFactory.DownloadProgressEvent(username, filename, $"Processing {Path.GetFileNameWithoutExtension(progressInfo.Item1)}",
|
||||||
Math.Clamp(progressInfo.Item2, 0F, 1F)));
|
Math.Clamp(progressInfo.Item2, 0F, 1F)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,8 +196,10 @@ public class DownloadController : BaseApiController
|
|||||||
public async Task<ActionResult> DownloadSeries(int seriesId)
|
public async Task<ActionResult> DownloadSeries(int seriesId)
|
||||||
{
|
{
|
||||||
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
||||||
if (series == null) return BadRequest("Invalid Series");
|
if (series == null) return BadRequest("Invalid Series");
|
||||||
|
|
||||||
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -595,13 +595,7 @@ public class OpdsController : BaseApiController
|
|||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(user.Id, true, GetUserParams(pageNumber), false);
|
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, user.Id);
|
||||||
if (readingLists == null)
|
|
||||||
{
|
|
||||||
return Unauthorized();
|
|
||||||
}
|
|
||||||
|
|
||||||
var readingList = readingLists.FirstOrDefault(rl => rl.Id == readingListId);
|
|
||||||
if (readingList == null)
|
if (readingList == null)
|
||||||
{
|
{
|
||||||
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
|
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
|
||||||
|
@ -39,9 +39,15 @@ public class ReadingListController : BaseApiController
|
|||||||
/// <param name="readingListId"></param>
|
/// <param name="readingListId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetList(int readingListId)
|
public async Task<ActionResult<ReadingListDto?>> GetList(int readingListId)
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId()));
|
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId());
|
||||||
|
if (readingList == null)
|
||||||
|
{
|
||||||
|
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-restricted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(readingList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -351,8 +351,10 @@ public class ReadingListRepository : IReadingListRepository
|
|||||||
|
|
||||||
public async Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
public async Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
||||||
{
|
{
|
||||||
|
var user = await _context.AppUser.FirstAsync(u => u.Id == userId);
|
||||||
return await _context.ReadingList
|
return await _context.ReadingList
|
||||||
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
||||||
|
.RestrictAgainstAgeRestriction(user.GetAgeRestriction())
|
||||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||||
.SingleOrDefaultAsync();
|
.SingleOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using API.Data;
|
|||||||
using API.DTOs.Account;
|
using API.DTOs.Account;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Errors;
|
using API.Errors;
|
||||||
|
using API.Extensions;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@ -78,8 +79,9 @@ public class AccountService : IAccountService
|
|||||||
}
|
}
|
||||||
public async Task<IEnumerable<ApiException>> ValidateUsername(string username)
|
public async Task<IEnumerable<ApiException>> ValidateUsername(string username)
|
||||||
{
|
{
|
||||||
|
// Reverted because of https://go.microsoft.com/fwlink/?linkid=2129535
|
||||||
if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName != null
|
if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName != null
|
||||||
&& x.NormalizedUserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)))
|
&& x.NormalizedUserName == username.ToUpper()))
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
|
@ -363,16 +363,15 @@ public class ArchiveService : IArchiveService
|
|||||||
tempPath = Path.Join(tempLocation, parentDirectory ?? _directoryService.FileSystem.FileInfo.New(path).Name);
|
tempPath = Path.Join(tempLocation, parentDirectory ?? _directoryService.FileSystem.FileInfo.New(path).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressCallback(Tuple.Create(_directoryService.FileSystem.FileInfo.New(path).Name, (1.0f * totalFiles) / count));
|
|
||||||
if (Tasks.Scanner.Parser.Parser.IsArchive(path))
|
if (Tasks.Scanner.Parser.Parser.IsArchive(path))
|
||||||
{
|
{
|
||||||
ExtractArchive(path, tempPath);
|
// Archives don't need to be put into a subdirectory of the same name
|
||||||
}
|
tempPath = _directoryService.GetParentDirectoryName(tempPath);
|
||||||
else
|
|
||||||
{
|
|
||||||
_directoryService.CopyFileToDirectory(path, tempPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progressCallback(Tuple.Create(_directoryService.FileSystem.FileInfo.New(path).Name, (1.0f * totalFiles) / count));
|
||||||
|
|
||||||
|
_directoryService.CopyFileToDirectory(path, tempPath);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,8 @@ export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
|||||||
constructor(private readonly changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
constructor(private readonly changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
||||||
private translocoService: TranslocoService) {}
|
private translocoService: TranslocoService) {}
|
||||||
|
|
||||||
transform(value: string) {
|
transform(value: string | Date | null) {
|
||||||
|
if (value === '' || value === null || value === undefined || (value instanceof String && value.split('T')[0] === '0001-01-01')) {
|
||||||
if (value === '' || value === null || value === undefined || value.split('T')[0] === '0001-01-01') {
|
|
||||||
return this.translocoService.translate('time-ago-pipe.never');
|
return this.translocoService.translate('time-ago-pipe.never');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
UI/Web/src/app/_pipes/utc-to-locale-date.pipe.ts
Normal file
24
UI/Web/src/app/_pipes/utc-to-locale-date.pipe.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'utcToLocaleDate',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* This is the same as the UtcToLocalTimePipe but returning a timezone aware DateTime object rather than a string.
|
||||||
|
* Use this when the next operation needs a Date object (like the TimeAgoPipe)
|
||||||
|
*/
|
||||||
|
export class UtcToLocaleDatePipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(utcDate: string | undefined | null): Date | null {
|
||||||
|
if (utcDate === '' || utcDate === null || utcDate === undefined || utcDate.split('T')[0] === '0001-01-01') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const browserLanguage = navigator.language;
|
||||||
|
const dateTime = DateTime.fromISO(utcDate, { zone: 'utc' }).toLocal().setLocale(browserLanguage);
|
||||||
|
return dateTime.toJSDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -535,7 +535,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<app-setting-item [title]="t('date-added-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
<app-setting-item [title]="t('date-added-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
{{chapter.createdUtc | utcToLocalTime | translocoDate: {dateStyle: 'short', timeStyle: 'short' } | defaultDate}}
|
{{chapter.createdUtc | utcToLocalTime:'short' | defaultDate}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-setting-item>
|
</app-setting-item>
|
||||||
</div>
|
</div>
|
||||||
|
@ -197,7 +197,12 @@ export class EditChapterModalComponent implements OnInit {
|
|||||||
this.editForm.addControl('language', new FormControl(this.chapter.language, []));
|
this.editForm.addControl('language', new FormControl(this.chapter.language, []));
|
||||||
this.editForm.addControl('isbn', new FormControl(this.chapter.isbn, []));
|
this.editForm.addControl('isbn', new FormControl(this.chapter.isbn, []));
|
||||||
this.editForm.addControl('ageRating', new FormControl(this.chapter.ageRating, []));
|
this.editForm.addControl('ageRating', new FormControl(this.chapter.ageRating, []));
|
||||||
this.editForm.addControl('releaseDate', new FormControl(this.chapter.releaseDate, []));
|
|
||||||
|
if (this.chapter.releaseDate !== '0001-01-01T00:00:00') {
|
||||||
|
this.editForm.addControl('releaseDate', new FormControl(this.chapter.releaseDate.substring(0, 10), []));
|
||||||
|
} else {
|
||||||
|
this.editForm.addControl('releaseDate', new FormControl('', []));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.editForm.addControl('genres', new FormControl(this.chapter.genres, []));
|
this.editForm.addControl('genres', new FormControl(this.chapter.genres, []));
|
||||||
@ -261,7 +266,11 @@ export class EditChapterModalComponent implements OnInit {
|
|||||||
const model = this.editForm.value;
|
const model = this.editForm.value;
|
||||||
const selectedIndex = this.editForm.get('coverImageIndex')?.value || 0;
|
const selectedIndex = this.editForm.get('coverImageIndex')?.value || 0;
|
||||||
|
|
||||||
this.chapter.releaseDate = model.releaseDate;
|
if (model.releaseDate === '') {
|
||||||
|
this.chapter.releaseDate = '0001-01-01T00:00:00';
|
||||||
|
} else {
|
||||||
|
this.chapter.releaseDate = model.releaseDate + 'T00:00:00';
|
||||||
|
}
|
||||||
this.chapter.ageRating = parseInt(model.ageRating + '', 10) as AgeRating;
|
this.chapter.ageRating = parseInt(model.ageRating + '', 10) as AgeRating;
|
||||||
this.chapter.genres = model.genres;
|
this.chapter.genres = model.genres;
|
||||||
this.chapter.tags = model.tags;
|
this.chapter.tags = model.tags;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<app-setting-item [title]="t('date-added-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
<app-setting-item [title]="t('date-added-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
{{volume.createdUtc | utcToLocalTime | translocoDate: {dateStyle: 'short', timeStyle: 'short' } | defaultDate}}
|
{{volume.createdUtc | utcToLocalTime:'short' | defaultDate}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-setting-item>
|
</app-setting-item>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<app-setting-item [title]="t('edit-collection-tags.last-sync-title')" [showEdit]="false" [canEdit]="false"
|
<app-setting-item [title]="t('edit-collection-tags.last-sync-title')" [showEdit]="false" [canEdit]="false"
|
||||||
[subtitle]="t('edit-collection-tags.last-sync-tooltip')">
|
[subtitle]="t('edit-collection-tags.last-sync-tooltip')">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
{{collection.lastSyncUtc | utcToLocalTime | date:'shortDate' | defaultDate}}
|
{{collection.lastSyncUtc | utcToLocalTime:'shortDate' | defaultDate}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-setting-item>
|
</app-setting-item>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
@if ((messageHub.onlineUsers$ | async)?.includes(member.username)) {
|
@if ((messageHub.onlineUsers$ | async)?.includes(member.username)) {
|
||||||
{{t('online-now-tooltip')}}
|
{{t('online-now-tooltip')}}
|
||||||
} @else {
|
} @else {
|
||||||
{{member.lastActiveUtc | utcToLocalTime | timeAgo | sentenceCase | defaultDate}}
|
{{member.lastActiveUtc | utcToLocaleDate | timeAgo | sentenceCase | defaultDate}}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -23,6 +23,7 @@ import {LoadingComponent} from "../../shared/loading/loading.component";
|
|||||||
import {TimeAgoPipe} from "../../_pipes/time-ago.pipe";
|
import {TimeAgoPipe} from "../../_pipes/time-ago.pipe";
|
||||||
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
||||||
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
||||||
|
import {UtcToLocaleDatePipe} from "../../_pipes/utc-to-locale-date.pipe";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manage-users',
|
selector: 'app-manage-users',
|
||||||
@ -30,7 +31,7 @@ import {DefaultModalOptions} from "../../_models/default-modal-options";
|
|||||||
styleUrls: ['./manage-users.component.scss'],
|
styleUrls: ['./manage-users.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [NgbTooltip, TagBadgeComponent, AsyncPipe, TitleCasePipe, DatePipe, TranslocoModule, DefaultDatePipe, NgClass, DefaultValuePipe, ReadMoreComponent, UtcToLocalTimePipe, LoadingComponent, NgIf, TimeAgoPipe, SentenceCasePipe]
|
imports: [NgbTooltip, TagBadgeComponent, AsyncPipe, TitleCasePipe, DatePipe, TranslocoModule, DefaultDatePipe, NgClass, DefaultValuePipe, ReadMoreComponent, UtcToLocalTimePipe, LoadingComponent, NgIf, TimeAgoPipe, SentenceCasePipe, UtcToLocaleDatePipe]
|
||||||
})
|
})
|
||||||
export class ManageUsersComponent implements OnInit {
|
export class ManageUsersComponent implements OnInit {
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@
|
|||||||
<div class="row g-0 mb-2">
|
<div class="row g-0 mb-2">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div>{{t('last-sync-title')}}</div>
|
<div>{{t('last-sync-title')}}</div>
|
||||||
<div>{{tag.lastSyncUtc | utcToLocalTime | date:'shortDate' | defaultDate}}</div>
|
<div>{{tag.lastSyncUtc | utcToLocalTime:'shortDate' | defaultDate}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div>{{t('source-url-title')}}</div>
|
<div>{{t('source-url-title')}}</div>
|
||||||
|
@ -589,7 +589,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<app-setting-item [title]="t('created-title')" [toggleOnViewClick]="false" [showEdit]="false">
|
<app-setting-item [title]="t('created-title')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
{{series.created | date:'shortDate'}}
|
{{series.created | utcToLocalTime:'shortDate'}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-setting-item>
|
</app-setting-item>
|
||||||
</div>
|
</div>
|
||||||
@ -665,7 +665,7 @@
|
|||||||
{{t('added-title')}} {{volume.createdUtc | utcToLocalTime | defaultDate}}
|
{{t('added-title')}} {{volume.createdUtc | utcToLocalTime | defaultDate}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{t('last-modified-title')}} {{volume.lastModifiedUtc | utcToLocalTime | translocoDate: {dateStyle: 'short' } | defaultDate}}
|
{{t('last-modified-title')}} {{volume.lastModifiedUtc | utcToLocalTime:'short' | defaultDate}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -74,10 +74,10 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{progressEvents[idx].createdUtc | utcToLocalTime | date:'shortDate' | defaultDate}}
|
{{progressEvents[idx].createdUtc | utcToLocalTime:'shortDate' | defaultDate}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{progressEvents[idx].lastModifiedUtc | utcToLocalTime | date:'shortDate' | defaultDate}}
|
{{progressEvents[idx].lastModifiedUtc | utcToLocalTime:'shortDate' | defaultDate}}
|
||||||
</td>
|
</td>
|
||||||
<!-- <td>-->
|
<!-- <td>-->
|
||||||
<!-- @if(editMode[idx]) {-->
|
<!-- @if(editMode[idx]) {-->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user