mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Polish for Release (#3714)
This commit is contained in:
parent
9d9938bce2
commit
c80d046fc7
@ -270,4 +270,15 @@ public class ScrobblingController : BaseApiController
|
|||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has the logged in user ran scrobble generation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("has-ran-scrobble-gen")]
|
||||||
|
public async Task<ActionResult<bool>> HasRanScrobbleGen()
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId());
|
||||||
|
return Ok(user is {HasRunScrobbleEventGeneration: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
using API.DTOs.Account;
|
using API.DTOs.Account;
|
||||||
|
|
||||||
namespace API.DTOs;
|
namespace API.DTOs;
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities.History;
|
||||||
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
|
using Kavita.Common.EnvironmentInfo;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace API.Data.ManualMigrations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v0.8.6 - Manually check when a user triggers scrobble event generation
|
||||||
|
/// </summary>
|
||||||
|
public static class ManualMigrateScrobbleEventGen
|
||||||
|
{
|
||||||
|
public static async Task Migrate(DataContext context, ILogger<Program> logger)
|
||||||
|
{
|
||||||
|
if (await context.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateScrobbleEventGen"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogCritical("Running ManualMigrateScrobbleEventGen migration - Please be patient, this may take some time. This is not an error");
|
||||||
|
|
||||||
|
var users = await context.Users
|
||||||
|
.Where(u => u.AniListAccessToken != null)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
if (await context.ScrobbleEvent.AnyAsync(se => se.AppUserId == user.Id))
|
||||||
|
{
|
||||||
|
user.HasRunScrobbleEventGeneration = true;
|
||||||
|
user.ScrobbleEventGenerationRan = DateTime.UtcNow;
|
||||||
|
context.AppUser.Update(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ChangeTracker.HasChanges())
|
||||||
|
{
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.ManualMigrationHistory.AddAsync(new ManualMigrationHistory()
|
||||||
|
{
|
||||||
|
Name = "ManualMigrateScrobbleEventGen",
|
||||||
|
ProductVersion = BuildInfo.Version.ToString(),
|
||||||
|
RanAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
logger.LogCritical("Running ManualMigrateScrobbleEventGen migration - Completed. This is not an error");
|
||||||
|
}
|
||||||
|
}
|
3409
API/Data/Migrations/20250408222330_ScrobbleGenerationDbCapture.Designer.cs
generated
Normal file
3409
API/Data/Migrations/20250408222330_ScrobbleGenerationDbCapture.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ScrobbleGenerationDbCapture : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "HasRunScrobbleEventGeneration",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "ScrobbleEventGenerationRan",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "HasRunScrobbleEventGeneration",
|
||||||
|
table: "AspNetUsers");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ScrobbleEventGenerationRan",
|
||||||
|
table: "AspNetUsers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -85,6 +85,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("EmailConfirmed")
|
b.Property<bool>("EmailConfirmed")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("HasRunScrobbleEventGeneration")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("LastActive")
|
b.Property<DateTime>("LastActive")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -124,6 +127,9 @@ namespace API.Data.Migrations
|
|||||||
.IsConcurrencyToken()
|
.IsConcurrencyToken()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ScrobbleEventGenerationRan")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("SecurityStamp")
|
b.Property<string>("SecurityStamp")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
@ -76,6 +76,16 @@ public class AppUser : IdentityUser<int>, IHasConcurrencyToken
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? MalAccessToken { get; set; }
|
public string? MalAccessToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has the user ran Scrobble Event Generation
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Only applicable for Kavita+ and when a Token is present</remarks>
|
||||||
|
public bool HasRunScrobbleEventGeneration { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The timestamp of when Scrobble Event Generation ran (Utc)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Kavita+ only</remarks>
|
||||||
|
public DateTime ScrobbleEventGenerationRan { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -624,8 +624,15 @@ public class ScrobblingService : IScrobblingService
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||||
if (user == null || string.IsNullOrEmpty(user.AniListAccessToken)) return;
|
if (user == null || string.IsNullOrEmpty(user.AniListAccessToken)) return;
|
||||||
|
if (user.HasRunScrobbleEventGeneration)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("User {UserName} has already run scrobble event generation, Kavita will not generate more events", user.UserName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var libAllowsScrobbling = (await _unitOfWork.LibraryRepository.GetLibrariesAsync())
|
var libAllowsScrobbling = (await _unitOfWork.LibraryRepository.GetLibrariesAsync())
|
||||||
.ToDictionary(lib => lib.Id, lib => lib.AllowScrobbling);
|
.ToDictionary(lib => lib.Id, lib => lib.AllowScrobbling);
|
||||||
|
|
||||||
@ -667,6 +674,14 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (series.PagesRead <= 0) continue; // Since we only scrobble when things are higher, we can
|
if (series.PagesRead <= 0) continue; // Since we only scrobble when things are higher, we can
|
||||||
await ScrobbleReadingUpdate(uId, series.Id);
|
await ScrobbleReadingUpdate(uId, series.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(uId);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
user.HasRunScrobbleEventGeneration = true;
|
||||||
|
user.ScrobbleEventGenerationRan = DateTime.UtcNow;
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +536,7 @@ public class CoverDbService : ICoverDbService
|
|||||||
if (!string.IsNullOrEmpty(filePath))
|
if (!string.IsNullOrEmpty(filePath))
|
||||||
{
|
{
|
||||||
// Additional check to see if downloaded image is similar and we have a higher resolution
|
// Additional check to see if downloaded image is similar and we have a higher resolution
|
||||||
if (chooseBetterImage)
|
if (chooseBetterImage && !string.IsNullOrEmpty(series.CoverImage))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -287,6 +287,7 @@ public class Startup
|
|||||||
|
|
||||||
// v0.8.6
|
// v0.8.6
|
||||||
await ManualMigrateScrobbleSpecials.Migrate(dataContext, logger);
|
await ManualMigrateScrobbleSpecials.Migrate(dataContext, logger);
|
||||||
|
await ManualMigrateScrobbleEventGen.Migrate(dataContext, logger);
|
||||||
|
|
||||||
// Update the version in the DB after all migrations are run
|
// Update the version in the DB after all migrations are run
|
||||||
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
|
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { AgeRestriction } from '../metadata/age-restriction';
|
import {AgeRestriction} from '../metadata/age-restriction';
|
||||||
import { Library } from '../library/library';
|
import {Library} from '../library/library';
|
||||||
|
|
||||||
export interface Member {
|
export interface Member {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
lastActive: string; // datetime
|
lastActive: string; // datetime
|
||||||
lastActiveUtc: string; // datetime
|
lastActiveUtc: string; // datetime
|
||||||
created: string; // datetime
|
created: string; // datetime
|
||||||
createdUtc: string; // datetime
|
createdUtc: string; // datetime
|
||||||
roles: string[];
|
roles: string[];
|
||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
ageRestriction: AgeRestriction;
|
ageRestriction: AgeRestriction;
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { AgeRestriction } from './metadata/age-restriction';
|
import {AgeRestriction} from './metadata/age-restriction';
|
||||||
import { Preferences } from './preferences/preferences';
|
import {Preferences} from './preferences/preferences';
|
||||||
|
|
||||||
// This interface is only used for login and storing/retrieving JWT from local storage
|
// This interface is only used for login and storing/retrieving JWT from local storage
|
||||||
export interface User {
|
export interface User {
|
||||||
username: string;
|
username: string;
|
||||||
token: string;
|
token: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
preferences: Preferences;
|
preferences: Preferences;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
email: string;
|
email: string;
|
||||||
ageRestriction: AgeRestriction;
|
ageRestriction: AgeRestriction;
|
||||||
|
hasRunScrobbleEventGeneration: boolean;
|
||||||
|
scrobbleEventGenerationRan: string; // datetime
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import { map } from 'rxjs/operators';
|
import {map} from 'rxjs/operators';
|
||||||
import { environment } from 'src/environments/environment';
|
import {environment} from 'src/environments/environment';
|
||||||
import { TextResonse } from '../_types/text-response';
|
import {TextResonse} from '../_types/text-response';
|
||||||
import {ScrobbleError} from "../_models/scrobbling/scrobble-error";
|
import {ScrobbleError} from "../_models/scrobbling/scrobble-error";
|
||||||
import {ScrobbleEvent} from "../_models/scrobbling/scrobble-event";
|
import {ScrobbleEvent} from "../_models/scrobbling/scrobble-event";
|
||||||
import {ScrobbleHold} from "../_models/scrobbling/scrobble-hold";
|
import {ScrobbleHold} from "../_models/scrobbling/scrobble-hold";
|
||||||
@ -56,6 +56,11 @@ export class ScrobblingService {
|
|||||||
return this.httpClient.get<{username: string, accessToken: string}>(this.baseUrl + 'scrobbling/mal-token');
|
return this.httpClient.get<{username: string, accessToken: string}>(this.baseUrl + 'scrobbling/mal-token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hasRunScrobbleGen() {
|
||||||
|
return this.httpClient.get(this.baseUrl + 'scrobbling/has-ran-scrobble-gen ', TextResonse).pipe(map(r => r === 'true'));
|
||||||
|
}
|
||||||
|
|
||||||
getScrobbleErrors() {
|
getScrobbleErrors() {
|
||||||
return this.httpClient.get<Array<ScrobbleError>>(this.baseUrl + 'scrobbling/scrobble-errors');
|
return this.httpClient.get<Array<ScrobbleError>>(this.baseUrl + 'scrobbling/scrobble-errors');
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ export class VersionService implements OnDestroy{
|
|||||||
|
|
||||||
// Check intervals
|
// Check intervals
|
||||||
private readonly VERSION_CHECK_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
private readonly VERSION_CHECK_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
||||||
private readonly OUT_OF_DATE_CHECK_INTERVAL = this.VERSION_CHECK_INTERVAL; // 2 * 60 * 60 * 1000; // 2 hours
|
private readonly OUT_OF_DATE_CHECK_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours
|
||||||
private readonly OUT_Of_BAND_AMOUNT = 2; // How many releases before we show "You're X releases out of date"
|
private readonly OUT_Of_BAND_AMOUNT = 3; // How many releases before we show "You're X releases out of date"
|
||||||
|
|
||||||
// Routes where version update modals should not be shown
|
// Routes where version update modals should not be shown
|
||||||
private readonly EXCLUDED_ROUTES = [
|
private readonly EXCLUDED_ROUTES = [
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
<ng-container *transloco="let t; read:'user-scrobble-history'">
|
<ng-container *transloco="let t; read:'user-scrobble-history'">
|
||||||
|
|
||||||
|
@let currentUser = accountService.currentUser$ | async;
|
||||||
|
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<button class="btn btn-outline-primary position-absolute custom-position" [disabled]="events.length > 0" (click)="generateScrobbleEvents()" [title]="t('generate-scrobble-events')">
|
<button class="btn btn-outline-primary position-absolute custom-position" [disabled]="hasRunScrobbleGen" (click)="generateScrobbleEvents()" [title]="t('generate-scrobble-events')">
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden ms-1">{{t('generate-scrobble-events')}}</span>
|
<i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden ms-1">{{t('generate-scrobble-events')}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (tokenExpired) {
|
@if (tokenExpired) {
|
||||||
<p class="alert alert-warning">{{t('token-expired')}}</p>
|
<p class="alert alert-warning">{{t('token-expired')}}</p>
|
||||||
} @else if (!(accountService.currentUser$ | async)!.preferences.aniListScrobblingEnabled) {
|
} @else if (!currentUser!.preferences.aniListScrobblingEnabled) {
|
||||||
<p class="alert alert-warning">{{t('scrobbling-disabled')}}</p>
|
<p class="alert alert-warning">{{t('scrobbling-disabled')}}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ export interface DataTablePage {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-scrobble-history',
|
selector: 'app-user-scrobble-history',
|
||||||
imports: [ScrobbleEventTypePipe, ReactiveFormsModule, TranslocoModule,
|
imports: [ScrobbleEventTypePipe, ReactiveFormsModule, TranslocoModule,
|
||||||
DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe, NgbTooltip, NgxDatatableModule, AsyncPipe],
|
DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe, NgbTooltip, NgxDatatableModule, AsyncPipe],
|
||||||
templateUrl: './user-scrobble-history.component.html',
|
templateUrl: './user-scrobble-history.component.html',
|
||||||
styleUrls: ['./user-scrobble-history.component.scss'],
|
styleUrls: ['./user-scrobble-history.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@ -66,12 +66,18 @@ export class UserScrobbleHistoryComponent implements OnInit {
|
|||||||
column: 'lastModifiedUtc',
|
column: 'lastModifiedUtc',
|
||||||
direction: 'desc'
|
direction: 'desc'
|
||||||
};
|
};
|
||||||
|
hasRunScrobbleGen: boolean = false;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.pageInfo.pageNumber = 0;
|
this.pageInfo.pageNumber = 0;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
||||||
|
this.scrobblingService.hasRunScrobbleGen().subscribe(res => {
|
||||||
|
this.hasRunScrobbleGen = res;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
})
|
||||||
|
|
||||||
this.scrobblingService.hasTokenExpired(ScrobbleProvider.AniList).subscribe(hasExpired => {
|
this.scrobblingService.hasTokenExpired(ScrobbleProvider.AniList).subscribe(hasExpired => {
|
||||||
this.tokenExpired = hasExpired;
|
this.tokenExpired = hasExpired;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
<div class="d-flex list-container">
|
<div class="d-flex list-container">
|
||||||
<ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: true }"></ng-container>
|
<ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: true }"></ng-container>
|
||||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||||
<ng-container [ngTemplateOutlet]="removeBtn" [ngTemplateOutletContext]="{$implicit: item, idx: i}"></ng-container>
|
|
||||||
|
@if (showRemoveButton) {
|
||||||
|
<ng-container [ngTemplateOutlet]="removeBtn" [ngTemplateOutletContext]="{$implicit: item, idx: i}"></ng-container>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -233,9 +233,13 @@
|
|||||||
|
|
||||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
||||||
[disabled]="!(formGroup.get('edit')?.value || false)" [showRemoveButton]="formGroup.get('edit')?.value || false">
|
[disabled]="!(formGroup.get('edit')?.value || false)" [showRemoveButton]="formGroup.get('edit')?.value || false">
|
||||||
|
|
||||||
<ng-template #draggableItem let-item let-position="idx">
|
<ng-template #draggableItem let-item let-position="idx">
|
||||||
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item"
|
||||||
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)" [showRead]="!(formGroup.get('edit')?.value || false)"></app-reading-list-item>
|
[position]="position" [libraryTypes]="libraryTypes"
|
||||||
|
[promoted]="item.promoted" (read)="readChapter($event)"
|
||||||
|
(remove)="itemRemoved($event, position)"
|
||||||
|
[showRemove]="false"/>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-draggable-ordered-list>
|
</app-draggable-ordered-list>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
.non-virtualized-container {
|
.non-virtualized-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 140px;
|
|
||||||
height: 140px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-toggle-split {
|
.dropdown-toggle-split {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<ng-container *transloco="let t; read: 'reading-list-item'">
|
<ng-container *transloco="let t; read: 'reading-list-item'">
|
||||||
<div class="d-flex flex-row g-0 mb-2 reading-list-item">
|
<div class="d-flex flex-row g-0reading-list-item">
|
||||||
<div class="d-none d-md-block pe-2">
|
<div class="d-none d-md-block pe-2">
|
||||||
<app-image width="106px" [styles]="{'max-height': '125px'}" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
<app-image width="106px" [styles]="{'max-height': '125px'}" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
||||||
@if (item.pagesRead === 0 && item.pagesTotal > 0) {
|
@if (item.pagesRead === 0 && item.pagesTotal > 0) {
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}">{{item.seriesName}}</a>
|
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}">{{item.seriesName}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-read-more [text]="item.summary || ''"></app-read-more>
|
<app-read-more [text]="item.summary || ''" [showToggle]="false" [maxLength]="500"></app-read-more>
|
||||||
|
|
||||||
|
|
||||||
@if (item.releaseDate !== '0001-01-01T00:00:00') {
|
@if (item.releaseDate !== '0001-01-01T00:00:00') {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user