
* Fixed bookmarking failing to convert to webp * Brought the ag-swipe/ng-swipe code into Kavita due to being abandoned by developer and angular requirements. * Fixed average reading time per week finally * Cleaned up some extra decimals on time duration pipe * Don't try to update index.html for base url on local. Fixed ag-swipe on prod mode. * Updated a link on theme manager to point to the new github * Range knobs should be primary color on firefox too * Implemented the ability to get thumbnails of pages inside an archive or pdf. * Updated packages and fixed opds-ps 1.2 issue * Fixed lock file * Allow Kavita's Swagger to hit instances with CORS * Added IP/Request logging for Security Audits * Linked up Summary tag from CBL into Kavita. * Redid the migration so SecurityEvent now has UTC date as well. * Split security logging to a separate file * Update to new versions of checkout and setup * Added a PR check on PR body to ensure that it doesn't contain any characters that break our discord hook. * Updating action * optimize regex in action * Fixed an issue where fit to width would cause the actual height of the image to be shown for pagination bars, instead of rendered. * Added some new code in GetPageFromFiles to ensure pages that exceed array map down to last file. * Added comment about robots * Fixed up unit tests for new ReaderService signature * Kavita now cleans up empty reading lists at night * Don't allow nightly cleanup to run if we are running media conversion tasks * Fixed some bugs in typeahead, it should behave much more reliably. * Fix an issue where emulate comic book wasn't extending to the bottom properly * Added support for Series Chapter 001 Volume 001 * Refactor XFrameOptions="SameOrigins" out to allow users to override in appsettings.json. * Added a rate limiter for some endpoints, but it doesn't seem to be triggering --------- Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
212 lines
6.4 KiB
TypeScript
212 lines
6.4 KiB
TypeScript
import { HttpParams } from '@angular/common/http';
|
|
import { Injectable } from '@angular/core';
|
|
import { Chapter } from 'src/app/_models/chapter';
|
|
import { LibraryType } from 'src/app/_models/library';
|
|
import { MangaFormat } from 'src/app/_models/manga-format';
|
|
import { PaginatedResult } from 'src/app/_models/pagination';
|
|
import { Series } from 'src/app/_models/series';
|
|
import { Volume } from 'src/app/_models/volume';
|
|
|
|
export enum KEY_CODES {
|
|
RIGHT_ARROW = 'ArrowRight',
|
|
LEFT_ARROW = 'ArrowLeft',
|
|
DOWN_ARROW = 'ArrowDown',
|
|
UP_ARROW = 'ArrowUp',
|
|
ESC_KEY = 'Escape',
|
|
SPACE = ' ',
|
|
ENTER = 'Enter',
|
|
G = 'g',
|
|
B = 'b',
|
|
F = 'f',
|
|
H = 'h',
|
|
BACKSPACE = 'Backspace',
|
|
DELETE = 'Delete',
|
|
SHIFT = 'Shift'
|
|
}
|
|
|
|
export enum Breakpoint {
|
|
Mobile = 768,
|
|
Tablet = 1280,
|
|
Desktop = 1440
|
|
}
|
|
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class UtilityService {
|
|
|
|
mangaFormatKeys: string[] = [];
|
|
|
|
constructor() { }
|
|
|
|
sortVolumes = (a: Volume, b: Volume) => {
|
|
if (a === b) { return 0; }
|
|
else if (a.number === 0) { return 1; }
|
|
else if (b.number === 0) { return -1; }
|
|
else {
|
|
return a.number < b.number ? -1 : 1;
|
|
}
|
|
}
|
|
|
|
sortChapters = (a: Chapter, b: Chapter) => {
|
|
return parseFloat(a.number) - parseFloat(b.number);
|
|
}
|
|
|
|
mangaFormatToText(format: MangaFormat): string {
|
|
if (this.mangaFormatKeys === undefined || this.mangaFormatKeys.length === 0) {
|
|
this.mangaFormatKeys = Object.keys(MangaFormat);
|
|
}
|
|
|
|
return this.mangaFormatKeys.filter(item => MangaFormat[format] === item)[0];
|
|
}
|
|
|
|
/**
|
|
* Formats a Chapter name based on the library it's in
|
|
* @param libraryType
|
|
* @param includeHash For comics only, includes a # which is used for numbering on cards
|
|
* @param includeSpace Add a space at the end of the string. if includeHash and includeSpace are true, only hash will be at the end.
|
|
* @returns
|
|
*/
|
|
formatChapterName(libraryType: LibraryType, includeHash: boolean = false, includeSpace: boolean = false) {
|
|
switch(libraryType) {
|
|
case LibraryType.Book:
|
|
return 'Book' + (includeSpace ? ' ' : '');
|
|
case LibraryType.Comic:
|
|
if (includeHash) {
|
|
return 'Issue #';
|
|
}
|
|
return 'Issue' + (includeSpace ? ' ' : '');
|
|
case LibraryType.Manga:
|
|
return 'Chapter' + (includeSpace ? ' ' : '');
|
|
}
|
|
}
|
|
|
|
cleanSpecialTitle(title: string) {
|
|
let cleaned = title.replace(/_/g, ' ').replace(/SP\d+/g, '').trim();
|
|
cleaned = cleaned.substring(0, cleaned.lastIndexOf('.'));
|
|
if (cleaned.trim() === '') {
|
|
return title;
|
|
}
|
|
return cleaned;
|
|
}
|
|
|
|
filter(input: string, filter: string): boolean {
|
|
if (input === null || filter === null || input === undefined || filter === undefined) return false;
|
|
const reg = /[_\.\-]/gi;
|
|
return input.toUpperCase().replace(reg, '').includes(filter.toUpperCase().replace(reg, ''));
|
|
}
|
|
|
|
filterMatches(input: string, filter: string): boolean {
|
|
if (input === null || filter === null || input === undefined || filter === undefined) return false;
|
|
const reg = /[_\.\-]/gi;
|
|
return input.toUpperCase().replace(reg, '') === filter.toUpperCase().replace(reg, '');
|
|
}
|
|
|
|
isVolume(d: any) {
|
|
return d != null && d.hasOwnProperty('chapters');
|
|
}
|
|
|
|
isChapter(d: any) {
|
|
return d != null && d.hasOwnProperty('volumeId');
|
|
}
|
|
|
|
isSeries(d: any) {
|
|
return d != null && d.hasOwnProperty('originalName');
|
|
}
|
|
|
|
asVolume(d: any) {
|
|
return <Volume>d;
|
|
}
|
|
|
|
asChapter(d: any) {
|
|
return <Chapter>d;
|
|
}
|
|
|
|
asSeries(d: any) {
|
|
return <Series>d;
|
|
}
|
|
|
|
getActiveBreakpoint(): Breakpoint {
|
|
if (window.innerWidth <= Breakpoint.Mobile) return Breakpoint.Mobile;
|
|
else if (window.innerWidth > Breakpoint.Mobile && window.innerWidth <= Breakpoint.Tablet) return Breakpoint.Tablet;
|
|
else if (window.innerWidth > Breakpoint.Tablet) return Breakpoint.Desktop
|
|
|
|
return Breakpoint.Desktop;
|
|
}
|
|
|
|
isInViewport(element: Element, additionalTopOffset: number = 0) {
|
|
const rect = element.getBoundingClientRect();
|
|
return (
|
|
rect.top >= additionalTopOffset &&
|
|
rect.left >= 0 &&
|
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
);
|
|
}
|
|
|
|
deepEqual(object1: any, object2: any) {
|
|
if ((object1 === null || object1 === undefined) && (object2 !== null || object2 !== undefined)) return false;
|
|
if ((object2 === null || object2 === undefined) && (object1 !== null || object1 !== undefined)) return false;
|
|
if (object1 === null && object2 === null) return true;
|
|
if (object1 === undefined && object2 === undefined) return true;
|
|
|
|
|
|
const keys1 = Object.keys(object1);
|
|
const keys2 = Object.keys(object2);
|
|
if (keys1.length !== keys2.length) {
|
|
return false;
|
|
}
|
|
for (const key of keys1) {
|
|
const val1 = object1[key];
|
|
const val2 = object2[key];
|
|
const areObjects = this.isObject(val1) && this.isObject(val2);
|
|
if (
|
|
areObjects && !this.deepEqual(val1, val2) ||
|
|
!areObjects && val1 !== val2
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
private isObject(object: any) {
|
|
return object != null && typeof object === 'object';
|
|
}
|
|
|
|
addPaginationIfExists(params: HttpParams, pageNum?: number, itemsPerPage?: number) {
|
|
if (pageNum !== null && pageNum !== undefined && itemsPerPage !== null && itemsPerPage !== undefined) {
|
|
params = params.append('pageNumber', pageNum + '');
|
|
params = params.append('pageSize', itemsPerPage + '');
|
|
}
|
|
return params;
|
|
}
|
|
|
|
createPaginatedResult(response: any, paginatedVariable: PaginatedResult<any[]> | undefined = undefined) {
|
|
if (paginatedVariable === undefined) {
|
|
paginatedVariable = new PaginatedResult();
|
|
}
|
|
if (response.body === null) {
|
|
paginatedVariable.result = [];
|
|
} else {
|
|
paginatedVariable.result = response.body;
|
|
}
|
|
|
|
const pageHeader = response.headers?.get('Pagination');
|
|
if (pageHeader !== null) {
|
|
paginatedVariable.pagination = JSON.parse(pageHeader);
|
|
}
|
|
|
|
return paginatedVariable;
|
|
}
|
|
|
|
getWindowDimensions() {
|
|
const windowWidth = window.innerWidth
|
|
|| document.documentElement.clientWidth
|
|
|| document.body.clientWidth;
|
|
const windowHeight = window.innerHeight
|
|
|| document.documentElement.clientHeight
|
|
|| document.body.clientHeight;
|
|
return [windowWidth, windowHeight];
|
|
}
|
|
}
|