+
{/* functional routes */}
diff --git a/src/setup/i18n.ts b/src/setup/i18n.ts
index ac86edcc..97ff2fd0 100644
--- a/src/setup/i18n.ts
+++ b/src/setup/i18n.ts
@@ -25,6 +25,15 @@ export const appLanguageOptions = langCodes.map((lang) => {
nativeName: "Pirate Tongue",
};
}
+
+ if (lang === "minion") {
+ return {
+ code: "minion",
+ name: "Minion",
+ nativeName: "Minionese",
+ };
+ }
+
const [langObj] = ISO6391.getLanguages([lang]);
if (!langObj)
throw new Error(`Language with code ${lang} cannot be found in database`);
diff --git a/src/stores/history/index.ts b/src/stores/history/index.ts
index 572358cf..8a85f777 100644
--- a/src/stores/history/index.ts
+++ b/src/stores/history/index.ts
@@ -43,11 +43,17 @@ export function useHistoryListener() {
export function useLastNonPlayerLink() {
const routes = useHistoryStore((s) => s.routes);
+ const location = useLocation();
const lastNonPlayerLink = useMemo(() => {
const reversedRoutes = [...routes];
reversedRoutes.reverse();
- const route = reversedRoutes.find((v) => !v.path.startsWith("/media"));
+ const route = reversedRoutes.find(
+ (v) =>
+ !v.path.startsWith("/media") && // cannot be a player link
+ location.pathname !== v.path && // cannot be current link
+ !v.path.startsWith("/s/") // cannot be a quick search link
+ );
return route?.path ?? "/";
- }, [routes]);
+ }, [routes, location]);
return lastNonPlayerLink;
}
diff --git a/src/stores/language/index.ts b/src/stores/language/index.tsx
similarity index 70%
rename from src/stores/language/index.ts
rename to src/stores/language/index.tsx
index 052127bc..ba33d59a 100644
--- a/src/stores/language/index.ts
+++ b/src/stores/language/index.tsx
@@ -1,8 +1,10 @@
import { useEffect } from "react";
+import { Helmet } from "react-helmet-async";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
+import { rtlLocales } from "@/assets/languages";
import i18n from "@/setup/i18n";
export interface LanguageStore {
@@ -24,10 +26,18 @@ export const useLanguageStore = create(
)
);
-export function useLanguageListener() {
+export function LanguageProvider() {
const language = useLanguageStore((s) => s.language);
useEffect(() => {
i18n.changeLanguage(language);
}, [language]);
+
+ const isRtl = rtlLocales.includes(language as any);
+
+ return (
+
+
+
+ );
}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 83da3298..913a59fd 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,5 +1,6 @@
import { allThemes, defaultTheme, safeThemeList } from "./themes";
-import type { Config } from "tailwindcss"
+import type { Config } from "tailwindcss";
+import plugin from "tailwindcss/plugin";
const themer = require("tailwindcss-themer");
@@ -8,20 +9,25 @@ const config: Config = {
safelist: safeThemeList,
theme: {
extend: {
+ /* breakpoints */
+ screens: {
+ ssm: "400px",
+ },
+
/* fonts */
fontFamily: {
- "open-sans": "'Open Sans'"
+ "open-sans": "'Open Sans'",
},
/* animations */
keyframes: {
"loading-pin": {
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
- "20%": { height: "1em", "background-color": "white" }
- }
+ "20%": { height: "1em", "background-color": "white" },
+ },
},
- animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
- }
+ animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
+ },
},
plugins: [
require("tailwind-scrollbar"),
@@ -33,9 +39,13 @@ const config: Config = {
selectors: [".theme-default"],
...defaultTheme,
},
- ...allThemes]
- })
- ]
+ ...allThemes,
+ ],
+ }),
+ plugin(({ addVariant }) => {
+ addVariant("dir-neutral", "[dir] &");
+ }),
+ ],
};
export default config;
diff --git a/vite.config.ts b/vite.config.ts
index 9a0018b0..8ebf8198 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -7,6 +7,9 @@ import path from "path";
import { handlebars } from "./plugins/handlebars";
import { loadEnv } from "vite";
+import tailwind from "tailwindcss";
+import rtl from "postcss-rtlcss";
+
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
return {
@@ -18,8 +21,8 @@ export default defineConfig(({ mode }) => {
env.VITE_APP_DOMAIN +
(env.VITE_NORMAL_ROUTER !== "true" ? "/#" : ""),
domain: env.VITE_APP_DOMAIN,
- env
- }
+ env,
+ },
}),
react({
babel: {
@@ -31,24 +34,24 @@ export default defineConfig(({ mode }) => {
modules: false,
useBuiltIns: "entry",
corejs: {
- version: "3.29"
- }
- }
- ]
- ]
- }
+ version: "3.29",
+ },
+ },
+ ],
+ ],
+ },
}),
VitePWA({
disable: env.VITE_PWA_ENABLED !== "true",
registerType: "autoUpdate",
workbox: {
maximumFileSizeToCacheInBytes: 4000000, // 4mb
- globIgnores: ["**ping.txt**"]
+ globIgnores: ["**ping.txt**"],
},
includeAssets: [
"favicon.ico",
"apple-touch-icon.png",
- "safari-pinned-tab.svg"
+ "safari-pinned-tab.svg",
],
manifest: {
name: "movie-web",
@@ -63,48 +66,53 @@ export default defineConfig(({ mode }) => {
src: "android-chrome-192x192.png",
sizes: "192x192",
type: "image/png",
- purpose: "any"
+ purpose: "any",
},
{
src: "android-chrome-512x512.png",
sizes: "512x512",
type: "image/png",
- purpose: "any"
+ purpose: "any",
},
{
src: "android-chrome-192x192.png",
sizes: "192x192",
type: "image/png",
- purpose: "maskable"
+ purpose: "maskable",
},
{
src: "android-chrome-512x512.png",
sizes: "512x512",
type: "image/png",
- purpose: "maskable"
- }
- ]
- }
+ purpose: "maskable",
+ },
+ ],
+ },
}),
loadVersion(),
checker({
overlay: {
- position: "tr"
+ position: "tr",
},
typescript: true, // check typescript build errors in dev server
eslint: {
// check lint errors in dev server
lintCommand: "eslint --ext .tsx,.ts src",
dev: {
- logLevel: ["error"]
- }
- }
- })
+ logLevel: ["error"],
+ },
+ },
+ }),
],
build: {
sourcemap: true,
},
+ css: {
+ postcss: {
+ plugins: [tailwind(), rtl()],
+ },
+ },
resolve: {
alias: {
@@ -112,12 +120,12 @@ export default defineConfig(({ mode }) => {
"@sozialhelden/ietf-language-tags": path.resolve(
__dirname,
"./node_modules/@sozialhelden/ietf-language-tags/dist/cjs"
- )
- }
+ ),
+ },
},
test: {
- environment: "jsdom"
- }
+ environment: "jsdom",
+ },
};
});