Merge branch 'main' into production
This commit is contained in:
commit
823fc6646a
35 changed files with 202 additions and 488 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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
|
||||
|
|
54
Gemfile.lock
54
Gemfile.lock
|
@ -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)
|
||||
|
|
11
Rakefile.rb
11
Rakefile.rb
|
@ -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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
v0.7.12
|
||||
v0.7.13
|
||||
|
|
|
@ -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
|
||||
|
|
1
packages/ruby/server.rb
Submodule
1
packages/ruby/server.rb
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit bf94e6f20c1f48ec002611912a53e0bf746f076f
|
8
packages/ruby/server/.gitignore
vendored
8
packages/ruby/server/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
build/
|
||||
tmp/
|
||||
node_modules/
|
||||
.bundle/
|
||||
*.log
|
||||
*.sh
|
||||
*.core
|
||||
Gemfile.lock
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
gemspec
|
77
packages/ruby/server/Gemfile.lock
Normal file
77
packages/ruby/server/Gemfile.lock
Normal 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
|
|
@ -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.
|
|
@ -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).
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,8 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "puma"
|
||||
require "puma/events"
|
||||
|
||||
class Puma::Server
|
||||
public :binder
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
require "bundler/setup"
|
||||
require "test/unit"
|
||||
require "server"
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 7.3 KiB |
|
@ -1,8 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>/index.html</title>
|
||||
</head>
|
||||
<body>
|
||||
<b>/index.html</b>
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||
(function() {
|
||||
console.log("Hello world");
|
||||
})();
|
|
@ -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,"صِرَاطَ الَّذِينَ أَنْعَمْتَ عَلَيْهِمْ غَيْرِ الْمَغْضُوبِ عَلَيْهِمْ وَلَا الضَّالِّينَ"]]
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
src/js/components/Head.tsx
Normal file
29
src/js/components/Head.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue