mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Last PR before Release (#2692)
This commit is contained in:
parent
07e96389fb
commit
5cf6077dfd
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -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
|
||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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
|
@ -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" />
|
||||
|
@ -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; }
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2880
API/Data/Migrations/20240205184724_ScrobbleEventError.Designer.cs
generated
Normal file
2880
API/Data/Migrations/20240205184724_ScrobbleEventError.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
API/Data/Migrations/20240205184724_ScrobbleEventError.cs
Normal file
57
API/Data/Migrations/20240205184724_ScrobbleEventError.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
#nullable enable
|
||||
|
||||
[Flags]
|
||||
public enum ChapterIncludes
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;"> </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"> CONFIRM EMAIL </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;"> </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"> </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"> </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"> </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"> CONFIRM EMAIL </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>
|
||||
|
@ -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;"> </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"> ACCEPT INVITE </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;"> </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"> </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"> </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"> </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"> ACCEPT INVITE </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>
|
||||
|
@ -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;"> </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"> RESET YOUR PASSWORD </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;"> </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"> </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"> </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"> </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"> RESET YOUR PASSWORD </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>
|
||||
|
@ -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;"> </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;"> </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"> </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"> </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"> </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>
|
||||
|
@ -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;"> </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;"> </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"> </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"> </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"> </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>
|
||||
|
320
API/EmailTemplates/base.html
Normal file
320
API/EmailTemplates/base.html
Normal 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;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
{{Body}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="20" style="font-size:20px; line-height:20px;"> </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"> </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"> </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"> </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>
|
@ -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; }
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
294
UI/Web/package-lock.json
generated
@ -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"
|
||||
|
@ -6,4 +6,7 @@ export interface UpdateVersionEvent {
|
||||
updateUrl: string;
|
||||
isDocker: boolean;
|
||||
publishDate: string;
|
||||
}
|
||||
isOnNightlyInRelease: boolean;
|
||||
isReleaseNewer: boolean;
|
||||
isReleaseEqual: boolean;
|
||||
}
|
||||
|
@ -18,4 +18,10 @@ export interface ScrobbleEvent {
|
||||
createdUtc: string;
|
||||
volumeNumber: number | null;
|
||||
chapterNumber: number | null;
|
||||
isErrored: boolean;
|
||||
/**
|
||||
* Null when not errored
|
||||
*/
|
||||
errorDetails: string | null;
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,3 +1,7 @@
|
||||
.icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -9,9 +9,13 @@
|
||||
<div class="card w-100 mb-2" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{update.updateTitle}}
|
||||
<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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
25
openapi.json
25
openapi.json
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user