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
|
# nanoc
|
||||||
gem "nanoc", "~> 4.12"
|
gem "nanoc", "~> 4.12"
|
||||||
gem "nanoc-webpack.rb", "~> 0.10"
|
gem "nanoc-webpack.rb", "~> 0.10"
|
||||||
gem "nanoc-tidy.rb", "~> 0.8.4"
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# dev
|
# dev
|
||||||
|
|
|
@ -60,9 +60,6 @@ GEM
|
||||||
nanoc-checking (~> 1.0)
|
nanoc-checking (~> 1.0)
|
||||||
nanoc-cli (~> 4.11, >= 4.11.15)
|
nanoc-cli (~> 4.11, >= 4.11.15)
|
||||||
nanoc-core (~> 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)
|
nanoc-webpack.rb (0.10.6)
|
||||||
ryo.rb (~> 0.5)
|
ryo.rb (~> 0.5)
|
||||||
test-cmd.rb (~> 0.12.4)
|
test-cmd.rb (~> 0.12.4)
|
||||||
|
@ -149,7 +146,6 @@ PLATFORMS
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
listen (~> 3.0)
|
listen (~> 3.0)
|
||||||
nanoc (~> 4.12)
|
nanoc (~> 4.12)
|
||||||
nanoc-tidy.rb (~> 0.8.4)
|
|
||||||
nanoc-webpack.rb (~> 0.10)
|
nanoc-webpack.rb (~> 0.10)
|
||||||
paint (~> 2.3)
|
paint (~> 2.3)
|
||||||
rake (~> 13.2)
|
rake (~> 13.2)
|
||||||
|
|
37
Rules
37
Rules
|
@ -7,7 +7,6 @@ require "ryo"
|
||||||
require "ryo/json"
|
require "ryo/json"
|
||||||
require "ryo/yaml"
|
require "ryo/yaml"
|
||||||
require "nanoc-webpack"
|
require "nanoc-webpack"
|
||||||
require "nanoc-tidy"
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Extensions
|
# 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"))
|
surahs = Ryo.from_json(path: File.join(dirs.content, "json", "surahs.json"))
|
||||||
tidy = `which tidy || which tidy5`.chomp
|
tidy = `which tidy || which tidy5`.chomp
|
||||||
buildenv = ENV["buildenv"] || "development"
|
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
|
# Rules
|
||||||
passthrough "/json/durations/*.json"
|
passthrough "/json/**/*.json"
|
||||||
require_rules "nanoc/rules/assets"
|
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,
|
filter :webpack,
|
||||||
argv: %w[--config etc/webpack.vendor.js]
|
argv: %w[--config etc/webpack.vendor.js]
|
||||||
write("/js/main/vendor.js")
|
write("/js/vendor.js")
|
||||||
end
|
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
|
compile("/manifest.webapp") do
|
||||||
write("/manifest.webapp")
|
write("/manifest.webapp")
|
||||||
end
|
end
|
||||||
|
|
||||||
compile("/**/*") { write(nil) }
|
compile("/**/*") { write(nil) }
|
||||||
layout("**/*", :erb)
|
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": {
|
"dependencies": {
|
||||||
"@preact/compat": "^17.1.2",
|
"@preact/compat": "^17.1.2",
|
||||||
"classnames": "^2.3",
|
"classnames": "^2.3",
|
||||||
"preact": "^10.23.2"
|
"preact": "^10.23.2",
|
||||||
|
"preact-router": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.25.4",
|
||||||
|
@ -3059,9 +3060,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/babel-loader": {
|
"node_modules/babel-loader": {
|
||||||
"version": "9.1.3",
|
"version": "9.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz",
|
||||||
"integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
|
"integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -5227,6 +5228,15 @@
|
||||||
"url": "https://opencollective.com/preact"
|
"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": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
@ -6381,6 +6391,7 @@
|
||||||
"license": "0BSDL",
|
"license": "0BSDL",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.0",
|
"@types/node": "^22.0",
|
||||||
|
"babel-loader": "^9.2.1",
|
||||||
"typescript": "^5.5"
|
"typescript": "^5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@
|
||||||
"eslint:apply": "npx eslint --config etc/eslint.config.mjs --fix src/js/"
|
"eslint:apply": "npx eslint --config etc/eslint.config.mjs --fix src/js/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "^10.23.2",
|
|
||||||
"@preact/compat": "^17.1.2",
|
"@preact/compat": "^17.1.2",
|
||||||
"classnames": "^2.3"
|
"classnames": "^2.3",
|
||||||
|
"preact": "^10.23.2",
|
||||||
|
"preact-router": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@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",
|
"name": "Quran",
|
||||||
"version": "0.1.0",
|
"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",
|
"main": "dist/index.js",
|
||||||
"types": ["dist/index.d.ts"],
|
"types": [
|
||||||
|
"dist/index.d.ts"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm exec tsc",
|
"build": "npx webpack --config etc/webpack.config.js",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -16,6 +18,7 @@
|
||||||
"license": "0BSDL",
|
"license": "0BSDL",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.0",
|
"@types/node": "^22.0",
|
||||||
|
"babel-loader": "^9.2.1",
|
||||||
"typescript": "^5.5"
|
"typescript": "^5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,14 +36,26 @@ class Quran {
|
||||||
readonly surahs: Surah[];
|
readonly surahs: Surah[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Array} The available locales
|
* @returns {Record<string, TLocale>} The available locales
|
||||||
*/
|
*/
|
||||||
static get locales(): TLocale[] {
|
static get locales(): Record<string, TLocale> {
|
||||||
return [
|
return {
|
||||||
{"name": "en", "displayName": "English", "direction": "ltr"},
|
"en": {"name": "en", "displayName": "English", "direction": "ltr"},
|
||||||
{"name": "ar", "displayName": "العربية", "direction": "rtl"},
|
"ar": {"name": "ar", "displayName": "العربية", "direction": "rtl"},
|
||||||
{"name": "fa", "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) {
|
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
|
** vNEXT
|
||||||
|
|
||||||
**** Remove ~kanit-regular.ttf~
|
** Add ~Quran.surahs~
|
||||||
This change falls back onto standard web fonts across all
|
The ~Quran.surahs~ getter returns an object where the
|
||||||
languages. This should help decrease bundle size, and save
|
key is the locale name, and the value is an array of
|
||||||
time processing the custom font during page load
|
Surah objects
|
||||||
|
|
||||||
**** Add src/js/main/vendor.ts
|
** Replace the play|pause icons
|
||||||
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 and pause icons
|
Replace the play and pause icons
|
||||||
|
|
||||||
**** Remove nanoc-gzip.rb
|
** Remove nanoc-gzip.rb
|
||||||
Unneccessary for a packaged KaiOS application
|
Unneccessary for a packaged KaiOS application
|
||||||
|
|
||||||
**** Remove loaders
|
** Remove loaders
|
||||||
Remove all loaders from ~/src/js/loaders/~, and the postman
|
Remove all loaders from ~/src/js/loaders/~, and the postman
|
||||||
package (~packages/typescript/postman~)
|
package (~packages/typescript/postman~)
|
||||||
|
|
||||||
**** KaiOS fork
|
** Replace multiple entry points with a single entry point
|
||||||
Fork a new branch: ~kaios/main~ dedicated to KaiOS
|
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/~
|
** Redesign as a Single Page Application (SPA)
|
||||||
Move a large portion of the website's configuration files to
|
The previous approach where we generated a HTML file
|
||||||
the ~/etc~ directory
|
for every surah in each language has been replaced by
|
||||||
|
a Single Page Application (SPA) that is more suitable
|
||||||
** v0.9.0
|
for KaiOS.
|
||||||
|
|
||||||
**** 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)
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
@import "base/colors";
|
|
||||||
@import "base/breakpoints";
|
@import "base/breakpoints";
|
||||||
@import "base/icon";
|
@import "base/icon";
|
||||||
@import "base/select";
|
@import "base/select";
|
||||||
|
@ -8,46 +7,111 @@ html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: $black;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ltr {
|
.ltr {
|
||||||
font-family: 'Arial', 'Tahoma', sans-serif;
|
font-family: "Kanit Regular";
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rtl {
|
.rtl {
|
||||||
font-family: 'Arial', 'Tahoma', sans-serif;
|
font-family: "Cairo Regular", "Arial", "Tahoma", sans-serif;
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible, .hidden {
|
.invisible, .hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outline-0 {
|
.outline-0 {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-y {
|
.scroll-y {
|
||||||
overflow-y: auto;
|
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 {
|
body .root .content.theme {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: $breakpoint-md;
|
max-width: $breakpoint-sm;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--color-accent);
|
||||||
|
|
||||||
|
/* <= $breakpoint-sm */
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
font-size: medium;
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
a[data-testid="h1"] {
|
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
|
* RTL languages
|
||||||
*/
|
*/
|
||||||
body .root .content.theme.rtl {
|
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 */
|
/* max-width */
|
||||||
$breakpoint-kaiOS-portrait: 240px;
|
|
||||||
$breakpoint-kaiOS-landscape: 320px;
|
|
||||||
|
|
||||||
/* Standard max widths */
|
|
||||||
$breakpoint-sm: 576px;
|
$breakpoint-sm: 576px;
|
||||||
$breakpoint-md: 768px;
|
$breakpoint-md: 768px;
|
||||||
$breakpoint-lg: 992px;
|
$breakpoint-lg: 992px;
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
$black: #454545;
|
|
||||||
$white: #FFF;
|
|
|
@ -57,6 +57,7 @@
|
||||||
.refresh.icon,
|
.refresh.icon,
|
||||||
.right-arrow.icon,
|
.right-arrow.icon,
|
||||||
.left-arrow.icon {
|
.left-arrow.icon {
|
||||||
|
fill: var(--primary-color);
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
@ -65,6 +66,14 @@
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pause.icon {
|
||||||
|
g rect {
|
||||||
|
width: 15px;
|
||||||
|
height: 40px;
|
||||||
|
fill: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ul.body.stream span {
|
ul.body.stream span {
|
||||||
.sound-on.icon,
|
.sound-on.icon,
|
||||||
.sound-off.icon {
|
.sound-off.icon {
|
||||||
|
@ -92,7 +101,7 @@
|
||||||
.root .content.theme.rtl {
|
.root .content.theme.rtl {
|
||||||
.stalled.icon {
|
.stalled.icon {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
right: 7px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.body.stream {
|
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 {
|
.root .content.theme {
|
||||||
|
.green {
|
||||||
|
background: #6d765b !important;
|
||||||
|
}
|
||||||
|
.blue {
|
||||||
|
background: #3383C3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.react-select {
|
.react-select {
|
||||||
z-index: 2;
|
|
||||||
.active {
|
.active {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-select.theme-select {
|
.react-select.theme-select {
|
||||||
|
ul li,
|
||||||
|
ul li a {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
.circle {
|
.circle {
|
||||||
width: 16px;
|
background: var(--primary-color);
|
||||||
height: 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-select.language-select {
|
.react-select.language-select {
|
||||||
li a {
|
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 {
|
body .root .content.theme {
|
||||||
ul.body.index {
|
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%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,9 +31,48 @@ body .root .content.theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
/* TODO: Remove footer ? */
|
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;
|
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 {
|
body .root .content.theme.rtl {
|
||||||
ul.body.index {
|
ul.body.index {
|
||||||
li a {
|
|
||||||
span:first-child {
|
|
||||||
border: 1px solid $black;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
li.surah {
|
li.surah {
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
span.transliterated { display: none; }
|
span.transliterated { display: none; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media (hover: hover) {
|
||||||
/* >= $breakpoint-xxl */
|
footer {
|
||||||
@media (min-width: $breakpoint-xxl) {
|
a {
|
||||||
ul.body.index {
|
&:hover {
|
||||||
li.surah a {
|
font-weight: normal;
|
||||||
span:last-child {
|
font-family: "Cairo Bold";
|
||||||
font-size: larger;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,40 +16,13 @@ body .root .content.theme {
|
||||||
animation: FadeIn 1s;
|
animation: FadeIn 1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul.body.stream:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body .root .content.theme.rtl {
|
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 {
|
ul.body.stream {
|
||||||
li.ayah p {
|
li.ayah p {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
@extend .font-amiri;
|
||||||
}
|
|
||||||
}
|
|
||||||
/* >= $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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,5 @@
|
||||||
.root .content.theme.blue.rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root .content.theme.blue {
|
.root .content.theme.blue {
|
||||||
@import "blue/base/colors";
|
--primary-color: #3383C3;
|
||||||
|
--secondary-color: #FFF;
|
||||||
.color-white {
|
--accent-color: #444;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
.root .content.theme.green {
|
||||||
@import "green/base/colors";
|
--primary-color: #6d765b;
|
||||||
|
--secondary-color: #FFF;
|
||||||
.color-primary {
|
--accent-color: #444;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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) {
|
export function LanguageSelect({ locale, isOpen, setIsOpen }: Props) {
|
||||||
|
const locales = Object.values(Quran.locales);
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
value={locale.name}
|
value={locale.name}
|
||||||
|
@ -15,9 +16,8 @@ export function LanguageSelect({ locale, isOpen, setIsOpen }: Props) {
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
>
|
>
|
||||||
{Quran.locales.map((l: TLocale, i: number) => {
|
{locales.map((l: TLocale, i: number) => {
|
||||||
const path = location.pathname;
|
const href = `/${l.name}`;
|
||||||
const href = path.replace(`/${locale.name}/`, `/${l.name}/`);
|
|
||||||
return (
|
return (
|
||||||
<Select.Option
|
<Select.Option
|
||||||
key={i}
|
key={i}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ReactNode, AnchorHTMLAttributes } from "react";
|
import type { ReactNode, AnchorHTMLAttributes } from "react";
|
||||||
|
import { Link } from "preact-router/match";
|
||||||
type Rest = AnchorHTMLAttributes<HTMLAnchorElement>;
|
type Rest = AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -6,5 +7,5 @@ type Props = {
|
||||||
} & Rest;
|
} & Rest;
|
||||||
|
|
||||||
export function Option({ children, ...rest }: Props) {
|
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 = {
|
type Props = {
|
||||||
theme: string;
|
theme: string;
|
||||||
setTheme: (theme: Theme) => void;
|
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"];
|
const themes: Theme[] = ["blue", "green"];
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select value={theme} className="theme-select">
|
||||||
value={theme}
|
|
||||||
className="theme-select"
|
|
||||||
isOpen={isOpen}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
>
|
|
||||||
{themes.map((t, i) => {
|
{themes.map((t, i) => {
|
||||||
return (
|
return (
|
||||||
<Select.Option
|
<Select.Option
|
||||||
key={i}
|
key={i}
|
||||||
onClick={() => setTheme(t)}
|
onClick={() => setTheme(t)}
|
||||||
className={classNames("block circle mb-1", t)}
|
className="flex justify-end w-10 h-6"
|
||||||
value={t}
|
value={t}
|
||||||
>
|
>
|
||||||
<span className="block w-full h-full" />
|
<span className={classNames("rounded w-5 h-5", t)} />
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -3,23 +3,16 @@ import { ThemeSelect } from "./ThemeSelect";
|
||||||
import { LanguageSelect } from "./LanguageSelect";
|
import { LanguageSelect } from "./LanguageSelect";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
|
||||||
setIsOpen: (v: boolean) => void;
|
|
||||||
value: string;
|
value: string;
|
||||||
children: JSX.Element[];
|
children: JSX.Element[];
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Select({
|
function Select({ value, children: options, className }: Props) {
|
||||||
value,
|
const [isOpen, setOpen] = useState<boolean>(false);
|
||||||
children: options,
|
|
||||||
className,
|
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
}: Props) {
|
|
||||||
const [option, setOption] = useState<JSX.Element | null>(null);
|
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 = () => setIsOpen(false);
|
const close = () => setOpen(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.addEventListener("click", close);
|
document.body.addEventListener("click", close);
|
||||||
|
@ -33,7 +26,7 @@ function Select({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"react-select flex flex-col h-full relative",
|
"react-select flex flex-col h-full relative z-10",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -44,7 +37,12 @@ function Select({
|
||||||
<li
|
<li
|
||||||
key={key}
|
key={key}
|
||||||
className={classNames({ hidden: isHidden })}
|
className={classNames({ hidden: isHidden })}
|
||||||
onClick={(e) => [e.stopPropagation(), setIsOpen(!isOpen)]}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const { ref } = n.props;
|
||||||
|
setOpen(!isOpen);
|
||||||
|
ref?.current?.click();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{n}
|
{n}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -21,8 +21,8 @@ export function SurahIndex({ locale, surahs, t }: Props) {
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const activeEl = useMemo(
|
const activeEl = useMemo(
|
||||||
() => document.activeElement,
|
() => document.activeElement,
|
||||||
[document.activeElement]
|
[document.activeElement],
|
||||||
)
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyPress = (e) => {
|
const onKeyPress = (e) => {
|
||||||
|
@ -36,18 +36,11 @@ export function SurahIndex({ locale, surahs, t }: Props) {
|
||||||
return () => activeEl.removeEventListener("keydown", onKeyPress);
|
return () => activeEl.removeEventListener("keydown", onKeyPress);
|
||||||
}, [activeEl, showLangDropdown, showThemeDropdown]);
|
}, [activeEl, showLangDropdown, showThemeDropdown]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const el = rootRef.current;
|
|
||||||
if (el) {
|
|
||||||
el.classList.remove("invisible");
|
|
||||||
}
|
|
||||||
}, [rootRef.current, theme]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"flex flex-col invisible h-full content surah-index theme",
|
"flex flex-col h-full content surah-index theme",
|
||||||
theme,
|
theme,
|
||||||
locale.name,
|
locale.name,
|
||||||
locale.direction,
|
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