Migrate to eslint v9
This commit is contained in:
parent
4f96451c6d
commit
843852201b
17 changed files with 664 additions and 2803 deletions
40
.eslintrc.js
40
.eslintrc.js
|
@ -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
12
eslint.config.mjs
Normal 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
3248
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -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",
|
||||
|
|
|
@ -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)]} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)) ||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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}/`);
|
||||
})();
|
||||
|
|
|
@ -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} />,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -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} />,
|
||||
);
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue