Last PR before Release (#2692)

This commit is contained in:
Joe Milazzo 2024-02-05 18:58:03 -06:00 committed by GitHub
parent 07e96389fb
commit 5cf6077dfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 3801 additions and 2044 deletions

View File

@ -1,5 +1,5 @@
name: Bug Report
description: Help us make Kavita better for everyone by submitting issues you run into while using the program.
description: Help us make Kavita better for everyone by submitting issues you run into while using the program.
title: "Put a short summary of what went wrong here"
labels: ["needs-triage"]
body:
@ -11,7 +11,7 @@ body:
attributes:
label: What happened?
description: Don't forget to tell us what steps you took so we can try to reproduce.
placeholder: Tell us what you see!
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
@ -19,13 +19,13 @@ body:
attributes:
label: What did you expect?
description: What did you expect to happen?
placeholder: Tell us what you expected to see! Go in as much detail as possible so we can confirm if the behavior is something that is broken.
placeholder: Tell us what you expected to see! Go in as much detail as possible so we can confirm if the behavior is something that is broken.
validations:
required: true
- type: dropdown
id: version
attributes:
label: Kavita Version Number - Don't see your version number listed? Then your install is out and date. Please update and see if your issue still persists.
label: Kavita Version Number - Don't see your version number listed? Then your install is out of date. Please update and see if your issue still persists.
multiple: false
options:
- 0.7.13 - Stable
@ -93,4 +93,4 @@ body:
attributes:
label: Additional Notes
description: Any other information about the issue not covered in this form?
placeholder: e.g. Running Kavita on a Raspberry Pi, updating from X version, using LSIO container, etc
placeholder: e.g. Running Kavita on a Raspberry Pi, updating from X version, using LSIO container, etc

View File

@ -3,6 +3,3 @@ contact_links:
- name: Feature Requests
url: https://github.com/Kareadita/Kavita/discussions
about: Suggest an idea for the Kavita project
- name: Report a security vulnerability
url: https://github.com/Kareadita/Kavita/security/advisories/new/
about: Privately report a security vulnerability

View File

@ -76,7 +76,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />

View File

@ -8,11 +8,13 @@ public class ScrobbleEventDto
public int SeriesId { get; set; }
public int LibraryId { get; set; }
public bool IsProcessed { get; set; }
public int? VolumeNumber { get; set; }
public float? VolumeNumber { get; set; }
public int? ChapterNumber { get; set; }
public DateTime LastModifiedUtc { get; set; }
public DateTime CreatedUtc { get; set; }
public float? Rating { get; set; }
public ScrobbleEventType ScrobbleEventType { get; set; }
public bool IsErrored { get; set; }
public string? ErrorDetails { get; set; }
}

View File

@ -38,4 +38,16 @@ public class UpdateNotificationDto
/// Date of the publish
/// </summary>
public required string PublishDate { get; init; }
/// <summary>
/// Is the server on a nightly within this release
/// </summary>
public bool IsOnNightlyInRelease { get; set; }
/// <summary>
/// Is the server on an older version
/// </summary>
public bool IsReleaseNewer { get; set; }
/// <summary>
/// Is the server on this version
/// </summary>
public bool IsReleaseEqual { get; set; }
}

View File

@ -18,50 +18,62 @@ public static class MigrateWantToReadExport
{
public static async Task Migrate(DataContext dataContext, IDirectoryService directoryService, ILogger<Program> logger)
{
var importFile = Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv");
if (File.Exists(importFile))
{
logger.LogCritical(
"Running MigrateWantToReadExport migration - Completed. This is not an error");
return;
}
logger.LogCritical(
"Running MigrateWantToReadExport migration - Please be patient, this may take some time. This is not an error");
await using var command = dataContext.Database.GetDbConnection().CreateCommand();
command.CommandText = "Select AppUserId, Id from Series WHERE AppUserId IS NOT NULL ORDER BY AppUserId;";
await dataContext.Database.OpenConnectionAsync();
await using var result = await command.ExecuteReaderAsync();
await using var writer = new StreamWriter(Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv"));
await using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);
// Write header
csvWriter.WriteField("AppUserId");
csvWriter.WriteField("Id");
await csvWriter.NextRecordAsync();
// Write data
while (await result.ReadAsync())
{
var appUserId = result["AppUserId"].ToString();
var id = result["Id"].ToString();
csvWriter.WriteField(appUserId);
csvWriter.WriteField(id);
await csvWriter.NextRecordAsync();
}
try
{
await dataContext.Database.CloseConnectionAsync();
writer.Close();
} catch (Exception) {/* Swallow */}
var importFile = Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv");
if (File.Exists(importFile))
{
logger.LogCritical(
"Running MigrateWantToReadExport migration - Completed. This is not an error");
return;
}
logger.LogCritical(
"Running MigrateWantToReadExport migration - Completed. This is not an error");
logger.LogCritical(
"Running MigrateWantToReadExport migration - Please be patient, this may take some time. This is not an error");
await using var command = dataContext.Database.GetDbConnection().CreateCommand();
command.CommandText = "Select AppUserId, Id from Series WHERE AppUserId IS NOT NULL ORDER BY AppUserId;";
await dataContext.Database.OpenConnectionAsync();
await using var result = await command.ExecuteReaderAsync();
await using var writer =
new StreamWriter(Path.Join(directoryService.ConfigDirectory, "want-to-read-migration.csv"));
await using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);
// Write header
csvWriter.WriteField("AppUserId");
csvWriter.WriteField("Id");
await csvWriter.NextRecordAsync();
// Write data
while (await result.ReadAsync())
{
var appUserId = result["AppUserId"].ToString();
var id = result["Id"].ToString();
csvWriter.WriteField(appUserId);
csvWriter.WriteField(id);
await csvWriter.NextRecordAsync();
}
try
{
await dataContext.Database.CloseConnectionAsync();
writer.Close();
}
catch (Exception)
{
/* Swallow */
}
logger.LogCritical(
"Running MigrateWantToReadExport migration - Completed. This is not an error");
}
catch (Exception ex)
{
// On new installs, the db isn't setup yet, so this has nothing to do
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class ScrobbleEventError : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<float>(
name: "VolumeNumber",
table: "ScrobbleEvent",
type: "REAL",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AddColumn<string>(
name: "ErrorDetails",
table: "ScrobbleEvent",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsErrored",
table: "ScrobbleEvent",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ErrorDetails",
table: "ScrobbleEvent");
migrationBuilder.DropColumn(
name: "IsErrored",
table: "ScrobbleEvent");
migrationBuilder.AlterColumn<int>(
name: "VolumeNumber",
table: "ScrobbleEvent",
type: "INTEGER",
nullable: true,
oldClrType: typeof(float),
oldType: "REAL",
oldNullable: true);
}
}
}

View File

@ -1522,9 +1522,15 @@ namespace API.Data.Migrations
b.Property<DateTime>("CreatedUtc")
.HasColumnType("TEXT");
b.Property<string>("ErrorDetails")
.HasColumnType("TEXT");
b.Property<int>("Format")
.HasColumnType("INTEGER");
b.Property<bool>("IsErrored")
.HasColumnType("INTEGER");
b.Property<bool>("IsProcessed")
.HasColumnType("INTEGER");
@ -1558,8 +1564,8 @@ namespace API.Data.Migrations
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.Property<int?>("VolumeNumber")
.HasColumnType("INTEGER");
b.Property<float?>("VolumeNumber")
.HasColumnType("REAL");
b.HasKey("Id");

View File

@ -8,6 +8,7 @@ using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
#nullable enable
public interface IAppUserSmartFilterRepository
{
@ -55,6 +56,7 @@ public class AppUserSmartFilterRepository : IAppUserSmartFilterRepository
public async Task<AppUserSmartFilter?> GetById(int smartFilterId)
{
return await _context.AppUserSmartFilter.FirstOrDefaultAsync(d => d.Id == smartFilterId);
return await _context.AppUserSmartFilter
.FirstOrDefaultAsync(d => d.Id == smartFilterId);
}
}

View File

@ -14,6 +14,7 @@ using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
#nullable enable
[Flags]
public enum ChapterIncludes

View File

@ -5,12 +5,14 @@ using System.Threading.Tasks;
using API.Constants;
using API.DTOs;
using API.DTOs.Recommendation;
using API.DTOs.Scrobbling;
using API.DTOs.SeriesDetail;
using API.Entities;
using API.Entities.Enums;
using API.Entities.Metadata;
using API.Extensions;
using API.Extensions.QueryExtensions;
using API.Services.Plus;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.AspNetCore.Identity;
@ -31,8 +33,9 @@ public interface IExternalSeriesMetadataRepository
Task<SeriesDetailPlusDto> GetSeriesDetailPlusDto(int seriesId);
Task LinkRecommendationsToSeries(Series series);
Task<bool> IsBlacklistedSeries(int seriesId);
Task CreateBlacklistedSeries(int seriesId);
Task CreateBlacklistedSeries(int seriesId, bool saveChanges = true);
Task RemoveFromBlacklist(int seriesId);
Task<IList<int>> GetAllSeriesIdsWithoutMetadata(int limit);
}
public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepository
@ -197,14 +200,19 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor
/// Creates a new instance against SeriesId and Saves to the DB
/// </summary>
/// <param name="seriesId"></param>
public async Task CreateBlacklistedSeries(int seriesId)
/// <param name="saveChanges"></param>
public async Task CreateBlacklistedSeries(int seriesId, bool saveChanges = true)
{
if (seriesId <= 0) return;
if (seriesId <= 0 || await _context.SeriesBlacklist.AnyAsync(s => s.SeriesId == seriesId)) return;
await _context.SeriesBlacklist.AddAsync(new SeriesBlacklist()
{
SeriesId = seriesId
});
await _context.SaveChangesAsync();
if (saveChanges)
{
await _context.SaveChangesAsync();
}
}
/// <summary>
@ -224,4 +232,16 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor
await _context.SaveChangesAsync();
}
}
public async Task<IList<int>> GetAllSeriesIdsWithoutMetadata(int limit)
{
return await _context.Series
.Where(s => !ExternalMetadataService.NonEligibleLibraryTypes.Contains(s.Library.Type))
.Where(s => s.ExternalSeriesMetadata == null || s.ExternalSeriesMetadata.ValidUntilUtc < DateTime.UtcNow)
.OrderByDescending(s => s.Library.Type)
.ThenBy(s => s.NormalizedName)
.Select(s => s.Id)
.Take(limit)
.ToListAsync();
}
}

View File

@ -1,348 +1,36 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Kavita - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap');
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;"> Your account's email has been updated on {{InvitingUser}}'s Kavita instance. Click the button to validate your email.</div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">Email Change Update</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Your account's email has been updated on {{InvitingUser}}'s Kavita instance.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please click the following link to validate your email change. The email is not changed until you complete validation.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;CONFIRM EMAIL&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">Email Change Update</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Your account's email has been updated on {{InvitingUser}}'s Kavita instance.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please click the following link to validate your email change. The email is not changed until you complete validation.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;CONFIRM EMAIL&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</table>

View File

@ -1,348 +1,36 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Kavita - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap');
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;"> You have been invited to {{InvitingUser}}'s Kavita instance. Click the button to accept the invite. </div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">You've Been Invited</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">You have been invited to {{InvitingUser}}'s Kavita instance.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please click the following link to setup an account for yourself and start reading.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;ACCEPT INVITE&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">You've Been Invited</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">You have been invited to {{InvitingUser}}'s Kavita instance.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please click the following link to setup an account for yourself and start reading.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;ACCEPT INVITE&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</table>

View File

@ -1,348 +1,36 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Kavita - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url(https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap);
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;"> Email confirmation is required for continued access. Click the button to confirm your email. </div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">Forgot your password?</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">That's okay, it happens! Click on the button below to reset your password.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;RESET YOUR PASSWORD&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
<tr>
<td valign="top" style="text-align: center; padding: 0px 20px 0px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If you did not perform this action, ignore this email.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">Forgot your password?</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">That's okay, it happens! Click on the button below to reset your password.</p>
</td>
</tr>
<tr>
<td valign="top" align="center" style="text-align: center; padding: 15px 0px 20px 0px;">
<!-- Button : BEGIN -->
<center>
<table role="presentation" align="center" cellspacing="0" cellpadding="0" border="0" class="center-on-narrow" style="text-align: center;">
<tr>
<td style="border-radius: 50px; background: #153643; text-align: center;" class="button-td">
<a href="{{Link}}" style="background: #153643; border: 15px solid #153643; font-family: 'Montserrat', sans-serif; font-size: 14px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 50px; font-weight: bold;" class="button-a"> <span style="color:#ffffff;" class="button-link">&nbsp;&nbsp;&nbsp;&nbsp;RESET YOUR PASSWORD&nbsp;&nbsp;&nbsp;&nbsp;</span> </a>
</td>
</tr>
</table>
</center>
<!-- Button : END -->
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 0px 20px 0px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If you did not perform this action, ignore this email.</p>
</td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 12px; line-height: 20px;">
<p style="margin: 0;">If the button above does not work, please find the link here: <a style="color:inherit;margin: 0;width: 100%;word-break: break-all;" href="{{Link}}">{{Link}}</a></p>
</td>
</tr>
</table>

View File

@ -1,325 +1,11 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Event - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap');
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;">
This is a Test Email
</div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">This is a Test Email</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">If you've received this email, your KavitaEmail is setup</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">This is a Test Email</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">If you've received this email, your KavitaEmail is setup</p>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>
</tr>
</table>

View File

@ -1,323 +1,11 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Event - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap');
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;"> You've been sent a file from Kavita! </div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">You sent a file from Kavita</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please find attached the file(s) you've sent.</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>
<table>
<tr>
<td valign="top" style="text-align: center; padding: 20px 0 10px 20px;">
<h1 style="margin: 0; font-family: 'Montserrat', sans-serif; font-size: 30px; line-height: 36px; font-weight: bold;">You sent a file from Kavita</h1> </td>
</tr>
<tr>
<td valign="top" style="text-align: center; padding: 10px 20px 15px 20px; font-family: sans-serif; font-size: 18px; line-height: 20px;">
<p style="margin: 0;">Please find attached the file(s) you've sent.</p>
</td>
</tr>
</table>

View File

@ -0,0 +1,320 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<!-- utf-8 works for most cases -->
<meta name="viewport" content="width=device-width">
<!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting">
<!-- Disable auto-scale in iOS 10 Mail entirely -->
<title>Kavita - [Plain HTML]</title>
<!-- The title tag shows in email notifications, like Android 4.4. -->
<!-- Web Font / @font-face : BEGIN -->
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
<!--[if mso]>
<style>
* {
font-family: Arial, sans-serif !important;
}
</style>
<![endif]-->
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
<!--<![endif]-->
<!-- Web Font / @font-face : END -->
<!-- CSS Reset -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Spartan:wght@500;700&display=swap');
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
table table table {
table-layout: auto;
}
i {
color: #fff;
font-size: 26px;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode: bicubic;
}
/* What it does: A work-around for email clients meddling in triggered links. */
*[x-apple-data-detectors],
/* iOS */
.x-gmail-data-detectors,
/* Gmail */
.x-gmail-data-detectors *,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display: none !important;
}
/* What it does: Prevents underlining the button text in Windows 10 */
.button-link {
text-decoration: none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* Thanks to Eric Lepetit @ericlepetitsf) for help troubleshooting */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
/* iPhone 6 and 6+ */
.email-container {
min-width: 375px !important;
}
}
</style>
<!-- Progressive Enhancements -->
<style>
/* What it does: Hover styles for buttons */
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td:hover,
.button-a:hover {
background: #000000 !important;
border-color: #000000 !important;
color: white !important;
}
/* Media Queries */
@media screen and (max-width: 480px) {
/* What it does: Forces elements to resize to the full width of their container. Useful for resizing images beyond their max-width. */
.fluid {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* What it does: Generic utility class for centering. Useful for images, buttons, and nested tables. */
.center-on-narrow {
text-align: center !important;
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
float: none !important;
}
table.center-on-narrow {
display: inline-block !important;
}
/* What it does: Adjust typography on small screens to improve readability */
.email-container p {
font-size: 17px !important;
line-height: 22px !important;
}
}
</style>
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body width="100%" bgcolor="#F1F1F1" style="margin: 0; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #F1F1F1; text-align: left;">
<!-- Visually Hidden Preheader Text : BEGIN -->
<div style="display:none;font-size:1px;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;mso-hide:all;font-family: sans-serif;"> Your account's email has been updated on {{InvitingUser}}'s Kavita instance. Click the button to validate your email.</div>
<!-- Visually Hidden Preheader Text : END -->
<!--
Set the email width. Defined in two places:
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 680px.
2. MSO tags for Desktop Windows Outlook enforce a 680px width.
Note: The Fluid and Responsive templates have a different width (600px). The hybrid grid is more "fragile", and I've found that 680px is a good width. Change with caution.
-->
<div style="max-width: 680px; margin: auto;" class="email-container">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;" class="email-container">
<!-- HEADER : BEGIN -->
<tr>
<td bgcolor="#4AC694">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="margin-left: -10px;padding: 10px 40px 10px 40px; text-align: center;"> <img src="https://www.kavitareader.com/img/email/logo-256.png" alt="kavita_logo" width="75" style="height:auto;display:inline-block;vertical-align:middle;" />
<div style="min-height:75px;line-height:75px;display:inline-block;vertical-align:middle;color:#fff;font-family: 'Spartan', sans-serif;font-size:2rem;font-weight:700;">Kavita</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- HEADER : END -->
<!-- HERO : BEGIN -->
<tr>
<td bgcolor="#fff" align="center" valign="top" style="text-align: center; background-position: center center !important; background-size: cover !important;">
<!--[if gte mso 9]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px; height:380px; background-position: center center !important;">
<v:fill type="tile" src="background.png" color="#222222" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div>
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center" width="500">
<tr>
<td align="center" valign="middle" width="500">
<![endif]-->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="max-width:500px; margin: auto;">
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
<tr>
<td align="center" valign="middle">
{{Body}}
</td>
</tr>
<tr>
<td height="20" style="font-size:20px; line-height:20px;">&nbsp;</td>
</tr>
</table>
</div>
</td>
</tr>
<tr>
<!-- SOCIAL : BEGIN -->
<tr>
<td bgcolor="#292828">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 15px 30px; text-align: center;">
<table align="center" style="text-align: center;">
<tr>
<td>
<a href="https://discord.gg/b52wT37kt7"><img style="width:25px" src="https://www.kavitareader.com/img/email/discord-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Discord"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://www.reddit.com/r/KavitaManga/"><img style="width:25px" src="https://www.kavitareader.com/img/email/reddit-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Reddit"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://github.com/Kareadita/Kavita/"><img style="width:25px" src="https://www.kavitareader.com/img/email/github-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Github"></a>
</td>
<td width="20">&nbsp;</td>
<td>
<a href="https://opencollective.com/kavita"><img style="width:25px" src="https://www.kavitareader.com/img/email/open-collective-white.png" width="" height="" style="margin:0; padding:0; border:none; display:block;" border="0" alt="Open Collective"></a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- SOCIAL : END -->
</tr>
</table>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
<!-- HERO : END -->
</table>
<!-- Email Body : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
</center>
</body>
</html>

View File

@ -36,12 +36,20 @@ public class ScrobbleEvent : IEntityDate
/// <summary>
/// Depends on the ScrobbleEvent if filled in
/// </summary>
public int? VolumeNumber { get; set; } // TODO: Migrate this to float
public float? VolumeNumber { get; set; }
/// <summary>
/// Has this event been processed and pushed to Provider
/// </summary>
public bool IsProcessed { get; set; }
/// <summary>
/// Was there an error processing this event
/// </summary>
public bool IsErrored { get; set; }
/// <summary>
/// The error details
/// </summary>
public string? ErrorDetails { get; set; }
/// <summary>
/// The date this was processed
/// </summary>
public DateTime? ProcessDateUtc { get; set; }

View File

@ -246,10 +246,15 @@ public class EmailService : IEmailService
};
email.From.Add(new MailboxAddress(smtpConfig.SenderDisplayName, smtpConfig.SenderAddress));
// Inject the body into the base template
var fullBody = UpdatePlaceHolders(await GetEmailBody("base"), new List<KeyValuePair<string, string>>()
{
new ("{{Body}}", userEmailOptions.Body)
});
var body = new BodyBuilder
{
HtmlBody = userEmailOptions.Body
HtmlBody = fullBody
};
if (userEmailOptions.Attachments != null)

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
@ -14,6 +15,7 @@ using API.Entities.Metadata;
using API.Extensions;
using AutoMapper;
using Flurl.Http;
using Hangfire;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
using Kavita.Common.Helpers;
@ -49,6 +51,15 @@ public interface IExternalMetadataService
Task<ExternalSeriesDetailDto?> GetExternalSeriesDetail(int? aniListId, long? malId, int? seriesId);
Task<SeriesDetailPlusDto> GetSeriesDetailPlus(int seriesId, LibraryType libraryType);
Task ForceKavitaPlusRefresh(int seriesId);
Task FetchExternalDataTask();
/// <summary>
/// This is an entry point and provides a level of protection against calling upstream API. Will only allow 100 new
/// series to fetch data within a day and enqueues background jobs at certain times to fetch that data.
/// </summary>
/// <param name="seriesId"></param>
/// <param name="libraryType"></param>
/// <returns></returns>
Task GetNewSeriesData(int seriesId, LibraryType libraryType);
}
public class ExternalMetadataService : IExternalMetadataService
@ -58,6 +69,7 @@ public class ExternalMetadataService : IExternalMetadataService
private readonly IMapper _mapper;
private readonly ILicenseService _licenseService;
private readonly TimeSpan _externalSeriesMetadataCache = TimeSpan.FromDays(30);
public static readonly ImmutableArray<LibraryType> NonEligibleLibraryTypes = ImmutableArray.Create<LibraryType>(LibraryType.Comic);
private readonly SeriesDetailPlusDto _defaultReturn = new()
{
Recommendations = null,
@ -72,6 +84,8 @@ public class ExternalMetadataService : IExternalMetadataService
_mapper = mapper;
_licenseService = licenseService;
FlurlHttp.ConfigureClient(Configuration.KavitaPlusApiUrl, cli =>
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
}
@ -83,7 +97,34 @@ public class ExternalMetadataService : IExternalMetadataService
/// <returns></returns>
public static bool IsPlusEligible(LibraryType type)
{
return type != LibraryType.Comic;
return !NonEligibleLibraryTypes.Contains(type);
}
/// <summary>
/// This is a task that runs on a schedule and slowly fetches data from Kavita+ to keep
/// data in the DB non-stale and fetched.
/// </summary>
/// <remarks>To avoid blasting Kavita+ API, this only processes a few records. The goal is to slowly build </remarks>
/// <returns></returns>
[DisableConcurrentExecution(60 * 60 * 60)]
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public async Task FetchExternalDataTask()
{
// Find all Series that are eligible and limit
var ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetAllSeriesIdsWithoutMetadata(25);
if (ids.Count == 0) return;
_logger.LogInformation("Started Refreshing {Count} series data from Kavita+", ids.Count);
var count = 0;
foreach (var seriesId in ids)
{
// TODO: Rewrite this so it's streamlined and not multiple DB calls
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeBySeriesIdAsync(seriesId);
await GetSeriesDetailPlus(seriesId, libraryType);
await Task.Delay(1500);
count++;
}
_logger.LogInformation("Finished Refreshing {Count} series data from Kavita+", count);
}
/// <summary>
@ -104,6 +145,15 @@ public class ExternalMetadataService : IExternalMetadataService
await _unitOfWork.CommitAsync();
}
[DisableConcurrentExecution(60 * 60 * 60)]
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public Task GetNewSeriesData(int seriesId, LibraryType libraryType)
{
// TODO: Implement this task
if (!IsPlusEligible(libraryType)) return Task.CompletedTask;
return Task.CompletedTask;
}
/// <summary>
/// Retrieves Metadata about a Recommended External Series
/// </summary>
@ -153,6 +203,7 @@ public class ExternalMetadataService : IExternalMetadataService
{
var data = await _unitOfWork.SeriesRepository.GetPlusSeriesDto(seriesId);
if (data == null) return _defaultReturn;
_logger.LogDebug("Fetching Kavita+ Series Detail data for {SeriesName}", data.SeriesName);
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
var result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail")

View File

@ -89,6 +89,9 @@ public class ScrobblingService : IScrobblingService
ScrobbleProvider.AniList
};
private const string UnknownSeriesErrorMessage = "Series cannot be matched for Scrobbling";
private const string AccessTokenErrorMessage = "Access Token needs to be rotated to continue scrobbling";
public ScrobblingService(IUnitOfWork unitOfWork, ITokenService tokenService,
IEventHub eventHub, ILogger<ScrobblingService> logger, ILicenseService licenseService,
@ -374,7 +377,7 @@ public class ScrobblingService : IScrobblingService
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(series.LibraryId);
if (library is not {AllowScrobbling: true}) return true;
if (library.Type == LibraryType.Comic) return true;
if (!ExternalMetadataService.IsPlusEligible(library.Type)) return true;
return false;
}
@ -424,14 +427,18 @@ public class ScrobblingService : IScrobblingService
if (response.ErrorMessage != null && response.ErrorMessage.Contains("Too Many Requests"))
{
_logger.LogInformation("Hit Too many requests, sleeping to regain requests");
await Task.Delay(TimeSpan.FromMinutes(5));
await Task.Delay(TimeSpan.FromMinutes(10));
} else if (response.ErrorMessage != null && response.ErrorMessage.Contains("Unauthorized"))
{
_logger.LogInformation("Kavita+ responded with Unauthorized. Please check your subscription");
_logger.LogCritical("Kavita+ responded with Unauthorized. Please check your subscription");
await _licenseService.HasActiveLicense(true);
evt.IsErrored = true;
evt.ErrorDetails = "Kavita+ subscription no longer active";
throw new KavitaException("Kavita+ responded with Unauthorized. Please check your subscription");
} else if (response.ErrorMessage != null && response.ErrorMessage.Contains("Access token is invalid"))
{
evt.IsErrored = true;
evt.ErrorDetails = AccessTokenErrorMessage;
throw new KavitaException("Access token is invalid");
}
else if (response.ErrorMessage != null && response.ErrorMessage.Contains("Unknown Series"))
@ -442,12 +449,16 @@ public class ScrobblingService : IScrobblingService
{
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{
Comment = "Unknown Series",
Comment = UnknownSeriesErrorMessage,
Details = data.SeriesName,
LibraryId = evt.LibraryId,
SeriesId = evt.SeriesId
});
await _unitOfWork.ExternalSeriesMetadataRepository.CreateBlacklistedSeries(evt.SeriesId, false);
}
evt.IsErrored = true;
evt.ErrorDetails = UnknownSeriesErrorMessage;
} else if (response.ErrorMessage != null && response.ErrorMessage.StartsWith("Review"))
{
// Log the Series name and Id in ScrobbleErrors
@ -462,8 +473,11 @@ public class ScrobblingService : IScrobblingService
SeriesId = evt.SeriesId
});
}
evt.IsErrored = true;
evt.ErrorDetails = "Review was unable to be saved due to upstream requirements";
}
evt.IsErrored = true;
_logger.LogError("Scrobbling failed due to {ErrorMessage}: {SeriesName}", response.ErrorMessage, data.SeriesName);
throw new KavitaException($"Scrobbling failed due to {response.ErrorMessage}: {data.SeriesName}");
}
@ -479,12 +493,14 @@ public class ScrobblingService : IScrobblingService
{
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{
Comment = "Unknown Series",
Comment = UnknownSeriesErrorMessage,
Details = data.SeriesName,
LibraryId = evt.LibraryId,
SeriesId = evt.SeriesId
});
}
evt.IsErrored = true;
evt.ErrorDetails = "Bad payload from Scrobble Provider";
throw new KavitaException("Bad payload from Scrobble Provider");
}
throw;
@ -602,11 +618,10 @@ public class ScrobblingService : IScrobblingService
.ToImmutableHashSet();
var errors = (await _unitOfWork.ScrobbleRepository.GetScrobbleErrors())
.Where(e => e.Comment == "Unknown Series")
.Where(e => e.Comment == "Unknown Series" || e.Comment == UnknownSeriesErrorMessage || e.Comment == AccessTokenErrorMessage)
.Select(e => e.SeriesId)
.ToList();
var readEvents = (await _unitOfWork.ScrobbleRepository.GetByEvent(ScrobbleEventType.ChapterRead))
.Where(e => librariesWithScrobbling.Contains(e.LibraryId))
.Where(e => !errors.Contains(e.SeriesId))
@ -674,7 +689,7 @@ public class ScrobblingService : IScrobblingService
MALId = (int?) evt.MalId,
ScrobbleEventType = evt.ScrobbleEventType,
ChapterNumber = evt.ChapterNumber,
VolumeNumber = evt.VolumeNumber,
VolumeNumber = (int?) evt.VolumeNumber,
AniListToken = evt.AppUser.AniListAccessToken,
SeriesName = evt.Series.Name,
LocalizedSeriesName = evt.Series.LocalizedName,
@ -706,7 +721,7 @@ public class ScrobblingService : IScrobblingService
MALId = (int?) evt.MalId,
ScrobbleEventType = evt.ScrobbleEventType,
ChapterNumber = evt.ChapterNumber,
VolumeNumber = evt.VolumeNumber,
VolumeNumber = (int?) evt.VolumeNumber,
AniListToken = evt.AppUser.AniListAccessToken,
SeriesName = evt.Series.Name,
LocalizedSeriesName = evt.Series.LocalizedName,
@ -770,6 +785,23 @@ public class ScrobblingService : IScrobblingService
return 0;
}
if (await _unitOfWork.ExternalSeriesMetadataRepository.IsBlacklistedSeries(evt.SeriesId))
{
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{
Comment = UnknownSeriesErrorMessage,
Details = $"User: {evt.AppUser.UserName} Series: {evt.Series.Name}",
LibraryId = evt.LibraryId,
SeriesId = evt.SeriesId
});
evt.IsErrored = true;
evt.ErrorDetails = "Series cannot be matched for Scrobbling";
evt.ProcessDateUtc = DateTime.UtcNow;
_unitOfWork.ScrobbleRepository.Update(evt);
await _unitOfWork.CommitAsync();
return 0;
}
var count = await SetAndCheckRateLimit(userRateLimits, evt.AppUser, license.Value);
userRateLimits[evt.AppUserId] = count;
if (count == 0)
@ -796,6 +828,9 @@ public class ScrobblingService : IScrobblingService
if (ex.Message.Contains("Access token is invalid"))
{
_logger.LogCritical("Access Token for UserId: {UserId} needs to be rotated to continue scrobbling", evt.AppUser.Id);
evt.IsErrored = true;
evt.ErrorDetails = AccessTokenErrorMessage;
_unitOfWork.ScrobbleRepository.Update(evt);
return progressCounter;
}
}

View File

@ -56,6 +56,7 @@ public class TaskScheduler : ITaskScheduler
private readonly IMediaConversionService _mediaConversionService;
private readonly IScrobblingService _scrobblingService;
private readonly ILicenseService _licenseService;
private readonly IExternalMetadataService _externalMetadataService;
public static BackgroundJobServer Client => new ();
public const string ScanQueue = "scan";
@ -68,10 +69,11 @@ public class TaskScheduler : ITaskScheduler
public const string BackupTaskId = "backup";
public const string ScanLibrariesTaskId = "scan-libraries";
public const string ReportStatsTaskId = "report-stats";
public const string CheckScrobblingTokens = "check-scrobbling-tokens";
public const string ProcessScrobblingEvents = "process-scrobbling-events";
public const string ProcessProcessedScrobblingEvents = "process-processed-scrobbling-events";
public const string LicenseCheck = "license-check";
public const string CheckScrobblingTokensId = "check-scrobbling-tokens";
public const string ProcessScrobblingEventsId = "process-scrobbling-events";
public const string ProcessProcessedScrobblingEventsId = "process-processed-scrobbling-events";
public const string LicenseCheckId = "license-check";
public const string KavitaPlusDataRefreshId = "kavita+-data-refresh";
private static readonly ImmutableArray<string> ScanTasks =
["ScannerService", "ScanLibrary", "ScanLibraries", "ScanFolder", "ScanSeries"];
@ -88,7 +90,8 @@ public class TaskScheduler : ITaskScheduler
IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService,
ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService,
IThemeService themeService, IWordCountAnalyzerService wordCountAnalyzerService, IStatisticService statisticService,
IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService)
IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService,
IExternalMetadataService externalMetadataService)
{
_cacheService = cacheService;
_logger = logger;
@ -105,6 +108,7 @@ public class TaskScheduler : ITaskScheduler
_mediaConversionService = mediaConversionService;
_scrobblingService = scrobblingService;
_licenseService = licenseService;
_externalMetadataService = externalMetadataService;
}
public async Task ScheduleTasks()
@ -121,7 +125,8 @@ public class TaskScheduler : ITaskScheduler
}
else
{
RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => ScanLibraries(false), Cron.Daily, RecurringJobOptions);
RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => ScanLibraries(false),
Cron.Daily, RecurringJobOptions);
}
setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskBackup)).Value;
@ -134,19 +139,24 @@ public class TaskScheduler : ITaskScheduler
// Override daily and make 2am so that everything on system has cleaned up and no blocking
schedule = Cron.Daily(2);
}
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(), () => schedule, RecurringJobOptions);
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(),
() => schedule, RecurringJobOptions);
}
else
{
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(), Cron.Weekly, RecurringJobOptions);
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(),
Cron.Weekly, RecurringJobOptions);
}
setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskCleanup)).Value;
_logger.LogDebug("Scheduling Cleanup Task for {Setting}", setting);
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(), CronConverter.ConvertToCronNotation(setting), RecurringJobOptions);
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(),
CronConverter.ConvertToCronNotation(setting), RecurringJobOptions);
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(), Cron.Daily, RecurringJobOptions);
RecurringJob.AddOrUpdate(UpdateYearlyStatsTaskId, () => _statisticService.UpdateServerStatistics(), Cron.Monthly, RecurringJobOptions);
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(),
Cron.Daily, RecurringJobOptions);
RecurringJob.AddOrUpdate(UpdateYearlyStatsTaskId, () => _statisticService.UpdateServerStatistics(),
Cron.Monthly, RecurringJobOptions);
await ScheduleKavitaPlusTasks();
}
@ -159,14 +169,23 @@ public class TaskScheduler : ITaskScheduler
{
return;
}
RecurringJob.AddOrUpdate(CheckScrobblingTokens, () => _scrobblingService.CheckExternalAccessTokens(), Cron.Daily, RecurringJobOptions);
RecurringJob.AddOrUpdate(CheckScrobblingTokensId, () => _scrobblingService.CheckExternalAccessTokens(),
Cron.Daily, RecurringJobOptions);
BackgroundJob.Enqueue(() => _scrobblingService.CheckExternalAccessTokens()); // We also kick off an immediate check on startup
RecurringJob.AddOrUpdate(LicenseCheck, () => _licenseService.HasActiveLicense(true), LicenseService.Cron, RecurringJobOptions);
RecurringJob.AddOrUpdate(LicenseCheckId, () => _licenseService.HasActiveLicense(true),
LicenseService.Cron, RecurringJobOptions);
BackgroundJob.Enqueue(() => _licenseService.HasActiveLicense(true));
// KavitaPlus Scrobbling (every 4 hours)
RecurringJob.AddOrUpdate(ProcessScrobblingEvents, () => _scrobblingService.ProcessUpdatesSinceLastSync(), "0 */4 * * *", RecurringJobOptions);
RecurringJob.AddOrUpdate(ProcessProcessedScrobblingEvents, () => _scrobblingService.ClearProcessedEvents(), Cron.Daily, RecurringJobOptions);
RecurringJob.AddOrUpdate(ProcessScrobblingEventsId, () => _scrobblingService.ProcessUpdatesSinceLastSync(),
"0 */4 * * *", RecurringJobOptions);
RecurringJob.AddOrUpdate(ProcessProcessedScrobblingEventsId, () => _scrobblingService.ClearProcessedEvents(),
Cron.Daily, RecurringJobOptions);
// Backfilling/Freshening Reviews/Rating/Recommendations (TODO: This will come in v0.8.x)
// RecurringJob.AddOrUpdate(KavitaPlusDataRefreshId,
// () => _externalMetadataService.FetchExternalDataTask(), Cron.Hourly(Rnd.Next(0, 59)),
// RecurringJobOptions);
}
#region StatsTasks

View File

@ -13,6 +13,7 @@ using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.Helpers.Builders;
using API.Services.Plus;
using API.Services.Tasks.Metadata;
using API.Services.Tasks.Scanner.Parser;
using API.SignalR;
@ -57,6 +58,7 @@ public class ProcessSeries : IProcessSeries
private readonly IWordCountAnalyzerService _wordCountAnalyzerService;
private readonly ICollectionTagService _collectionTagService;
private readonly IReadingListService _readingListService;
private readonly IExternalMetadataService _externalMetadataService;
private Dictionary<string, Genre> _genres;
private IList<Person> _people;
@ -66,7 +68,7 @@ public class ProcessSeries : IProcessSeries
public ProcessSeries(IUnitOfWork unitOfWork, ILogger<ProcessSeries> logger, IEventHub eventHub,
IDirectoryService directoryService, ICacheHelper cacheHelper, IReadingItemService readingItemService,
IFileService fileService, IMetadataService metadataService, IWordCountAnalyzerService wordCountAnalyzerService,
ICollectionTagService collectionTagService, IReadingListService readingListService)
ICollectionTagService collectionTagService, IReadingListService readingListService, IExternalMetadataService externalMetadataService)
{
_unitOfWork = unitOfWork;
_logger = logger;
@ -79,6 +81,7 @@ public class ProcessSeries : IProcessSeries
_wordCountAnalyzerService = wordCountAnalyzerService;
_collectionTagService = collectionTagService;
_readingListService = readingListService;
_externalMetadataService = externalMetadataService;
_genres = new Dictionary<string, Genre>();
@ -236,8 +239,9 @@ public class ProcessSeries : IProcessSeries
if (seriesAdded)
{
// See if any recommendations can link up to the series
// See if any recommendations can link up to the series and pre-fetch external metadata for the series
_logger.LogInformation("Linking up External Recommendations new series (if applicable)");
await _externalMetadataService.GetNewSeriesData(series.Id, series.Library.Type);
await _unitOfWork.ExternalSeriesMetadataRepository.LinkRecommendationsToSeries(series);
await _eventHub.SendMessageAsync(MessageFactory.SeriesAdded,
MessageFactory.SeriesAddedEvent(series.Id, series.Name, series.LibraryId), false);

View File

@ -47,7 +47,7 @@ public interface IVersionUpdaterService
{
Task<UpdateNotificationDto?> CheckForUpdate();
Task PushUpdate(UpdateNotificationDto update);
Task<IEnumerable<UpdateNotificationDto>> GetAllReleases();
Task<IList<UpdateNotificationDto>> GetAllReleases();
Task<int> GetNumberOfReleasesBehind();
}
@ -82,10 +82,21 @@ public class VersionUpdaterService : IVersionUpdaterService
return CreateDto(update);
}
public async Task<IEnumerable<UpdateNotificationDto>> GetAllReleases()
public async Task<IList<UpdateNotificationDto>> GetAllReleases()
{
var updates = await GetGithubReleases();
return updates.Select(CreateDto).Where(d => d != null)!;
var updateDtos = updates.Select(CreateDto)
.Where(d => d != null)
.OrderByDescending(d => d!.PublishDate)
.Select(d => d!)
.ToList();
// Find the latest dto
var latestRelease = updateDtos[0]!;
var isNightly = BuildInfo.Version > new Version(latestRelease.UpdateVersion);
latestRelease.IsOnNightlyInRelease = isNightly;
return updateDtos;
}
public async Task<int> GetNumberOfReleasesBehind()
@ -108,7 +119,9 @@ public class VersionUpdaterService : IVersionUpdaterService
UpdateTitle = update.Name,
UpdateUrl = update.Html_Url,
IsDocker = OsInfo.IsDocker,
PublishDate = update.Published_At
PublishDate = update.Published_At,
IsReleaseEqual = BuildInfo.Version == updateVersion,
IsReleaseNewer = BuildInfo.Version < updateVersion,
};
}

View File

@ -142,8 +142,9 @@ public class TokenService : ITokenService
return jwtClaim?.Value;
}
public bool HasTokenExpired(string token)
public bool HasTokenExpired(string? token)
{
if (string.IsNullOrEmpty(token)) return true;
var tokenHandler = new JwtSecurityTokenHandler();
var tokenContent = tokenHandler.ReadJwtToken(token);
return tokenContent.ValidTo <= DateTime.UtcNow;

294
UI/Web/package-lock.json generated
View File

@ -302,18 +302,6 @@
"node": ">=8.6.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
@ -335,27 +323,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@angular-devkit/build-webpack": {
"version": "0.1701.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1701.0.tgz",
@ -610,39 +577,6 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@angular/cli/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@angular/cli/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@angular/common": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.1.0.tgz",
@ -681,7 +615,6 @@
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.1.0.tgz",
"integrity": "sha512-WDpO4WvC5ItjaRexnpFpKPpT+cu+5GYkWF8h74iHhfxOgU+gaQiMWERHylWCqF25AzmhKu0iI3ZZtaIJ6qqwog==",
"dev": true,
"dependencies": {
"@babel/core": "7.23.2",
"@jridgewell/sourcemap-codec": "^1.4.14",
@ -3728,6 +3661,39 @@
"nx": ">= 16 <= 18"
}
},
"node_modules/@nx/devkit/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@nx/devkit/node_modules/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@nx/devkit/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@nx/nx-darwin-arm64": {
"version": "17.2.8",
"resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-17.2.8.tgz",
@ -4956,39 +4922,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/parser": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz",
@ -5111,18 +5044,6 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@ -5138,27 +5059,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/utils": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz",
@ -5184,39 +5084,6 @@
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz",
@ -5675,7 +5542,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -5921,7 +5787,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -6227,7 +6092,6 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
@ -6476,8 +6340,7 @@
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/cookie": {
"version": "0.5.0",
@ -7367,7 +7230,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
@ -7377,7 +7239,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@ -8457,7 +8318,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@ -9232,7 +9092,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@ -11009,7 +10868,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -11335,6 +11193,18 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/nx/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/nx/node_modules/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz",
@ -11347,6 +11217,21 @@
"node": "*"
}
},
"node_modules/nx/node_modules/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/nx/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -11359,6 +11244,12 @@
"node": ">=8"
}
},
"node_modules/nx/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@ -12010,39 +11901,6 @@
"webpack": "^5.0.0"
}
},
"node_modules/postcss-loader/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/postcss-loader/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/postcss-loader/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
@ -12400,7 +12258,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@ -12411,8 +12268,7 @@
"node_modules/reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
"dev": true
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"node_modules/regenerate": {
"version": "1.4.2",
@ -12864,7 +12720,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"devOptional": true
},
"node_modules/sass": {
"version": "1.69.7",
@ -12977,10 +12833,9 @@
}
},
"node_modules/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dev": true,
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -12995,7 +12850,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@ -13006,8 +12860,7 @@
"node_modules/semver/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/send": {
"version": "0.18.0",
@ -14114,7 +13967,6 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@ -6,4 +6,7 @@ export interface UpdateVersionEvent {
updateUrl: string;
isDocker: boolean;
publishDate: string;
}
isOnNightlyInRelease: boolean;
isReleaseNewer: boolean;
isReleaseEqual: boolean;
}

View File

@ -18,4 +18,10 @@ export interface ScrobbleEvent {
createdUtc: string;
volumeNumber: number | null;
chapterNumber: number | null;
isErrored: boolean;
/**
* Null when not errored
*/
errorDetails: string | null;
}

View File

@ -73,8 +73,13 @@
</ng-container>
</td>
<td>
<i class="fa-regular fa-circle icon" aria-hidden="true" *ngIf="!item.isProcessed"></i>
<i class="fa-solid fa-check-circle icon" aria-hidden="true" *ngIf="item.isProcessed"></i>
@if(item.isProcessed) {
<i class="fa-solid fa-check-circle icon" aria-hidden="true"></i>
} @else if (item.isErrored) {
<i class="fa-solid fa-circle-exclamation icon error" aria-hidden="true" [ngbTooltip]="item.errorDetails"></i>
} @else {
<i class="fa-regular fa-circle icon" aria-hidden="true"></i>
}
<span class="visually-hidden" attr.aria-labelledby="scrobble-history--{{idx}}">
{{item.isProcessed ? t('processed') : t('not-processed')}}
</span>

View File

@ -1,3 +1,7 @@
.icon {
color: var(--primary-color);
}
.error {
color: var(--error-color);
}

View File

@ -5,7 +5,7 @@ import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.se
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {ScrobbleEvent, ScrobbleEventType} from "../../_models/scrobbling/scrobble-event";
import {ScrobbleEventTypePipe} from "../scrobble-event-type.pipe";
import {NgbPagination} from "@ng-bootstrap/ng-bootstrap";
import {NgbPagination, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {ScrobbleEventSortField} from "../../_models/scrobbling/scrobble-event-filter";
import {debounceTime, take} from "rxjs/operators";
import {PaginatedResult, Pagination} from "../../_models/pagination";
@ -20,7 +20,7 @@ import {ToastrService} from "ngx-toastr";
@Component({
selector: 'app-user-scrobble-history',
standalone: true,
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule, DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe],
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule, DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe, NgbTooltip],
templateUrl: './user-scrobble-history.component.html',
styleUrls: ['./user-scrobble-history.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@ -9,9 +9,13 @@
<div class="card w-100 mb-2" style="width: 18rem;">
<div class="card-body">
<h4 class="card-title">{{update.updateTitle}}&nbsp;
<span class="badge bg-secondary" *ngIf="isNightly(update)">{{t('nightly', {version: update.currentVersion})}}</span>
<span class="badge bg-secondary" *ngIf="update.updateVersion === update.currentVersion">{{t('installed')}}</span>
<span class="badge bg-secondary" *ngIf="update.updateVersion > update.currentVersion">{{t('available')}}</span>
@if (update.isOnNightlyInRelease) {
<span class="badge bg-secondary">{{t('nightly', {version: update.currentVersion})}}</span>
} @else if (update.isReleaseEqual) {
<span class="badge bg-secondary">{{t('installed')}}</span>
} @else if (update.isReleaseNewer && indx === 0) {
<span class="badge bg-secondary">{{t('available')}}</span>
}
</h4>
<h6 class="card-subtitle mb-1 mt-1 text-muted">{{t('published-label')}}{{update.publishDate | date: 'short'}}</h6>

View File

@ -28,28 +28,4 @@ export class ChangelogComponent implements OnInit {
this.cdRef.markForCheck();
});
}
isNightly(update: UpdateVersionEvent) {
// Split the version numbers into arrays
const updateVersionArr = update.updateVersion.split('.');
const currentVersionArr = update.currentVersion.split('.');
// Compare the first three parts of the version numbers
for (let i = 0; i < 3; i++) {
const updatePart = parseInt(updateVersionArr[i]);
const currentPart = parseInt(currentVersionArr[i]);
// If any part of the update version is less than the corresponding part of the current version, return true
if (updatePart < currentPart) {
return true;
}
// If any part of the update version is greater than the corresponding part of the current version, return false
else if (updatePart > currentPart) {
return false;
}
}
// If all parts are equal, compare the length of the version numbers
return updateVersionArr.length < currentVersionArr.length;
}
}

View File

@ -30,7 +30,7 @@
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50">
<ng-container *ngFor="let item of webtoonImages | async; let index = index;">
<img src="{{item.src}}" style="display: block"
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}} {{initFinished ? '' : 'full-opacity'}}"
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}"
rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
</ng-container>
</div>

View File

@ -10,9 +10,12 @@
<button class="btn btn-outline-secondary" type="button" (click)="copy()" [title]="t('copy')">
<span class="visually-hidden">Copy</span><i class="fa fa-copy" aria-hidden="true"></i>
</button>
<button class="btn btn-danger" type="button" [ngbTooltip]="tipContent" (click)="refresh()" *ngIf="showRefresh">
<span class="visually-hidden">Regenerate</span><i class="fa fa-sync-alt" aria-hidden="true"></i>
@if (showRefresh) {
<button class="btn btn-danger" type="button" [ngbTooltip]="tipContent" (click)="refresh()">
<span class="visually-hidden">Regenerate</span><i class="fa fa-sync-alt" aria-hidden="true"></i>
</button>
}
<ng-template #tipContent>
{{t('regen-warning')}}
</ng-template>

View File

@ -57,7 +57,9 @@ export class ApiKeyComponent implements OnInit {
key = translate('api-key.no-key');
}
this.showRefresh = !this.accountService.hasReadOnlyRole(user!);
if (this.showRefresh) {
this.showRefresh = !this.accountService.hasReadOnlyRole(user!);
}
if (this.transform != undefined) {
this.key = this.transform(key);

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
},
"version": "0.7.13.18"
"version": "0.7.13.20"
},
"servers": [
{
@ -17345,8 +17345,8 @@
"type": "boolean"
},
"volumeNumber": {
"type": "integer",
"format": "int32",
"type": "number",
"format": "float",
"nullable": true
},
"chapterNumber": {
@ -17377,6 +17377,13 @@
],
"type": "integer",
"format": "int32"
},
"isErrored": {
"type": "boolean"
},
"errorDetails": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
@ -19500,6 +19507,18 @@
"type": "string",
"description": "Date of the publish",
"nullable": true
},
"isOnNightlyInRelease": {
"type": "boolean",
"description": "Is the server on a nightly within this release"
},
"isReleaseNewer": {
"type": "boolean",
"description": "Is the server on an older version"
},
"isReleaseEqual": {
"type": "boolean",
"description": "Is the server on this version"
}
},
"additionalProperties": false,