Merge pull request #137 from ReflectsLight/prettier

Add prettier GitHub action
This commit is contained in:
Robert 2023-07-13 14:17:26 -03:00 committed by GitHub
commit c55ab60dfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 429 additions and 337 deletions

View file

@ -17,9 +17,10 @@ module.exports = {
"@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,
"no-return-assign": 0,
"no-useless-return": 0,
"quotes": 2,
"quotes": 0,
"object-curly-spacing": 2,
"n/no-callback-literal": 0,
},

View file

@ -21,6 +21,15 @@ jobs:
run: bundle exec rake lint:eslint
- name: rubocop
run: bundle exec rake lint:rubocop
prettier:
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v2
- name: Prepare environment
uses: './.github/actions/prepare-env'
- name: prettier
run: npm exec prettier -- --check src/js/
build:
runs-on: ubuntu-latest
steps:

View file

@ -3,6 +3,6 @@
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"printWidth": 80,
"printWidth": 90,
"arrowParens": "avoid"
}

View file

@ -3,7 +3,8 @@
"workspaces": ["./packages/typescript/*"],
"scripts": {
"eslint": "node ./node_modules/eslint/bin/eslint.js src/js/",
"eslint-autofix": "node ./node_modules/eslint/bin/eslint.js --fix src/js/"
"eslint-autofix": "node ./node_modules/eslint/bin/eslint.js --fix src/js/",
"prettier-autofix": "npm exec prettier -- --write src/js/"
},
"dependencies": {
"react": "^18.2.0",

View file

@ -1,9 +1,9 @@
import React from 'react';
import { Select, SelectOption } from 'components/Select';
import React from "react";
import { Select, SelectOption } from "components/Select";
interface Props {
locale: string
onChange: (o: SelectOption) => void
locale: string;
onChange: (o: SelectOption) => void;
}
export function LanguageSelect({ locale, onChange }: Props) {

View file

@ -1,23 +1,25 @@
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import React, { useState, useEffect } from "react";
import classnames from "classnames";
export type ChangeEvent = React.MouseEvent<HTMLLIElement> & {target: HTMLLIElement};
export type ChangeEvent = React.MouseEvent<HTMLLIElement> & {
target: HTMLLIElement;
};
export interface SelectOption {
innerText: string
value: string
reactEvent: ChangeEvent
innerText: string;
value: string;
reactEvent: ChangeEvent;
}
interface Props {
value: string
children: JSX.Element[]
onChange: (e: SelectOption) => void
className?: string
value: string;
children: JSX.Element[];
onChange: (e: SelectOption) => void;
className?: string;
}
const findOption = (value: string, children: JSX.Element[]) => {
const activeOption = children.find((o) => o.props.value === value);
const activeOption = children.find(o => o.props.value === value);
if (activeOption) {
return activeOption.props.children;
} else {
@ -27,17 +29,19 @@ const findOption = (value: string, children: JSX.Element[]) => {
const createOption = (e: ChangeEvent, children: JSX.Element[]): SelectOption => {
const { target } = e;
const value = target.getAttribute('data-value')!;
const value = target.getAttribute("data-value")!;
return {
innerText: findOption(value, children),
value,
reactEvent: e
reactEvent: e,
};
};
export function Select({ value, children, onChange, className }: Props) {
const [open, setOpen] = useState<boolean>(false);
const [activeOption, setActiveOption] = useState<string | null>(findOption(value, children));
const [activeOption, setActiveOption] = useState<string | null>(
findOption(value, children),
);
const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => {
e.stopPropagation();
setOpen(true);
@ -52,11 +56,11 @@ export function Select({ value, children, onChange, className }: Props) {
};
useEffect(() => {
document.body.addEventListener('click', () => setOpen(false));
document.body.addEventListener("click", () => setOpen(false));
}, []);
return (
<div className={classnames('react-select', className)}>
<div className={classnames("react-select", className)}>
<span className="active-option" onClick={openSelect}>
{activeOption}
</span>

View file

@ -1,7 +1,7 @@
import React from 'react';
import React from "react";
interface Props {
onClick: () => void
onClick: () => void;
}
export function PlayShape({ onClick }: Props) {
@ -25,10 +25,28 @@ export function SoundOnShape({ onClick }: Props) {
return (
<svg viewBox="0 0 100 100" className="svg sound-on" onClick={onClick}>
<g>
<polygon fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" points="3,32 3,20 15,20 33,2 33,32 33,62 15,44
3,44 "/>
<path fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" d="M41,42c5.522,0,10-4.478,10-10s-4.478-10-10-10"/>
<path fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" d="M41,12c11.046,0,20,8.954,20,20s-8.954,20-20,20"/>
<polygon
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
points="3,32 3,20 15,20 33,2 33,32 33,62 15,44
3,44 "
/>
<path
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
d="M41,42c5.522,0,10-4.478,10-10s-4.478-10-10-10"
/>
<path
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
d="M41,12c11.046,0,20,8.954,20,20s-8.954,20-20,20"
/>
</g>
</svg>
);
@ -38,10 +56,34 @@ export function SoundOffShape({ onClick }: Props) {
return (
<svg viewBox="0 0 100 100" className="svg sound-off" onClick={onClick}>
<g>
<polygon fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" points="4,32 4,20 16,20 34,2 34,32 34,62 16,44
4,44 "/>
<line fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" x1="42" y1="23" x2="60" y2="41"/>
<line fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" x1="42" y1="41" x2="60" y2="23"/>
<polygon
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
points="4,32 4,20 16,20 34,2 34,32 34,62 16,44
4,44 "
/>
<line
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
x1="42"
y1="23"
x2="60"
y2="41"
/>
<line
fill="none"
stroke="#000000"
strokeWidth="2"
strokeMiterlimit="10"
x1="42"
y1="41"
x2="60"
y2="23"
/>
</g>
</svg>
);
@ -49,9 +91,18 @@ export function SoundOffShape({ onClick }: Props) {
export function RefreshShape({ onClick }: Props) {
return (
<svg onClick={onClick} className="shape refresh" x="0px" y="0px" width="438.542px" height="438.542px" viewBox="0 0 438.542 438.542">
<g>
<path d="M427.408,19.697c-7.803-3.23-14.463-1.902-19.986,3.999l-37.116,36.834C349.94,41.305,326.672,26.412,300.5,15.848
<svg
onClick={onClick}
className="shape refresh"
x="0px"
y="0px"
width="438.542px"
height="438.542px"
viewBox="0 0 438.542 438.542"
>
<g>
<path
d="M427.408,19.697c-7.803-3.23-14.463-1.902-19.986,3.999l-37.116,36.834C349.94,41.305,326.672,26.412,300.5,15.848
C274.328,5.285,247.251,0.003,219.271,0.003c-29.692,0-58.052,5.808-85.08,17.417c-27.03,11.61-50.347,27.215-69.951,46.82
c-19.605,19.607-35.214,42.921-46.824,69.949C5.807,161.219,0,189.575,0,219.271c0,29.687,5.807,58.05,17.417,85.079
c11.613,27.031,27.218,50.347,46.824,69.952c19.604,19.599,42.921,35.207,69.951,46.818c27.028,11.611,55.388,17.419,85.08,17.419
@ -62,23 +113,24 @@ export function RefreshShape({ onClick }: Props) {
c0-19.795,3.858-38.691,11.563-56.674c7.707-17.985,18.127-33.547,31.261-46.678c13.135-13.134,28.693-23.555,46.682-31.265
c17.983-7.707,36.879-11.563,56.671-11.563c38.259,0,71.475,13.039,99.646,39.116l-39.409,39.394
c-5.903,5.711-7.231,12.279-4.001,19.701c3.241,7.614,8.856,11.42,16.854,11.42h127.906c4.949,0,9.23-1.807,12.848-5.424
c3.613-3.616,5.42-7.898,5.42-12.847V36.55C438.542,28.558,434.84,22.943,427.408,19.697z"/>
</g>
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
</svg>
c3.613-3.616,5.42-7.898,5.42-12.847V36.55C438.542,28.558,434.84,22.943,427.408,19.697z"
/>
</g>
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
<g />
</svg>
);
}

View file

@ -1,33 +1,37 @@
import * as Quran from 'lib/Quran';
import React, { useEffect } from 'react';
import { formatNumber, TFunction } from 'lib/i18n';
import { Slice } from 'lib/Quran/Slice';
import classNames from 'classnames';
import * as Quran from "lib/Quran";
import React, { useEffect } from "react";
import { formatNumber, TFunction } from "lib/i18n";
import { Slice } from "lib/Quran/Slice";
import classNames from "classnames";
interface Props {
surah: Quran.Surah
stream: Quran.Ayat
locale: Quran.Locale
slice: Slice
endOfStream: boolean
isPaused: boolean
t: TFunction
surah: Quran.Surah;
stream: Quran.Ayat;
locale: Quran.Locale;
slice: Slice;
endOfStream: boolean;
isPaused: boolean;
t: TFunction;
}
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t }: Props) {
const className = classNames('body', 'stream');
const style: React.CSSProperties = endOfStream || isPaused ?
{ 'overflowY': 'auto' } :
{ 'overflowY': 'hidden' };
export function Stream({
surah,
stream,
locale,
slice,
endOfStream,
isPaused,
t,
}: Props) {
const className = classNames("body", "stream");
const style: React.CSSProperties =
endOfStream || isPaused ? { overflowY: "auto" } : { overflowY: "hidden" };
const ayat = stream.map((ayah: Quran.Ayah) => {
return (
<li key={ayah.id} className="ayah fade">
<span className="surah-id ayah-id">
{t(locale, 'surah')}{' '}
{formatNumber(surah.id, locale)}
{t(locale, 'comma')}{' '}
{t(locale, 'ayah')}{' '}
{formatNumber(ayah.id, locale)}
{t(locale, "surah")} {formatNumber(surah.id, locale)}
{t(locale, "comma")} {t(locale, "ayah")} {formatNumber(ayah.id, locale)}
</span>
<p>{ayah.text}</p>
</li>
@ -35,12 +39,12 @@ export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t
});
useEffect(() => {
const ul: HTMLElement = document.querySelector('ul.stream')!;
const ul: HTMLElement = document.querySelector("ul.stream")!;
if (slice.coversOneAyah) {
const li: HTMLLIElement = ul.querySelector('li:last-child')!;
const li: HTMLLIElement = ul.querySelector("li:last-child")!;
li.scrollIntoView();
} else {
ul.scroll({ top: ul.scrollHeight, behavior: 'smooth' });
ul.scroll({ top: ul.scrollHeight, behavior: "smooth" });
}
}, [stream]);

View file

@ -1,22 +1,22 @@
import React from 'react';
import { Select, SelectOption } from 'components/Select';
import { set as setCookie } from 'es-cookie';
import React from "react";
import { Select, SelectOption } from "components/Select";
import { set as setCookie } from "es-cookie";
interface Props {
setTheme: (theme: string) => void
theme: string
setTheme: (theme: string) => void;
theme: string;
}
export function ThemeSelect ({ setTheme, theme }: Props) {
export function ThemeSelect({ setTheme, theme }: Props) {
const onThemeChange = (o: SelectOption) => {
setCookie('theme', o.value, { domain: location.host, expires: 365 });
setCookie("theme", o.value, { domain: location.host, expires: 365 });
setTheme(o.value);
};
return (
<Select value={theme} onChange={onThemeChange}>
<option value='moon'>🌛</option>
<option value='leaf'>🌿</option>
<option value="moon">🌛</option>
<option value="leaf">🌿</option>
</Select>
);
}

View file

@ -1,18 +1,26 @@
import React, { useEffect, useState } from 'react';
import * as Quran from 'lib/Quran';
import { formatNumber } from 'lib/i18n';
import React, { useEffect, useState } from "react";
import * as Quran from "lib/Quran";
import { formatNumber } from "lib/i18n";
interface Props {
surah: Quran.Surah
locale: Quran.Locale
stream: Quran.Ayat
soundOn: boolean
setStream: (stream: Quran.Ayat) => void
isPaused: boolean
isStalled: boolean
surah: Quran.Surah;
locale: Quran.Locale;
stream: Quran.Ayat;
soundOn: boolean;
setStream: (stream: Quran.Ayat) => void;
isPaused: boolean;
isStalled: boolean;
}
export function Timer ({ surah, stream, isStalled, soundOn, setStream, locale, isPaused }: Props) {
export function Timer({
surah,
stream,
isStalled,
soundOn,
setStream,
locale,
isPaused,
}: Props) {
const ayah = stream[stream.length - 1];
const [ms, setMs] = useState(ayah.readTimeMs);
const [tid, setTid] = useState<ReturnType<typeof setTimeout>>();
@ -35,9 +43,5 @@ export function Timer ({ surah, stream, isStalled, soundOn, setStream, locale, i
}
}, [isStalled, isPaused, soundOn, ms]);
return (
<div className='timer'>
{formatNumber(ms / 1000, locale)}
</div>
);
return <div className="timer">{formatNumber(ms / 1000, locale)}</div>;
}

View file

@ -1,25 +1,25 @@
export interface Locale {
fromBrowser: () => string
fromPath: () => string | undefined
fromBrowser: () => string;
fromPath: () => string | undefined;
}
export function Locale(window: Window): Locale {
const self = Object.create(null);
const { navigator, location } = window;
const locales = ['ar', 'en'];
const locales = ["ar", "en"];
self.fromBrowser = () => {
return navigator
.languages
.map((lang) => lang.substr(0, 2))
.find((locale) => locales.includes(locale)) || 'en';
return (
navigator.languages
.map(lang => lang.substr(0, 2))
.find(locale => locales.includes(locale)) || "en"
);
};
self.fromPath = () => {
return location
.pathname
.split('/')
.filter((s) => s.length)
return location.pathname
.split("/")
.filter(s => s.length)
.at(0);
};

View file

@ -1,9 +1,9 @@
import * as JSON from 'lib/Quran/JSON';
import { Ayah } from 'lib/Quran/Ayah';
import { Surah } from 'lib/Quran/Surah';
import * as JSON from "lib/Quran/JSON";
import { Ayah } from "lib/Quran/Ayah";
import { Surah } from "lib/Quran/Surah";
type Locale = 'ar' | 'en';
type Locale = "ar" | "en";
type Ayat = Ayah[];
type Reciter = { id: string, name: string, nationality: string, url: string };
type Reciter = { id: string; name: string; nationality: string; url: string };
export { Surah, Ayah, Ayat, Reciter, Locale, JSON };

View file

@ -1,9 +1,9 @@
import * as Quran from 'lib/Quran';
import * as Quran from "lib/Quran";
export type Ayah = {
id: number
text: string
readTimeMs: number
id: number;
text: string;
readTimeMs: number;
};
export function Ayah(ayah: Quran.JSON.Ayah): Ayah {

View file

@ -1,11 +1,11 @@
interface Surah {
id: string
place_of_revelation: string
transliterated_name: string
translated_name: string
ayahs: number
slug: string
codepoints: number[]
id: string;
place_of_revelation: string;
transliterated_name: string;
translated_name: string;
ayahs: number;
slug: string;
codepoints: number[];
}
type Ayah = [number, string];
type Ayat = Ayah[];

View file

@ -1,11 +1,11 @@
export interface Slice {
begin: number
end: number | null
coversOneAyah: boolean
coversOneSurah: boolean
coversSubsetOfSurah: boolean
subsetLength: number
toParam: () => string | null
begin: number;
end: number | null;
coversOneAyah: boolean;
coversOneSurah: boolean;
coversSubsetOfSurah: boolean;
subsetLength: number;
toParam: () => string | null;
}
export function Slice(begin: number, end: number | null): Slice {
@ -32,7 +32,7 @@ const getSubsetLength = (slice: Slice) => {
const digitsRange = /^(\d{1,3})\.\.(\d{1,3})$/;
const digits = /^\d{1,3}$/;
Slice.fromParam = function(param: string | null): Slice {
Slice.fromParam = function (param: string | null): Slice {
if (!param) {
return Slice(1, 286);
}

View file

@ -1,4 +1,4 @@
import * as Quran from 'lib/Quran';
import * as Quran from "lib/Quran";
type TimeSlot = [number, number];
type TimeSlots = [TimeSlot];
@ -8,20 +8,28 @@ export class Surah {
ayat: Quran.Ayat;
#surah: Quran.JSON.Surah;
static fromDOMNode(locale: Quran.Locale, node: HTMLScriptElement, timeNode: HTMLScriptElement) {
static fromDOMNode(
locale: Quran.Locale,
node: HTMLScriptElement,
timeNode: HTMLScriptElement,
) {
const json = JSON.parse(node.innerText);
const timeSlots: TimeSlots = JSON.parse(timeNode.innerText);
const surah = Surah.fromJSON(locale, json.shift(), json);
surah.ayat.map((ayah, i) => ayah.readTimeMs = timeSlots[i][1] * 1000);
surah.ayat.map((ayah, i) => (ayah.readTimeMs = timeSlots[i][1] * 1000));
return surah;
}
static fromJSON(locale: Quran.Locale, surah: Quran.JSON.Surah, ayat: Quran.JSON.Ayat = []) {
static fromJSON(
locale: Quran.Locale,
surah: Quran.JSON.Surah,
ayat: Quran.JSON.Ayat = [],
) {
return new Surah(locale, surah, this.mapFromJSON(ayat));
}
static mapFromJSON(ayat: Quran.JSON.Ayat) {
return ayat.map((ayah) => Quran.Ayah.fromJSON(ayah));
return ayat.map(ayah => Quran.Ayah.fromJSON(ayah));
}
constructor(locale: Quran.Locale, surah: Quran.JSON.Surah, ayat: Quran.Ayat) {
@ -43,7 +51,7 @@ export class Surah {
}
get localizedName() {
if (this.locale === 'ar') {
if (this.locale === "ar") {
return this.name;
} else {
return this.#surah.translated_name;

View file

@ -1,7 +1,7 @@
import * as Quran from 'lib/Quran';
import * as Quran from "lib/Quran";
type PhraseMap<T> = {
[key: string]: undefined | string | PhraseMap<T>
[key: string]: undefined | string | PhraseMap<T>;
};
export type TFunction = (locale: Quran.Locale, key: string) => string;
@ -9,20 +9,20 @@ export type TFunction = (locale: Quran.Locale, key: string) => string;
export function i18n(json: string): TFunction {
const phrases: PhraseMap<string> = JSON.parse(json);
return function (locale: Quran.Locale, key: string) {
const path = key.split('.');
const path = key.split(".");
const phrase = path.reduce(
(o, k) => typeof(o) === 'object' ? o[k] : o,
phrases[locale]
(o, k) => (typeof o === "object" ? o[k] : o),
phrases[locale],
);
return typeof phrase === 'string' ? phrase : key;
return typeof phrase === "string" ? phrase : key;
};
}
export function formatNumber(number: number, locale: Quran.Locale): string {
const numLocale = locale === 'ar' ? 'ar-SA' : locale;
const numLocale = locale === "ar" ? "ar-SA" : locale;
const options = { maximumFractionDigits: 1 };
return new Intl.NumberFormat(numLocale, options)
.format(number)
.split(/([^\d])/)
.join(' ');
.format(number)
.split(/([^\d])/)
.join(" ");
}

View file

@ -1,31 +1,31 @@
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import classNames from 'classnames';
import { get as getCookie } from 'es-cookie';
import * as Quran from 'lib/Quran';
import { SelectOption } from 'components/Select';
import { ThemeSelect } from 'components/ThemeSelect';
import { LanguageSelect } from 'components/LanguageSelect';
import { i18n, formatNumber, TFunction } from 'lib/i18n';
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import classNames from "classnames";
import { get as getCookie } from "es-cookie";
import * as Quran from "lib/Quran";
import { SelectOption } from "components/Select";
import { ThemeSelect } from "components/ThemeSelect";
import { LanguageSelect } from "components/LanguageSelect";
import { i18n, formatNumber, TFunction } from "lib/i18n";
interface Props {
locale: Quran.Locale
surahs: Quran.Surah[]
t: TFunction
locale: Quran.Locale;
surahs: Quran.Surah[];
t: TFunction;
}
function SurahIndex({ locale, surahs, t }: Props) {
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
const [theme, setTheme] = useState(getCookie("theme") || "moon");
const onLanguageChange = (o: SelectOption) => {
document.location.replace(`/${o.value}/`);
};
return (
<div className={classNames('content', 'theme', theme, locale)}>
<div className={classNames("content", "theme", theme, locale)}>
<div className="header">
<a href={'/' + locale} className="image" />
<a href={"/" + locale} className="image" />
</div>
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
<div className="row title">{t(locale, "TheNobleQuran")}</div>
<div className="row dropdown-row">
<ThemeSelect theme={theme} setTheme={setTheme} />
<LanguageSelect locale={locale} onChange={onLanguageChange} />
@ -34,12 +34,8 @@ function SurahIndex({ locale, surahs, t }: Props) {
{surahs.map((surah, key) => (
<li className="surah" key={key}>
<a href={`/${locale}/${surah.slug}`}>
<div className="surah id">
{formatNumber(surah.id, locale)}
</div>
<div className="surah name">
{surah.localizedName}
</div>
<div className="surah id">{formatNumber(surah.id, locale)}</div>
<div className="surah name">{surah.localizedName}</div>
<div className="surah name transliterated" lang="en">
{surah.transliteratedName}
</div>
@ -48,26 +44,22 @@ function SurahIndex({ locale, surahs, t }: Props) {
))}
</ul>
<a href={`/${locale}/random`} className="row surah choose-random">
{t(locale, 'ChooseRandomChapter')}
{t(locale, "ChooseRandomChapter")}
</a>
</div>
);
}
(function () {
const root: HTMLElement = document.querySelector(".root")!;
const locale = root.getAttribute("data-locale") as Quran.Locale;
const script: HTMLScriptElement = document.querySelector(".json.surahs")!;
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
const surahs: Quran.Surah[] = JSON.parse(script.innerText).map(
(el: Quran.JSON.Surah) => {
return Quran.Surah.fromJSON(locale, el);
},
);
(function() {
const root: HTMLElement = document.querySelector('.root')!;
const locale = root.getAttribute('data-locale') as Quran.Locale;
const script: HTMLScriptElement = document.querySelector('.json.surahs')!;
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
const surahs: Quran.Surah[] = JSON.parse(script.innerText)
.map((el: Quran.JSON.Surah) => {
return Quran.Surah.fromJSON(locale, el);
});
ReactDOM
.createRoot(root)
.render(
<SurahIndex locale={locale} surahs={surahs} t={t} />
);
ReactDOM.createRoot(root).render(<SurahIndex locale={locale} surahs={surahs} t={t} />);
})();

View file

@ -1,29 +1,30 @@
import postman, { item } from 'postman';
import postman, { item } from "postman";
(function() {
const parent: HTMLElement = document.querySelector('.postman.loader')!;
const progressBar: HTMLProgressElement = parent.querySelector('progress')!;
const progressNumber: HTMLSpanElement = parent.querySelector('.percentage')!;
const inlineStyle: HTMLStyleElement = document.querySelector('.css.postman')!;
(function () {
const parent: HTMLElement = document.querySelector(".postman.loader")!;
const progressBar: HTMLProgressElement = parent.querySelector("progress")!;
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
postman(
item.script('/js/pages/surah/index.js'),
item.css('/css/pages/surah/index.css'),
item.image('/images/moon.svg'),
item.image('/images/leaf.svg'),
item.font('Kanit Regular', 'url(/fonts/kanit-regular.ttf)'),
item.font('Vazirmatn Regular', 'url(/fonts/vazirmatn-regular.ttf)'),
item.font('Roboto Mono Regular', 'url(/fonts/roboto-mono-regular.ttf)'),
item.script("/js/pages/surah/index.js"),
item.css("/css/pages/surah/index.css"),
item.image("/images/moon.svg"),
item.image("/images/leaf.svg"),
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
item.progress((percent: number) => {
progressBar.value = percent;
progressNumber.innerText = `${percent.toFixed(0)}%`;
})
).fetch()
.then((pkg) => {
inlineStyle.remove();
parent.remove();
pkg.fonts.forEach((f) => document.fonts.add(f));
pkg.css.forEach((s) => document.head.appendChild(s));
pkg.scripts.forEach((s) => document.body.appendChild(s));
});
}),
)
.fetch()
.then(pkg => {
inlineStyle.remove();
parent.remove();
pkg.fonts.forEach(f => document.fonts.add(f));
pkg.css.forEach(s => document.head.appendChild(s));
pkg.scripts.forEach(s => document.body.appendChild(s));
});
})();

View file

@ -1,9 +1,9 @@
import { Locale } from 'lib/Locale';
import { Locale } from "lib/Locale";
(function () {
const surahId: number = Math.ceil(Math.random() * 114);
const locale = Locale(window);
const el: HTMLElement = document.querySelector('.json.slugs')!;
const el: HTMLElement = document.querySelector(".json.slugs")!;
const slugs = JSON.parse(el.innerText);
location.replace(`/${locale.fromPath()}/${slugs[surahId]}`);
})();

View file

@ -1,6 +1,6 @@
import { Locale } from 'lib/Locale';
import { Locale } from "lib/Locale";
(function(window, location) {
(function (window, location) {
const locale = Locale(window).fromBrowser();
location.replace(`/${locale}/`);
})(window, location);

View file

@ -1,24 +1,30 @@
import React, { useRef, useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
import classNames from 'classnames';
import { get as getCookie } from 'es-cookie';
import { Timer } from 'components/Timer';
import { Stream } from 'components/Stream';
import { SelectOption } from 'components/Select';
import { ThemeSelect } from 'components/ThemeSelect';
import { LanguageSelect } from 'components/LanguageSelect';
import { PlayShape, PauseShape, SoundOnShape, SoundOffShape, RefreshShape } from 'components/Shape';
import * as Quran from 'lib/Quran';
import { Slice } from 'lib/Quran/Slice';
import { i18n, TFunction } from 'lib/i18n';
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
import classNames from "classnames";
import { get as getCookie } from "es-cookie";
import { Timer } from "components/Timer";
import { Stream } from "components/Stream";
import { SelectOption } from "components/Select";
import { ThemeSelect } from "components/ThemeSelect";
import { LanguageSelect } from "components/LanguageSelect";
import {
PlayShape,
PauseShape,
SoundOnShape,
SoundOffShape,
RefreshShape,
} from "components/Shape";
import * as Quran from "lib/Quran";
import { Slice } from "lib/Quran/Slice";
import { i18n, TFunction } from "lib/i18n";
interface Props {
node: HTMLScriptElement
reciters: Quran.Reciter[]
locale: Quran.Locale
slice: Slice
paused: boolean
t: TFunction
node: HTMLScriptElement;
reciters: Quran.Reciter[];
locale: Quran.Locale;
slice: Slice;
paused: boolean;
t: TFunction;
}
const getTimeSlots = (reciter: Quran.Reciter) => {
@ -32,13 +38,15 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
const [isPaused, setIsPaused] = useState<boolean>(paused);
const [soundOn, setSoundOn] = useState<boolean>(false);
const [isStalled, setIsStalled] = useState<boolean>(false);
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
const [theme, setTheme] = useState(getCookie("theme") || "moon");
const [reciter] = useState<Quran.Reciter>(reciters[0]);
const [surah] = useState<Quran.Surah>(Quran.Surah.fromDOMNode(locale, node, getTimeSlots(reciter)));
const [surah] = useState<Quran.Surah>(
Quran.Surah.fromDOMNode(locale, node, getTimeSlots(reciter)),
);
const readyToRender = stream.length > 0;
const audioRef = useRef<HTMLAudioElement>(null);
const { url: baseUrl } = reciter;
const ayah = stream[stream.length-1];
const ayah = stream[stream.length - 1];
const src = `${baseUrl}/${surah.id}/${ayah?.id}.mp3`;
const getAyahParam = (slice: Slice, stream: Quran.Ayat) => {
if (slice.coversSubsetOfSurah) {
@ -50,13 +58,16 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
const onLanguageChange = (o: SelectOption) => {
const locale = o.value;
const params = [
['ayah', getAyahParam(slice, stream)],
['paused', isPaused ? 't' : null]
["ayah", getAyahParam(slice, stream)],
["paused", isPaused ? "t" : null],
];
const query = params.filter(([, v]) => v).flatMap(([k, v]) => `${k}=${v}`).join('&');
const query = params
.filter(([, v]) => v)
.flatMap(([k, v]) => `${k}=${v}`)
.join("&");
location.replace(`/${locale}/${surah.slug}/?${query}`);
};
const endOfStream = (function() {
const endOfStream = (function () {
if (slice.coversOneAyah || slice.coversOneSurah) {
return stream.length === surah.ayat.length;
} else if (slice.coversSubsetOfSurah) {
@ -81,28 +92,27 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
} else if (isPaused || !soundOn) {
audio.pause();
} else if (soundOn) {
audio.play()
.catch(() => setSoundOn(false));
audio.play().catch(() => setSoundOn(false));
}
}, [stream, isPaused, soundOn]);
useEffect(() => {
const audio = audioRef.current;
audio.addEventListener('ended', () => audio.setAttribute('src', src));
audio.addEventListener('stalled', () => setIsStalled(true));
audio.addEventListener('waiting', () => setIsStalled(true));
audio.addEventListener('playing', () => setIsStalled(false));
audio.addEventListener('play', () => setIsStalled(false));
audio.addEventListener("ended", () => audio.setAttribute("src", src));
audio.addEventListener("stalled", () => setIsStalled(true));
audio.addEventListener("waiting", () => setIsStalled(true));
audio.addEventListener("playing", () => setIsStalled(false));
audio.addEventListener("play", () => setIsStalled(false));
}, []);
return (
<div className={classNames('content', 'theme', theme, locale)}>
<div className={classNames("content", "theme", theme, locale)}>
<div className="header">
<a href={'/' + locale} className="image" />
<a href={"/" + locale} className="image" />
</div>
{readyToRender && (
<>
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
<div className="row title">{t(locale, "TheNobleQuran")}</div>
<div className="row dropdown-row">
<ThemeSelect theme={theme} setTheme={setTheme} />
<LanguageSelect locale={locale} onChange={onLanguageChange} />
@ -115,66 +125,70 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
<span lang="en">{surah.transliteratedName}</span>
</div>
)}
{readyToRender &&
<Stream
slice={slice}
surah={surah}
stream={stream}
locale={locale}
endOfStream={endOfStream}
isPaused={isPaused}
t={t}
/>
}
<div className={classNames({ 'justify-end': readyToRender && endOfStream }, 'row')}>
{readyToRender && isPaused && !endOfStream &&
<PlayShape onClick={() => setIsPaused(false)} />}
{readyToRender && !isPaused && !endOfStream &&
<PauseShape onClick={() => setIsPaused(true)} />}
{readyToRender && !endOfStream && soundOn &&
<SoundOnShape onClick={() => setSoundOn(false)} />}
{readyToRender && !endOfStream && !soundOn &&
<SoundOffShape onClick={() => setSoundOn(true)} />}
{readyToRender && !endOfStream &&
<Timer
surah={surah}
setStream={setStream}
stream={stream}
locale={locale}
isPaused={isPaused}
soundOn={soundOn}
isStalled={isStalled}
/>}
{readyToRender && endOfStream &&
<RefreshShape onClick={() => setStream([])} />}
{readyToRender && (
<Stream
slice={slice}
surah={surah}
stream={stream}
locale={locale}
endOfStream={endOfStream}
isPaused={isPaused}
t={t}
/>
)}
<div className={classNames({ "justify-end": readyToRender && endOfStream }, "row")}>
{readyToRender && isPaused && !endOfStream && (
<PlayShape onClick={() => setIsPaused(false)} />
)}
{readyToRender && !isPaused && !endOfStream && (
<PauseShape onClick={() => setIsPaused(true)} />
)}
{readyToRender && !endOfStream && soundOn && (
<SoundOnShape onClick={() => setSoundOn(false)} />
)}
{readyToRender && !endOfStream && !soundOn && (
<SoundOffShape onClick={() => setSoundOn(true)} />
)}
{readyToRender && !endOfStream && (
<Timer
surah={surah}
setStream={setStream}
stream={stream}
locale={locale}
isPaused={isPaused}
soundOn={soundOn}
isStalled={isStalled}
/>
)}
{readyToRender && endOfStream && <RefreshShape onClick={() => setStream([])} />}
</div>
<audio src={src} ref={audioRef} />
</div>
);
}
(function() {
const root: HTMLElement = document.querySelector('.root')!;
const locale = root.getAttribute('data-locale') as Quran.Locale;
const node: HTMLScriptElement = document.querySelector('script.surah')!;
const toBoolean = (str: string | null): boolean => str !== null && ['1', 't', 'true', 'yes'].includes(str);
(function () {
const root: HTMLElement = document.querySelector(".root")!;
const locale = root.getAttribute("data-locale") as Quran.Locale;
const node: HTMLScriptElement = document.querySelector("script.surah")!;
const toBoolean = (str: string | null): boolean =>
str !== null && ["1", "t", "true", "yes"].includes(str);
const params = new URLSearchParams(location.search);
const slice = Slice.fromParam(params.get('ayah'));
const paused = toBoolean(params.get('paused'));
const reciters = JSON.parse(document.querySelector<HTMLElement>('.json.reciters')!.innerText);
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
const slice = Slice.fromParam(params.get("ayah"));
const paused = toBoolean(params.get("paused"));
const reciters = JSON.parse(
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
);
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
ReactDOM
.createRoot(root)
.render(
<SurahStream
reciters={reciters}
node={node}
locale={locale}
slice={slice}
paused={paused}
t={t}
/>
);
ReactDOM.createRoot(root).render(
<SurahStream
reciters={reciters}
node={node}
locale={locale}
slice={slice}
paused={paused}
t={t}
/>,
);
})();

View file

@ -1,41 +1,43 @@
import postman, { item } from 'postman';
import * as Quran from 'lib/Quran';
import postman, { item } from "postman";
import * as Quran from "lib/Quran";
(function() {
const parent: HTMLElement = document.querySelector('.postman.loader')!;
const progressBar: HTMLProgressElement = parent.querySelector('progress')!;
const progressNumber: HTMLSpanElement = parent.querySelector('.percentage')!;
const inlineStyle: HTMLStyleElement = document.querySelector('.css.postman')!;
const { locale, surahId } = document.querySelector<HTMLElement>('.root')!.dataset;
const reciters = JSON.parse(document.querySelector<HTMLElement>('.json.reciters')!.innerText);
(function () {
const parent: HTMLElement = document.querySelector(".postman.loader")!;
const progressBar: HTMLProgressElement = parent.querySelector("progress")!;
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
const { locale, surahId } = document.querySelector<HTMLElement>(".root")!.dataset;
const reciters = JSON.parse(
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
);
postman(
item.script('/js/pages/surah/stream.js'),
item.css('/css/pages/surah/stream.css'),
item.image('/images/moon.svg'),
item.image('/images/leaf.svg'),
item.font('Kanit Regular', 'url(/fonts/kanit-regular.ttf)'),
item.font('Vazirmatn Regular', 'url(/fonts/vazirmatn-regular.ttf)'),
item.font('Roboto Mono Regular', 'url(/fonts/roboto-mono-regular.ttf)'),
item.json(`/${locale}/${surahId}/surah.json`, { className: 'surah' }),
item.script("/js/pages/surah/stream.js"),
item.css("/css/pages/surah/stream.css"),
item.image("/images/moon.svg"),
item.image("/images/leaf.svg"),
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
item.json(`/${locale}/${surahId}/surah.json`, { className: "surah" }),
...reciters.map((reciter: Quran.Reciter) => {
const { url: baseUrl } = reciter;
return item.json(
`${baseUrl}/time_slots/${surahId}.json`,
{ className: `reciter time-slots ${reciter.id}` }
);
return item.json(`${baseUrl}/time_slots/${surahId}.json`, {
className: `reciter time-slots ${reciter.id}`,
});
}),
item.progress((percent: number) => {
progressBar.value = percent;
progressNumber.innerText = `${percent.toFixed(0)}%`;
})
).fetch()
.then((pkg) => {
}),
)
.fetch()
.then(pkg => {
inlineStyle.remove();
parent.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.appendChild(s));
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.appendChild(s));
});
})();