Move towards a Single Page Application (SPA) approach
This commit is contained in:
parent
5d4d5a5e8d
commit
eec625498d
51 changed files with 3354 additions and 761 deletions
2
.bundle/config
Normal file
2
.bundle/config
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
BUNDLE_PATH: ".gems"
|
1
Gemfile
1
Gemfile
|
@ -5,7 +5,6 @@ source "https://rubygems.org"
|
|||
# nanoc
|
||||
gem "nanoc", "~> 4.12"
|
||||
gem "nanoc-webpack.rb", "~> 0.10"
|
||||
gem "nanoc-tidy.rb", "~> 0.8.4"
|
||||
|
||||
##
|
||||
# dev
|
||||
|
|
|
@ -60,9 +60,6 @@ GEM
|
|||
nanoc-checking (~> 1.0)
|
||||
nanoc-cli (~> 4.11, >= 4.11.15)
|
||||
nanoc-core (~> 4.11, >= 4.11.15)
|
||||
nanoc-tidy.rb (0.8.4)
|
||||
ryo.rb (~> 0.5)
|
||||
test-cmd.rb (~> 0.12.4)
|
||||
nanoc-webpack.rb (0.10.6)
|
||||
ryo.rb (~> 0.5)
|
||||
test-cmd.rb (~> 0.12.4)
|
||||
|
@ -149,7 +146,6 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
listen (~> 3.0)
|
||||
nanoc (~> 4.12)
|
||||
nanoc-tidy.rb (~> 0.8.4)
|
||||
nanoc-webpack.rb (~> 0.10)
|
||||
paint (~> 2.3)
|
||||
rake (~> 13.2)
|
||||
|
|
37
Rules
37
Rules
|
@ -7,7 +7,6 @@ require "ryo"
|
|||
require "ryo/json"
|
||||
require "ryo/yaml"
|
||||
require "nanoc-webpack"
|
||||
require "nanoc-tidy"
|
||||
|
||||
##
|
||||
# Extensions
|
||||
|
@ -23,36 +22,32 @@ tdata = Ryo.from_json(path: File.join(dirs.content, "json", "t.json"))
|
|||
surahs = Ryo.from_json(path: File.join(dirs.content, "json", "surahs.json"))
|
||||
tidy = `which tidy || which tidy5`.chomp
|
||||
buildenv = ENV["buildenv"] || "development"
|
||||
etcdir = File.join(__dir__, "etc")
|
||||
globals = {buildenv:, locales:, tidy:, tdata:, surahs:, name_by_id:}
|
||||
|
||||
##
|
||||
# Filters
|
||||
Nanoc::Tidy
|
||||
.default_argv
|
||||
.replace([*Nanoc::Tidy.default_argv, "-upper"].uniq)
|
||||
|
||||
##
|
||||
# Rules
|
||||
passthrough "/json/durations/*.json"
|
||||
passthrough "/json/**/*.json"
|
||||
require_rules "nanoc/rules/assets"
|
||||
require_rules "nanoc/rules/redirect", globals
|
||||
require_rules "nanoc/rules/random", globals
|
||||
require_rules "nanoc/rules/surah-stream", globals
|
||||
require_rules "nanoc/rules/surah-index", globals
|
||||
|
||||
compile "/js/main/vendor.ts" do
|
||||
compile "/js/vendor.ts" do
|
||||
filter :webpack,
|
||||
argv: %w[--config etc/webpack.vendor.js]
|
||||
write("/js/main/vendor.js")
|
||||
write("/js/vendor.js")
|
||||
end
|
||||
|
||||
compile("/js/index.tsx") do
|
||||
filter :webpack,
|
||||
argv: ["--config", "etc/webpack.#{buildenv}.js"],
|
||||
depend_on: ["/js/components", "/js/lib", "/js/hooks", "/css"]
|
||||
write("/js/index.js")
|
||||
end
|
||||
|
||||
compile("/html/index.html") do
|
||||
write("/index.html")
|
||||
end
|
||||
|
||||
compile("/manifest.webapp") do
|
||||
write("/manifest.webapp")
|
||||
end
|
||||
|
||||
compile("/**/*") { write(nil) }
|
||||
layout("**/*", :erb)
|
||||
|
||||
postprocess do
|
||||
# Remove build artifacts
|
||||
system "rm -rf #{nanoc.output_dir}/json/"
|
||||
end
|
||||
|
|
19
package-lock.json
generated
19
package-lock.json
generated
|
@ -11,7 +11,8 @@
|
|||
"dependencies": {
|
||||
"@preact/compat": "^17.1.2",
|
||||
"classnames": "^2.3",
|
||||
"preact": "^10.23.2"
|
||||
"preact": "^10.23.2",
|
||||
"preact-router": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
|
@ -3059,9 +3060,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/babel-loader": {
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
|
||||
"integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz",
|
||||
"integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -5227,6 +5228,15 @@
|
|||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/preact-router": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-4.1.2.tgz",
|
||||
"integrity": "sha512-uICUaUFYh+XQ+6vZtQn1q+X6rSqwq+zorWOCLWPF5FAsQh3EJ+RsDQ9Ee+fjk545YWQHfUxhrBAaemfxEnMOUg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
@ -6381,6 +6391,7 @@
|
|||
"license": "0BSDL",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
"typescript": "^5.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
"eslint:apply": "npx eslint --config etc/eslint.config.mjs --fix src/js/"
|
||||
},
|
||||
"dependencies": {
|
||||
"preact": "^10.23.2",
|
||||
"@preact/compat": "^17.1.2",
|
||||
"classnames": "^2.3"
|
||||
"classnames": "^2.3",
|
||||
"preact": "^10.23.2",
|
||||
"preact-router": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
|
|
17
packages/typescript/Quran/.editorconfig
Normal file
17
packages/typescript/Quran/.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*.html]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.rb, *.erb]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js, *.ts, *.tsx]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.scss]
|
||||
indent_style = space
|
||||
indent_size = 2
|
0
packages/typescript/Quran/index.ts
Normal file
0
packages/typescript/Quran/index.ts
Normal file
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"name": "Quran",
|
||||
"version": "0.1.0",
|
||||
"description": "The Noble Quran: a programmer's interface",
|
||||
"description": "A programmer's interface to The Noble Quran",
|
||||
"main": "dist/index.js",
|
||||
"types": ["dist/index.d.ts"],
|
||||
"types": [
|
||||
"dist/index.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm exec tsc",
|
||||
"build": "npx webpack --config etc/webpack.config.js",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -16,6 +18,7 @@
|
|||
"license": "0BSDL",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
"typescript": "^5.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,14 +36,26 @@ class Quran {
|
|||
readonly surahs: Surah[];
|
||||
|
||||
/**
|
||||
* @returns {Array} The available locales
|
||||
* @returns {Record<string, TLocale>} The available locales
|
||||
*/
|
||||
static get locales(): TLocale[] {
|
||||
return [
|
||||
{"name": "en", "displayName": "English", "direction": "ltr"},
|
||||
{"name": "ar", "displayName": "العربية", "direction": "rtl"},
|
||||
{"name": "fa", "displayName": "فارسی", "direction": "rtl"}
|
||||
];
|
||||
static get locales(): Record<string, TLocale> {
|
||||
return {
|
||||
"en": {"name": "en", "displayName": "English", "direction": "ltr"},
|
||||
"ar": {"name": "ar", "displayName": "العربية", "direction": "rtl"},
|
||||
"fa": {"name": "fa", "displayName": "فارسی", "direction": "rtl"}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Record<string, Surah[]>} The available surahs
|
||||
*/
|
||||
static get surahs(): Record<string, Surah[]> {
|
||||
const result: Record<string, Surah[]> = {}
|
||||
const surahs: Record<string, TSurah[]> = require("@json/surahs");
|
||||
for (const locale in surahs) {
|
||||
result[locale] = surahs[locale].map((surah: TSurah) => new Surah(surah));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor(self: TQuran) {
|
||||
|
|
2744
packages/typescript/Quran/src/json/surahs.json
Normal file
2744
packages/typescript/Quran/src/json/surahs.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,56 +2,28 @@
|
|||
|
||||
** vNEXT
|
||||
|
||||
**** Remove ~kanit-regular.ttf~
|
||||
This change falls back onto standard web fonts across all
|
||||
languages. This should help decrease bundle size, and save
|
||||
time processing the custom font during page load
|
||||
** Add ~Quran.surahs~
|
||||
The ~Quran.surahs~ getter returns an object where the
|
||||
key is the locale name, and the value is an array of
|
||||
Surah objects
|
||||
|
||||
**** Add src/js/main/vendor.ts
|
||||
Add a new entry point that bundles preact, and other third
|
||||
party dependencies. Third-party imports are now in a single
|
||||
place rather than duplicated throughout app components
|
||||
|
||||
**** Reduce bundle size
|
||||
Remove build artifacts and features that don't apply on
|
||||
KaiOS (eg: opengraph tags)
|
||||
|
||||
**** Replace the play|pause icons
|
||||
** Replace the play|pause icons
|
||||
Replace the play and pause icons
|
||||
|
||||
**** Remove nanoc-gzip.rb
|
||||
** Remove nanoc-gzip.rb
|
||||
Unneccessary for a packaged KaiOS application
|
||||
|
||||
**** Remove loaders
|
||||
** Remove loaders
|
||||
Remove all loaders from ~/src/js/loaders/~, and the postman
|
||||
package (~packages/typescript/postman~)
|
||||
|
||||
**** KaiOS fork
|
||||
Fork a new branch: ~kaios/main~ dedicated to KaiOS
|
||||
** Replace multiple entry points with a single entry point
|
||||
The ~/src/js/main/*~ directory - which previously contained
|
||||
multiple entry points - has been reduced to a single entry
|
||||
point: ~/src/js/index.tsx~
|
||||
|
||||
**** Add ~etc/~
|
||||
Move a large portion of the website's configuration files to
|
||||
the ~/etc~ directory
|
||||
|
||||
** v0.9.0
|
||||
|
||||
**** Add ~share/al-quran.reflectslight.io/documentation/~
|
||||
Replace ~share/doc/al-quran.reflectslight.io~
|
||||
|
||||
**** Add new recitation
|
||||
Add a new recitation by Hani ar-Rifai
|
||||
|
||||
**** Replace ~opengraph.rb~ with ~_opengraph.html.erb~
|
||||
Simplify how we render opengraph meta tags
|
||||
|
||||
**** Move to nodejs for scss compiler
|
||||
Replace the deprecated Ruby scss compiler with the nodejs compiler
|
||||
|
||||
**** Add ~audio.base_url~ to nanoc.yaml
|
||||
Provide extra flexibility for audio content
|
||||
|
||||
**** Rename packages/typescript/Quran/ properties
|
||||
Introduce urlName, translitName to Surah objects
|
||||
|
||||
**** eslint upgrade
|
||||
Migrate to the most recent version of eslint (^9.8)
|
||||
** Redesign as a Single Page Application (SPA)
|
||||
The previous approach where we generated a HTML file
|
||||
for every surah in each language has been replaced by
|
||||
a Single Page Application (SPA) that is more suitable
|
||||
for KaiOS.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
@import "base/colors";
|
||||
@import "base/breakpoints";
|
||||
@import "base/icon";
|
||||
@import "base/select";
|
||||
|
@ -8,46 +7,111 @@ html {
|
|||
height: 100%;
|
||||
body {
|
||||
height: 100%;
|
||||
color: $black;
|
||||
margin: 0;
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ltr {
|
||||
font-family: 'Arial', 'Tahoma', sans-serif;
|
||||
font-family: "Kanit Regular";
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.rtl {
|
||||
font-family: 'Arial', 'Tahoma', sans-serif;
|
||||
font-family: "Cairo Regular", "Arial", "Tahoma", sans-serif;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.invisible, .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.outline-0 {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.scroll-y {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.font-cairo {
|
||||
font-family: "Cairo Regular";
|
||||
}
|
||||
|
||||
.font-cairo-bold {
|
||||
font-family: "Cairo Bold";
|
||||
}
|
||||
|
||||
.font-amiri {
|
||||
font-family: "Amiri Regular", "Scheherazade", "Arial", sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body .root .content.theme {
|
||||
margin: 0 auto;
|
||||
max-width: $breakpoint-md;
|
||||
width: 85%;
|
||||
font-size: medium;
|
||||
max-width: $breakpoint-sm;
|
||||
width: 100%;
|
||||
color: var(--color-accent);
|
||||
|
||||
/* <= $breakpoint-sm */
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
header {
|
||||
a[data-testid="h1"] {
|
||||
font-size: large;
|
||||
background: var(--primary-color);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
ul.body {
|
||||
scrollbar-color: var(--primary-color) var(--secondary-color);
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
color: var(--secondary-color) !important;
|
||||
}
|
||||
|
||||
.color-accent {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.background-primary {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.background-secondary {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
.background-accent {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
|
||||
.border-accent {
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RTL languages
|
||||
*/
|
||||
body .root .content.theme.rtl {
|
||||
direction: rtl;
|
||||
header a[data-testid="h1"] {
|
||||
font-family: "Cairo Bold";
|
||||
}
|
||||
}
|
||||
|
||||
html[dir="rtl"] {
|
||||
.font-extrabold {
|
||||
font-family: "Cairo Bold" !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/* KaiOS: max widths */
|
||||
$breakpoint-kaiOS-portrait: 240px;
|
||||
$breakpoint-kaiOS-landscape: 320px;
|
||||
|
||||
/* Standard max widths */
|
||||
/* max-width */
|
||||
$breakpoint-sm: 576px;
|
||||
$breakpoint-md: 768px;
|
||||
$breakpoint-lg: 992px;
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
$black: #454545;
|
||||
$white: #FFF;
|
|
@ -57,6 +57,7 @@
|
|||
.refresh.icon,
|
||||
.right-arrow.icon,
|
||||
.left-arrow.icon {
|
||||
fill: var(--primary-color);
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
@ -65,6 +66,14 @@
|
|||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.pause.icon {
|
||||
g rect {
|
||||
width: 15px;
|
||||
height: 40px;
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
ul.body.stream span {
|
||||
.sound-on.icon,
|
||||
.sound-off.icon {
|
||||
|
@ -92,7 +101,7 @@
|
|||
.root .content.theme.rtl {
|
||||
.stalled.icon {
|
||||
left: 0px;
|
||||
right: 7px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
ul.body.stream {
|
||||
|
@ -112,3 +121,42 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content.theme {
|
||||
ul.body.stream li, footer {
|
||||
.play.icon {
|
||||
fill: var(--primary-color);
|
||||
stroke: var(--secondary-color);
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.pause.icon {
|
||||
rect {
|
||||
fill: var(--primary-color);
|
||||
stroke: var(--secondary-color);
|
||||
stroke-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.refresh.icon {
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
|
||||
.sound-on.icon, .sound-off.icon {
|
||||
polygon {
|
||||
fill: var(--primary-color);
|
||||
stroke-width: 2;
|
||||
}
|
||||
}
|
||||
.right-arrow.icon,
|
||||
.left-arrow.icon {
|
||||
g {
|
||||
fill: var(--secondary--color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stalled.icon {
|
||||
div { background: var(--secondary-color); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,37 @@
|
|||
.root .content.theme {
|
||||
.green {
|
||||
background: #6d765b !important;
|
||||
}
|
||||
.blue {
|
||||
background: #3383C3 !important;
|
||||
}
|
||||
|
||||
.react-select {
|
||||
z-index: 2;
|
||||
.active {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.react-select.theme-select {
|
||||
ul li,
|
||||
ul li a {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.circle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 12px;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.react-select.language-select {
|
||||
li a {
|
||||
border: 1px solid $black;
|
||||
background: var(--primary-color);
|
||||
color: var(--secondary-color);
|
||||
&:active, &:visited, &:link {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
|
||||
body .root .content.theme {
|
||||
ul.body.index {
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
li, li a {
|
||||
li, li a {
|
||||
color: var(--accent-color);
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +31,47 @@ body .root .content.theme {
|
|||
}
|
||||
|
||||
footer {
|
||||
/* TODO: Remove footer ? */
|
||||
display: none;
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
&:active, &:link, &:visited {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
color: var(--accent-color);
|
||||
border: 1px solid var(--primary-color);
|
||||
&:focus {
|
||||
outline-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-sm) {
|
||||
border-top: 1px solid #f2f2f2;
|
||||
.right-arrow,
|
||||
.left-arrow {
|
||||
display: none;
|
||||
}
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-sm) {
|
||||
input[data-testid="SurahIndex/Filter"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media(hover: none) {
|
||||
input[data-testid="SurahIndex/Filter"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,24 +80,19 @@ body .root .content.theme {
|
|||
*/
|
||||
body .root .content.theme.rtl {
|
||||
ul.body.index {
|
||||
li a {
|
||||
span:first-child {
|
||||
border: 1px solid $black;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
li.surah {
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
width: 100%;
|
||||
}
|
||||
span.transliterated { display: none; }
|
||||
}
|
||||
}
|
||||
|
||||
/* >= $breakpoint-xxl */
|
||||
@media (min-width: $breakpoint-xxl) {
|
||||
ul.body.index {
|
||||
li.surah a {
|
||||
span:last-child {
|
||||
font-size: larger;
|
||||
@media (hover: hover) {
|
||||
footer {
|
||||
a {
|
||||
&:hover {
|
||||
font-weight: normal;
|
||||
font-family: "Cairo Bold";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,40 +16,13 @@ body .root .content.theme {
|
|||
animation: FadeIn 1s;
|
||||
}
|
||||
}
|
||||
ul.body.stream:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
body .root .content.theme.rtl {
|
||||
ul.body.stream {
|
||||
li.ayah p {
|
||||
line-height: 1.7;
|
||||
max-width: 470px;
|
||||
}
|
||||
}
|
||||
/* <= $breakpoint-sm */
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
ul.body.stream {
|
||||
li.ayah p {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* >= $breakpoint-xxl */
|
||||
@media (min-width: $breakpoint-xxl) {
|
||||
ul.body.stream {
|
||||
$gap: 2rem;
|
||||
margin-top: $gap;
|
||||
li.ayah {
|
||||
font-size: larger;
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
||||
footer {
|
||||
.timer {
|
||||
font-size: larger;
|
||||
}
|
||||
width: 100%;
|
||||
@extend .font-amiri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,5 @@
|
|||
.root .content.theme.blue.rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.root .content.theme.blue {
|
||||
@import "blue/base/colors";
|
||||
|
||||
.color-white {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.color-accent {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.background-primary {
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.background-secondary {
|
||||
background: $secondary-color;
|
||||
}
|
||||
|
||||
.background-accent {
|
||||
background: $accent-color;
|
||||
}
|
||||
--primary-color: #3383C3;
|
||||
--secondary-color: #FFF;
|
||||
--accent-color: #444;
|
||||
}
|
||||
|
||||
@import "blue/base";
|
||||
@import "blue/main/SurahIndex";
|
||||
@import "blue/main/SurahStream";
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
@import "base/icon";
|
||||
@import "base/select";
|
||||
|
||||
.root .content.theme.blue {
|
||||
@import "base/colors";
|
||||
scrollbar-color: $primary-color #FFF;
|
||||
|
||||
header {
|
||||
a[data-testid="h1"] {
|
||||
background: $primary-color;
|
||||
border: 1px solid $black;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
$primary-color: #3383C3;
|
||||
$secondary-color: #3383C3;
|
||||
$accent-color: lighten($primary-color, 38%);
|
||||
$white: #FFF;
|
|
@ -1,40 +0,0 @@
|
|||
.content.theme.blue {
|
||||
@import "themes/blue/base/colors";
|
||||
|
||||
ul.body.stream li, footer {
|
||||
.play.icon {
|
||||
fill: $primary-color;
|
||||
stroke: $secondary-color;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.pause.icon {
|
||||
path {
|
||||
fill: $primary-color;
|
||||
stroke: $secondary-color;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.refresh.icon {
|
||||
fill: $primary-color;
|
||||
}
|
||||
|
||||
.sound-on.icon, .sound-off.icon {
|
||||
polygon {
|
||||
fill: $primary-color;
|
||||
stroke-width: 2;
|
||||
}
|
||||
}
|
||||
.right-arrow.icon,
|
||||
.left-arrow.icon {
|
||||
g {
|
||||
fill: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stalled.icon {
|
||||
div { background: $secondary-color; }
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
.content.theme {
|
||||
@import "themes/blue/base/colors";
|
||||
.blue {
|
||||
background: $secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.content.theme.blue {
|
||||
@import "themes/blue/base/colors";
|
||||
|
||||
.react-select.theme-select {
|
||||
.active .circle {
|
||||
background: $secondary-color;
|
||||
}
|
||||
ul li.blue .circle {
|
||||
background: $secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.react-select.language-select {
|
||||
li a {
|
||||
background: $secondary-color;
|
||||
color: $white;
|
||||
&:active, &:visited, &:link {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
.root .surah-index.content.theme.blue {
|
||||
@import "themes/blue/base/colors";
|
||||
@import "base/breakpoints";
|
||||
|
||||
ul.body.index {
|
||||
li.surah a {
|
||||
&:active, &:link, &:visited {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
border: 1px solid $black;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
a {
|
||||
&:active, &:link, &:visited {
|
||||
color: $primary-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
input {
|
||||
border: 1px solid $secondary-color;
|
||||
&:focus {
|
||||
outline-color: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.root .content.theme.blue.en {
|
||||
@import "themes/blue/base/colors";
|
||||
header {
|
||||
div {
|
||||
color: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.root .content.theme.blue.rtl {
|
||||
@import "themes/blue/base/colors";
|
||||
ul.body.index {
|
||||
li.surah a {
|
||||
span.id {
|
||||
color: $secondary-color;
|
||||
}
|
||||
span.name {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
.root .content.theme.blue {
|
||||
@import "themes/blue/base/colors";
|
||||
@import "base/breakpoints";
|
||||
|
||||
ul.body.stream {
|
||||
li.ayah {
|
||||
span span {
|
||||
color: $secondary-color;
|
||||
}
|
||||
p { }
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
.timer {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +1,5 @@
|
|||
.root .content.theme.green.rtl {
|
||||
@import "green/base/colors";
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.root .content.theme.green {
|
||||
@import "green/base/colors";
|
||||
|
||||
.color-primary {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.color-accent {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.color-white {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.background-primary {
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.background-secondary {
|
||||
background: $secondary-color;
|
||||
}
|
||||
|
||||
.background-accent {
|
||||
background: $accent-color;
|
||||
}
|
||||
--primary-color: #6d765b;
|
||||
--secondary-color: #FFF;
|
||||
--accent-color: #444;
|
||||
}
|
||||
|
||||
@import "green/base";
|
||||
@import "green/main/SurahIndex";
|
||||
@import "green/main/SurahStream";
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
@import "base/icon";
|
||||
@import "base/select";
|
||||
|
||||
.root .content.theme.green {
|
||||
@import "base/colors";
|
||||
scrollbar-color: $primary-color #FFF;
|
||||
|
||||
header {
|
||||
a[data-testid="h1"] {
|
||||
background: $primary-color;
|
||||
border: 1px solid $black;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
$primary-color: #6d765b;
|
||||
$secondary-color: #6C755A;
|
||||
$accent-color: #FFF;
|
|
@ -1,41 +0,0 @@
|
|||
.content.theme.green {
|
||||
@import "themes/green/base/colors";
|
||||
|
||||
ul.body.stream li, footer {
|
||||
.play.icon {
|
||||
g path {
|
||||
fill: $primary-color;
|
||||
stroke: $primary-color;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.pause.icon {
|
||||
g path {
|
||||
fill: $primary-color;
|
||||
stroke: $primary-color;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.refresh.icon {
|
||||
fill: $primary-color;
|
||||
}
|
||||
|
||||
.sound-on.icon, .sound-off.icon {
|
||||
g polygon {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
.right-arrow.icon,
|
||||
.left-arrow.icon {
|
||||
g {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stalled.icon {
|
||||
div { background: $primary-color; }
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
.content.theme {
|
||||
@import "themes/green/base/colors";
|
||||
.green {
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.content.theme.green {
|
||||
@import "themes/green/base/colors";
|
||||
|
||||
.react-select.theme-select {
|
||||
.active {
|
||||
.circle {
|
||||
background: $primary-color;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-select.language-select {
|
||||
li a {
|
||||
background: $primary-color;
|
||||
color: $white;
|
||||
&:active, &:visited, &:link {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
.root .surah-index.content.theme.green {
|
||||
@import "base/breakpoints";
|
||||
@import "themes/green/base/colors";
|
||||
|
||||
ul.body.index a {
|
||||
&:active, &:link, &:visited {
|
||||
color: $primary-color;
|
||||
}
|
||||
span:first-child {
|
||||
border: 1px solid $black;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
a {
|
||||
&:active, &:link, &:visited {
|
||||
color: $primary-color;
|
||||
}
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
input {
|
||||
border: 1px solid $primary-color;
|
||||
&:focus {
|
||||
outline-color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
.root .content.theme.green {
|
||||
@import "themes/green/base/colors";
|
||||
@import "base/breakpoints";
|
||||
|
||||
header {
|
||||
h1, h1 a { color: $primary-color; }
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
ul.body.stream {
|
||||
li.ayah {
|
||||
color: $primary-color;
|
||||
p { color: $black; }
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
.timer {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.sound-on.icon, .svg.sound-off.icon {
|
||||
polygon {
|
||||
fill: $primary-color;
|
||||
stroke-width: 2;
|
||||
}
|
||||
}
|
||||
}
|
244
src/css/vendor/tail.scss
vendored
244
src/css/vendor/tail.scss
vendored
File diff suppressed because one or more lines are too long
11
src/html/index.html
Normal file
11
src/html/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/js/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app mount root h-full"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= context.locale %>" dir="<%= context.dir %>">
|
||||
<head>
|
||||
<title><%= t(context.locale, "TheNobleQuran") %></title>
|
||||
<meta name="description" content="<%= t(context.locale, 'meta.random.description') %>">
|
||||
<%= erb("_version.html.erb") %>
|
||||
<link
|
||||
rel="canonical"
|
||||
href="<%= base_url %>/<%= context.locale %>/random/"
|
||||
/>
|
||||
<% context.locales.each do |locale| %>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="<%= base_url %>/<%= locale %>/random/"
|
||||
hreflang="<%= locale %>" />
|
||||
<% end %>
|
||||
<%= erb("_favicon.html.erb") %>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/js/main/random.js?v=<%= commit %>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<title><%= t("en", "TheNobleQuran") %></title>
|
||||
<meta name="description" content="<%= t('en', 'meta.index.description') %>">
|
||||
<%= erb("_version.html.erb") %>
|
||||
<link rel="canonical" href="<%= base_url %>/en/" />
|
||||
<% locales.each do |locale| %>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="<%= base_url %>/<%= locale %>/"
|
||||
hreflang="<%= locale %>" />
|
||||
<% end %>
|
||||
<%= erb("_favicon.html.erb") %>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/js/main/redirect.js?v=<%= commit %>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= context.locale %>" dir="<%= context.dir %>">
|
||||
<head>
|
||||
<title><%= t(context.locale, "TheNobleQuran") %></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="<%= t(context.locale, 'meta.index.description') %>">
|
||||
<%= erb("_version.html.erb") %>
|
||||
<link
|
||||
rel="canonical"
|
||||
href="<%= base_url %>/<%= context.locale %>/"
|
||||
/>
|
||||
<% context.locales.each do |locale| %>
|
||||
<link rel="alternate"
|
||||
href="<%= base_url %>/<%= locale %>/"
|
||||
hreflang="<%= locale %>" />
|
||||
<% end %>
|
||||
<%= erb("_favicon.html.erb") %>
|
||||
</head>
|
||||
<body>
|
||||
<div class="root h-full"></div>
|
||||
<script type="text/javascript" src="/js/main/vendor.js?v=<%= commit %>"></script>
|
||||
<script type="text/javascript" src="/js/main/surah-index.js?v=<%= commit %>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,31 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= context.locale %>" dir="<%= context.dir %>">
|
||||
<head>
|
||||
<title><%= context.surah.name %></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="<%= t(context.locale, 'meta.stream.description') %>">
|
||||
<%= erb("_version.html.erb") %>
|
||||
<link
|
||||
rel="canonical"
|
||||
href="<%= base_url %>/<%= context.locale %>/<%= context.surah.urlName %>/"
|
||||
/>
|
||||
<% context.locales.each do |locale| %>
|
||||
<link rel="alternate"
|
||||
href="<%= base_url %>/<%= locale %>/<%= context.surah.urlName %>/"
|
||||
hreflang="<%= locale %>" />
|
||||
<% end %>
|
||||
<%= erb("_favicon.html.erb") %>
|
||||
</head>
|
||||
<body>
|
||||
<div class="root"
|
||||
data-surah-id="<%= context.surah.id %>"
|
||||
data-audio-base-url="<%= audio_base_url %>">
|
||||
</div>
|
||||
<%= inline_json("/json/%{locale}/%{surah_id}/info.json", context:, class_name: "info") %>
|
||||
<%= inline_json("/json/%{locale}/%{surah_id}/surah.json", context:, class_name: "surah") %>
|
||||
<%= inline_json("/json/durations/%{surah_id}.json", context:, class_name: "durations") %>
|
||||
<script src="/js/main/vendor.js?v=<%= commit %>"></script>
|
||||
<script src="/js/main/surah-stream.js?v=<%= commit %>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -8,6 +8,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export function LanguageSelect({ locale, isOpen, setIsOpen }: Props) {
|
||||
const locales = Object.values(Quran.locales);
|
||||
return (
|
||||
<Select
|
||||
value={locale.name}
|
||||
|
@ -15,9 +16,8 @@ export function LanguageSelect({ locale, isOpen, setIsOpen }: Props) {
|
|||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
>
|
||||
{Quran.locales.map((l: TLocale, i: number) => {
|
||||
const path = location.pathname;
|
||||
const href = path.replace(`/${locale.name}/`, `/${l.name}/`);
|
||||
{locales.map((l: TLocale, i: number) => {
|
||||
const href = `/${l.name}`;
|
||||
return (
|
||||
<Select.Option
|
||||
key={i}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode, AnchorHTMLAttributes } from "react";
|
||||
import { Link } from "preact-router/match";
|
||||
type Rest = AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
type Props = {
|
||||
value: string;
|
||||
|
@ -6,5 +7,5 @@ type Props = {
|
|||
} & Rest;
|
||||
|
||||
export function Option({ children, ...rest }: Props) {
|
||||
return <a {...rest}>{children}</a>;
|
||||
return <Link {...rest}>{children}</Link>;
|
||||
}
|
||||
|
|
|
@ -4,28 +4,21 @@ import type { Theme } from "~/hooks/useTheme";
|
|||
type Props = {
|
||||
theme: string;
|
||||
setTheme: (theme: Theme) => void;
|
||||
isOpen: boolean;
|
||||
setIsOpen: (b: boolean) => void;
|
||||
};
|
||||
|
||||
export function ThemeSelect({ theme, setTheme, isOpen, setIsOpen }: Props) {
|
||||
export function ThemeSelect({ theme, setTheme }: Props) {
|
||||
const themes: Theme[] = ["blue", "green"];
|
||||
return (
|
||||
<Select
|
||||
value={theme}
|
||||
className="theme-select"
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
>
|
||||
<Select value={theme} className="theme-select">
|
||||
{themes.map((t, i) => {
|
||||
return (
|
||||
<Select.Option
|
||||
key={i}
|
||||
onClick={() => setTheme(t)}
|
||||
className={classNames("block circle mb-1", t)}
|
||||
className="flex justify-end w-10 h-6"
|
||||
value={t}
|
||||
>
|
||||
<span className="block w-full h-full" />
|
||||
<span className={classNames("rounded w-5 h-5", t)} />
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -3,23 +3,16 @@ import { ThemeSelect } from "./ThemeSelect";
|
|||
import { LanguageSelect } from "./LanguageSelect";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (v: boolean) => void;
|
||||
value: string;
|
||||
children: JSX.Element[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function Select({
|
||||
value,
|
||||
children: options,
|
||||
className,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
}: 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 close = () => setIsOpen(false);
|
||||
const close = () => setOpen(false);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener("click", close);
|
||||
|
@ -33,7 +26,7 @@ function Select({
|
|||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"react-select flex flex-col h-full relative",
|
||||
"react-select flex flex-col h-full relative z-10",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
@ -44,7 +37,12 @@ function Select({
|
|||
<li
|
||||
key={key}
|
||||
className={classNames({ hidden: isHidden })}
|
||||
onClick={(e) => [e.stopPropagation(), setIsOpen(!isOpen)]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const { ref } = n.props;
|
||||
setOpen(!isOpen);
|
||||
ref?.current?.click();
|
||||
}}
|
||||
>
|
||||
{n}
|
||||
</li>
|
||||
|
|
|
@ -21,8 +21,8 @@ export function SurahIndex({ locale, surahs, t }: Props) {
|
|||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const activeEl = useMemo(
|
||||
() => document.activeElement,
|
||||
[document.activeElement]
|
||||
)
|
||||
[document.activeElement],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyPress = (e) => {
|
||||
|
@ -36,18 +36,11 @@ export function SurahIndex({ locale, surahs, t }: Props) {
|
|||
return () => activeEl.removeEventListener("keydown", onKeyPress);
|
||||
}, [activeEl, showLangDropdown, showThemeDropdown]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = rootRef.current;
|
||||
if (el) {
|
||||
el.classList.remove("invisible");
|
||||
}
|
||||
}, [rootRef.current, theme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
className={classNames(
|
||||
"flex flex-col invisible h-full content surah-index theme",
|
||||
"flex flex-col h-full content surah-index theme",
|
||||
theme,
|
||||
locale.name,
|
||||
locale.direction,
|
||||
|
|
37
src/js/index.tsx
Normal file
37
src/js/index.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Quran } from "Quran";
|
||||
import { T } from "~/lib/t";
|
||||
import { SurahIndex } from "~/components/SurahIndex";
|
||||
import { render } from "preact";
|
||||
import { useState, useEffect, useMemo, useRef } from "preact/hooks";
|
||||
import * as React from "preact/compat";
|
||||
import classNames from "classnames";
|
||||
import { Router } from "preact-router";
|
||||
import "core-js";
|
||||
|
||||
const exports = {
|
||||
React,
|
||||
render,
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
classNames,
|
||||
};
|
||||
Object.assign(window, exports);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const Main = (function () {
|
||||
const t = T(require("@json/t.json"));
|
||||
return () => {
|
||||
return (
|
||||
<Router>
|
||||
<SurahIndex path="/" locale={Quran.locales["en"]} surahs={Quran.surahs["en"]} t={t} />
|
||||
<SurahIndex path="/en" locale={Quran.locales["en"]} surahs={Quran.surahs["en"]} t={t} />
|
||||
<SurahIndex path="/ar" locale={Quran.locales["ar"]} surahs={Quran.surahs["ar"]} t={t} />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
})();
|
||||
|
||||
render(<Main />, document.querySelector(".mount"));
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
(function () {
|
||||
const nameById = require("@json/nameById.json");
|
||||
const surahId = parseInt(Math.ceil(Math.random() * 114));
|
||||
const name = nameById[surahId];
|
||||
const locale = location.pathname.slice(1, 3);
|
||||
location.replace(["", locale, name, ""].join("/"));
|
||||
})();
|
|
@ -1,10 +0,0 @@
|
|||
import { Quran } from "Quran";
|
||||
(function () {
|
||||
const defaultl = "en";
|
||||
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;
|
||||
location.replace(`/${locale}/`);
|
||||
})();
|
|
@ -1,17 +0,0 @@
|
|||
import { Surah, TSurah, Quran } from "Quran";
|
||||
import { T } from "~/lib/t";
|
||||
import { SurahIndex } from "~/components/SurahIndex";
|
||||
|
||||
(function () {
|
||||
const doc = document.documentElement;
|
||||
const root = doc.querySelector(".root")!;
|
||||
const t = T(require("@json/t.json"));
|
||||
const byLocale = require("@json/surahs");
|
||||
const locale = (() => {
|
||||
return Quran.locales.find((ll) => ll.name === doc.lang);
|
||||
})()!;
|
||||
const surahs: Surah[] = byLocale[locale.name].map(
|
||||
(e: TSurah) => new Surah(e),
|
||||
);
|
||||
render(<SurahIndex locale={locale} surahs={surahs} t={t} />, root);
|
||||
})();
|
|
@ -1,34 +0,0 @@
|
|||
import { Quran, Surah, Ayah, TSurah } from "Quran";
|
||||
import { T } from "~/lib/t";
|
||||
import { SurahStream } from "~/components/SurahStream";
|
||||
|
||||
(function () {
|
||||
const doc = document.documentElement;
|
||||
const root = doc.querySelector(".root")!;
|
||||
const t = T(require("@json/t.json"));
|
||||
const locale = (() => {
|
||||
return Quran.locales.find((ll) => ll.name === doc.lang);
|
||||
})()!;
|
||||
|
||||
/*
|
||||
* Configure an instance of Surah
|
||||
*/
|
||||
const node1: HTMLScriptElement = doc.querySelector(".json.info")!;
|
||||
const node2: HTMLScriptElement = doc.querySelector(".json.surah")!;
|
||||
const node3: HTMLScriptElement = doc.querySelector(".json.durations")!;
|
||||
const blob1: TSurah = JSON.parse(node1.innerText)!;
|
||||
const blob2: Array<[number, string]> = JSON.parse(node2.innerText)!;
|
||||
const blob3: Array<[number, number]> = JSON.parse(node3.innerText)!;
|
||||
const surah = new Surah(blob1);
|
||||
for (let i = 0; i < blob2.length; i++) {
|
||||
const [id, body] = blob2[i] as [number, string];
|
||||
surah.ayat.push(new Ayah({ id, body }));
|
||||
}
|
||||
for (let i = 0; i < surah.ayat.length; i++) {
|
||||
const ayah = surah.ayat[i];
|
||||
const [, ms] = blob3[i];
|
||||
ayah.ms = ms * 1000;
|
||||
}
|
||||
|
||||
render(<SurahStream surah={surah} locale={locale} t={t} />, root);
|
||||
})();
|
|
@ -1,16 +0,0 @@
|
|||
import { render } from "preact";
|
||||
import { useState, useEffect, useMemo, useRef } from "preact/hooks";
|
||||
import * as React from "preact/compat";
|
||||
import classNames from "classnames";
|
||||
import "core-js";
|
||||
|
||||
const exports = {
|
||||
React,
|
||||
render,
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
classNames,
|
||||
};
|
||||
Object.assign(window, exports);
|
Loading…
Reference in a new issue