Angular 15 (#1764)

* Updated ngx-virtual-scroller

* Removed the karma test config as it's breaking migration

* Reverted to pre angular 15

* Upgraded packages and reverted target to ES6 for older devices

* It's broken. Need to also find the safari version for old Ipads

* Fixes some code in default pipe and many updates to packages. Removed support for old iOS versions as it restricted Kavita from using newer features. Build still broken.

* More progress in getting build working on Angular 15. Removed polyfills.ts for new angular config

* Remove all.css for icons and use scss instead

* Removed stuff that isn't needed

* Migrated extended linting to eslint, ran on project and updated issues. Removed a duplicate component that did nothing. Fixed a few places where lifecycle hooks werent being called as interface wasn't implemented.

* App builds correctly. Source maps are still needed.

* Fixed source maps and removed more testing stuff. I will re-add later in another release when I figure out how to properly tackle dependencies on backend.

* Reverted back to old source map definition
This commit is contained in:
Joe Milazzo 2023-01-30 06:27:52 -08:00 committed by GitHub
parent 12d0ae6f2c
commit f64f232e51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 7393 additions and 7937 deletions

16
UI/Web/.editorconfig Normal file
View File

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

51
UI/Web/.eslintrc.json Normal file
View File

@ -0,0 +1,51 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"prefix": "app",
"style": "kebab-case",
"type": "element"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"prefix": "app",
"style": "camelCase",
"type": "attribute"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@ -1,4 +0,0 @@
{
"cookies": [],
"origins": []
}

View File

@ -1,7 +1,10 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": "6b518972-3ce0-486d-bc55-740bf8308c77"
"analytics": "6b518972-3ce0-486d-bc55-740bf8308c77",
"schematicCollections": [
"@angular-eslint/schematics"
]
},
"version": 1,
"newProjectRoot": "projects",
@ -26,7 +29,10 @@
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"zone.js"
],
"inlineStyleLanguage": "scss",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/assets",
@ -37,11 +43,6 @@
"output": "/assets/"
}
],
"sourceMap": {
"hidden": false,
"scripts": true,
"styles": true
},
"styles": [
"src/styles.scss",
"node_modules/@fortawesome/fontawesome-free/css/all.min.css"
@ -51,6 +52,11 @@
"node_modules/lazysizes/plugins/rias/ls.rias.min.js",
"node_modules/lazysizes/plugins/attrchange/ls.attrchange.min.js"
],
"sourceMap": {
"hidden": false,
"scripts": true,
"styles": true
},
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
@ -106,47 +112,14 @@
"browserTarget": "kavita-webui:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/assets",
"src/site.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-eslint/builder:lint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "kavita-webui:serve"
},
"configurations": {
"production": {
"devServerTarget": "kavita-webui:serve:production"
}
}
}
}
}

View File

@ -1,398 +0,0 @@
// import { test, expect, Page } from '@playwright/test';
// test.beforeEach(async ({ page }) => {
// await page.goto('https://demo.playwright.dev/todomvc');
// });
// const TODO_ITEMS = [
// 'buy some cheese',
// 'feed the cat',
// 'book a doctors appointment'
// ];
// test.describe('New Todo', () => {
// test('should allow me to add todo items', async ({ page }) => {
// // Create 1st todo.
// await page.locator('.new-todo').fill(TODO_ITEMS[0]);
// await page.locator('.new-todo').press('Enter');
// // Make sure the list only has one todo item.
// await expect(page.locator('.view label')).toHaveText([
// TODO_ITEMS[0]
// ]);
// // Create 2nd todo.
// await page.locator('.new-todo').fill(TODO_ITEMS[1]);
// await page.locator('.new-todo').press('Enter');
// // Make sure the list now has two todo items.
// await expect(page.locator('.view label')).toHaveText([
// TODO_ITEMS[0],
// TODO_ITEMS[1]
// ]);
// await checkNumberOfTodosInLocalStorage(page, 2);
// });
// test('should clear text input field when an item is added', async ({ page }) => {
// // Create one todo item.
// await page.locator('.new-todo').fill(TODO_ITEMS[0]);
// await page.locator('.new-todo').press('Enter');
// // Check that input is empty.
// await expect(page.locator('.new-todo')).toBeEmpty();
// await checkNumberOfTodosInLocalStorage(page, 1);
// });
// test('should append new items to the bottom of the list', async ({ page }) => {
// // Create 3 items.
// await createDefaultTodos(page);
// // Check test using different methods.
// await expect(page.locator('.todo-count')).toHaveText('3 items left');
// await expect(page.locator('.todo-count')).toContainText('3');
// await expect(page.locator('.todo-count')).toHaveText(/3/);
// // Check all items in one call.
// await expect(page.locator('.view label')).toHaveText(TODO_ITEMS);
// await checkNumberOfTodosInLocalStorage(page, 3);
// });
// test('should show #main and #footer when items added', async ({ page }) => {
// await page.locator('.new-todo').fill(TODO_ITEMS[0]);
// await page.locator('.new-todo').press('Enter');
// await expect(page.locator('.main')).toBeVisible();
// await expect(page.locator('.footer')).toBeVisible();
// await checkNumberOfTodosInLocalStorage(page, 1);
// });
// });
// test.describe('Mark all as completed', () => {
// test.beforeEach(async ({ page }) => {
// await createDefaultTodos(page);
// await checkNumberOfTodosInLocalStorage(page, 3);
// });
// test.afterEach(async ({ page }) => {
// await checkNumberOfTodosInLocalStorage(page, 3);
// });
// test('should allow me to mark all items as completed', async ({ page }) => {
// // Complete all todos.
// await page.locator('.toggle-all').check();
// // Ensure all todos have 'completed' class.
// await expect(page.locator('.todo-list li')).toHaveClass(['completed', 'completed', 'completed']);
// await checkNumberOfCompletedTodosInLocalStorage(page, 3);
// });
// test('should allow me to clear the complete state of all items', async ({ page }) => {
// // Check and then immediately uncheck.
// await page.locator('.toggle-all').check();
// await page.locator('.toggle-all').uncheck();
// // Should be no completed classes.
// await expect(page.locator('.todo-list li')).toHaveClass(['', '', '']);
// });
// test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => {
// const toggleAll = page.locator('.toggle-all');
// await toggleAll.check();
// await expect(toggleAll).toBeChecked();
// await checkNumberOfCompletedTodosInLocalStorage(page, 3);
// // Uncheck first todo.
// const firstTodo = page.locator('.todo-list li').nth(0);
// await firstTodo.locator('.toggle').uncheck();
// // Reuse toggleAll locator and make sure its not checked.
// await expect(toggleAll).not.toBeChecked();
// await firstTodo.locator('.toggle').check();
// await checkNumberOfCompletedTodosInLocalStorage(page, 3);
// // Assert the toggle all is checked again.
// await expect(toggleAll).toBeChecked();
// });
// });
// test.describe('Item', () => {
// test('should allow me to mark items as complete', async ({ page }) => {
// // Create two items.
// for (const item of TODO_ITEMS.slice(0, 2)) {
// await page.locator('.new-todo').fill(item);
// await page.locator('.new-todo').press('Enter');
// }
// // Check first item.
// const firstTodo = page.locator('.todo-list li').nth(0);
// await firstTodo.locator('.toggle').check();
// await expect(firstTodo).toHaveClass('completed');
// // Check second item.
// const secondTodo = page.locator('.todo-list li').nth(1);
// await expect(secondTodo).not.toHaveClass('completed');
// await secondTodo.locator('.toggle').check();
// // Assert completed class.
// await expect(firstTodo).toHaveClass('completed');
// await expect(secondTodo).toHaveClass('completed');
// });
// test('should allow me to un-mark items as complete', async ({ page }) => {
// // Create two items.
// for (const item of TODO_ITEMS.slice(0, 2)) {
// await page.locator('.new-todo').fill(item);
// await page.locator('.new-todo').press('Enter');
// }
// const firstTodo = page.locator('.todo-list li').nth(0);
// const secondTodo = page.locator('.todo-list li').nth(1);
// await firstTodo.locator('.toggle').check();
// await expect(firstTodo).toHaveClass('completed');
// await expect(secondTodo).not.toHaveClass('completed');
// await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// await firstTodo.locator('.toggle').uncheck();
// await expect(firstTodo).not.toHaveClass('completed');
// await expect(secondTodo).not.toHaveClass('completed');
// await checkNumberOfCompletedTodosInLocalStorage(page, 0);
// });
// test('should allow me to edit an item', async ({ page }) => {
// await createDefaultTodos(page);
// const todoItems = page.locator('.todo-list li');
// const secondTodo = todoItems.nth(1);
// await secondTodo.dblclick();
// await expect(secondTodo.locator('.edit')).toHaveValue(TODO_ITEMS[1]);
// await secondTodo.locator('.edit').fill('buy some sausages');
// await secondTodo.locator('.edit').press('Enter');
// // Explicitly assert the new text value.
// await expect(todoItems).toHaveText([
// TODO_ITEMS[0],
// 'buy some sausages',
// TODO_ITEMS[2]
// ]);
// await checkTodosInLocalStorage(page, 'buy some sausages');
// });
// });
// test.describe('Editing', () => {
// test.beforeEach(async ({ page }) => {
// await createDefaultTodos(page);
// await checkNumberOfTodosInLocalStorage(page, 3);
// });
// test('should hide other controls when editing', async ({ page }) => {
// const todoItem = page.locator('.todo-list li').nth(1);
// await todoItem.dblclick();
// await expect(todoItem.locator('.toggle')).not.toBeVisible();
// await expect(todoItem.locator('label')).not.toBeVisible();
// await checkNumberOfTodosInLocalStorage(page, 3);
// });
// test('should save edits on blur', async ({ page }) => {
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(1).dblclick();
// await todoItems.nth(1).locator('.edit').fill('buy some sausages');
// await todoItems.nth(1).locator('.edit').dispatchEvent('blur');
// await expect(todoItems).toHaveText([
// TODO_ITEMS[0],
// 'buy some sausages',
// TODO_ITEMS[2],
// ]);
// await checkTodosInLocalStorage(page, 'buy some sausages');
// });
// test('should trim entered text', async ({ page }) => {
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(1).dblclick();
// await todoItems.nth(1).locator('.edit').fill(' buy some sausages ');
// await todoItems.nth(1).locator('.edit').press('Enter');
// await expect(todoItems).toHaveText([
// TODO_ITEMS[0],
// 'buy some sausages',
// TODO_ITEMS[2],
// ]);
// await checkTodosInLocalStorage(page, 'buy some sausages');
// });
// test('should remove the item if an empty text string was entered', async ({ page }) => {
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(1).dblclick();
// await todoItems.nth(1).locator('.edit').fill('');
// await todoItems.nth(1).locator('.edit').press('Enter');
// await expect(todoItems).toHaveText([
// TODO_ITEMS[0],
// TODO_ITEMS[2],
// ]);
// });
// test('should cancel edits on escape', async ({ page }) => {
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(1).dblclick();
// await todoItems.nth(1).locator('.edit').press('Escape');
// await expect(todoItems).toHaveText(TODO_ITEMS);
// });
// });
// test.describe('Counter', () => {
// test('should display the current number of todo items', async ({ page }) => {
// await page.locator('.new-todo').fill(TODO_ITEMS[0]);
// await page.locator('.new-todo').press('Enter');
// await expect(page.locator('.todo-count')).toContainText('1');
// await page.locator('.new-todo').fill(TODO_ITEMS[1]);
// await page.locator('.new-todo').press('Enter');
// await expect(page.locator('.todo-count')).toContainText('2');
// await checkNumberOfTodosInLocalStorage(page, 2);
// });
// });
// test.describe('Clear completed button', () => {
// test.beforeEach(async ({ page }) => {
// await createDefaultTodos(page);
// });
// test('should display the correct text', async ({ page }) => {
// await page.locator('.todo-list li .toggle').first().check();
// await expect(page.locator('.clear-completed')).toHaveText('Clear completed');
// });
// test('should remove completed items when clicked', async ({ page }) => {
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(1).locator('.toggle').check();
// await page.locator('.clear-completed').click();
// await expect(todoItems).toHaveCount(2);
// await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
// });
// test('should be hidden when there are no items that are completed', async ({ page }) => {
// await page.locator('.todo-list li .toggle').first().check();
// await page.locator('.clear-completed').click();
// await expect(page.locator('.clear-completed')).toBeHidden();
// });
// });
// test.describe('Persistence', () => {
// test('should persist its data', async ({ page }) => {
// for (const item of TODO_ITEMS.slice(0, 2)) {
// await page.locator('.new-todo').fill(item);
// await page.locator('.new-todo').press('Enter');
// }
// const todoItems = page.locator('.todo-list li');
// await todoItems.nth(0).locator('.toggle').check();
// await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
// await expect(todoItems).toHaveClass(['completed', '']);
// // Ensure there is 1 completed item.
// checkNumberOfCompletedTodosInLocalStorage(page, 1);
// // Now reload.
// await page.reload();
// await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
// await expect(todoItems).toHaveClass(['completed', '']);
// });
// });
// test.describe('Routing', () => {
// test.beforeEach(async ({ page }) => {
// await createDefaultTodos(page);
// // make sure the app had a chance to save updated todos in storage
// // before navigating to a new view, otherwise the items can get lost :(
// // in some frameworks like Durandal
// await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
// });
// test('should allow me to display active items', async ({ page }) => {
// await page.locator('.todo-list li .toggle').nth(1).check();
// await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// await page.locator('.filters >> text=Active').click();
// await expect(page.locator('.todo-list li')).toHaveCount(2);
// await expect(page.locator('.todo-list li')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
// });
// test('should respect the back button', async ({ page }) => {
// await page.locator('.todo-list li .toggle').nth(1).check();
// await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// await test.step('Showing all items', async () => {
// await page.locator('.filters >> text=All').click();
// await expect(page.locator('.todo-list li')).toHaveCount(3);
// });
// await test.step('Showing active items', async () => {
// await page.locator('.filters >> text=Active').click();
// });
// await test.step('Showing completed items', async () => {
// await page.locator('.filters >> text=Completed').click();
// });
// await expect(page.locator('.todo-list li')).toHaveCount(1);
// await page.goBack();
// await expect(page.locator('.todo-list li')).toHaveCount(2);
// await page.goBack();
// await expect(page.locator('.todo-list li')).toHaveCount(3);
// });
// test('should allow me to display completed items', async ({ page }) => {
// await page.locator('.todo-list li .toggle').nth(1).check();
// await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// await page.locator('.filters >> text=Completed').click();
// await expect(page.locator('.todo-list li')).toHaveCount(1);
// });
// test('should allow me to display all items', async ({ page }) => {
// await page.locator('.todo-list li .toggle').nth(1).check();
// await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// await page.locator('.filters >> text=Active').click();
// await page.locator('.filters >> text=Completed').click();
// await page.locator('.filters >> text=All').click();
// await expect(page.locator('.todo-list li')).toHaveCount(3);
// });
// test('should highlight the currently applied filter', async ({ page }) => {
// await expect(page.locator('.filters >> text=All')).toHaveClass('selected');
// await page.locator('.filters >> text=Active').click();
// // Page change - active items.
// await expect(page.locator('.filters >> text=Active')).toHaveClass('selected');
// await page.locator('.filters >> text=Completed').click();
// // Page change - completed items.
// await expect(page.locator('.filters >> text=Completed')).toHaveClass('selected');
// });
// });
// async function createDefaultTodos(page: Page) {
// for (const item of TODO_ITEMS) {
// await page.locator('.new-todo').fill(item);
// await page.locator('.new-todo').press('Enter');
// }
// }
// async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
// return await page.waitForFunction(e => {
// return JSON.parse(localStorage['react-todos']).length === e;
// }, expected);
// }
// async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
// return await page.waitForFunction(e => {
// return JSON.parse(localStorage['react-todos']).filter(i => i.completed).length === e;
// }, expected);
// }
// async function checkTodosInLocalStorage(page: Page, title: string) {
// return await page.waitForFunction(t => {
// return JSON.parse(localStorage['react-todos']).map(i => i.title).includes(t);
// }, title);
// }

View File

@ -1,37 +0,0 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};

View File

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
}
}

View File

@ -1,18 +0,0 @@
import { test, expect } from '@playwright/test';
test('When not authenticated, should be redirected to login page', async ({ page }) => {
await page.goto('http://localhost:4200/', { waitUntil: 'networkidle' });
expect(page.url()).toBe('http://localhost:4200/login');
});
test('When not authenticated, should be redirected to login page from an authenticated page', async ({ page }) => {
await page.goto('http://localhost:4200/library', { waitUntil: 'networkidle' });
expect(page.url()).toBe('http://localhost:4200/login');
});
// Not sure how to test when we need localStorage: https://github.com/microsoft/playwright/issues/6258
// test('When authenticated, should be redirected to library page', async ({ page }) => {
// await page.goto('http://localhost:4200/', { waitUntil: 'networkidle' });
// console.log('url: ', page.url());
// expect(page.url()).toBe('http://localhost:4200/library');
// });

View File

@ -1,43 +0,0 @@
import { expect, test } from "@playwright/test";
test('When not authenticated, should be redirected to login page', async ({ page }) => {
await page.goto('http://localhost:4200/', { waitUntil: 'networkidle' });
expect(page.url()).toBe('http://localhost:4200/login');
});
test('Should be able to log in', async ({ page }) => {
await page.goto('http://localhost:4200/login', { waitUntil: 'networkidle' });
const username = page.locator('#username');
expect(username).toBeEditable();
const password = page.locator('#password');
expect(password).toBeEditable();
await username.type('Joe');
await password.type('P4ssword');
const button = page.locator('button[type="submit"]');
await button.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(200);
expect(page.url()).toBe('http://localhost:4200/library');
});
test('Should get a toastr when no username', async ({ page }) => {
await page.goto('http://localhost:4200/login', { waitUntil: 'networkidle' });
const username = page.locator('#username');
expect(username).toBeEditable();
await username.type('');
const button = page.locator('button[type="submit"]');
await button.click();
await page.waitForTimeout(100);
const toastr = page.locator('#toast-container div[role="alertdialog"]')
await expect(toastr).toHaveText('Invalid username');
expect(page.url()).toBe('http://localhost:4200/login');
});

View File

@ -1,34 +0,0 @@
import { expect, test } from "@playwright/test";
test('When on login page, clicking Forgot Password should redirect', async ({ page }) => {
await page.goto('http://localhost:4200/login', { waitUntil: 'networkidle' });
await page.click('a[routerlink="/registration/reset-password"]')
await page.waitForLoadState('networkidle');
expect(page.url()).toBe('http://localhost:4200/registration/reset-password');
});
test('Going directly to reset url should stay on the page', async ({page}) => {
await page.goto('http://localhost:4200/registration/reset-password', { waitUntil: 'networkidle' });
const email = page.locator('#email');
expect(email).toBeEditable();
})
test('Submitting an email, should give a prompt to user, redirect back to login', async ({ page }) => {
await page.goto('http://localhost:4200/registration/reset-password', { waitUntil: 'networkidle' });
const email = page.locator('#email');
expect(email).toBeEditable();
await email.type('XXX@gmail.com');
const button = page.locator('button[type="submit"]');
await button.click();
const toastr = page.locator('#toast-container div[role="alertdialog"]')
await expect(toastr).toHaveText('An email will be sent to the email if it exists in our database');
await page.waitForLoadState('networkidle');
expect(page.url()).toBe('http://localhost:4200/login');
});

View File

@ -1,13 +0,0 @@
import { expect, test } from "@playwright/test";
test.use({ storageState: 'storage/admin.json' });
test('When on login page, side nav should not render', async ({ page }) => {
await page.goto('http://localhost:4200/login', { waitUntil: 'networkidle' });
await expect(page.locator(".side-nav")).toHaveCount(0)
});
test('When on library page, side nav should render', async ({ page }) => {
await page.goto('http://localhost:4200/library', { waitUntil: 'networkidle' });
await expect(page.locator(".side-nav")).toHaveCount(1)
});

View File

@ -1,13 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2020",
"types": [
"jasmine",
"node"
]
}
}

View File

@ -1,46 +0,0 @@
import { Browser, chromium, FullConfig, request } from '@playwright/test';
async function globalSetup(config: FullConfig) {
let requestContext = await request.newContext();
var token = await requestContext.post('http://localhost:5000/account/login', {
form: {
'user': 'Joe',
'password': 'P4ssword'
}
});
//console.log(token.json());
// Save signed-in state to 'storageState.json'.
//await requestContext.storageState({ path: 'adminStorageState.json' });
await requestContext.dispose();
requestContext = await request.newContext();
await requestContext.post('http://localhost:5000/account/login', {
form: {
'user': 'nonadmin',
'password': 'P4ssword'
}
});
// Save signed-in state to 'storageState.json'.
//await requestContext.storageState({ path: 'nonAdminStorageState.json' });
await requestContext.dispose();
}
// async function globalSetup (config: FullConfig) {
// const browser = await chromium.launch()
// await saveStorage(browser, 'nonadmin', 'P4ssword', 'storage/user.json')
// await saveStorage(browser, 'Joe', 'P4ssword', 'storage/admin.json')
// await browser.close()
// }
async function saveStorage (browser: Browser, username: string, password: string, saveStoragePath: string) {
const page = await browser.newPage()
await page.goto('http://localhost:5000/account/login')
await page.type('#username', username)
await page.type('#password', password)
await page.click('button[type="submit"]')
await page.context().storageState({ path: saveStoragePath })
}
export default globalSetup;

View File

@ -1,4 +0,0 @@
{
"cookies": [],
"origins": []
}

13855
UI/Web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,75 +15,66 @@
},
"private": true,
"dependencies": {
"@angular-slider/ngx-slider": "^2.0.3",
"@angular/animations": "^14.1.1",
"@angular/cdk": "^13.2.2",
"@angular/common": "^14.1.1",
"@angular/compiler": "^14.1.1",
"@angular/core": "^14.1.1",
"@angular/forms": "^14.1.1",
"@angular/localize": "^14.1.1",
"@angular/platform-browser": "^14.1.1",
"@angular/platform-browser-dynamic": "^14.1.1",
"@angular/router": "^14.1.1",
"@fortawesome/fontawesome-free": "^6.0.0",
"@iharbeck/ngx-virtual-scroller": "^13.0.4",
"@microsoft/signalr": "^6.0.2",
"@ng-bootstrap/ng-bootstrap": "^13.0.0",
"@popperjs/core": "^2.11.2",
"@swimlane/ngx-charts": "^20.1.0",
"@angular/animations": "^15.1.2",
"@angular/cdk": "^15.1.2",
"@angular/common": "^15.1.2",
"@angular/compiler": "^15.1.2",
"@angular/core": "^15.1.2",
"@angular/forms": "^15.1.2",
"@angular/localize": "^15.1.2",
"@angular/platform-browser": "^15.1.2",
"@angular/platform-browser-dynamic": "^15.1.2",
"@angular/router": "^15.1.2",
"@fortawesome/fontawesome-free": "^6.2.0",
"@iharbeck/ngx-virtual-scroller": "^15.0.0",
"@microsoft/signalr": "^7.0.2",
"@ng-bootstrap/ng-bootstrap": "^14.0.1",
"@popperjs/core": "^2.11.6",
"@swimlane/ngx-charts": "^20.1.2",
"@tweenjs/tween.js": "^18.6.4",
"@types/file-saver": "^2.0.5",
"bootstrap": "^5.2.0",
"bowser": "^2.11.0",
"bootstrap": "^5.2.3",
"browser": "^0.2.6",
"eventsource": "^2.0.2",
"file-saver": "^2.0.5",
"lazysizes": "^5.3.2",
"ng-circle-progress": "^1.6.0",
"ng-circle-progress": "^1.7.1",
"ng-swipe": "^2.0.1",
"ngx-color-picker": "^12.0.0",
"ngx-extended-pdf-viewer": "^15.0.0",
"ngx-file-drop": "^14.0.1",
"ngx-toastr": "^14.2.1",
"ngx-color-picker": "^13.0.0",
"ngx-extended-pdf-viewer": "^15.2.2",
"ngx-file-drop": "^14.0.2",
"ngx-slider-v2": "^15.0.3",
"ngx-toastr": "^16.0.2",
"requires": "^1.0.2",
"rxjs": "~7.5.4",
"rxjs": "^7.8.0",
"screenfull": "^6.0.2",
"swiper": "^8.4.4",
"tslib": "^2.3.1",
"webpack-bundle-analyzer": "^4.5.0",
"zone.js": "~0.11.4"
"swiper": "^8.4.6",
"tslib": "^2.3.0",
"webpack-bundle-analyzer": "^4.7.0",
"zone.js": "~0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.1.1",
"@angular/cli": "^14.1.1",
"@angular/compiler-cli": "^14.1.1",
"@playwright/test": "^1.23.2",
"@angular-devkit/build-angular": "^15.1.3",
"@angular-eslint/builder": "15.2.0",
"@angular-eslint/eslint-plugin": "15.2.0",
"@angular-eslint/eslint-plugin-template": "15.2.0",
"@angular-eslint/schematics": "15.2.0",
"@angular-eslint/template-parser": "15.2.0",
"@angular/cli": "^15.1.3",
"@angular/compiler-cli": "^15.1.2",
"@playwright/test": "^1.30.0",
"@types/d3": "^7.4.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.17",
"codelyzer": "^6.0.2",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.1",
"ajv": "^7.2.4",
"eslint": "^8.31.0",
"jest": "^27.5.1",
"jest-preset-angular": "^11.1.0",
"jest-preset-angular": "^11.1.2",
"karma-coverage": "~2.2.0",
"playwright": "^1.24.2",
"protractor": "~7.0.0",
"playwright": "^1.30.0",
"ts-node": "~10.5.0",
"tslint": "^6.1.3",
"typescript": "~4.7.4"
},
"jest": {
"preset": "jest-preset-angular",
"setupFilesAfterEnv": [
"<rootDir>/setupJest.ts"
],
"testPathIgnorePatterns": [
"<rootDir>/node_modules/",
"<rootDir>/dist/"
],
"globals": {
"ts-jest": {
"tsConfig": "<rootDir>/tsconfig.spec.json",
"stringifyContentPathRegex": "\\.html$"
}
}
"typescript": "~4.9.4"
}
}

View File

@ -1,106 +0,0 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './e2e',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:4200',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
globalSetup: require.resolve('./global-setup'),
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
// {
// name: 'webkit',
// use: {
// ...devices['Desktop Safari'],
// },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
};
export default config;

View File

@ -1,19 +0,0 @@
import 'jest-preset-angular';
/* global mocks for jsdom */
const mock = () => {
let storage: { [key: string]: string } = {};
return {
getItem: (key: string) => (key in storage ? storage[key] : null),
setItem: (key: string, value: string) => (storage[key] = value || ''),
removeItem: (key: string) => delete storage[key],
clear: () => (storage = {})
};
};
Object.defineProperty(window, 'localStorage', { value: mock() });
Object.defineProperty(window, 'sessionStorage', { value: mock() });
Object.defineProperty(window, 'getComputedStyle', {
value: () => ['-webkit-appearance'],
});

View File

@ -1 +0,0 @@
<p>devices works!</p>

View File

@ -1,15 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-devices',
templateUrl: './devices.component.html',
styleUrls: ['./devices.component.scss']
})
export class DevicesComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Member } from 'src/app/_models/auth/member';
@ -9,7 +9,7 @@ import { AccountService } from 'src/app/_services/account.service';
templateUrl: './reset-password-modal.component.html',
styleUrls: ['./reset-password-modal.component.scss']
})
export class ResetPasswordModalComponent implements OnInit {
export class ResetPasswordModalComponent {
@Input() member!: Member;
errorMessage = '';
@ -19,9 +19,6 @@ export class ResetPasswordModalComponent implements OnInit {
constructor(public modal: NgbActiveModal, private accountService: AccountService) { }
ngOnInit(): void {
}
save() {
this.accountService.resetPassword(this.member.username, this.resetPasswordForm.value.password,'').subscribe(() => {
this.modal.close();

View File

@ -19,7 +19,7 @@
<div>Shared Folders: {{library.folders.length + ' folders'}}</div>
<div>
Last Scanned:
<span *ngIf="library.lastScanned == '0001-01-01T00:00:00'; else activeDate">Never</span>
<span *ngIf="library.lastScanned === '0001-01-01T00:00:00'; else activeDate">Never</span>
<ng-template #activeDate>
{{library.lastScanned | timeAgo}}
</ng-template>

View File

@ -53,7 +53,7 @@
</h4>
<div class="user-info">
<div>Last Active:
<span *ngIf="member.lastActive == '0001-01-01T00:00:00'; else activeDate">Never</span>
<span *ngIf="member.lastActive === '0001-01-01T00:00:00'; else activeDate">Never</span>
<ng-template #activeDate>
{{member.lastActive | date: 'short'}}
</ng-template>

View File

@ -1,15 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
@Component({
selector: 'app-announcements',
templateUrl: './announcements.component.html',
styleUrls: ['./announcements.component.scss']
})
export class AnnouncementsComponent implements OnInit {
export class AnnouncementsComponent {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -3,9 +3,9 @@
<div [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async), 'content-wrapper': navService.sideNavVisibility$ | async}">
<a id="content"></a>
<app-side-nav *ngIf="navService.sideNavVisibility$ | async as sideNavVisibile"></app-side-nav>
<div class="container-fluid" [ngClass]="{'g-0': !(navService.sideNavVisibility$ | async)}">
<div class="container-fluid" [ngClass]="{'g-0': (navService.sideNavVisibility$ | async) === false}">
<div style="padding: 20px 0 0;" *ngIf="navService.sideNavVisibility$ | async else noSideNav">
<div class="companion-bar" [ngClass]="{'companion-bar-content': !(navService.sideNavCollapsed$ | async)}">
<div class="companion-bar" [ngClass]="{'companion-bar-content': (navService.sideNavCollapsed$ | async) === false}">
<router-outlet></router-outlet>
</div>
</div>

View File

@ -1,6 +1,6 @@
import { Component, HostListener, Inject, OnInit } from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { distinctUntilChanged, map, take } from 'rxjs/operators';
import { map, take } from 'rxjs/operators';
import { AccountService } from './_services/account.service';
import { LibraryService } from './_services/library.service';
import { MessageHubService } from './_services/message-hub.service';
@ -8,7 +8,6 @@ import { NavService } from './_services/nav.service';
import { filter } from 'rxjs/operators';
import { NgbModal, NgbRatingConfig } from '@ng-bootstrap/ng-bootstrap';
import { DOCUMENT } from '@angular/common';
import { DeviceService } from './_services/device.service';
import { Observable } from 'rxjs';
@Component({

View File

@ -12,7 +12,6 @@ import { ErrorInterceptor } from './_interceptors/error.interceptor';
import { SAVER, getSaver } from './shared/_providers/saver.provider';
import { SidenavModule } from './sidenav/sidenav.module';
import { NavModule } from './nav/nav.module';
import { DevicesComponent } from './_components/devices/devices.component';
@ -24,7 +23,6 @@ if (disableAnimations) console.error("Web Animations have been disabled as your
@NgModule({
declarations: [
AppComponent,
DevicesComponent,
],
imports: [
HttpClientModule,

View File

@ -11,7 +11,7 @@
</div>
<ng-template #nestedChildren>
<ul *ngFor="let chapterGroup of chapters" class="chapter-title">
<li class="{{chapterGroup.page == pageNum ? 'active': ''}}" (click)="loadChapterPage(chapterGroup.page, '')">
<li class="{{chapterGroup.page === pageNum ? 'active': ''}}" (click)="loadChapterPage(chapterGroup.page, '')">
{{chapterGroup.title}}
</li>
<ul *ngFor="let chapter of chapterGroup.children">

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { Subject } from 'rxjs';
import { BookChapterItem } from '../../_models/book-chapter-item';
@ -8,7 +8,7 @@ import { BookChapterItem } from '../../_models/book-chapter-item';
styleUrls: ['./table-of-contents.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
export class TableOfContentsComponent implements OnInit, OnDestroy {
export class TableOfContentsComponent implements OnDestroy {
@Input() chapterId!: number;
@Input() pageNum!: number;
@ -23,9 +23,6 @@ export class TableOfContentsComponent implements OnInit, OnDestroy {
constructor() {}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
@ -13,7 +13,7 @@ import { CollectionTagService } from 'src/app/_services/collection-tag.service';
styleUrls: ['./bulk-add-to-collection.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BulkAddToCollectionComponent implements OnInit {
export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
@Input() title!: string;
/**

View File

@ -177,9 +177,6 @@ export class BulkSelectionService {
action.children = action.children.filter((childAction) => this.applyFilter(childAction, allowedActions));
// action.children?.forEach((childAction) => {
// this.applyFilter(childAction, allowedActions);
// });
return ret;
}

View File

@ -95,7 +95,7 @@
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Cover]" [disabled]="!(isAdmin$ | async)">
<li [ngbNavItem]="tabs[TabID.Cover]" [disabled]="(isAdmin$ | async) === false">
<a ngbNavLink>{{tabs[TabID.Cover].title}}</a>
<ng-template ngbNavContent>
<app-cover-image-chooser [(imageUrls)]="imageUrls"
@ -108,7 +108,7 @@
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Files]" [disabled]="!(isAdmin$ | async)">
<li [ngbNavItem]="tabs[TabID.Files]" [disabled]="(isAdmin$ | async) === false">
<a ngbNavLink>{{tabs[TabID.Files].title}}</a>
<ng-template ngbNavContent>
<h4 *ngIf="!utilityService.isChapter(data)">{{utilityService.formatChapterName(libraryType) + 's'}}</h4>
@ -145,7 +145,7 @@
<div class="col" *ngIf="data.hasOwnProperty('created')">
Added:
<!-- TODO: This data.created can be removed after v0.5.5 release -->
<ng-container *ngIf="file.created == '0001-01-01T00:00:00'; else fileDate">
<ng-container *ngIf="file.created === '0001-01-01T00:00:00'; else fileDate">
{{data.created | date: 'short' | defaultDate}}
</ng-container>
<ng-template #fileDate>

View File

@ -7,7 +7,7 @@
</span>
<span *ngIf="header !== undefined && header.length > 0">
{{header}}&nbsp;
<span class="badge bg-primary rounded-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination != undefined">{{pagination.totalItems}}</span>
<span class="badge bg-primary rounded-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination !== undefined">{{pagination.totalItems}}</span>
</span>
</h2>
</div>

View File

@ -9,9 +9,9 @@
<ng-template #submenu let-list="list">
<ng-container *ngFor="let action of list">
<!-- Non Submenu items -->
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 || action.dynamicList != undefined ; else submenuDropdown">
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 || action.dynamicList !== undefined ; else submenuDropdown">
<ng-container *ngIf="action.dynamicList != undefined && (action.dynamicList | async | dynamicList) as dList; else justItem">
<ng-container *ngIf="action.dynamicList !== undefined && (action.dynamicList | async | dynamicList) as dList; else justItem">
<ng-container *ngFor="let dynamicItem of dList">
<button ngbDropdownItem (click)="performDynamicClick($event, action, dynamicItem)">{{dynamicItem.title}}</button>
</ng-container>

View File

@ -41,7 +41,7 @@
<span class="visually-hidden">(promoted)</span>
</span>
<ng-container *ngIf="format | mangaFormat as formatString">
<i class="fa {{format | mangaFormatIcon}} me-1" aria-hidden="true" *ngIf="format != MangaFormat.UNKNOWN" title="{{formatString}}"></i>
<i class="fa {{format | mangaFormatIcon}} me-1" aria-hidden="true" *ngIf="format !== MangaFormat.UNKNOWN" title="{{formatString}}"></i>
<span class="visually-hidden">{{formatString}}</span>
</ng-container>
{{title}}

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
@Component({
@ -7,10 +7,8 @@ import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
styleUrls: ['./chapter-metadata-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChapterMetadataDetailComponent implements OnInit {
export class ChapterMetadataDetailComponent {
@Input() chapter: ChapterMetadata | undefined;
constructor() { }
ngOnInit(): void {}
}

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { Download } from 'src/app/shared/_models/download';
import { DownloadEvent } from 'src/app/shared/_services/download.service';
@ -9,16 +9,12 @@ import { DownloadEvent } from 'src/app/shared/_services/download.service';
styleUrls: ['./download-indicator.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DownloadIndicatorComponent implements OnInit {
export class DownloadIndicatorComponent {
/**
* Observable that represents when the download completes
*/
@Input() download$!: Observable<Download | DownloadEvent | null> | null;
constructor(private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
}
constructor() { }
}

View File

@ -1,26 +1,26 @@
<ng-container [ngSwitch]="libraryType">
<ng-container *ngSwitchCase="LibraryType.Comic">
<ng-container *ngIf="titleName != '' && prioritizeTitleName; else fullComicTitle">
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullComicTitle">
{{titleName}}
</ng-container>
<ng-template #fullComicTitle>
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
<ng-container *ngIf="includeVolume && volumeTitle != ''">
{{entity.number != 0 ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
{{entity.number !== 0 ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
</ng-container>
{{entity.number != 0 ? (isChapter ? 'Issue #' + entity.number : volumeTitle) : 'Special'}}
{{entity.number !== 0 ? (isChapter ? 'Issue #' + entity.number : volumeTitle) : 'Special'}}
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="LibraryType.Manga">
<ng-container *ngIf="titleName != '' && prioritizeTitleName; else fullMangaTitle">
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullMangaTitle">
{{titleName}}
</ng-container>
<ng-template #fullMangaTitle>
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
<ng-container *ngIf="includeVolume && volumeTitle != ''">
{{entity.number != 0 ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
{{entity.number !== 0 ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
</ng-container>
{{entity.number != 0 ? (isChapter ? 'Chapter ' + entity.number : volumeTitle) : 'Special'}}
{{entity.number !== 0 ? (isChapter ? 'Chapter ' + entity.number : volumeTitle) : 'Special'}}
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="LibraryType.Book">

View File

@ -22,7 +22,7 @@
</button>
</h5>
<!-- This isn't perfect, but it might work. TODO: Polish this-->
<h6 class="text-muted" [ngClass]="{'subtitle-with-actionables' : actions.length > 0}" *ngIf="Title != '' && showTitle">{{Title}}</h6>
<h6 class="text-muted" [ngClass]="{'subtitle-with-actionables' : actions.length > 0}" *ngIf="Title !== '' && showTitle">{{Title}}</h6>
<ng-container *ngIf="summary.length > 0">
<div class="mt-2 ps-2">
<app-read-more [text]="summary" [blur]="pagesRead === 0 && blur" [maxLength]="250"></app-read-more>

View File

@ -19,7 +19,7 @@ import { ManagaReaderService } from '../../_series/managa-reader.service';
styleUrls: ['./double-no-cover-renderer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DoubleNoCoverRendererComponent implements OnInit {
export class DoubleNoCoverRendererComponent implements OnInit, OnDestroy {
@Input() readerSettings$!: Observable<ReaderSetting>;
@Input() image$!: Observable<HTMLImageElement | null>;

View File

@ -117,7 +117,7 @@
<div class="fixed-bottom overlay" *ngIf="menuOpen" [@slideFromBottom]="menuOpen">
<div class="mb-3" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined">
<div class="mb-3" *ngIf="pageOptions !== undefined && pageOptions.ceil !== undefined">
<span class="visually-hidden" id="slider-info"></span>
<div class="row g-0">
<button class="btn btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>

View File

@ -2,7 +2,7 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, E
import { DOCUMENT } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, debounceTime, distinctUntilChanged, forkJoin, fromEvent, map, merge, Observable, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs';
import { LabelType, ChangeContext, Options } from '@angular-slider/ngx-slider';
import { LabelType, ChangeContext, Options } from 'ngx-slider-v2';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { FormGroup, FormBuilder, FormControl } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

View File

@ -4,7 +4,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { MangaReaderRoutingModule } from './manga-reader.router.module';
import { SharedModule } from '../shared/shared.module';
import { NgxSliderModule } from '@angular-slider/ngx-slider';
import { NgxSliderModule } from 'ngx-slider-v2';
import { InfiniteScrollerComponent } from './_components/infinite-scroller/infinite-scroller.component';
import { ReaderSharedModule } from '../reader-shared/reader-shared.module';
import { PipeModule } from '../pipe/pipe.module';

View File

@ -1,5 +1,5 @@
<form [formGroup]="typeaheadForm" class="grouped-typeahead">
<div class="typeahead-input" [ngClass]="{'focused': hasFocus == true}" (click)="onInputFocus($event)">
<div class="typeahead-input" [ngClass]="{'focused': hasFocus}" (click)="onInputFocus($event)">
<div class="search">
<input #input [id]="id" type="text" autocomplete="off" formControlName="typeahead" [placeholder]="placeholder"
aria-haspopup="listbox" aria-owns="dropdown" aria-expanded="hasFocus && (grouppedData.persons.length || grouppedData.collections.length || grouppedData.series.length || grouppedData.persons.length || grouppedData.tags.length || grouppedData.genres.length)"

View File

@ -6,7 +6,7 @@ import { Pipe, PipeTransform } from '@angular/core';
export class DefaultValuePipe implements PipeTransform {
transform(value: any, replacementString = '—'): string {
if (value === null || value === undefined || value === '' || value === Infinity || value === NaN) return replacementString;
if (value === null || value === undefined || value === '' || value === Infinity || Number.isNaN(value)) return replacementString;
return value;
}

View File

@ -1,5 +1,5 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
export interface IndexUpdateEvent {
fromPosition: number;
@ -18,7 +18,7 @@ export interface ItemRemoveEvent {
styleUrls: ['./draggable-ordered-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DraggableOrderedListComponent implements OnInit {
export class DraggableOrderedListComponent {
@Input() accessibilityMode: boolean = false;
/**
@ -32,9 +32,6 @@ export class DraggableOrderedListComponent implements OnInit {
constructor(private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
}
drop(event: CdkDragDrop<string[]>) {
if (event.previousIndex === event.currentIndex) return;
moveItemInArray(this.items, event.previousIndex, event.currentIndex);

View File

@ -29,7 +29,7 @@
</h5>
<div class="ps-1 d-none d-md-inline-block">
<ng-container *ngIf="item.seriesFormat | mangaFormat as formatString">
<i class="fa {{item.seriesFormat | mangaFormatIcon}}" aria-hidden="true" *ngIf="item.seriesFormat != MangaFormat.UNKNOWN" title="{{formatString}}"></i>
<i class="fa {{item.seriesFormat | mangaFormatIcon}}" aria-hidden="true" *ngIf="item.seriesFormat !== MangaFormat.UNKNOWN" title="{{formatString}}"></i>
<span class="visually-hidden">{{formatString}}</span>&nbsp;
</ng-container>

View File

@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { LibraryType } from 'src/app/_models/library';
import { MangaFormat } from 'src/app/_models/manga-format';
import { ReadingListItem } from 'src/app/_models/reading-list';
@ -11,7 +10,7 @@ import { ImageService } from 'src/app/_services/image.service';
styleUrls: ['./reading-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReadingListItemComponent implements OnInit {
export class ReadingListItemComponent {
@Input() item!: ReadingListItem;
@Input() position: number = 0;
@ -28,10 +27,7 @@ export class ReadingListItemComponent implements OnInit {
return MangaFormat;
}
constructor(public imageService: ImageService, private utilityService: UtilityService,
private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {}
constructor(public imageService: ImageService) { }
readChapter(item: ReadingListItem) {
this.read.emit(item);

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
@ -15,7 +15,7 @@ import { MemberService } from 'src/app/_services/member.service';
styleUrls: ['./register.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RegisterComponent implements OnInit {
export class RegisterComponent {
registerForm: FormGroup = new FormGroup({
email: new FormControl('', [Validators.email]),
@ -34,9 +34,6 @@ export class RegisterComponent implements OnInit {
});
}
ngOnInit(): void {
}
submit() {
const model = this.registerForm.getRawValue();
this.accountService.register(model).subscribe((user) => {

View File

@ -138,7 +138,7 @@
<div class="card-container row g-0" #container>
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
<ng-container *ngIf="!item.isChapter; else chapterCardItem">
<app-card-item class="col-auto mt-2 mb-2" *ngIf="item.volume.number != 0" [entity]="item.volume" [title]="item.volume.name" (click)="openVolume(item.volume)"
<app-card-item class="col-auto mt-2 mb-2" *ngIf="item.volume.number !== 0" [entity]="item.volume" [title]="item.volume.name" (click)="openVolume(item.volume)"
[imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
[read]="item.volume.pagesRead" [total]="item.volume.pages" [actions]="volumeActions"
(selection)="bulkSelectionService.handleCardSelection('volume', scroll.viewPortInfo.startIndexWithBuffer + idx, volumes.length, $event)"
@ -159,7 +159,7 @@
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
<ng-container *ngIf="!item.isChapter; else chapterListItem">
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number != 0"
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number !== 0"
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)"
[blur]="user?.preferences?.blurUnreadSummaries || false">

View File

@ -1,9 +1,10 @@
import { Directive, Input, HostListener, OnInit, ElementRef, Inject } from '@angular/core';
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[a11y-click]'
})
export class A11yClickDirective {
export class A11yClickDirective implements OnInit {
@Input('a11y-click') keyCodes!: string;
keyCodeArray!: string[];

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'app-loading',
@ -6,7 +6,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit }
styleUrls: ['./loading.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoadingComponent implements OnInit {
export class LoadingComponent {
@Input() loading: boolean = false;
@Input() message: string = '';
@ -15,10 +15,5 @@ export class LoadingComponent implements OnInit {
*/
@Input() absolute: boolean = false;
constructor(private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
console.log('absolute: ', this.absolute);
}
constructor() { }
}

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Person } from '../../_models/metadata/person';
@Component({
@ -7,13 +7,9 @@ import { Person } from '../../_models/metadata/person';
styleUrls: ['./person-badge.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonBadgeComponent implements OnInit {
export class PersonBadgeComponent {
@Input() person!: Person;
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,4 +1,4 @@
<ng-container *ngIf="format != MangaFormat.UNKNOWN">
<ng-container *ngIf="format !== MangaFormat.UNKNOWN">
<i class="fa {{format | mangaFormatIcon}}" aria-hidden="true" title="{{format | mangaFormat}}"></i>&nbsp;
<ng-content></ng-content>
</ng-container>

View File

@ -15,7 +15,7 @@
<div class="active-highlight"></div>
<span class="phone-hidden" title="{{title}}">
<div>
<ng-container *ngIf="imageUrl != null && imageUrl != ''; else iconImg">
<ng-container *ngIf="imageUrl !== null && imageUrl !== ''; else iconImg">
<img [src]="imageUrl" alt="icon" class="side-nav-img">
</ng-container>
<ng-template #iconImg><i class="fa {{icon}}" aria-hidden="true"></i></ng-template>

View File

@ -1,5 +1,5 @@
<ng-container>
<div class="side-nav" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async), 'hidden' :!(navService.sideNavVisibility$ | async)}" *ngIf="accountService.currentUser$ | async as user">
<div class="side-nav" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async), 'hidden': (navService.sideNavVisibility$ | async) === false}" *ngIf="accountService.currentUser$ | async as user">
<!-- <app-side-nav-item icon="fa-user-circle align-self-center phone-hidden" [title]="user.username | sentenceCase" link="/preferences/">
<ng-container actions>
Todo: This will be customize dashboard/side nav controls
@ -13,7 +13,7 @@
<app-side-nav-item icon="fa-list-ol" title="Reading Lists" link="/lists/"></app-side-nav-item>
<app-side-nav-item icon="fa-bookmark" title="Bookmarks" link="/bookmarks/"></app-side-nav-item>
<app-side-nav-item icon="fa-regular fa-rectangle-list" title="All Series" link="/all-series/" *ngIf="libraries.length > 0"></app-side-nav-item>
<div class="mb-2 mt-3 ms-2 me-2" *ngIf="libraries.length > 10 && !(navService?.sideNavCollapsed$ | async)">
<div class="mb-2 mt-3 ms-2 me-2" *ngIf="libraries.length > 10 && (navService?.sideNavCollapsed$ | async) === false">
<label for="filter" class="form-label visually-hidden">Filter</label>
<div class="form-group">
<input id="filter" autocomplete="off" class="form-control" [(ngModel)]="filterQuery" type="text" aria-describedby="reset-input">

View File

@ -40,7 +40,7 @@
</div>
<div *ngIf="!isAddLibrary">
Last Scanned:
<span *ngIf="library.lastScanned == '0001-01-01T00:00:00'; else activeDate">Never</span>
<span *ngIf="library.lastScanned === '0001-01-01T00:00:00'; else activeDate">Never</span>
<ng-template #activeDate>
{{library.lastScanned | date: 'short'}}
</ng-template>
@ -157,7 +157,7 @@
<button type="button" class="btn btn-light" (click)="reset()">Reset</button>
<button type="button" class="btn btn-secondary" (click)="close()">Cancel</button>
<ng-container *ngIf="isAddLibrary && setupStep != 3; else editLibraryButton">
<ng-container *ngIf="isAddLibrary && setupStep !== 3; else editLibraryButton">
<button type="button" class="btn btn-primary" (click)="nextStep()" [disabled]="isNextDisabled() || libraryForm.invalid">Next</button>
</ng-container>
<ng-template #editLibraryButton>

View File

@ -1,9 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { LegendPosition } from '@swimlane/ngx-charts';
import { Subject, combineLatest, map, takeUntil, Observable } from 'rxjs';
import { Subject, map, takeUntil, Observable } from 'rxjs';
import { DayOfWeek, StatisticsService } from 'src/app/_services/statistics.service';
import { compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
import { PieDataItem } from '../../_models/pie-data-item';
import { StatCount } from '../../_models/stat-count';
import { DayOfWeekPipe } from '../../_pipes/day-of-week.pipe';
@ -13,7 +12,7 @@ import { DayOfWeekPipe } from '../../_pipes/day-of-week.pipe';
templateUrl: './day-breakdown.component.html',
styleUrls: ['./day-breakdown.component.scss']
})
export class DayBreakdownComponent implements OnInit {
export class DayBreakdownComponent implements OnDestroy {
private readonly onDestroy = new Subject<void>();
@ -42,11 +41,6 @@ export class DayBreakdownComponent implements OnInit {
);
}
ngOnInit(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core';
import { FormControl } from '@angular/forms';
import { LegendPosition } from '@swimlane/ngx-charts';
import { Observable, Subject, map, takeUntil, combineLatest, BehaviorSubject } from 'rxjs';
@ -12,7 +12,7 @@ import { PieDataItem } from '../../_models/pie-data-item';
styleUrls: ['./publication-status-stats.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PublicationStatusStatsComponent implements OnInit {
export class PublicationStatusStatsComponent implements OnDestroy {
@ViewChildren(SortableHeader<PieDataItem>) headers!: QueryList<SortableHeader<PieDataItem>>;
@ -48,11 +48,6 @@ export class PublicationStatusStatsComponent implements OnInit {
);
}
ngOnInit(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostListener, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { BehaviorSubject, map, Observable, of, shareReplay, Subject, takeUntil, tap } from 'rxjs';
import { BehaviorSubject, map, Observable, shareReplay, Subject, takeUntil } from 'rxjs';
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import { Series } from 'src/app/_models/series';
@ -18,7 +18,7 @@ import { GenericListModalComponent } from '../_modals/generic-list-modal/generic
styleUrls: ['./server-stats.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ServerStatsComponent implements OnInit, OnDestroy {
export class ServerStatsComponent implements OnDestroy {
releaseYears$!: Observable<Array<PieDataItem>>;
mostActiveUsers$!: Observable<Array<PieDataItem>>;
@ -89,9 +89,6 @@ export class ServerStatsComponent implements OnInit, OnDestroy {
);
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();

View File

@ -5,7 +5,7 @@
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="tooltip" role="button" tabindex="0" *ngIf="description && description.length > 0"></i>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item" [ngClass]="{'underline': handleClick != undefined}" *ngFor="let item of data" (click)="doClick(item)">
<li class="list-group-item" [ngClass]="{'underline': handleClick !== undefined}" *ngFor="let item of data" (click)="doClick(item)">
<ng-container *ngIf="image && image(item) as url">
<app-image *ngIf="url && url.length > 0" width="32px" maxHeight="32px" class="img-top me-1" [imageUrl]="url"></app-image>
</ng-container>

View File

@ -1,7 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CompactNumberPipe } from 'src/app/pipe/compact-number.pipe';
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
import { StatisticsService } from 'src/app/_services/statistics.service';
import { GenericListModalComponent } from '../_modals/generic-list-modal/generic-list-modal.component';
@ -11,7 +10,7 @@ import { GenericListModalComponent } from '../_modals/generic-list-modal/generic
styleUrls: ['./user-stats-info-cards.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserStatsInfoCardsComponent implements OnInit {
export class UserStatsInfoCardsComponent {
@Input() totalPagesRead: number = 0;
@Input() totalWordsRead: number = 0;
@ -22,9 +21,6 @@ export class UserStatsInfoCardsComponent implements OnInit {
constructor(private statsService: StatisticsService, private modalService: NgbModal) { }
ngOnInit(): void {
}
openPageByYearList() {
const numberPipe = new CompactNumberPipe();
this.statsService.getPagesPerYear().subscribe(yearCounts => {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Observable, of, Subject, takeUntil, shareReplay, map, take } from 'rxjs';
import { AgeRestriction } from 'src/app/_models/metadata/age-restriction';
@ -12,7 +12,7 @@ import { AccountService } from 'src/app/_services/account.service';
styleUrls: ['./change-age-restriction.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChangeAgeRestrictionComponent implements OnInit {
export class ChangeAgeRestrictionComponent implements OnInit, OnDestroy {
user: User | undefined = undefined;
hasChangeAgeRestrictionAbility: Observable<boolean> = of(false);

View File

@ -19,7 +19,7 @@
<div class="row g-0 mt-2">
<h4>Devices</h4>
<p *ngIf="devices.length == 0">
<p *ngIf="devices.length === 0">
There are no devices setup yet
</p>
<ng-container *ngFor="let device of devices">

View File

@ -12,7 +12,7 @@ import { AccountService } from 'src/app/_services/account.service';
styleUrls: ['./theme-manager.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ThemeManagerComponent implements OnInit, OnDestroy {
export class ThemeManagerComponent implements OnDestroy {
currentTheme: SiteTheme | undefined;
isAdmin: boolean = false;
@ -40,9 +40,6 @@ export class ThemeManagerComponent implements OnInit, OnDestroy {
});
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();

View File

@ -1,5 +1,5 @@
import { DOCUMENT } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AfterContentChecked, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, ActivatedRoute } from '@angular/router';
import { Subject, take, debounceTime, takeUntil } from 'rxjs';
@ -27,7 +27,7 @@ import { SeriesService } from 'src/app/_services/series.service';
styleUrls: ['./want-to-read.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WantToReadComponent implements OnInit, OnDestroy {
export class WantToReadComponent implements OnInit, OnDestroy, AfterContentChecked {
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;

View File

@ -1,3 +1,5 @@
/// <reference types="@angular/localize" />
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

View File

@ -1,67 +0,0 @@
/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
import '@angular/localize/init';
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -1,4 +1,4 @@
@use '../node_modules/swiper/swiper.scss' as swiper;
@import '../node_modules/swiper/swiper';
// Import themes which define the css variables we use to customize the app
@ -9,7 +9,8 @@
@import './theme/variables';
// Bootstrap must be after _colors since we define the colors there
@import '~bootstrap/scss/bootstrap';
@import '../node_modules/bootstrap/scss/bootstrap';
// Import all the customized theme overrides

View File

@ -1,6 +1,5 @@
/* Default styles for Kavita */
:root {
//@import './dark.scss'; // Just re-import variables from dark since that's all we support
--color-scheme: dark;
--primary-color: #4ac694;
--primary-color-dark-shade: #3B9E76;

View File

@ -1,4 +0,0 @@
{
"cookies": [],
"origins": []
}

View File

@ -1,4 +0,0 @@
{
"cookies": [],
"origins": []
}

View File

@ -3,11 +3,12 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
"types": [
"@angular/localize"
]
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"

View File

@ -4,7 +4,7 @@
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"removeComments": true /* Do not emit comments to output. */,
"removeComments": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
@ -16,15 +16,16 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES6",
"target": "ES2022",
"module": "es2020",
"useDefineForClassFields": false,
"lib": [
"es2019",
"es2018",
"ES2022",
"dom"
]
],
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true,

View File

@ -1,155 +0,0 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
false,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
false,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
false,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"comment-format": [
false
]
}
}

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
},
"version": "0.6.1.29"
"version": "0.6.1.30"
},
"servers": [
{