Migrate to eslint v9

This commit is contained in:
0x1eef 2024-07-28 23:41:26 -03:00
parent 4f96451c6d
commit 843852201b
17 changed files with 664 additions and 2803 deletions

View file

@ -1,40 +0,0 @@
module.exports = {
extends: ["standard-with-typescript", "standard-jsx", "prettier"],
"plugins": ["prettier"],
parserOptions: {
project: "./tsconfig.json",
},
rules: {
"@typescript-eslint/member-delimiter-style": 2,
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/no-extra-semi": "error",
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/strict-boolean-expressions": 0,
"@typescript-eslint/no-floating-promises": 0,
"@typescript-eslint/prefer-nullish-coalescing": 0,
"@typescript-eslint/restrict-template-expressions": 0,
"@typescript-eslint/promise-function-async": 0,
"@typescript-eslint/consistent-type-definitions": 0,
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
"@typescript-eslint/no-redeclare": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-var-requires": 0,
"no-return-assign": 0,
"no-useless-return": 0,
"quotes": 0,
"object-curly-spacing": 2,
"n/no-callback-literal": 0,
"prettier/prettier": [
"error",
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"printWidth": 90,
"arrowParens": "avoid"
}
]
},
};

12
eslint.config.mjs Normal file
View file

@ -0,0 +1,12 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-plugin-prettier/recommended';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
rules: {'@typescript-eslint/no-var-requires': 0},
}
)

3248
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,17 +16,17 @@
"@types/css-font-loading-module": "^0.0.13",
"@types/react": "^18.0",
"@types/react-dom": "^18.0",
"sass": "^1.77",
"css-loader": "^7.1",
"sass-loader": "^16.0",
"style-loader": "^4.0",
"esbuild-loader": "^4.1",
"eslint": "^9.8",
"eslint-config-prettier": "^9.1",
"eslint-plugin-prettier": "^5.2",
"eslint-plugin-prettier": "^5.2.1",
"prettier": "^3.3",
"sass": "^1.77",
"sass-loader": "^16.0",
"style-loader": "^4.0",
"typescript": "^5.5",
"ts-standard": "^12.0",
"typescript-eslint": "^8.0.0-alpha.10",
"url": "^0.11",
"webpack": "^5.93",
"webpack-cli": "^5.1",

View file

@ -38,7 +38,9 @@ export function AudioControl({
}, [hidden, enabled, ayah?.id, audioBaseUrl]);
useEffect(() => {
const el: HTMLDivElement | null = document.querySelector("[data-audio-base-url]");
const el: HTMLDivElement | null = document.querySelector(
"[data-audio-base-url]",
);
const url = el?.dataset?.audioBaseUrl;
if (url?.length) {
setAudioBaseUrl(url);
@ -73,7 +75,10 @@ export function AudioControl({
useEffect(() => {
if (audioStatus) {
onStatusChange(audioStatus, [() => setEnabled(true), () => setEnabled(false)]);
onStatusChange(audioStatus, [
() => setEnabled(true),
() => setEnabled(false),
]);
}
}, [audioStatus]);
@ -83,8 +88,12 @@ export function AudioControl({
return (
<>
{enabled && <SoundOnIcon onClick={() => [setEnabled(false), pause(audio)]} />}
{!enabled && <SoundOffIcon onClick={() => [setEnabled(true), play(audio)]} />}
{enabled && (
<SoundOnIcon onClick={() => [setEnabled(false), pause(audio)]} />
)}
{!enabled && (
<SoundOffIcon onClick={() => [setEnabled(true), play(audio)]} />
)}
</>
);
}

View file

@ -13,7 +13,7 @@ type Props = {
function Select({ value, children: options, className }: Props) {
const [isOpen, setOpen] = useState<boolean>(false);
const [option, setOption] = useState<JSX.Element | null>(null);
const sortedOptions = options.sort(n => (value === n.props.value ? -1 : 1));
const sortedOptions = options.sort((n) => (value === n.props.value ? -1 : 1));
const close = () => setOpen(false);
useEffect(() => {
@ -22,11 +22,16 @@ function Select({ value, children: options, className }: Props) {
}, []);
useEffect(() => {
setOption(options.find(n => value === n.props.value) || null);
setOption(options.find((n) => value === n.props.value) || null);
}, [value]);
return (
<div className={classNames("react-select flex flex-col h-full relative", className)}>
<div
className={classNames(
"react-select flex flex-col h-full relative",
className,
)}
>
<ul className="m-0 p-0 list-none text-base h-full mb-5">
{sortedOptions.map((n: JSX.Element, key: number) => {
const isHidden = !isOpen && option?.props.value !== n.props.value;
@ -34,7 +39,7 @@ function Select({ value, children: options, className }: Props) {
<li
key={key}
className={classNames({ hidden: isHidden })}
onClick={e => [e.stopPropagation(), setOpen(!isOpen)]}
onClick={(e) => [e.stopPropagation(), setOpen(!isOpen)]}
>
{n}
</li>

View file

@ -19,7 +19,7 @@ export function Filter({ locale, t, setIndex, surahs }: Props) {
} else {
const regexp = new RegExp(value, "i");
const newIndex = surahs.filter(
surah =>
(surah) =>
regexp.test(surah.name) ||
regexp.test(surah.roman.name) ||
regexp.test(String(surah.id)) ||

View file

@ -69,7 +69,8 @@ export function SurahIndex({ locale, surahs, t }: Props) {
{surah.roman.name}
</span>
<span className="ayat flex justify-end text-sm">
{formatNumber(locale, surah.numberOfAyah)} {t(locale, "ayat")}
{formatNumber(locale, surah.numberOfAyah)}{" "}
{t(locale, "ayat")}
</span>
</div>
</div>

View file

@ -13,7 +13,14 @@ type Props = {
t: TFunction;
};
export function Stream({ locale, surah, stream, endOfStream, isPaused, t }: Props) {
export function Stream({
locale,
surah,
stream,
endOfStream,
isPaused,
t,
}: Props) {
const className = endOfStream || isPaused ? ["scroll-y"] : [];
const ref = useRef<HTMLUListElement>(null);
const ul = useMemo<JSX.Element>(() => {
@ -35,13 +42,15 @@ export function Stream({ locale, surah, stream, endOfStream, isPaused, t }: Prop
key={ayah.id}
className={classNames("ayah fade", { "mb-6": rtl, "mb-4": ltr })}
>
<span className={classNames("flex h-8 items-center", { "mb-2": rtl })}>
<span
className={classNames("flex h-8 items-center", { "mb-2": rtl })}
>
<AudioControl
hidden={!(isPaused || endOfStream)}
audio={new Audio()}
surah={surah}
ayah={ayah}
onStatusChange={(status, [_, disable]) => {
onStatusChange={(status, [, disable]) => {
if (status === "end") {
disable();
}
@ -49,8 +58,9 @@ export function Stream({ locale, surah, stream, endOfStream, isPaused, t }: Prop
/>
<span>
{t(locale, "surah")} {formatNumber(locale, surah.id)}
{t(locale, "comma")} {t(locale, "ayah")} {formatNumber(locale, ayah.id)}{" "}
{t(locale, "of")} {formatNumber(locale, surah.ayat.length)}
{t(locale, "comma")} {t(locale, "ayah")}{" "}
{formatNumber(locale, ayah.id)} {t(locale, "of")}{" "}
{formatNumber(locale, surah.ayat.length)}
</span>
</span>
<p className="m-0">{ayah.body}</p>

View file

@ -4,7 +4,12 @@ import type { Surah, Ayah, TAyat, TLocale } from "Quran";
import { useTheme } from "~/hooks/useTheme";
import { AudioControl, TAudioStatus } from "~/components/AudioControl";
import { Head } from "~/components/Head";
import { PlayIcon, PauseIcon, RefreshIcon, StalledIcon } from "~/components/Icon";
import {
PlayIcon,
PauseIcon,
RefreshIcon,
StalledIcon,
} from "~/components/Icon";
import { Timer } from "~/components/Timer";
import { TFunction } from "~/lib/t";
import { Stream } from "./Stream";
@ -65,8 +70,12 @@ export function SurahStream({ surah, locale, t }: Props) {
t={t}
/>
<footer className="flex justify-between items-center h-16">
{!endOfStream && isPaused && <PlayIcon onClick={() => setIsPaused(false)} />}
{!endOfStream && !isPaused && <PauseIcon onClick={() => setIsPaused(true)} />}
{!endOfStream && isPaused && (
<PlayIcon onClick={() => setIsPaused(false)} />
)}
{!endOfStream && !isPaused && (
<PauseIcon onClick={() => setIsPaused(true)} />
)}
<span
className={classNames("sound-box flex flex-col items-end w-16", {
hidden: endOfStream,
@ -77,7 +86,7 @@ export function SurahStream({ surah, locale, t }: Props) {
surah={surah}
ayah={ayah}
hidden={endOfStream}
onStatusChange={status => {
onStatusChange={(status) => {
setAudioStatus(status);
}}
/>

View file

@ -6,9 +6,11 @@ const THEMES: Theme[] = ["blue", "green"];
const DEFAULT_THEME = "blue";
export function useTheme(): Result {
const cookies = Object.fromEntries(document.cookie.split(";").map(e => e.split("=")));
const cookies = Object.fromEntries(
document.cookie.split(";").map((e) => e.split("=")),
);
const [theme, setTheme] = useState<Theme>(
THEMES.find(t => t === cookies.theme) || DEFAULT_THEME,
THEMES.find((t) => t === cookies.theme) || DEFAULT_THEME,
);
const set = (t: Theme) => {
if (THEMES.includes(t)) {

View file

@ -17,9 +17,14 @@ export function T(phrases: PhraseMap<string>): TFunction {
};
}
export function formatNumber(locale: TLocale, num: number, options = {}): string {
export function formatNumber(
locale: TLocale,
num: number,
options = {},
): string {
const numl = locale.name === "ar" ? "ar-SA" : locale.name;
return new Intl.NumberFormat(numl, { maximumFractionDigits: 1, ...options }).format(
num,
);
return new Intl.NumberFormat(numl, {
maximumFractionDigits: 1,
...options,
}).format(num);
}

View file

@ -3,7 +3,9 @@ import { formatNumber } from "~/lib/t";
(function () {
const doc = document.documentElement;
const rev = doc.querySelector("meta[name='revision']")!.getAttribute("content")!;
const rev = doc
.querySelector("meta[name='revision']")!
.getAttribute("content")!;
const locale = {
name: doc.lang,
direction: doc.dir as "rtl" | "ltr",
@ -21,14 +23,19 @@ import { formatNumber } from "~/lib/t";
item.font("Mada Regular", "url(/fonts/mada-regular.ttf"),
item.progress((percent: number) => {
progressBar.value = percent;
progressNumber.innerText = formatNumber(locale, Number(percent.toFixed(0)));
progressNumber.innerText = formatNumber(
locale,
Number(percent.toFixed(0)),
);
}),
)
.fetch()
.then(pkg => {
[loader, style].forEach(el => el.remove());
pkg.fonts.forEach(f => document.fonts.add(f));
pkg.css.forEach(s => document.head.appendChild(s));
pkg.scripts.forEach(s => document.body.removeChild(document.body.appendChild(s)));
.then((pkg) => {
[loader, style].forEach((el) => el.remove());
pkg.fonts.forEach((f) => document.fonts.add(f));
pkg.css.forEach((s) => document.head.appendChild(s));
pkg.scripts.forEach((s) =>
document.body.removeChild(document.body.appendChild(s)),
);
});
})();

View file

@ -3,7 +3,9 @@ import { formatNumber } from "~/lib/t";
(function () {
const doc = document.documentElement;
const rev = doc.querySelector("meta[name='revision']")!.getAttribute("content")!;
const rev = doc
.querySelector("meta[name='revision']")!
.getAttribute("content")!;
const { surahId } = document.querySelector<HTMLElement>(".root")!.dataset;
const locale = {
name: doc.lang,
@ -27,15 +29,20 @@ import { formatNumber } from "~/lib/t";
/* eslint-enable */
item.progress((percent: number) => {
progressBar.value = percent;
progressNumber.innerText = formatNumber(locale, Number(percent.toFixed(0)));
progressNumber.innerText = formatNumber(
locale,
Number(percent.toFixed(0)),
);
}),
)
.fetch()
.then(pkg => {
[loader, style].forEach(el => el.remove());
pkg.fonts.forEach(f => document.fonts.add(f));
pkg.css.forEach(s => document.head.appendChild(s));
pkg.json.forEach(o => document.body.appendChild(o));
pkg.scripts.forEach(s => document.body.removeChild(document.body.appendChild(s)));
.then((pkg) => {
[loader, style].forEach((el) => el.remove());
pkg.fonts.forEach((f) => document.fonts.add(f));
pkg.css.forEach((s) => document.head.appendChild(s));
pkg.json.forEach((o) => document.body.appendChild(o));
pkg.scripts.forEach((s) =>
document.body.removeChild(document.body.appendChild(s)),
);
});
})();

View file

@ -1,10 +1,10 @@
import { Quran } from "Quran";
(function () {
const defaultl = "en";
const locales = Quran.locales.map(l => l.name);
const locales = Quran.locales.map((l) => l.name);
const locale =
navigator.languages
.map(s => s.slice(0, 2).toLowerCase())
.find(s => locales.includes(s)) || defaultl;
.map((s) => s.slice(0, 2).toLowerCase())
.find((s) => locales.includes(s)) || defaultl;
location.replace(`/${locale}/`);
})();

View file

@ -10,9 +10,13 @@ import { SurahIndex } from "~/components/SurahIndex";
const t = T(require("@json/t.json"));
const byLocale = require("@json/surahs");
const locale = (() => {
return Quran.locales.find(ll => ll.name === doc.lang);
return Quran.locales.find((ll) => ll.name === doc.lang);
})()!;
const surahs: Surah[] = byLocale[locale.name].map((e: TSurah) => new Surah(e));
const surahs: Surah[] = byLocale[locale.name].map(
(e: TSurah) => new Surah(e),
);
ReactDOM.createRoot(root).render(<SurahIndex locale={locale} surahs={surahs} t={t} />);
ReactDOM.createRoot(root).render(
<SurahIndex locale={locale} surahs={surahs} t={t} />,
);
})();

View file

@ -9,7 +9,7 @@ import { SurahStream } from "~/components/SurahStream";
const root = doc.querySelector(".root")!;
const t = T(require("@json/t.json"));
const locale = (() => {
return Quran.locales.find(ll => ll.name === doc.lang);
return Quran.locales.find((ll) => ll.name === doc.lang);
})()!;
/*
@ -32,5 +32,7 @@ import { SurahStream } from "~/components/SurahStream";
ayah.ms = ms * 1000;
}
ReactDOM.createRoot(root).render(<SurahStream surah={surah} locale={locale} t={t} />);
ReactDOM.createRoot(root).render(
<SurahStream surah={surah} locale={locale} t={t} />,
);
})();