Kavita/UI/Web/src/app/shared/_services/utility.service.ts
Joe Milazzo c10acb1279
Security Event Logging & Bugfixes (#1882)
* 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>
2023-03-16 13:57:34 -07:00

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];
}
}