diff --git a/gulpfile.js b/gulpfile.js index 11c9db9..3d74071 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -4,6 +4,7 @@ const gulpLoadPlugins = require('gulp-load-plugins') const terser = require('terser'); const del = require('del'); const browserify = require('browserify'); +const tsify = require('tsify') const babelify = require('babelify'); const source = require('vinyl-source-stream'); const buffer = require('vinyl-buffer'); @@ -124,13 +125,14 @@ gulp.task('scripts', () => { const streams = []; modules.forEach(module => { - inputs.push(`${src.scripts}/${module}.js`); + inputs.push(`${src.scripts}/${module}.ts`); streams.push(source(`${module}.js`)); }); const b = browserify(inputs, { debug: isDev }); let outstream = b + .plugin(tsify) .transform('babelify') .plugin(factor, { outputs: streams }) .bundle() @@ -206,7 +208,7 @@ Watchers gulp.task('watch', (done) => { gulp.watch(`${src.styles}/**/*.scss`, gulp.series('clean:build', 'styles', 'dist:copy', 'dist:zip')) - gulp.watch(`${src.scripts}/**/*.js`, gulp.series('clean:build', 'scripts', 'dist:copy', 'dist:zip')) + gulp.watch(`${src.scripts}/**/*.ts`, gulp.series('clean:build', 'scripts', 'dist:copy', 'dist:zip')) gulp.watch(`${src.images}/**/*`, gulp.series('clean:build', 'images', 'dist:copy', 'dist:zip')) diff --git a/package-lock.json b/package-lock.json index 25847f7..1520892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1488,12 +1488,37 @@ "dev": true, "optional": true }, + "@types/chrome": { + "version": "0.0.127", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.127.tgz", + "integrity": "sha512-hBB9EApLYKKn2GvklVkTxVP6vZvxsH9okyIRUinNtMzZHIgIKWQk/ESbX+O5g4Bihfy38+aFGn7Kl7Cxou5JUg==", + "dev": true, + "requires": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/filesystem": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", + "dev": true, + "requires": { + "@types/filewriter": "*" + } + }, + "@types/filewriter": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz", + "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=", + "dev": true + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -1504,6 +1529,12 @@ "@types/node": "*" } }, + "@types/har-format": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.5.tgz", + "integrity": "sha512-IG8AE1m2pWtPqQ7wXhFhy6Q59bwwnLwO36v5Rit2FrbXCIp8Sk8E2PfUCreyrdo17STwFSKDAkitVuVYbpEHvQ==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1651,6 +1682,12 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -6736,6 +6773,44 @@ } } }, + "gulp-typescript": { + "version": "6.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", + "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0", + "vinyl-fs": "^3.0.3" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, "gulp-zip": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-5.0.2.tgz", @@ -11772,9 +11847,8 @@ "dev": true }, "spacetime": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.12.2.tgz", - "integrity": "sha512-w0St4Q9X8KtuZ/JY8+FM8a4hMrAoNNUWQCt9UQQAUzwk8eDW5wrGh4SaNvEg+9cjLF++vixm6SgJyC6F7ALF/A==" + "version": "github:Serraniel/spacetime#84ae159680732067195b84744e172f4e2497b4ec", + "from": "github:Serraniel/spacetime#bugfix/#255-typescript-constructor-options-missing" }, "sparkles": { "version": "1.0.1", @@ -12138,6 +12212,12 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -12569,6 +12649,51 @@ "glob": "^7.1.2" } }, + "tsconfig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha1-X0J45wGACWeo/Dg/0ZZIh48qbjo=", + "dev": true, + "requires": { + "any-promise": "^1.3.0", + "parse-json": "^2.2.0", + "strip-bom": "^2.0.0", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + } + } + }, + "tsify": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/tsify/-/tsify-5.0.2.tgz", + "integrity": "sha512-Pdo3ZO8CAgbQgNcFRBmfbgsPP+4TsD0itbSF5YgTnxKBXfg6WkQ79e4/bqBaq/7cEYa7vIOM1pHxnux8rJJnzg==", + "dev": true, + "requires": { + "convert-source-map": "^1.1.0", + "fs.realpath": "^1.0.0", + "object-assign": "^4.1.0", + "semver": "^6.1.0", + "through2": "^2.0.0", + "tsconfig": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -12602,6 +12727,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "dev": true + }, "umd": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", @@ -13019,6 +13150,12 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "web-ext-types": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-ext-types/-/web-ext-types-3.2.1.tgz", + "integrity": "sha512-oQZYDU3W8X867h8Jmt3129kRVKklz70db40Y6OzoTTuzOJpF/dB2KULJUf0txVPyUUXuyzV8GmT3nVvRHoG+Ew==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 20875fd..f38d96f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "homepage": "https://github.com/Serraniel/AniwatchPlus#readme", "dependencies": { "color": "^3.1.3", - "spacetime": "^6.12.2", + "spacetime": "github:Serraniel/spacetime#bugfix/#255-typescript-constructor-options-missing", "uuid": "^8.3.2" }, "devDependencies": { @@ -45,6 +45,7 @@ "@babel/plugin-proposal-private-methods": "^7.12.1", "@babel/preset-env": "^7.12.11", "@babel/register": "^7.12.10", + "@types/chrome": "0.0.127", "babelify": "^10.0.0", "browserify": "^17.0.0", "cross-env": "^7.0.3", @@ -66,12 +67,16 @@ "gulp-size": "^3.0.0", "gulp-sourcemaps": "^3.0.0", "gulp-terser": "^2.0.0", + "gulp-typescript": "^6.0.0-alpha.1", "gulp-zip": "^5.0.2", "merge-stream": "^2.0.0", "postcss": "^8.2.1", "sass": "^1.30.0", "terser": "^5.5.1", + "tsify": "^5.0.2", + "typescript": "^4.1.3", "vinyl-buffer": "^1.0.1", - "vinyl-source-stream": "^2.0.0" + "vinyl-source-stream": "^2.0.0", + "web-ext-types": "^3.2.1" } } diff --git a/src/javascript/app.js b/src/javascript/app.ts similarity index 100% rename from src/javascript/app.js rename to src/javascript/app.ts diff --git a/src/javascript/browserApi/storageProvider.js b/src/javascript/browserApi/storageProvider.js deleted file mode 100644 index 0f84b1d..0000000 --- a/src/javascript/browserApi/storageProvider.js +++ /dev/null @@ -1,84 +0,0 @@ -const { assigned } = require("../utils/helpers") - -class StorageProviderChromium { - - setData(key, value) { - let obj = {}; - obj[key] = value; - - this.getStorage().set(obj); - } - - getData(key, defaultValue, callback) { - this.getStorage().get(key, items => { - if (assigned(items) && assigned(items[key])) { - callback(items[key]); - } - else { - callback(defaultValue); - } - }) - } - - getStorage() { - if (assigned(chrome.storage.sync)) { - return chrome.storage.sync; - } - - return chrome.storage.local; - } -} - - -class StorageProviderFirefox { - - setData(key, value) { - let obj = {}; - obj[key] = value; - - this.getStorage().set(obj); - } - - getData(key, defaultValue, callback) { - let promise = this.getStorage().get(key); - - promise.then(items => { - if (assigned(items) && assigned(items[key])) { - callback(items[key]); - } - else { - callback(defaultValue); - } - }); - } - - getStorage() { - if (assigned(browser.storage.sync)) { - return browser.storage.sync; - } - - return browser.storage.local; - } -} - -let __storageProvieder; - -function createStorageProvider() { - // chrome based browser - if (assigned(chrome?.app)) { - __storageProvieder = new StorageProviderChromium(); - } - // firefox - else { - __storageProvieder = new StorageProviderFirefox(); - } - -} - -export function getGlobalStorageProvider() { - if (!assigned(__storageProvieder)) { - createStorageProvider(); - } - - return __storageProvieder; -} \ No newline at end of file diff --git a/src/javascript/browserApi/storageProvider.ts b/src/javascript/browserApi/storageProvider.ts new file mode 100644 index 0000000..0a2f812 --- /dev/null +++ b/src/javascript/browserApi/storageProvider.ts @@ -0,0 +1,117 @@ +import { assigned } from '../utils/helpers'; + +enum BrowserApi { + Unknown, + Chromium, + Firefox, +} + +export type ConfigurationStorageBooleanCallback = (value: boolean) => void; + +export interface ICustomBrowserStorageProvider { + setDataAsBoolean(key: string, value: boolean): void; + getDataAsBoolean(key: string, defaultValue: boolean, callback: ConfigurationStorageBooleanCallback): void; +} + +class StorageProviderChromium implements ICustomBrowserStorageProvider { + + setDataAsBoolean(key: string, value: boolean): void { + let obj = {}; + obj[key] = value; + + this.getStorage().set(obj); + } + + getDataAsBoolean(key: string, defaultValue: boolean, callback: ConfigurationStorageBooleanCallback): void { + this.getStorage().get(key, items => { + if (assigned(items) && assigned(items[key])) { + callback(items[key]); + } + else { + callback(defaultValue); + } + }) + } + + private getStorage(): chrome.storage.StorageArea { + if (assigned(chrome.storage.sync)) { + return chrome.storage.sync; + } + + return chrome.storage.local; + } +} + + +class StorageProviderFirefox implements ICustomBrowserStorageProvider { + + setDataAsBoolean(key: string, value: boolean): void { + let obj = {}; + obj[key] = value; + + this.getStorage().set(obj); + } + + getDataAsBoolean(key: string, defaultValue: boolean, callback: ConfigurationStorageBooleanCallback): void { + let promise = this.getStorage().get(key); + + promise.then(items => { + if (assigned(items) && assigned(items[key])) { + callback(items[key] as boolean); + } + else { + callback(defaultValue); + } + }); + } + + private getStorage(): browser.storage.StorageArea { + if (assigned(browser.storage.sync)) { + return browser.storage.sync; + } + + return browser.storage.local; + } +} + +let __storageProvieder: ICustomBrowserStorageProvider; + +function getBrowserApi(): BrowserApi { + if (typeof chrome !== 'undefined') { + if (typeof browser !== 'undefined') { + return BrowserApi.Firefox; + } + + return BrowserApi.Chromium; + } + else if (typeof browser !== 'undefined') { + return BrowserApi.Firefox; + } + + return BrowserApi.Unknown; +} + +function createStorageProvider() { + + let api = getBrowserApi(); + + // chromium + if (api === BrowserApi.Chromium) { + __storageProvieder = new StorageProviderChromium(); + } + // firefox + else if (api === BrowserApi.Firefox) { + __storageProvieder = new StorageProviderFirefox(); + } + else { + throw "Unknown browser API. Cannot create storage provider."; + } +} + +export function getGlobalStorageProvider(): ICustomBrowserStorageProvider { + if (!assigned(__storageProvieder)) { + createStorageProvider(); + } + + return __storageProvieder; +} \ No newline at end of file diff --git a/src/javascript/configuration/configuration.js b/src/javascript/configuration/configuration.ts similarity index 79% rename from src/javascript/configuration/configuration.js rename to src/javascript/configuration/configuration.ts index 1b677d9..64207c6 100644 --- a/src/javascript/configuration/configuration.js +++ b/src/javascript/configuration/configuration.ts @@ -1,4 +1,4 @@ -import { getGlobalStorageProvider } from "../browserApi/storageProvider"; +import { ConfigurationStorageBooleanCallback, getGlobalStorageProvider } from "../browserApi/storageProvider"; import { assigned } from "../utils/helpers"; // website @@ -17,17 +17,18 @@ export const SETTINGS_playerAutoplayAfterScreenshot = 'playerAutoplayAfterScreen // w2g export const SETTINGS_w2gDisplayCharacterCounter = 'w2gDisplayCharacterCounter'; class Configuration { + settingsCache: Map; + constructor() { this.settingsCache = new Map(); } - getProperty(key, callback) { + getProperty(key: string, callback: ConfigurationStorageBooleanCallback): void { if (this.settingsCache.has(key)) { callback(this.settingsCache.get(key)); } else { - // OOOPS // currently all settings are default true. This isnĀ“t a problem but there should be much better soloutions after migration to typescript.... - getGlobalStorageProvider().getData(key, true, value => { + getGlobalStorageProvider().getDataAsBoolean(key, true, value => { this.settingsCache.set(key, value); callback(value); }); @@ -35,7 +36,7 @@ class Configuration { } } -let __globalConfig; +let __globalConfig: Configuration; export function getGlobalConfiguration() { if (!assigned(__globalConfig)) { @@ -43,4 +44,4 @@ export function getGlobalConfiguration() { } return __globalConfig; -} \ No newline at end of file +} diff --git a/src/javascript/enhancements/anilyr.js b/src/javascript/enhancements/anilyr.ts similarity index 57% rename from src/javascript/enhancements/anilyr.js rename to src/javascript/enhancements/anilyr.ts index 2b4d6cb..7e32362 100644 --- a/src/javascript/enhancements/anilyr.js +++ b/src/javascript/enhancements/anilyr.ts @@ -5,26 +5,27 @@ import * as helper from '../utils/helpers'; const SCREENSHOT_TOOLTIP_ID = 'anilyr-screenshots-tooltip'; const PLAYER_ID = 'player'; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_playerAutoplayAfterScreenshot, value => { if (value) { - core.registerScript(node => { - if (helper.isHtmlElement(node) && node.id === SCREENSHOT_TOOLTIP_ID) { - observeScreenshotTooltip(node); + core.registerScript((node: Node) => { + let element = node as HTMLElement; + if (helper.assigned(element) && element.id === SCREENSHOT_TOOLTIP_ID) { + observeScreenshotTooltip(element); } }, "^/anime/[0-9]*/[0-9]*$"); } }); } -function observeScreenshotTooltip(tooltip) { +function observeScreenshotTooltip(tooltip: HTMLElement): void { let observer = new MutationObserver(mutations => { mutations.forEach(mutation => { // Switched to invisible - if (!mutation.oldValue.includes('display: none') && mutation.target.style.display == 'none') { - let player = findPlayer(); - if (helper.assigned(player)) { - resumePlayer(player); + if (!mutation.oldValue.includes('display: none') && helper.isHtmlElement(mutation.target) && (mutation.target as HTMLElement).style.display == 'none') { + let playerElement = findPlayerElement(); + if (helper.assigned(playerElement)) { + resumePlayer(playerElement); } } }); @@ -37,17 +38,15 @@ function observeScreenshotTooltip(tooltip) { }); } -function findPlayer() { - const PLAYER_TAG_NAME = 'VIDEO'; // tagName gives UpperCase - +function findPlayerElement(): HTMLVideoElement { let playerCandidate = document.getElementById(PLAYER_ID); - if (playerCandidate.tagName === PLAYER_TAG_NAME) { + if (playerCandidate instanceof HTMLVideoElement) { return playerCandidate; } return undefined; } -function resumePlayer(player) { +function resumePlayer(player: HTMLVideoElement) { player.play(); } \ No newline at end of file diff --git a/src/javascript/enhancements/animeRequests.js b/src/javascript/enhancements/animeRequests.ts similarity index 76% rename from src/javascript/enhancements/animeRequests.js rename to src/javascript/enhancements/animeRequests.ts index ce86be0..9e9922c 100644 --- a/src/javascript/enhancements/animeRequests.js +++ b/src/javascript/enhancements/animeRequests.ts @@ -1,14 +1,13 @@ import { getGlobalConfiguration, SETTINGS_requestBeautifyPage } from '../configuration/configuration'; import * as core from '../utils/aniwatchCore'; import * as color from '../utils/colors'; -import * as helper from '../utils/helpers'; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_requestBeautifyPage, value => { if (value) { - core.registerScript(node => { + core.registerScript((node: Node) => { // run the scripts - if (helper.isHtmlElement(node)) { + if (node instanceof HTMLElement) { changeFollowedStarColor(node); changeBorderColorOwnRequests(node); removeUnknownUsers(node); @@ -18,20 +17,20 @@ export function init() { }); } -function changeFollowedStarColor(node) { - const starIcon = 'star'; +function changeFollowedStarColor(node: HTMLElement): void { + const STAR_ICON = 'star'; // find stars - let followedItems = Array.from(node.querySelectorAll('i')).filter(i => i.innerText.trim() === starIcon); + let followedItems = Array.from(node.querySelectorAll('i')).filter(i => i.innerText.trim() === STAR_ICON); // change color followedItems.forEach(item => item.style.color = color.aniBlue); } -function changeBorderColorOwnRequests(node) { - const targetTagName = 'MD-LIST-ITEM'; // tagName is upper case +function changeBorderColorOwnRequests(node: HTMLElement): void { + const TARGET_TAG_NAME = 'MD-LIST-ITEM'; // tagName is upper case - let updateFunc = item => { + let updateFunc = (item: HTMLElement): void => { let profileLink = item.querySelectorAll('a[href*="/profile/"]:not([href="/profile/false"])'); // highlight left border for own request @@ -41,7 +40,7 @@ function changeBorderColorOwnRequests(node) { } // are we target tag? - if (node.tagName === targetTagName) { + if (node.tagName === TARGET_TAG_NAME) { updateFunc(node); } else { // find items -> all @@ -49,15 +48,17 @@ function changeBorderColorOwnRequests(node) { // update borders requestItems.forEach(item => { - updateFunc(item); + if (item instanceof HTMLElement) { + updateFunc(item); + } }); } } -function removeUnknownUsers(node) { - const targetTagName = 'MD-LIST-ITEM'; // tagName is upper case +function removeUnknownUsers(node: HTMLElement): void { + const TARGET_TAG_NAME = 'MD-LIST-ITEM'; // tagName is upper case - let updateFunc = item => { + let updateFunc = (item: Element) => { // find user profile link -> own request let profileLink = item.querySelectorAll('a[href*="/profile/"]:not([href="/profile/false"])'); @@ -66,7 +67,7 @@ function removeUnknownUsers(node) { let lowerDiv = upperDiv.parentElement.nextElementSibling; // remember Data - let anime = lowerDiv.innerText; + let anime = lowerDiv.textContent; let profileData = upperDiv.innerHTML; // add user note if own request @@ -92,7 +93,7 @@ function removeUnknownUsers(node) { upperDiv.appendChild(bElement); } - if (node.tagName === targetTagName) { + if (node.tagName === TARGET_TAG_NAME) { updateFunc(node); } else { // find items -> all diff --git a/src/javascript/enhancements/cssEnhancements.js b/src/javascript/enhancements/cssEnhancements.js deleted file mode 100644 index ce62948..0000000 --- a/src/javascript/enhancements/cssEnhancements.js +++ /dev/null @@ -1,61 +0,0 @@ -import { getGlobalConfiguration, SETTINGS_websiteHideUnusedTabs, SETTINGS_websiteOptimizeListAppearance } from '../configuration/configuration'; -import * as core from '../utils/aniwatchCore'; -import * as helper from '../utils/helpers'; - -export function init() { - getGlobalConfiguration().getProperty(SETTINGS_websiteHideUnusedTabs, value => { - // if disabled, add class to avoid our css optimizations - if (!value) { - let disableFunc = node => { - if (helper.isHtmlElement(node)) { - let disableNode = node => { - node.classList.add('awp-hide-unused-disabled') - } - - if (node.tagName === 'MD-TAB-ITEM') { - disableNode(node); - } - else { - node.querySelectorAll('md-tab-item').forEach(node => disableNode(node)); - } - } - }; - - core.registerScript(node => { - disableFunc(node); - }, ".*"); - - core.runAfterLoad(() => { - disableFunc(document.body); - }, ".*"); - } - }); - - getGlobalConfiguration().getProperty(SETTINGS_websiteOptimizeListAppearance, value => { - // if disabled, add class to avoid our css optimizations - if (!value) { - let disableFunc = node => { - if (helper.isHtmlElement(node)) { - let disableNode = node => { - node.classList.add('awp-list-disabled') - } - - if (node.tagName === 'MD-LIST-ITEM') { - disableNode(node); - } - else { - node.querySelectorAll('md-list-item').forEach(node => disableNode(node)); - } - } - } - - core.registerScript(node => { - disableFunc(node); - }, ".*"); - - core.runAfterLoad(() => { - disableFunc(document.body); - }, ".*"); - } - }); -} \ No newline at end of file diff --git a/src/javascript/enhancements/cssEnhancements.ts b/src/javascript/enhancements/cssEnhancements.ts new file mode 100644 index 0000000..801e95f --- /dev/null +++ b/src/javascript/enhancements/cssEnhancements.ts @@ -0,0 +1,60 @@ +import { getGlobalConfiguration, SETTINGS_websiteHideUnusedTabs, SETTINGS_websiteOptimizeListAppearance } from '../configuration/configuration'; +import * as core from '../utils/aniwatchCore'; + +export function init(): void { + getGlobalConfiguration().getProperty(SETTINGS_websiteHideUnusedTabs, value => { + // if disabled, add class to avoid our css optimizations + if (!value) { + let disableFunc = (node: Element) => { + let disableNode = (node: Element) => { + node.classList.add('awp-hide-unused-disabled') + } + + if (node.tagName === 'MD-TAB-ITEM') { + disableNode(node); + } + else { + node.querySelectorAll('md-tab-item').forEach(node => disableNode(node)); + } + }; + + core.registerScript((node: Node) => { + if (node instanceof Element) { + disableFunc(node); + } + }, ".*"); + + core.runAfterLoad(() => { + disableFunc(document.body); + }, ".*"); + } + }); + + getGlobalConfiguration().getProperty(SETTINGS_websiteOptimizeListAppearance, value => { + // if disabled, add class to avoid our css optimizations + if (!value) { + let disableFunc = (node: Element) => { + let disableNode = (node: Element) => { + node.classList.add('awp-list-disabled') + } + + if (node.tagName === 'MD-LIST-ITEM') { + disableNode(node); + } + else { + node.querySelectorAll('md-list-item').forEach(node => disableNode(node)); + } + } + + core.registerScript(node => { + if (node instanceof Element) { + disableFunc(node); + } + }, ".*"); + + core.runAfterLoad(() => { + disableFunc(document.body); + }, ".*"); + } + }); +} \ No newline at end of file diff --git a/src/javascript/enhancements/fontColor.js b/src/javascript/enhancements/fontColor.ts similarity index 62% rename from src/javascript/enhancements/fontColor.js rename to src/javascript/enhancements/fontColor.ts index 6d3ddeb..9938169 100644 --- a/src/javascript/enhancements/fontColor.js +++ b/src/javascript/enhancements/fontColor.ts @@ -7,7 +7,7 @@ const BADGE_CLASS = 'label'; const DARKCOLOR_CLASS = 'awp-fontColor-dark'; const __observedBadges = []; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_websiteOptimizeFontColors, value => { if (value) { core.runAfterLoad(() => { @@ -18,30 +18,30 @@ export function init() { checkRunColorOptimization(document.documentElement); }, ".*"); - core.registerScript(node => { - checkRunColorOptimization(node); + core.registerScript((node: Node) => { + if (node instanceof Element) { + checkRunColorOptimization(node); + } }, ".*"); } }); } -function checkRunColorOptimization(node) { +function checkRunColorOptimization(node: Element): void { // run the scripts - if (helper.isHtmlElement(node)) { - if (node.classList.contains(BADGE_CLASS)) { - tryRegisterObserverForBadge(node); - optimizeFontColorsBadges(node); - } - else { - node.querySelectorAll(`.${BADGE_CLASS}`).forEach(element => { - tryRegisterObserverForBadge(element); - optimizeFontColorsBadges(element); - }); - } + if (node.classList.contains(BADGE_CLASS)) { + tryRegisterObserverForBadge(node); + optimizeFontColorsBadges(node); + } + else { + node.querySelectorAll(`.${BADGE_CLASS}`).forEach(element => { + tryRegisterObserverForBadge(element); + optimizeFontColorsBadges(element); + }); } } -function tryRegisterObserverForBadge(badge) { +function tryRegisterObserverForBadge(badge: Node): void { // some badges change there color via late loading so we also have to observe the classlist // example: Navigating from a list to an anime -> "Currently Airing" late loads the color badge // this only happens when navigating from a list, direct loading works @@ -52,12 +52,14 @@ function tryRegisterObserverForBadge(badge) { let obsever = new MutationObserver(mutations => { mutations.forEach(mutation => { - // prevent recursive calls when our class is added / removed - if ((mutation.oldValue?.indexOf(DARKCOLOR_CLASS) ?? -1 ^ mutation.target?.classList?.indexOf(DARKCOLOR_CLASS) ?? -1) === 0) { - return; - } - else { - optimizeFontColorsBadges(mutation.target); + // prevent recursive calls when our class is added / removed + if (mutation.target instanceof Element) { + if ((mutation.oldValue?.indexOf(DARKCOLOR_CLASS) >= 0 ?? false) !== (mutation.target?.classList?.contains(DARKCOLOR_CLASS) ?? false)) { + return; + } + else { + optimizeFontColorsBadges(mutation.target); + } } }); }); @@ -71,7 +73,7 @@ function tryRegisterObserverForBadge(badge) { __observedBadges.push(badge); } -function optimizeFontColorsBadges(badge) { +function optimizeFontColorsBadges(badge: Element): void { let colorStr = window.getComputedStyle(badge, null).getPropertyValue('background-color'); // some elements do not have a computed background color diff --git a/src/javascript/enhancements/languageDisplay.js b/src/javascript/enhancements/languageDisplay.ts similarity index 71% rename from src/javascript/enhancements/languageDisplay.js rename to src/javascript/enhancements/languageDisplay.ts index a76529a..60f29a2 100644 --- a/src/javascript/enhancements/languageDisplay.js +++ b/src/javascript/enhancements/languageDisplay.ts @@ -2,12 +2,14 @@ import { getGlobalConfiguration, SETTINGS_animeLanguageDisplay } from '../config import * as core from '../utils/aniwatchCore'; import * as helper from '../utils/helpers'; -export function init() { +const MANIPULATED_ATTR_NAME = 'awpManipulated'; + +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_animeLanguageDisplay, value => { if (value) { - core.registerScript(node => { + core.registerScript((node: Node) => { // run the scripts - if (helper.isHtmlElement(node)) { + if (node instanceof Element) { updateLanguageDisplay(node) } }, "^/anime/[0-9]*$"); @@ -15,34 +17,35 @@ export function init() { }); } -function updateLanguageDisplay(node) { - const listNodeName = 'MD-LIST-ITEM'; - const boxNodeName = 'DIV'; - const boxClassName = 'card-margin'; +function updateLanguageDisplay(node: Element): void { + const LIST_NODE_NAME = 'MD-LIST-ITEM'; + const BOX_NODE_NAME = 'DIV'; + const BOX_CLASS_NAME = 'card-margin'; - if (node.nodeName === listNodeName) { + if (node.nodeName === LIST_NODE_NAME) { updateLanguageDisplayListMode(node); } - else if (node.nodeName === boxNodeName && node.classList.contains(boxClassName)) { + else if (node.nodeName === BOX_NODE_NAME && node.classList.contains(BOX_CLASS_NAME)) { updateLanguageDisplayBoxMode(node); } } -function updateLanguageDisplayListMode(node) { +function updateLanguageDisplayListMode(node: Element): void { // last column with flags let col = node.querySelector('h3.layout-align-end-center'); - if (!helper.assigned(col) || col.awpManipulated) { + + if (!helper.assigned(col) || col.hasAttribute(MANIPULATED_ATTR_NAME)) { return; } doUpdateLanguageDisplay(col, false); } -function updateLanguageDisplayBoxMode(node) { +function updateLanguageDisplayBoxMode(node: Element): void { // last column with flags let col = node.querySelector('div.layout-align-end-start'); - if (!helper.assigned(col) || col.awpManipulated) { + if (!helper.assigned(col) || col.hasAttribute(MANIPULATED_ATTR_NAME)) { return; } @@ -50,27 +53,27 @@ function updateLanguageDisplayBoxMode(node) { } -function doUpdateLanguageDisplay(parent, isBoxedModed) { - const listLangPrefix = 'ep.lang.'; - const boxLangPrefix = 'episodeObject.lang.'; +function doUpdateLanguageDisplay(parent: Element, isBoxedModed: boolean): void { + const LIST_LANG_PREFIX = 'ep.lang.'; + const BOX_LANG_PREFIX = 'episodeObject.lang.'; // aniwatch uses different prefixes in list und box mode :/ - let realLangPrefix = isBoxedModed ? boxLangPrefix : listLangPrefix; + let realLangPrefix = isBoxedModed ? BOX_LANG_PREFIX : LIST_LANG_PREFIX; - const dubSuffix = 'dub'; - const subSuffix = 'sub'; + const DUB_SUFFIX = 'dub'; + const SUB_SUFFIX = 'sub'; - const dubIcon = 'volume_up'; - const subIcon = 'closed_caption'; - const zeroWidthSpace = ''; // ​ + const DUB_ICON = 'volume_up'; + const SUB_ICON = 'closed_caption'; + const ZERO_WIDTH_SPACE_CHARACTER = ''; // ​ - let subs = []; - let dubs = []; + let subs: Array = []; + let dubs: Array = []; // find subs let subCols = parent.querySelectorAll('[ng-hide*="sub"]'); - subCols.forEach(element => { + subCols.forEach((element: Element) => { let langAttr = element.attributes['ng-hide'].value; - let lang = langAttr.substring(langAttr.indexOf(realLangPrefix) + realLangPrefix.length, langAttr.indexOf(subSuffix)); + let lang = langAttr.substring(langAttr.indexOf(realLangPrefix) + realLangPrefix.length, langAttr.indexOf(SUB_SUFFIX)); if (element.attributes['aria-hidden'].value == 'false') { subs.push(lang); } @@ -78,9 +81,9 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { // find dubs let dubCols = parent.querySelectorAll('[ng-hide*="dub"]'); - dubCols.forEach(element => { + dubCols.forEach((element: Element) => { let langAttr = element.attributes['ng-hide'].value; - let lang = langAttr.substring(langAttr.indexOf(realLangPrefix) + realLangPrefix.length, langAttr.indexOf(dubSuffix)); + let lang = langAttr.substring(langAttr.indexOf(realLangPrefix) + realLangPrefix.length, langAttr.indexOf(DUB_SUFFIX)); if (element.attributes['aria-hidden'].value == 'false') { dubs.push(lang); } @@ -106,12 +109,12 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { let dubIconDiv = document.createElement('i'); if (iconsRequired) { dubIconDiv.classList.add('material-icons', 'mr-3'); - dubIconDiv.innerText = dubIcon; + dubIconDiv.innerText = DUB_ICON; } // add dummy with 24px for correct presentation else { dubIconDiv.style.height = '24px'; - dubIconDiv.innerText = zeroWidthSpace; + dubIconDiv.innerText = ZERO_WIDTH_SPACE_CHARACTER; } dubDiv.appendChild(dubIconDiv); @@ -132,16 +135,16 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { let subIconDiv = document.createElement('i'); if (iconsRequired) { subIconDiv.classList.add('material-icons', 'mr-3'); - subIconDiv.innerText = subIcon; + subIconDiv.innerText = SUB_ICON; } // add dummy with 24px for correct presentation else { subIconDiv.style.height = '24px'; - subIconDiv.innerText = zeroWidthSpace; + subIconDiv.innerText = ZERO_WIDTH_SPACE_CHARACTER; } subDiv.appendChild(subIconDiv); - subs.forEach(lang => { + subs.forEach((lang: string) => { let langIcon = document.createElement('i'); langIcon.classList.add('flag', `flag-${lang}`, 'mg-all-1'); subDiv.appendChild(langIcon); @@ -154,7 +157,7 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { } if (dubs.length > 0) { - dubs.forEach(lang => { + dubs.forEach((lang: string) => { let colDiv = document.createElement('div'); colDiv.setAttribute('layout', 'column'); colDiv.classList.add('layout-column'); @@ -167,12 +170,12 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { let dubIconDiv = document.createElement('i'); if (iconsRequired) { dubIconDiv.classList.add('material-icons', 'mr-3'); - dubIconDiv.innerText = dubIcon; + dubIconDiv.innerText = DUB_ICON; } // add dummy with 24px for correct presentation else { dubIconDiv.style.height = '24px'; - dubIconDiv.innerText = zeroWidthSpace; + dubIconDiv.innerText = ZERO_WIDTH_SPACE_CHARACTER; } dubDiv.appendChild(dubIconDiv); @@ -193,12 +196,12 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { let subIconDiv = document.createElement('i'); if (iconsRequired) { subIconDiv.classList.add('material-icons', 'mr-3'); - subIconDiv.innerText = subIcon; + subIconDiv.innerText = SUB_ICON; } // add dummy with 24px for correct presentation else { subIconDiv.style.height = '24px'; - subIconDiv.innerText = zeroWidthSpace; + subIconDiv.innerText = ZERO_WIDTH_SPACE_CHARACTER; } subDiv.appendChild(subIconDiv); @@ -216,13 +219,17 @@ function doUpdateLanguageDisplay(parent, isBoxedModed) { }); parent.querySelectorAll('.layout-column:not(:last-child)').forEach(div => { - div.style.borderRight = '1px solid rgba(155,155,155, 0.2)'; + if (div instanceof HTMLElement) { + div.style.borderRight = '1px solid rgba(155,155,155, 0.2)'; + } }) parent.querySelectorAll('.layout-column').forEach(div => { - div.style.paddingLeft = '2px'; - div.style.paddingRight = '2px'; + if (div instanceof HTMLElement) { + div.style.paddingLeft = '2px'; + div.style.paddingRight = '2px'; + } }) - parent.awpManipulated = true; + parent.setAttribute('awpManipulated', String(true)); } diff --git a/src/javascript/enhancements/notifications.js b/src/javascript/enhancements/notifications.ts similarity index 84% rename from src/javascript/enhancements/notifications.js rename to src/javascript/enhancements/notifications.ts index 10df5ff..768d419 100644 --- a/src/javascript/enhancements/notifications.js +++ b/src/javascript/enhancements/notifications.ts @@ -2,7 +2,7 @@ import { getGlobalConfiguration, SETTINGS_websiteShowNotificationsCountInTab } f import * as core from '../utils/aniwatchCore'; import * as helper from '../utils/helpers'; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_websiteShowNotificationsCountInTab, value => { if (value) { core.runAfterLoad(() => { @@ -16,17 +16,17 @@ export function init() { }); } -function getNotificationCount() { +function getNotificationCount(): number { if (core.isLoggedIn()) { let menuUserText = document.getElementById('materialize-menu-dropdown').innerText.split('\n')[4]; - let notificationCount = menuUserText.match(/\d+/)?.[0] ?? 0; + let notificationCount = parseInt(menuUserText.match(/\d+/)?.[0]) ?? 0; return notificationCount; } else { return 0; } } -function updateNotificationsInTitle() { +function updateNotificationsInTitle(): void { let count = getNotificationCount(); if (helper.assigned(count) && count > 0) { diff --git a/src/javascript/enhancements/quickSearch.js b/src/javascript/enhancements/quickSearch.ts similarity index 86% rename from src/javascript/enhancements/quickSearch.js rename to src/javascript/enhancements/quickSearch.ts index 4094756..95f52d0 100644 --- a/src/javascript/enhancements/quickSearch.js +++ b/src/javascript/enhancements/quickSearch.ts @@ -5,7 +5,7 @@ import * as helper from '../utils/helpers'; const quickSearchID = 'ea-quickSearch'; const quickSearchLink = 'ea-quickSearchLink'; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_websiteDisplayQuickSearch, value => { if (value) { core.runAfterLoad(() => { @@ -15,7 +15,7 @@ export function init() { }); } -function initSearch() { +function initSearch(): void { let entry = document.createElement('li'); entry.setAttribute('ng-repeat', 'item in navbar'); entry.setAttribute('ng-class', '{\'anime-indicator\': item[\'@attributes\'].title==\'Anime\'}'); @@ -45,13 +45,13 @@ function initSearch() { document.addEventListener('keypress', event => handleSearchForShiftF(event)); // additionally, the last dropdown ul has a "right: 0px" style, which has to be fixed with auto, otherwhise it will pop up in the wrong position - Array.from(menu.querySelectorAll('ul.dropdown')).slice(-1)[0].style.right = 'auto'; + (Array.from(menu.querySelectorAll('ul.dropdown')).slice(-1)[0] as HTMLElement).style.right = 'auto'; } -function handleQuickSearch(event) { +function handleQuickSearch(event: KeyboardEvent): void { if (event.key === 'Enter') { - let quickSearchElement = document.getElementById(quickSearchID); - let linkElement = document.getElementById(quickSearchLink); + let quickSearchElement = document.getElementById(quickSearchID) as HTMLInputElement; + let linkElement = document.getElementById(quickSearchLink) as HTMLAnchorElement; let url = new URL(window.location.origin) url.pathname = '/search'; @@ -67,10 +67,10 @@ function handleQuickSearch(event) { } } -function handleSearchForShiftF(event) { +function handleSearchForShiftF(event: KeyboardEvent): void { if (helper.isShiftPressed) { // check if some kind of input is focused already; we then prevent our hotkey - if (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement || document.activeElement.isContentEditable) { + if (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement || ((document.activeElement as HTMLElement)?.isContentEditable ?? false)) { return; } diff --git a/src/javascript/enhancements/timeConversion.js b/src/javascript/enhancements/timeConversion.ts similarity index 88% rename from src/javascript/enhancements/timeConversion.js rename to src/javascript/enhancements/timeConversion.ts index a479759..ce118b6 100644 --- a/src/javascript/enhancements/timeConversion.js +++ b/src/javascript/enhancements/timeConversion.ts @@ -6,7 +6,7 @@ import * as helper from '../utils/helpers'; const __alteredNodes = []; const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_websiteAutoTimeConversion, value => { if (value) { // The regexp pattern matches anything except the airing page. @@ -20,18 +20,18 @@ export function init() { updateTimestamps(document.documentElement); }, "^/(?!airing).*$"); - core.registerScript(node => { + core.registerScript((node: Node) => { updateTimestamps(node); }, "^/(?!airing).*$"); } }); } -function getSpaceDateTimeFormat(use24Format) { +function getSpaceDateTimeFormat(use24Format: boolean): string { return `${getSpaceDateFormat()} ${getSpaceTimeFormat(use24Format)}`; } -function getSpaceTimeFormat(use24Format) { +function getSpaceTimeFormat(use24Format: boolean): string { if (use24Format) { return '{time-24}'; } @@ -39,11 +39,11 @@ function getSpaceTimeFormat(use24Format) { return '{time}'; } -function getSpaceDateFormat() { +function getSpaceDateFormat(): string { return '{date}. {month-short} {year}'; } -function tryUpdateDateTime(node) { +function tryUpdateDateTime(node: Node): boolean { const REG_DATETIME = /(\d{2}(\/|\.)){2}\d{4} *\d?\d:\d{2}( (AM|PM))?/g; const REG_TIME = /\d?\d:\d{2}/; const REG_AMPM = /\s(am|pm)/i; @@ -81,7 +81,7 @@ function tryUpdateDateTime(node) { let datetime = spacetime(processedStr, 'UTC+1', { dmy: true }); datetime = datetime.goto(spacetime().tz); - let replaceText = datetime.format(getSpaceDateTimeFormat(use24Format)); + let replaceText = String(datetime.format(getSpaceDateTimeFormat(use24Format))); node.textContent = node.textContent.replace(hit, replaceText); }); @@ -89,7 +89,7 @@ function tryUpdateDateTime(node) { return true; } -function tryUpdateDate(node) { +function tryUpdateDate(node: Node): boolean { const REG_DATE = /(\d{2}(\/|\.)){2}\d{4}/g; let hits = Array.from(node.textContent.matchAll(REG_DATE), match => match[0]); @@ -101,7 +101,7 @@ function tryUpdateDate(node) { hits.forEach(hit => { let datetime = spacetime(hit, 'UTC+1', { dmy: true }); datetime = datetime.goto(spacetime().tz); - let replaceText = datetime.format(getSpaceDateFormat()); + let replaceText = String(datetime.format(getSpaceDateFormat())); node.textContent = node.textContent.replace(hit, replaceText); }); @@ -109,7 +109,7 @@ function tryUpdateDate(node) { return true; } -function tryUpdateTime(node) { +function tryUpdateTime(node: Node): boolean { const REG_TIME = /\d?\d:\d{2}( (AM|PM))?/g; const REG_AMPM = /\s(am|pm)/i; @@ -148,7 +148,7 @@ function tryUpdateTime(node) { datetime = datetime.goto('UTC+1'); datetime = datetime.time(processedStr); datetime = datetime.goto(spacetime().tz); - let replaceText = datetime.format(getSpaceTimeFormat(use24Format)); + let replaceText = String(datetime.format(getSpaceTimeFormat(use24Format))); node.textContent = node.textContent.replace(hit, replaceText); @@ -168,7 +168,7 @@ function tryUpdateTime(node) { // if day changed if (dOffset != 0) { - let dayNode = node.parentNode.previousElementSibling; + let dayNode = (node.parentNode as Element)?.previousElementSibling; if (helper.assigned(dayNode)) { for (let i = 0; i < DAYS.length; i++) { if (dayNode.textContent.indexOf(DAYS[i]) >= 0) { @@ -183,13 +183,17 @@ function tryUpdateTime(node) { return true; } -function tryUpdateTimeZone(node) { +function tryUpdateTimeZone(node: Node): boolean { const HINT_UTC = 'UTC+1'; if (node.textContent === HINT_UTC) { let tzMeta = spacetime().timezone(); node.textContent = `${tzMeta.name} (UTC${tzMeta.current.offset >= 0 ? '+' : ''}${tzMeta.current.offset})`; + + return true; } + + return false; } function updateTimestamps(node) { diff --git a/src/javascript/enhancements/watch2getherChat.js b/src/javascript/enhancements/watch2getherChat.ts similarity index 88% rename from src/javascript/enhancements/watch2getherChat.js rename to src/javascript/enhancements/watch2getherChat.ts index 0f102b3..8ca0526 100644 --- a/src/javascript/enhancements/watch2getherChat.js +++ b/src/javascript/enhancements/watch2getherChat.ts @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import { getGlobalConfiguration, SETTINGS_w2gDisplayCharacterCounter } from '../configuration/configuration'; import { assigned } from '../utils/helpers'; -export function init() { +export function init(): void { getGlobalConfiguration().getProperty(SETTINGS_w2gDisplayCharacterCounter, value => { if (value) { core.runAfterLocationChange(() => { @@ -13,8 +13,8 @@ export function init() { }); } -function manipulateChatInput() { - let textarea = document.querySelector('.chat-input textarea'); +function manipulateChatInput(): void { + let textarea = document.querySelector('.chat-input textarea') as HTMLTextAreaElement; // avoid duplicate registration if (assigned(textarea.dataset.charCounterId)) { @@ -24,7 +24,7 @@ function manipulateChatInput() { addCharCounter(textarea); } -function addCharCounter(textarea) { +function addCharCounter(textarea: HTMLTextAreaElement): void { let chatDiv = textarea.parentElement.parentElement; // div with chat input and controls let controlRow = chatDiv.children[1]; // row with controls let btn = controlRow.querySelector('button'); // find send button @@ -45,7 +45,7 @@ function addCharCounter(textarea) { }); } -function updateCharCounter(textarea, charCounterSpan) { +function updateCharCounter(textarea: HTMLTextAreaElement, charCounterSpan: HTMLSpanElement): void { const SHAKE_CLASS = 'awp-w2g-chatCounter-max'; let current = textarea.value.length; diff --git a/src/javascript/settings.js b/src/javascript/settings.ts similarity index 59% rename from src/javascript/settings.js rename to src/javascript/settings.ts index 86e4208..8d67105 100644 --- a/src/javascript/settings.js +++ b/src/javascript/settings.ts @@ -5,25 +5,28 @@ const OPTION_SELECTOR = 'input[type="checkbox"'; function storeOptions() { document.querySelectorAll(OPTION_SELECTOR).forEach(optionElement => { - getGlobalStorageProvider().setData(optionElement.id, optionElement.checked); + let optionInputElement = optionElement as HTMLInputElement; + getGlobalStorageProvider().setDataAsBoolean(optionInputElement.id, optionInputElement.checked); }); } function restoreOptions() { - document.querySelectorAll(OPTION_SELECTOR).forEach(optionElement => { - let defaultValue = optionElement.dataset.defaultValue === 'true' ? true : false; + document.querySelectorAll(OPTION_SELECTOR).forEach(optionElement => { + let optionInputElement = optionElement as HTMLInputElement; + let defaultValue = optionInputElement.dataset.defaultValue === 'true' ? true : false; - getGlobalStorageProvider().getData(optionElement.id, defaultValue, value => { - optionElement.checked = value; + getGlobalStorageProvider().getDataAsBoolean(optionInputElement.id, defaultValue, value => { + optionInputElement.checked = value; }); }); } function resetOptions() { document.querySelectorAll(OPTION_SELECTOR).forEach(optionElement => { - let defaultValue = optionElement.dataset.defaultValue === 'true' ? true : false; + let optionInputElement = optionElement as HTMLInputElement; + let defaultValue = optionInputElement.dataset.defaultValue === 'true' ? true : false; - optionElement.checked = defaultValue; + optionInputElement.checked = defaultValue; }); } diff --git a/src/javascript/utils/aniwatchCore.js b/src/javascript/utils/aniwatchCore.ts similarity index 68% rename from src/javascript/utils/aniwatchCore.js rename to src/javascript/utils/aniwatchCore.ts index 0900f8d..28eabc1 100644 --- a/src/javascript/utils/aniwatchCore.js +++ b/src/javascript/utils/aniwatchCore.ts @@ -1,11 +1,24 @@ import * as helper from './helpers'; -/* SCRIPT LOGICS */ -let __scripts = []; -let __afterLoadScripts = []; -let __afterLocationChangeScripts = []; +type ScriptCallback = () => void; +type NodeScriptCallback = (node: Node) => void; -export function initCore() { +type ScriptObj = { + function: ScriptCallback, + pattern: string +} + +type NodeScriptObj = { + function: NodeScriptCallback, + pattern: string +} + +/* SCRIPT LOGICS */ +let __scripts: Array = []; +let __afterLoadScripts: Array = []; +let __afterLocationChangeScripts: Array = []; + +export function initCore(): void { let observer = new MutationObserver(mutations => { mutations.forEach(mutation => { for (let i = 0; i < mutation.addedNodes.length; i++) { @@ -46,11 +59,11 @@ export function initCore() { helper.onReady(() => awaitPageLoaded()); } -export function registerScript(func, pattern = '.*') { - __scripts.push({ "function": func, "pattern": pattern }); +export function registerScript(func: NodeScriptCallback, pattern: string = '.*'): void { + __scripts.push({ function: func, pattern: pattern }); } -export function runScripts(node) { +export function runScripts(node: Node): void { __scripts.forEach(script => { if (window.location.pathname.match(script.pattern)) { script.function(node); @@ -58,20 +71,20 @@ export function runScripts(node) { }); } -function findPreloader() { +function findPreloader(): HTMLElement { return document.getElementById('preloader'); } -export function runAfterLoad(func, pattern = '.*') { +export function runAfterLoad(func: ScriptCallback, pattern: string = '.*'): void { let preloader = findPreloader(); if (typeof preloader !== undefined && preloader.style.display !== "none") { - __afterLoadScripts.push({ "function": func, "pattern": pattern }); + __afterLoadScripts.push({ function: func, pattern: pattern }); } else { func(); } } -function awaitPageLoaded() { +function awaitPageLoaded(): void { let preLoader = findPreloader(); let runScripts = () => { @@ -87,9 +100,9 @@ function awaitPageLoaded() { return; } - let loop = setInterval(() => { + let loop = window.setInterval(() => { if (preLoader.style.display === "none" && document.readyState === 'complete') { - clearInterval(loop); + window.clearInterval(loop); runScripts(); } @@ -97,12 +110,12 @@ function awaitPageLoaded() { } /* PATHNAME LOGIC */ -export function runAfterLocationChange(func, pattern = '.*') { - __afterLocationChangeScripts.push({ "function": func, "pattern": pattern }); +export function runAfterLocationChange(func: ScriptCallback, pattern: string = '.*'): void { + __afterLocationChangeScripts.push({ function: func, pattern: pattern }); } /* LOGIN LOGIC */ -export function isLoggedIn() { +export function isLoggedIn(): boolean { let menu = document.getElementById('materialize-menu-dropdown'); let result = true; diff --git a/src/javascript/utils/colors.js b/src/javascript/utils/colors.ts similarity index 100% rename from src/javascript/utils/colors.js rename to src/javascript/utils/colors.ts diff --git a/src/javascript/utils/helpers.js b/src/javascript/utils/helpers.ts similarity index 65% rename from src/javascript/utils/helpers.js rename to src/javascript/utils/helpers.ts index 0ab13ec..e9c01fd 100644 --- a/src/javascript/utils/helpers.js +++ b/src/javascript/utils/helpers.ts @@ -1,16 +1,16 @@ -export var isShiftPressed = false; -export var isCtrlPressed = false; +export var isShiftPressed: boolean = false; +export var isCtrlPressed: boolean = false; -export function isHtmlElement(object) { +export function isHtmlElement(object: any) { return object instanceof HTMLElement; } -export function initHelpers() { +export function initHelpers(): void { document.addEventListener('keydown', event => handleKeyDown(event)); document.addEventListener('keyup', event => handleKeyUp(event)); } -export function onReady(fn) { +export function onReady(fn: () => void) { if (document.readyState != 'loading') { fn(); } else { @@ -18,19 +18,19 @@ export function onReady(fn) { } } -export function assigned(obj) { +export function assigned(obj: any): boolean { return !(typeof obj === 'undefined' || obj === null); } -function handleKeyDown(event) { +function handleKeyDown(event: KeyboardEvent) { handleKeyToggle(event, true); } -function handleKeyUp(event) { +function handleKeyUp(event: KeyboardEvent) { handleKeyToggle(event, false); } -function handleKeyToggle(event, isPressed) { +function handleKeyToggle(event: KeyboardEvent, isPressed: boolean) { if (event.key === 'Shift') { isShiftPressed = isPressed; } else if (event.key === 'Control') { @@ -38,7 +38,7 @@ function handleKeyToggle(event, isPressed) { } } -export function findTextNodes(baseNode) { +export function findTextNodes(baseNode: Node): Array { if (!assigned(baseNode)) { baseNode = document.documentElement; } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c7e9ab4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + // You have to explicitly set @types to get DefinitelyTyped type definitions + "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"], + // Default would be DOM,ES6,DOM.Iterable,ScriptHost (see https://www.typescriptlang.org/docs/handbook/compiler-options.html). However the ES 2020 is required for string.prototype.matchAll (see https://stackoverflow.com/a/57298833). + "lib": ["ES2020", "DOM", "DOM.Iterable", "ScriptHost"] + } + } \ No newline at end of file