Merge branch 'main' into production

This commit is contained in:
0x1eef 2024-03-16 21:38:09 -03:00
commit b3d0a8c6ad
30 changed files with 113 additions and 151 deletions

View file

@ -110,7 +110,7 @@ GEM
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (1.13.0)
ryo.rb (0.4.7)
ryo.rb (0.5.1)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)

66
Rules
View file

@ -2,23 +2,19 @@
# frozen_string_literal: true
require "ryo"
require "ryo/json"
require "nanoc-gzip"
require "nanoc-webpack"
require "nanoc-tidy"
require_relative "nanoc/lib/require_rules"
locales = %w[ar en]
slugs = Ryo.from(
JSON.parse(
File.read(File.join(Dir.getwd, "src", "json", "slugs.json"))
)
)
i18n = Ryo.from(
JSON.parse(
File.read(File.join(Dir.getwd, "src", "json", "i18n.json"))
)
)
buildenv = ENV["buildenv"] || "development"
##
# Common vars
locales = %w[ar en]
json_dir = File.join(Dir.getwd, "src", "json")
name_by_id = Ryo.from_json_file("#{json_dir}/nameById.json")
i18n = Ryo.from_json_file("#{json_dir}/i18n.json")
buildenv = ENV["buildenv"] || "development"
Nanoc::Webpack.default_options.merge!(
"--config" => "webpack.#{buildenv}.js"
)
@ -26,44 +22,8 @@ Nanoc::Tidy.default_options.merge!(
"-upper" => true
)
def require_rules(rules, locals = {}, target = binding)
locals.each { target.local_variable_set(_1, _2) }
path = File.join(Dir.getwd, rules)
target.eval(
if File.readable?(path)
File.read(path)
elsif File.readable?("#{path}.rb")
File.read("#{path}.rb")
elsif File.readable?("#{path}.rules")
File.read("#{path}.rules")
else
raise LoadError, "#{path} is not readable"
end
)
end
##
# Inline CSS / JSON rules
compile "/i18n.json" do
filter(:minify_json)
write(nil)
end
compile "/surahs.json" do
filter(:minify_json)
write(nil)
end
compile "/slugs.json" do
filter(:minify_json)
write(nil)
end
compile "/recitations.json" do
filter(:minify_json)
write(nil)
end
# See packages/typescript/postman
compile "/css/postman.scss" do
filter :sass, syntax: :scss, style: :compact
filter :rainpress
@ -73,7 +33,7 @@ end
##
# /sitemap.xml
compile "/sitemap.xml.erb" do
filter(:erb, locals: {locales:, slugs:})
filter(:erb, locals: {locales:, name_by_id:})
filter(:strip)
write("/sitemap.xml")
end
@ -88,8 +48,8 @@ end
# Require rules
require_rules "nanoc/rules/assets"
require_rules "nanoc/rules/redirect"
require_rules "nanoc/rules/random", {locales:, i18n:, slugs:}
require_rules "nanoc/rules/stream", {locales:, i18n:, slugs:}
require_rules "nanoc/rules/random", {locales:}
require_rules "nanoc/rules/stream", {locales:, i18n:, name_by_id:}
require_rules "nanoc/rules/index", {locales:, i18n:}
##

View file

@ -0,0 +1,15 @@
def require_rules(rules, locals = {}, target = binding)
locals.each { target.local_variable_set(_1, _2) }
path = File.join(Dir.getwd, rules)
target.eval(
if File.readable?(path)
File.read(path)
elsif File.readable?("#{path}.rb")
File.read("#{path}.rb")
elsif File.readable?("#{path}.rules")
File.read("#{path}.rules")
else
raise LoadError, "#{path} is not readable"
end
)
end

View file

@ -15,6 +15,6 @@ locales.each do |locale|
end
compile "/js/main/random.ts" do
filter(:webpack)
filter(:webpack, depend_on: ["/js/lib/"])
write("/js/main/random.js")
end

View file

@ -12,6 +12,6 @@ compile "/html/redirect.html.erb" do
end
compile "/js/main/redirect.ts" do
filter(:webpack)
filter(:webpack, depend_on: ["/js/lib/"])
write("/js/main/redirect.js")
end

View file

@ -12,22 +12,22 @@ compile "/*/*/surah.json" do
end
end
Ryo.each(slugs) do |id, slug|
Ryo.each(name_by_id) do |id, transliterated_name|
writer = ->(locale, identifier:) do
name = i18n[locale].surahs.names[id.to_i - 1]
context = Ryo.from(
filename: "stream.html.erb",
locale:,
locales:,
surah: {id:, name:, slug:}
surah: {id:, name:, transliterated_name:}
)
filter(:erb, locals: {context:})
filter(:tidy)
write "/#{locale}/#{identifier}/index.html"
end
locales.each do |locale|
compile "/html/stream.html.erb", rep: "/#{locale}/#{slug}/index.html" do
instance_exec(locale, identifier: slug, &writer)
compile "/html/stream.html.erb", rep: "/#{locale}/#{transliterated_name}/index.html" do
instance_exec(locale, identifier: transliterated_name, &writer)
end
compile "/html/stream.html.erb", rep: "/#{locale}/#{id}/index.html" do
instance_exec(locale, identifier: id, &writer)
@ -37,8 +37,7 @@ end
compile "/js/main/surah-stream.tsx" do
filter :webpack,
depend_on: ["/js/components", "/js/lib/", "/js/hooks"],
reject: proc { _1.include?("WebPackage") }
depend_on: ["/js/components", "/js/lib/", "/js/hooks"]
write "/js/main/surah-stream.js"
filter :gzip
write "/js/main/surah-stream.js.gz"

View file

@ -5,7 +5,6 @@
<%= erb("partials/favicon.html.erb") %>
</head>
<body>
<%= inline_json("/json/slugs.json") %>
<script src="/js/main/random.js"></script>
</body>
</html>

View file

@ -10,14 +10,14 @@
<link
rel="canonical"
hreflang="<%= context.locale %>"
href="https://al-quran.reflectslight.io/<%= context.locale %>/<%= context.surah.slug %>/"
href="https://al-quran.reflectslight.io/<%= context.locale %>/<%= context.surah.transliterated_name %>/"
/>
<% context.locales.each do |locale| %>
<link
rel="alternate"
type="text/html"
hreflang="<%= locale %>"
href="https://al-quran.reflectslight.io/<%= locale %>/<%= context.surah.slug %>/"
href="https://al-quran.reflectslight.io/<%= locale %>/<%= context.surah.transliterated_name %>/"
/>
<% end %>
<link rel="icon" href="/favicon.svg">

View file

@ -1,7 +1,7 @@
import url from "url";
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
import React, { useEffect, useMemo, useState } from "react";
import { SoundOnIcon, SoundOffIcon } from "components/Icon";
import { SoundOnIcon, SoundOffIcon } from "~/components/Icon";
type Props = {
recitation: Quran.Recitation;

View file

@ -1,5 +1,5 @@
import React from "react";
import { Select } from "components/Select";
import { Select } from "~/components/Select";
interface Props {
locale: string;

View file

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useRef } from "react";
import * as Quran from "lib/Quran";
import { AudioControl } from "components/AudioControl";
import { formatNumber, TFunction } from "lib/i18n";
import * as Quran from "~/lib/Quran";
import { AudioControl } from "~/components/AudioControl";
import { formatNumber, TFunction } from "~/lib/i18n";
import classNames from "classnames";
interface Props {

View file

@ -1,11 +1,11 @@
import * as Quran from "lib/Quran";
import React, { useRef, useState, useEffect } from "react";
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 { SurahIndexFilter } from "components/SurahIndexFilter";
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 { SurahIndexFilter } from "~/components/SurahIndexFilter";
import classNames from "classnames";
interface Props {

View file

@ -1,6 +1,6 @@
import React from "react";
import { TFunction } from "lib/i18n";
import * as Quran from "lib/Quran";
import { TFunction } from "~/lib/i18n";
import * as Quran from "~/lib/Quran";
type Props = {
t: TFunction;

View file

@ -1,14 +1,19 @@
import * as Quran from "lib/Quran";
import React, { useState, useEffect, useRef } from "react";
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 { PlayIcon, PauseIcon, RefreshIcon, StalledIcon } from "components/Icon";
import classNames from "classnames";
import { TFunction } from "lib/i18n";
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 {
PlayIcon,
PauseIcon,
RefreshIcon,
StalledIcon,
} from "~/components/Icon";
import { TFunction } from "~/lib/i18n";
interface Props {
node: HTMLScriptElement;

View file

@ -1,6 +1,7 @@
import React from "react";
import { Select } from "components/Select";
import type { Theme } from "hooks/useTheme";
import { Select } from "~/components/Select";
import type { Theme } from "~/hooks/useTheme";
interface Props {
theme: string;
setTheme: (theme: Theme) => void;

View file

@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import * as Quran from "lib/Quran";
import { formatNumber } from "lib/i18n";
import * as Quran from "~/lib/Quran";
import { formatNumber } from "~/lib/i18n";
interface Props {
surah: Quran.Surah;

View file

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

View file

@ -1,6 +1,6 @@
import * as JSON from "lib/Quran/JSON";
import { Ayah } from "lib/Quran/Ayah";
import { Surah } from "lib/Quran/Surah";
import * as JSON from "~/lib/Quran/JSON";
import { Ayah } from "~/lib/Quran/Ayah";
import { Surah } from "~/lib/Quran/Surah";
type Locale = "ar" | "en";
type Ayat = Ayah[];

View file

@ -1,4 +1,4 @@
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
export type Ayah = {
id: number;

View file

@ -1,4 +1,4 @@
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
type TimeSlot = [number, number];
type TimeSlots = [TimeSlot];

View file

@ -1,4 +1,4 @@
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
type PhraseMap<T> = {
[key: string]: undefined | string | PhraseMap<T>;

View file

@ -1,5 +1,5 @@
import postman, { item } from "postman";
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
(function () {
const parent: HTMLElement = document.querySelector(".postman.loader")!;

View file

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

View file

@ -1,6 +1,12 @@
import { Locale } from "lib/Locale";
(function (window, location) {
const locale = Locale(window).fromBrowser();
location.replace(`/${locale}/`);
})(window, location);
(function () {
const LOCALES = ["ar", "en"];
const DEFAULT_LOCALE = "en";
function getUserLocale() {
return (
navigator.languages
.map(s => s.slice(0, 2))
.find(s => LOCALES.includes(s)) || DEFAULT_LOCALE
);
}
location.replace(["", getUserLocale(), ""].join("/"));
})();

View file

@ -1,8 +1,8 @@
import * as Quran from "lib/Quran";
import React from "react";
import ReactDOM from "react-dom/client";
import { i18n } from "lib/i18n";
import { SurahIndex } from "components/SurahIndex";
import * as Quran from "~/lib/Quran";
import { i18n } from "~/lib/i18n";
import { SurahIndex } from "~/components/SurahIndex";
(function () {
const root: HTMLElement = document.querySelector(".root")!;

View file

@ -1,8 +1,8 @@
import * as Quran from "lib/Quran";
import * as Quran from "~/lib/Quran";
import React from "react";
import ReactDOM from "react-dom/client";
import { i18n } from "lib/i18n";
import { SurahStream } from "components/SurahStream";
import { i18n } from "~/lib/i18n";
import { SurahStream } from "~/components/SurahStream";
(function () {
const root: HTMLElement = document.querySelector(".root")!;

View file

@ -7,9 +7,9 @@
<priority>1.0</priority>
<changefreq>weekly</changefreq>
</url>
<% Ryo.each(slugs) do |_id, slug| %>
<% Ryo.each(name_by_id) do |_id, transliterated_name| %>
<url>
<loc>https://al-quran.reflectslight.io/<%= locale %>/<%= slug %>/</loc>
<loc>https://al-quran.reflectslight.io/<%= locale %>/<%= transliterated_name %>/</loc>
<priority>0.7</priority>
<changefreq>weekly</changefreq>
</url>

View file

@ -12,6 +12,9 @@
"lib": [ "ES2020", "DOM" ],
"baseUrl": "src/",
"paths": { "*": ["js/*"] },
"paths": {
"~/*": ["./js/*"],
"@json/*": ["./json/*"]
},
}
}

View file

@ -3,9 +3,12 @@ const process = require("process");
module.exports = {
resolve: {
roots: [path.resolve("src/js"), path.resolve("node_modules")],
modules: [path.resolve("src/js"), path.resolve("node_modules")],
extensions: [".js", ".ts", ".tsx"],
modules: [path.resolve(__dirname, "node_modules")],
alias: {
"@json": path.resolve(__dirname, "src/json"),
"~": path.resolve(__dirname, "src/js"),
},
extensions: [".js", ".ts", ".tsx", ".json"],
},
module: {
rules: [