Merge branch 'main' into production

This commit is contained in:
0x1eef 2024-03-25 02:08:07 -03:00
commit 823fc6646a
35 changed files with 202 additions and 488 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "submodules/surah-name-glyphs"]
path = submodules/surah-name-glyphs
url = https://github.com/ReflectsLight/surah-name-glyphs
[submodule "packages/ruby/server.rb"]
path = packages/ruby/server.rb
url = https://github.com/0x1eef/server.rb

View file

@ -15,7 +15,7 @@ gem "sass", "~> 3.7"
##
# Web server
gem "server.rb", path: "./packages/ruby/server"
gem "server.rb", path: "./packages/ruby/server.rb"
##
# Other

View file

@ -1,5 +1,5 @@
PATH
remote: packages/ruby/server
remote: packages/ruby/server.rb
specs:
server.rb (0.1.0)
puma (~> 6.3)
@ -11,6 +11,8 @@ GEM
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
base64 (0.2.0)
coderay (1.1.3)
colored (1.2)
concurrent-ruby (1.2.3)
cri (2.15.11)
@ -26,31 +28,34 @@ GEM
json_schema (0.21.0)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
listen (3.8.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lockf.rb (0.12.0)
lockf.rb (0.13.0)
memo_wise (1.8.0)
memoize (1.3.1)
nanoc (4.12.19)
method_source (1.0.0)
nanoc (4.12.20)
addressable (~> 2.5)
colored (~> 1.2)
nanoc-checking (~> 1.0, >= 1.0.2)
nanoc-cli (= 4.12.19)
nanoc-core (= 4.12.19)
nanoc-cli (= 4.12.20)
nanoc-core (= 4.12.20)
nanoc-deploying (~> 1.0)
parallel (~> 1.12)
tty-command (~> 0.8)
tty-which (~> 0.4)
nanoc-checking (1.0.2)
nanoc-cli (~> 4.12, >= 4.12.4)
nanoc-core (~> 4.12, >= 4.12.4)
nanoc-cli (4.12.19)
nanoc-checking (1.0.3)
nanoc-cli (~> 4.12, >= 4.12.5)
nanoc-core (~> 4.12, >= 4.12.5)
nanoc-cli (4.12.20)
cri (~> 2.15)
diff-lcs (~> 1.3)
nanoc-core (= 4.12.19)
nanoc-core (= 4.12.20)
pry
zeitwerk (~> 2.1)
nanoc-core (4.12.19)
nanoc-core (4.12.20)
base64 (~> 0.2)
concurrent-ruby (~> 1.1)
ddmetrics (~> 1.0)
ddplugin (~> 1.0)
@ -68,9 +73,9 @@ GEM
nanoc-gzip.rb (0.2.3)
nanoc (~> 4.12)
nanoc-tidy.rb (0.4.0)
nanoc-webpack.rb (0.5.6)
ryo.rb (~> 0.4)
nio4r (2.7.0)
nanoc-webpack.rb (0.7.0)
ryo.rb (~> 0.5)
nio4r (2.7.1)
paint (2.3.0)
parallel (1.24.0)
parser (3.3.0.5)
@ -78,13 +83,16 @@ GEM
racc
pastel (0.8.0)
tty-color (~> 0.5)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.1.2)
stringio
public_suffix (5.0.4)
puma (6.4.2)
nio4r (~> 2.0)
racc (1.7.3)
rack (3.0.9)
rack (3.0.10)
rainbow (3.1.1)
rainpress (1.0.1)
rb-fsevent (0.11.2)
@ -93,7 +101,7 @@ GEM
rbtree (0.4.6)
regexp_parser (2.9.0)
rexml (3.2.6)
rubocop (1.60.2)
rubocop (1.62.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@ -101,11 +109,11 @@ GEM
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.20.2)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
@ -121,10 +129,10 @@ GEM
sorted_set (1.0.3)
rbtree
set (~> 1.0)
standard (1.34.0)
standard (1.35.1)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.60)
rubocop (~> 1.62.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.3)
standard-custom (1.0.2)
@ -134,7 +142,7 @@ GEM
lint_roller (~> 1.1)
rubocop-performance (~> 1.20.2)
stringio (3.1.0)
test-cmd.rb (0.5.2)
test-cmd.rb (0.6.0)
tty-color (0.6.0)
tty-command (0.10.1)
pastel (~> 0.8)

View file

@ -12,11 +12,16 @@ load "rake/tasks/nanoc.rake"
load "rake/tasks/submodules.rake"
desc "Serve the website on localhost"
task :server, [:host, :port] do |_t, args|
task :server, [:protocol] do |_t, args|
require "server"
build_dir = Ryo.from(YAML.load_file("./nanoc.yaml")).output_dir
nanoc = Ryo.from(YAML.load_file("./nanoc.yaml"))
h = args.to_h
s = Server.for_dir(build_dir, h.slice(:host,:port))
o = if h[:protocol] == 'unix'
{unix: nanoc.server.unix.path}
else
{host: nanoc.server.tcp.host, port: nanoc.server.tcp.port}
end
s = Server.dir(nanoc.output_dir, o)
s.start(block: true)
rescue Interrupt
s.stop

View file

@ -1 +1 @@
v0.7.12
v0.7.13

View file

@ -19,3 +19,10 @@ data_sources:
encoding: UTF-8
content_dir: src/
layouts_dir: src/layouts
server:
unix:
path: /tmp/al-quran.reflectslight.io
tcp:
host: 127.0.0.1
port: 7777

@ -0,0 +1 @@
Subproject commit bf94e6f20c1f48ec002611912a53e0bf746f076f

View file

@ -1,8 +0,0 @@
build/
tmp/
node_modules/
.bundle/
*.log
*.sh
*.core
Gemfile.lock

View file

@ -1,40 +0,0 @@
##
# Plugins
require:
- standard
##
# Defaults: standard-rb
inherit_gem:
standard: config/base.yml
##
# All cops
AllCops:
TargetRubyVersion: 3.2
Include:
- lib/*.rb
- lib/**/*.rb
- test/*_test.rb
##
# Enabled
Style/FrozenStringLiteralComment:
Enabled: true
##
# Disabled
Layout/ArgumentAlignment:
Enabled: false
Layout/MultilineMethodCallIndentation:
Enabled: false
Layout/EmptyLineBetweenDefs:
Enabled: false
Style/TrivialAccessors:
Enabled: false
Lint/NestedMethodDefinition:
Exclude:
- test/server_dir_test.rb
Style/SingleLineMethods:
Exclude:
- test/server_dir_test.rb

View file

@ -1,4 +0,0 @@
# frozen_string_literal: true
source "https://rubygems.org"
gemspec

View file

@ -0,0 +1,77 @@
PATH
remote: .
specs:
server.rb (0.1.0)
json (= 2.6.1)
puma (~> 6.3)
racc (= 1.6.0)
rack (~> 3.0)
set (= 1.0.2)
stringio (= 3.0.1)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.6.1)
language_server-protocol (3.17.0.3)
lint_roller (1.0.0)
nio4r (2.5.9)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
power_assert (2.0.3)
puma (6.3.0)
nio4r (~> 2.0)
racc (1.6.0)
rack (3.0.8)
rack-test (2.1.0)
rack (>= 1.3)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.2.5)
rubocop (1.52.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-performance (1.18.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.13.0)
set (1.0.2)
standard (1.29.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.52.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.1.0)
standard-custom (1.0.1)
lint_roller (~> 1.0)
standard-performance (1.1.0)
lint_roller (~> 1.0)
rubocop-performance (~> 1.18.0)
stringio (3.0.1)
test-unit (3.6.1)
power_assert
unicode-display_width (2.4.2)
PLATFORMS
x86_64-openbsd
DEPENDENCIES
rack-test (~> 2.1)
server.rb!
standard (~> 1.24)
test-unit (~> 3.5)
BUNDLED WITH
2.3.26

View file

@ -1,15 +0,0 @@
Copyright (C) 2023 by 0x1eef <0x1eef@protonmail.com>
Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.

View file

@ -1,40 +0,0 @@
## About
server.rb implements a static file web server
by using the fast performing Ruby web server
[Puma](https://github.com/puma/puma)
and a small
[Rack](https://github.com/rack/rack)
application.
## Examples
### Server.for_dir
The `Server.for_dir` method returns a Server instance
that serves the contents of a directory. `Server#start` spawns
a new thread to listen for requests, and afterwards returns
the thread. `Thread#join` can block execution at that point,
or execution can continue as normal by not calling `Thread#join`:
```ruby
require "server"
##
# Create a Server instance for the contents of a directory
server = Server.for_dir("./build/website/")
##
# Start listening for connections
thr = server.start
##
# Prevent the main thread from exiting
thr.join
```
## License
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/).
<br>
See [LICENSE](./LICENSE).

View file

@ -1,54 +0,0 @@
# frozen_string_literal: true
class Server
require "rack"
require_relative "server/puma"
require_relative "server/gzip"
require_relative "server/etag"
require_relative "server/dir"
def self.app(path)
Rack::Builder.app do
use Server::ETag
run Server::Dir.new(path)
end
end
def self.for_dir(path, options = {})
host = options.delete(:host) || "127.0.0.1"
port = options.delete(:port) || 7777
new app(path), options.merge!(
binds: ["tcp://#{host}:#{port}"],
tcp_host: host,
tcp_port: port
)
end
def initialize(app, options = {})
@app = app
@options = default_options.merge!(options)
@events = Puma::Events.new
@server = Puma::Server.new(@app, @events, @options)
end
def start(block: false)
@server.binder.parse(@options[:binds])
thr = @server.run
block ? thr.join : thr
end
def stop
@server.stop
end
private
def default_options
{
supported_http_methods: %w[GET HEAD],
min_threads: 1,
max_threads: 5,
workers: 1
}
end
end

View file

@ -1,47 +0,0 @@
# frozen_string_literal: true
##
# A rack application that serves the contents
# of a directory over HTTP.
class Server::Dir
prepend Server::Gzip
def initialize(root)
@root = File.realpath(root)
@mime_types = {".ttf" => "font/ttf"}.freeze
end
def call(env)
finish Rack::Request.new(env)
rescue Errno::EPERM, Errno::EACCES
body = "Permission denied"
[403, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
rescue Errno::ENOENT
body = "The requested URL was not found"
[404, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
rescue => ex
body = "Internal server error (#{ex.class})"
[500, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
end
def finish(request)
path = find_path(request)
body = File.binread(path)
extn = File.extname(path)
[
200,
{"content-type" => mime_types[extn] || Rack::Mime.mime_type(extn),
"content-length" => body.bytesize},
body.each_line.to_a
]
end
private
attr_reader :root, :mime_types
def find_path(request)
path = File.join root, File.expand_path(request.path)
File.directory?(path) ? File.join(path, "index.html") : path
end
end

View file

@ -1,18 +0,0 @@
# frozen_string_literal: true
class Server::ETag < Rack::ETag
ETAGS = {}
def initialize(app)
@app = app
end
def call(env)
status, headers, body = super(env)
if headers["etag"] && headers["etag"] == env["HTTP_IF_NONE_MATCH"]
[304, headers, [""]]
else
[status, headers, body]
end
end
end

View file

@ -1,33 +0,0 @@
# frozen_string_literal: true
##
# A mixin module that serves a compressed version of
# a file when the file is found to exist on disk,
# and has a ".gz" file extension.
module Server::Gzip
def finish(request)
path = gzip_path(request)
if path
body = File.binread(path)
extn = File.extname(path[0..-4])
[
200,
{"content-type" => mime_types[extn] || Rack::Mime.mime_type(extn),
"content-encoding" => "gzip",
"content-length" => body.bytesize},
body.each_line
]
else
super
end
end
private
def gzip_path(request)
return unless request.get_header("accept-encoding")
&.include?("gzip")
path = "#{find_path(request)}.gz"
File.exist?(path) ? path : nil
end
end

View file

@ -1,8 +0,0 @@
# frozen_string_literal: true
require "puma"
require "puma/events"
class Puma::Server
public :binder
end

View file

@ -1,19 +0,0 @@
# frozen_string_literal: true
Gem::Specification.new do |gem|
gem.name = "server.rb"
gem.authors = ["0x1eef"]
gem.email = ["0x1eef@protonmail.com"]
gem.homepage = "https://github.com/0x1eef/server.rb#readme"
gem.version = "0.1.0"
gem.licenses = ["0BSD"]
gem.files = Dir["lib/*", "lib/**/*.rb"]
gem.require_paths = ["lib"]
gem.summary = "A static file web server"
gem.description = gem.summary
gem.add_runtime_dependency "puma", "~> 6.3"
gem.add_runtime_dependency "rack", "~> 3.0"
gem.add_development_dependency "standard", "~> 1.24"
gem.add_development_dependency "rack-test", "~> 2.1"
gem.add_development_dependency "test-unit", "~> 3.5"
end

View file

@ -1,87 +0,0 @@
# frozen_string_literal: true
require_relative "setup"
require "rack/test"
class ServerDirTest < Test::Unit::TestCase
include Rack::Test::Methods
def test_index
get "/"
assert_equal 200, last_response.status
assert_equal "text/html", last_response.content_type
assert_equal bytesize("./test/webroot/index.html"), last_response.content_length
end
def test_ttf_font
get "/fonts/roboto-mono-regular.ttf"
assert_equal 200, last_response.status
assert_equal "font/ttf", last_response.content_type
assert_equal bytesize("./test/webroot/fonts/roboto-mono-regular.ttf"),
last_response.content_length
end
def test_js_file
get "/js/index.js"
assert_equal 200, last_response.status
assert_equal "application/javascript", last_response.content_type
assert_equal bytesize("./test/webroot/js/index.js"),
last_response.content_length
end
def test_png_file
get "/images/0x1eef.png"
assert_equal 200, last_response.status
assert_equal "image/png", last_response.content_type
assert_equal bytesize("./test/webroot/images/0x1eef.png"),
last_response.content_length
end
def test_json_file
get "/json/1.json"
assert_equal 200, last_response.status
assert_equal "application/json", last_response.content_type
assert_equal bytesize("./test/webroot/json/1.json"),
last_response.content_length
end
def test_internal_server_error
def app.finish(request) raise "test" end
get "/"
assert_equal 500, last_response.status
assert_equal "text/plain", last_response.content_type
assert_equal "Internal server error (RuntimeError)".bytesize,
last_response.content_length
assert_equal "Internal server error (RuntimeError)",
last_response.body
end
def test_permission_denied
File.chmod 0, "./test/webroot/permission_denied.html"
get "/permission_denied.html"
assert_equal 403, last_response.status
assert_equal "text/plain", last_response.content_type
assert_equal "Permission denied".bytesize, last_response.content_length
assert_equal "Permission denied", last_response.body
ensure
File.chmod 0o440, "./test/webroot/permission_denied.html"
end
def test_page_not_found
get "/foobarbaz"
assert_equal 404, last_response.status
assert_equal "text/plain", last_response.content_type
assert_equal "The requested URL was not found".bytesize, last_response.content_length
assert_equal "The requested URL was not found", last_response.body
end
private
def app
@app ||= Server.app("./test/webroot/")
end
def bytesize(path)
File.binread(path).bytesize
end
end

View file

@ -1,3 +0,0 @@
require "bundler/setup"
require "test/unit"
require "server"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,8 +0,0 @@
<!DOCTYPE html>
<head>
<title>/index.html</title>
</head>
<body>
<b>/index.html</b>
</body>
</html>

View file

@ -1,3 +0,0 @@
(function() {
console.log("Hello world");
})();

View file

@ -1 +0,0 @@
[{"id":"1","place_of_revelation":"makkah","transliterated_name":"Al-Fatihah","translated_name":"The Opener","verse_count":7,"slug":"al-fatihah","codepoints":[1575,1604,1601,1575,1578,1581,1577]},[1,"بِسْمِ اللَّهِ الرَّحْمَـٰنِ الرَّحِيمِ"],[2,"الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ"],[3,"الرَّحْمَـٰنِ الرَّحِيمِ"],[4,"مَالِكِ يَوْمِ الدِّينِ"],[5,"إِيَّاكَ نَعْبُدُ وَإِيَّاكَ نَسْتَعِينُ"],[6,"اهْدِنَا الصِّرَاطَ الْمُسْتَقِيمَ"],[7,"صِرَاطَ الَّذِينَ أَنْعَمْتَ عَلَيْهِمْ غَيْرِ الْمَغْضُوبِ عَلَيْهِمْ وَلَا الضَّالِّينَ"]]

View file

@ -2,7 +2,7 @@ $black: #454545;
$max-width: 1024px;
* {
line-height: 1.5;
line-height: 1;
}
html[lang="en"] {
@ -87,6 +87,9 @@ body .root .content.en {
/* Arabic-specific rules */
body .root .content.theme.ar {
header {
h1 {
font-size: xx-large;
}
nav, div {
direction: ltr;
.react-select.language {

View file

@ -66,11 +66,10 @@
}
}
.content.theme ul.stream span.title {
.content.theme ul.stream span {
.sound-on.icon, .sound-off.icon {
width: 24px;
height: 24px;
transform: translate(0, 4px);
}
}

View file

@ -6,6 +6,14 @@ body .root .content.theme {
ul.body.stream {
scrollbar-gutter: stable;
li.ayah.fade {
.sound-on.icon, .sound-off.icon {
width: 24px;
height: 24px;
g {
transform: translate(0, 12px);
}
}
@keyframes FadeIn {
0% { opacity: 0;}
25% { opacity: 0.25; }
@ -28,7 +36,6 @@ body .root .content.theme {
body .root .content.theme.en {
ul.body.stream {
.sound-off.icon, .sound-on.icon {
transform: translate(0, 10px);
}
}
}
@ -48,12 +55,8 @@ body .root .content.theme.ar {
ul.body.stream {
li.ayah {
.title {
display: block;
margin-bottom: 5px;
}
.sound-off.icon, .sound-on.icon {
transform: rotate(180deg) translate(0, -4px);
transform: rotate(180deg);
}
}
}

View file

@ -0,0 +1,29 @@
import React from "react";
import type { ReactNode } from "react";
import { ThemeSelect } from "~/components/ThemeSelect";
import { LanguageSelect } from "~/components/LanguageSelect";
import * as Quran from "~/lib/Quran";
import { Theme } from "~/hooks/useTheme";
type Props = {
locale: Quran.Locale;
theme: string;
setTheme: (t: Theme) => void;
children: ReactNode;
};
export function Head({ locale, theme, setTheme, children }: Props) {
return (
<header className="flex flex-col h-18 mt-4 mb-4">
<h1 className="flex justify-center p-0 m-0">
<a className="no-underline" href={`/${locale}/`}>
{children}
</a>
</h1>
<nav className="flex flex-row justify-between">
<LanguageSelect locale={locale} />
<ThemeSelect theme={theme} setTheme={setTheme} />
</nav>
</header>
);
}

View file

@ -1,28 +1,24 @@
import React from "react";
import { Select } from "~/components/Select";
interface Props {
type Props = {
locale: string;
path?: string;
}
};
export function LanguageSelect({ locale, path = "" }: Props) {
export function LanguageSelect({ locale }: Props) {
return (
<Select
value={locale}
className="language"
onChange={(el: JSX.Element) => {
const locale = el.props.value;
const newPath = (() => {
if (path.endsWith("/") || path.length === 0) {
return path;
} else {
return `${path}/`;
}
})();
const newLocale = el.props.value;
const content = document.querySelector(".content.theme");
const path = location.pathname.replace(
new RegExp(`^/${locale}/`),
`/${newLocale}/`,
);
content.classList.add("invisible");
location.replace(`/${locale}/${newPath}`);
location.replace(path);
}}
>
<option value="ar">

View file

@ -26,19 +26,26 @@ export function Stream({
const className = endOfStream || isPaused ? ["scroll-y"] : [];
const ref = useRef<HTMLUListElement>();
const ul = useMemo<JSX.Element>(() => {
const ltr = locale === "en";
const rtl = locale === "ar";
return (
<ul
lang={locale}
className={classNames(
"body stream scroll-y list-none p-0 h-5/6",
"body stream scroll-y list-none p-0 m-0 mt-1 h-5/6",
...className,
)}
ref={ref}
>
{stream.map((ayah: Quran.Ayah) => {
return (
<li key={ayah.id} className="ayah fade">
<span className="title">
<li
key={ayah.id}
className={classNames("ayah fade", { "mb-6": rtl, "mb-4": ltr })}
>
<span
className={classNames("flex h-8 items-center", { "mb-2": rtl })}
>
{(isPaused || endOfStream) && (
<AudioControl
recitation={recitation}
@ -54,13 +61,13 @@ export function Stream({
{formatNumber(surah.ayat.length, locale)}
</span>
</span>
<p className="m-0 mb-3">{ayah.text}</p>
<p className="m-0">{ayah.text}</p>
</li>
);
})}
</ul>
);
}, [stream.length, isPaused]);
}, [stream.length, isPaused, endOfStream]);
useEffect(() => {
const el = ref.current;

View file

@ -1,10 +1,9 @@
import React, { useRef, useState, useEffect } from "react";
import * as Quran from "~/lib/Quran";
import { useTheme } from "~/hooks/useTheme";
import { ThemeSelect } from "~/components/ThemeSelect";
import { LanguageSelect } from "~/components/LanguageSelect";
import { formatNumber, TFunction } from "~/lib/i18n";
import { RightArrow } from "~/components/Icon";
import { Head } from "~/components/Head";
import { SurahIndexFilter } from "~/components/SurahIndexFilter";
import classNames from "classnames";
@ -35,23 +34,10 @@ export function SurahIndex({ locale, surahs, t }: Props) {
locale,
)}
>
<header
className={classNames("flex flex-col", {
"h-20": locale !== "ar",
"h-22": locale === "ar",
})}
>
<h1 className="flex justify-center p-0 mt-2">
<a className="no-underline" href={`/${locale}/`}>
{t(locale, "TheNobleQuran")}
</a>
</h1>
<nav className="flex flex-row justify-between">
<LanguageSelect locale={locale} />
<ThemeSelect theme={theme} setTheme={setTheme} />
</nav>
</header>
<ul className="body index scroll-y list-none p-0 h-5/6">
<Head locale={locale} theme={theme} setTheme={setTheme}>
{t(locale, "TheNobleQuran")}
</Head>
<ul className="body index scroll-y list-none p-0 m-0 h-5/6">
{index.map((surah, key) => (
<li className="surah" key={key}>
<a

View file

@ -4,9 +4,8 @@ import * as Quran from "~/lib/Quran";
import { useTheme } from "~/hooks/useTheme";
import { Timer } from "~/components/Timer";
import { Stream } from "~/components/Stream";
import { ThemeSelect } from "~/components/ThemeSelect";
import { LanguageSelect } from "~/components/LanguageSelect";
import { AudioControl } from "~/components/AudioControl";
import { Head } from "~/components/Head";
import {
PlayIcon,
PauseIcon,
@ -73,30 +72,9 @@ export function SurahStream({ node, recitations, locale, paused, t }: Props) {
)}
>
{readyToRender && (
<header
className={classNames("flex flex-col", {
"h-24": locale !== "ar",
"h-26": locale === "ar",
})}
>
<h1 className="flex justify-center p-0 mt-2">
<a className="no-underline color-primary" href={`/${locale}/`}>
{t(locale, "TheNobleQuran")}
</a>
</h1>
<nav className="flex flex-row justify-between">
<LanguageSelect locale={locale} path={surah.slug} />
<ThemeSelect theme={theme} setTheme={setTheme} />
</nav>
<div className="flex justify-between surah-name">
<span className="localized-name" lang={locale}>
{surah.localizedName}
</span>
<span className="transliterated-name" lang="en">
{surah.transliteratedName}
</span>
</div>
</header>
<Head locale={locale} theme={theme} setTheme={setTheme}>
{t(locale, "TheNobleQuran")}
</Head>
)}
{readyToRender && (
<Stream