pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #540 from movie-web/rtl

Right-to-left support for translations

authored by

William Oldham and committed by
GitHub
1ef2cf5b 7537b8c1

+198 -96
-2
.github/workflows/deploying.yml
··· 109 109 prerelease: false 110 110 111 111 - name: Upload release (PWA) 112 - id: upload-release-asset 113 112 uses: actions/upload-release-asset@v1 114 113 env: 115 114 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ··· 120 119 asset_content_type: application/zip 121 120 122 121 - name: Upload Release (Normal) 123 - id: upload-release-asset 124 122 uses: actions/upload-release-asset@v1 125 123 env: 126 124 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+39 -41
index.html
··· 1 1 <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="utf-8" /> 5 - <link rel="icon" href="/favicon.ico" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1.0, user-scalable=no" /> 7 - <meta 8 - name="description" 9 - content="The place for your favourite movies & shows" 10 - /> 2 + <html lang="en" dir="ltr"> 11 3 12 - <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> 13 - <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 14 - <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 15 - <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#120f1d" /> 16 - <meta name="msapplication-TileColor" content="#120f1d" /> 17 - <meta name="theme-color" content="#120f1d" /> 4 + <head> 5 + <meta charset="utf-8" /> 6 + <link rel="icon" href="/favicon.ico" /> 7 + <meta name="viewport" 8 + content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1.0, user-scalable=no" /> 9 + <meta name="description" content="The place for your favourite movies & shows" /> 18 10 19 - <link rel="preconnect" href="https://fonts.googleapis.com" /> 20 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 21 - <link 22 - href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" 23 - rel="stylesheet" 24 - /> 11 + <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> 12 + <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 13 + <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 14 + <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#120f1d" /> 15 + <meta name="msapplication-TileColor" content="#120f1d" /> 16 + <meta name="theme-color" content="#120f1d" /> 25 17 26 - <script src="/config.js"></script> 27 - <script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@8b821f445b83d51ef1b8f42c99b7346f6b47dce5/out.js"></script> 18 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 19 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 20 + <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" /> 28 21 29 - <!-- prevent darkreader extension from messing with our already dark site --> 30 - <meta name="darkreader-lock" /> 22 + <script src="/config.js"></script> 23 + <script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@8b821f445b83d51ef1b8f42c99b7346f6b47dce5/out.js"></script> 31 24 32 - <!-- disabling referrer can fix some provider problems --> 33 - <meta name="referrer" content="no-referrer" /> 25 + <!-- prevent darkreader extension from messing with our already dark site --> 26 + <meta name="darkreader-lock" /> 34 27 35 - <title>movie-web</title> 28 + <!-- disabling referrer can fix some provider problems --> 29 + <meta name="referrer" content="no-referrer" /> 36 30 37 - {{#if opensearchEnabled }} 38 - <!-- OpenSearch --> 39 - <link rel="search" type="application/opensearchdescription+xml" title="movie-web" href="/opensearch.xml"> 31 + <title>movie-web</title> 40 32 41 - <!-- Google Sitelinks --> 42 - <script type="application/ld+json"> 33 + {{#if opensearchEnabled }} 34 + <!-- OpenSearch --> 35 + <link rel="search" type="application/opensearchdescription+xml" title="movie-web" href="/opensearch.xml"> 36 + 37 + <!-- Google Sitelinks --> 38 + <script type="application/ld+json"> 43 39 { 44 40 "@context": "https://schema.org", 45 41 "@type": "WebSite", ··· 54 50 } 55 51 } 56 52 </script> 57 - {{/if}} 58 - </head> 59 - <body> 60 - <noscript>You need to enable JavaScript to run this app.</noscript> 61 - <div id="root"></div> 62 - <script type="module" src="/src/index.tsx"></script> 63 - </body> 64 - </html> 53 + {{/if}} 54 + </head> 55 + 56 + <body> 57 + <noscript>You need to enable JavaScript to run this app.</noscript> 58 + <div id="root"></div> 59 + <script type="module" src="/src/index.tsx"></script> 60 + </body> 61 + 62 + </html>
+3
package.json
··· 46 46 "immer": "^10.0.2", 47 47 "iso-639-1": "^3.1.0", 48 48 "lodash.isequal": "^4.5.0", 49 + "nanoid": "^5.0.4", 49 50 "node-forge": "^1.3.1", 50 51 "ofetch": "^1.0.0", 51 52 "react": "^17.0.2", ··· 98 99 "handlebars": "^4.7.7", 99 100 "jsdom": "^21.1.0", 100 101 "postcss": "^8.4.20", 102 + "postcss-rtl": "^2.0.0", 103 + "postcss-rtlcss": "^4.0.9", 101 104 "prettier": "^2.5.1", 102 105 "prettier-plugin-tailwindcss": "^0.1.7", 103 106 "tailwind-scrollbar": "^2.0.1",
+57
pnpm-lock.yaml
··· 71 71 lodash.isequal: 72 72 specifier: ^4.5.0 73 73 version: 4.5.0 74 + nanoid: 75 + specifier: ^5.0.4 76 + version: 5.0.4 74 77 node-forge: 75 78 specifier: ^1.3.1 76 79 version: 1.3.1 ··· 223 226 postcss: 224 227 specifier: '>=8.4.31' 225 228 version: 8.4.31 229 + postcss-rtl: 230 + specifier: ^2.0.0 231 + version: 2.0.0(postcss@8.4.31) 232 + postcss-rtlcss: 233 + specifier: ^4.0.9 234 + version: 4.0.9(postcss@8.4.31) 226 235 prettier: 227 236 specifier: ^2.5.1 228 237 version: 2.8.8 ··· 4747 4756 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 4748 4757 hasBin: true 4749 4758 4759 + /nanoid@5.0.4: 4760 + resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==} 4761 + engines: {node: ^18 || >=20} 4762 + hasBin: true 4763 + dev: false 4764 + 4750 4765 /natural-compare-lite@1.4.0: 4751 4766 resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} 4752 4767 dev: true ··· 5068 5083 dependencies: 5069 5084 postcss: 8.4.31 5070 5085 postcss-selector-parser: 6.0.13 5086 + dev: true 5087 + 5088 + /postcss-rtl@2.0.0(postcss@8.4.31): 5089 + resolution: {integrity: sha512-vFu78CvaGY9BafWRHNgDm6OjUxzRCWWCrp+KtnyXdgwibLwb/j5ls8Z/ubvOsk9B/Q2NLwSPrXRARKMaa9RBmA==} 5090 + engines: {node: '>=14.0.0'} 5091 + peerDependencies: 5092 + postcss: '>=8.4.31' 5093 + dependencies: 5094 + postcss: 8.4.31 5095 + rtlcss: 4.0.0 5096 + dev: true 5097 + 5098 + /postcss-rtlcss@4.0.9(postcss@8.4.31): 5099 + resolution: {integrity: sha512-dCNKEf+FgTv+EA3XI8ysg2RnpS5s3/iZmU+9qpCNFxHU/BhK+4hz7jyCsCAfo0CLnDrMPtaQENhwb+EGm1wh7Q==} 5100 + engines: {node: '>=18.0.0'} 5101 + peerDependencies: 5102 + postcss: '>=8.4.31' 5103 + dependencies: 5104 + postcss: 8.4.31 5105 + rtlcss: 4.1.1 5071 5106 dev: true 5072 5107 5073 5108 /postcss-selector-parser@6.0.13: ··· 5479 5514 dependencies: 5480 5515 '@babel/runtime': 7.22.11 5481 5516 dev: false 5517 + 5518 + /rtlcss@4.0.0: 5519 + resolution: {integrity: sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==} 5520 + engines: {node: '>=12.0.0'} 5521 + hasBin: true 5522 + dependencies: 5523 + escalade: 3.1.1 5524 + picocolors: 1.0.0 5525 + postcss: 8.4.31 5526 + strip-json-comments: 3.1.1 5527 + dev: true 5528 + 5529 + /rtlcss@4.1.1: 5530 + resolution: {integrity: sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==} 5531 + engines: {node: '>=12.0.0'} 5532 + hasBin: true 5533 + dependencies: 5534 + escalade: 3.1.1 5535 + picocolors: 1.0.0 5536 + postcss: 8.4.31 5537 + strip-json-comments: 3.1.1 5538 + dev: true 5482 5539 5483 5540 /run-parallel@1.2.0: 5484 5541 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+9
src/assets/css/index.css
··· 222 222 outline: 2px solid theme('colors.themePreview.primary'); 223 223 box-shadow: 0 0 10px theme('colors.themePreview.secondary'); 224 224 } 225 + 226 + [dir="rtl"] .transform { 227 + /* Invert horizontal X offset on transform (Tailwind RTL plugin does the rest) */ 228 + transform: translate(calc(var(--tw-translate-x) * -1), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important; 229 + } 230 + [dir="ltr"] .transform { 231 + /* default - otherwise it overwrites*/ 232 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important; 233 + }
+3
src/assets/languages.ts
··· 25 25 pirate, 26 26 minion, 27 27 }; 28 + export type Locales = keyof typeof locales; 29 + 30 + export const rtlLocales: Locales[] = [];
+3
src/backend/helpers/report.ts
··· 1 1 import { ScrapeMedia } from "@movie-web/providers"; 2 + import { nanoid } from "nanoid"; 2 3 import { ofetch } from "ofetch"; 3 4 import { useCallback } from "react"; 4 5 ··· 8 9 // for anybody who cares - these are anonymous metrics. 9 10 // They are just used for figuring out if providers are broken or not 10 11 const metricsEndpoint = "https://backend.movie-web.app/metrics/providers"; 12 + const batchId = () => nanoid(32); 11 13 12 14 export type ProviderMetric = { 13 15 tmdbId: string; ··· 34 36 method: "POST", 35 37 body: { 36 38 items, 39 + batchId: batchId(), 37 40 }, 38 41 }); 39 42 }
+10 -1
src/components/Icon.tsx
··· 1 + import classNames from "classnames"; 1 2 import { memo, useEffect, useRef } from "react"; 2 3 3 4 export enum Icons { ··· 152 153 return <ChromeCastButton />; 153 154 } 154 155 156 + const flipClass = 157 + props.icon === Icons.ARROW_LEFT || 158 + props.icon === Icons.ARROW_RIGHT || 159 + props.icon === Icons.CHEVRON_LEFT || 160 + props.icon === Icons.CHEVRON_RIGHT 161 + ? "rtl:-scale-x-100" 162 + : ""; 163 + 155 164 return ( 156 165 <span 157 166 dangerouslySetInnerHTML={{ __html: iconList[props.icon] }} // eslint-disable-line react/no-danger 158 - className={props.className} 167 + className={classNames(props.className, flipClass)} 159 168 /> 160 169 ); 161 170 });
+1 -1
src/components/overlays/positions/OverlayAnchorPosition.tsx
··· 64 64 transform: `translateX(${left}px) translateY(${top}px)`, 65 65 }} 66 66 className={classNames([ 67 - "pointer-events-auto z-10 inline-block origin-top-left touch-none", 67 + "[&>*]:pointer-events-auto z-10 flex dir-neutral:items-start justify-start dir-neutral:origin-top-left touch-none", 68 68 props.className, 69 69 ])} 70 70 >
+2 -2
src/components/player/atoms/EpisodeTitle.tsx
··· 9 9 if (meta?.type !== "show") return null; 10 10 11 11 return ( 12 - <div> 13 - <span className="text-white font-medium mr-3"> 12 + <div className="flex gap-3"> 13 + <span className="text-white font-medium"> 14 14 {t("media.episodeDisplay", { 15 15 season: meta?.season?.number, 16 16 episode: meta?.episode?.number,
+2 -2
src/components/player/atoms/ProgressBar.tsx
··· 119 119 }, [setDraggingTime, duration, dragPercentage]); 120 120 121 121 return ( 122 - <div className="w-full relative"> 122 + <div className="w-full relative" dir="ltr"> 123 123 <div className="top-0 absolute inset-x-0"> 124 124 <div 125 125 className="absolute bottom-0" ··· 158 158 159 159 {/* Actual progress bar */} 160 160 <div 161 - className="absolute top-0 left-0 h-full rounded-full bg-progress-filled flex justify-end items-center" 161 + className="absolute top-0 dir-neutral:left-0 h-full rounded-full bg-progress-filled flex justify-end items-center" 162 162 style={{ 163 163 width: `${ 164 164 Math.max(
+1
src/components/player/atoms/settings/CaptionSettingsView.tsx
··· 97 97 onTouchStart={dragMouseDown} 98 98 > 99 99 <div 100 + dir="ltr" 100 101 className={[ 101 102 "relative w-full h-1 bg-video-context-slider bg-opacity-25 rounded-full transition-[height] duration-100 group-hover/progress:h-1.5", 102 103 dragging ? "!h-1.5" : "",
+1 -1
src/components/player/base/BottomControls.tsx
··· 27 27 <div 28 28 onMouseOver={() => setHoveringAnyControls(true)} 29 29 onMouseOut={() => setHoveringAnyControls(false)} 30 - className="pointer-events-auto pl-[calc(2rem+env(safe-area-inset-left))] pr-[calc(2rem+env(safe-area-inset-right))] pb-3 mb-[env(safe-area-inset-bottom)] absolute bottom-0 w-full" 30 + className="pointer-events-auto z-10 pl-[calc(2rem+env(safe-area-inset-left))] pr-[calc(2rem+env(safe-area-inset-right))] pb-3 mb-[env(safe-area-inset-bottom)] absolute bottom-0 w-full" 31 31 > 32 32 <Transition animation="slide-up" show={props.show}> 33 33 {props.children}
+2 -8
src/components/player/internals/Button.tsx
··· 22 22 type="button" 23 23 onClick={(e) => props.onClick?.(e.currentTarget as HTMLButtonElement)} 24 24 className={classNames([ 25 - "tabbable p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-50 transition-transform duration-100 flex items-center", 25 + "tabbable p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-50 transition-transform duration-100 flex items-center gap-3", 26 26 props.activeClass ?? 27 27 "active:scale-110 active:bg-opacity-75 active:text-white", 28 28 props.className ?? "", 29 29 ])} 30 30 > 31 31 {props.icon && ( 32 - <Icon 33 - className={classNames( 34 - props.iconSizeClass || "text-2xl", 35 - props.children ? "mr-3" : "" 36 - )} 37 - icon={props.icon} 38 - /> 32 + <Icon className={props.iconSizeClass || "text-2xl"} icon={props.icon} /> 39 33 )} 40 34 {props.children} 41 35 </button>
+6 -6
src/components/utils/Lightbar.css
··· 6 6 user-select: none; 7 7 } 8 8 9 - .lightbar { 9 + [dir] .lightbar { 10 10 left: 50vw; 11 11 transform: translateX(-50%); 12 12 } ··· 16 16 width: 150vw; 17 17 } 18 18 19 - .lightbar { 19 + [dir] .lightbar { 20 20 left: -25vw; 21 21 transform: initial; 22 22 } 23 + 23 24 } 24 25 25 - .lightbar { 26 + [dir] .lightbar { 26 27 display: flex; 27 28 justify-content: center; 28 29 align-items: center; ··· 31 32 animation: boot var(--d) var(--animation) forwards; 32 33 } 33 34 34 - .lightbar-visual { 35 + [dir] .lightbar-visual { 35 36 left: 0; 36 37 --top: theme('colors.background.main'); 37 38 --bottom: theme('colors.lightBar.light'); ··· 57 58 58 59 @keyframes boot { 59 60 from { 60 - 61 61 opacity: 0.25; 62 62 } 63 63 ··· 74 74 100% { 75 75 transform: rotate(180deg) translateZ(0px) translateY(400px) scaleX(1); 76 76 } 77 - } 77 + }
+2 -1
src/pages/parts/player/PlayerPart.tsx
··· 88 88 </> 89 89 ) : null} 90 90 </div> 91 - <div className="hidden lg:flex justify-between"> 91 + <div className="hidden lg:flex justify-between" dir="ltr"> 92 92 <Player.LeftSideControls> 93 93 {status === playerStatus.PLAYING ? ( 94 94 <> ··· 130 130 </Player.BottomControls> 131 131 132 132 <Player.VolumeChangedPopout /> 133 + 133 134 <Player.NextEpisodeButton 134 135 controlsShowing={showTargets} 135 136 onChange={props.onMetaChange}
+6 -2
src/pages/parts/player/ScrapingPart.tsx
··· 85 85 currentProviderIndex = sourceOrder.length - 1; 86 86 87 87 return ( 88 - <div className="h-full w-full relative" ref={containerRef}> 88 + <div 89 + className="h-full w-full relative dir-neutral:origin-top-left flex" 90 + ref={containerRef} 91 + > 89 92 <div 90 93 className={classNames({ 91 - "absolute transition-[transform,opacity] opacity-0": true, 94 + "absolute transition-[transform,opacity] opacity-0 dir-neutral:left-0": 95 + true, 92 96 "!opacity-100": renderedOnce, 93 97 })} 94 98 ref={listRef}
+2 -2
src/setup/App.tsx
··· 23 23 import { SettingsPage } from "@/pages/Settings"; 24 24 import { Layout } from "@/setup/Layout"; 25 25 import { useHistoryListener } from "@/stores/history"; 26 - import { useLanguageListener } from "@/stores/language"; 26 + import { LanguageProvider } from "@/stores/language"; 27 27 28 28 function LegacyUrlView({ children }: { children: ReactElement }) { 29 29 const location = useLocation(); ··· 61 61 function App() { 62 62 useHistoryListener(); 63 63 useOnlineListener(); 64 - useLanguageListener(); 65 64 66 65 return ( 67 66 <Layout> 67 + <LanguageProvider /> 68 68 <Switch> 69 69 {/* functional routes */} 70 70 <Route exact path="/s/:query">
+11 -1
src/stores/language/index.ts src/stores/language/index.tsx
··· 1 1 import { useEffect } from "react"; 2 + import { Helmet } from "react-helmet-async"; 2 3 import { create } from "zustand"; 3 4 import { persist } from "zustand/middleware"; 4 5 import { immer } from "zustand/middleware/immer"; 5 6 7 + import { rtlLocales } from "@/assets/languages"; 6 8 import i18n from "@/setup/i18n"; 7 9 8 10 export interface LanguageStore { ··· 24 26 ) 25 27 ); 26 28 27 - export function useLanguageListener() { 29 + export function LanguageProvider() { 28 30 const language = useLanguageStore((s) => s.language); 29 31 30 32 useEffect(() => { 31 33 i18n.changeLanguage(language); 32 34 }, [language]); 35 + 36 + const isRtl = rtlLocales.includes(language as any); 37 + 38 + return ( 39 + <Helmet> 40 + <html dir={isRtl ? "rtl" : "ltr"} /> 41 + </Helmet> 42 + ); 33 43 }
+4
tailwind.config.ts
··· 1 1 import { allThemes, defaultTheme, safeThemeList } from "./themes"; 2 2 import type { Config } from "tailwindcss"; 3 + import plugin from "tailwindcss/plugin"; 3 4 4 5 const themer = require("tailwindcss-themer"); 5 6 ··· 40 41 }, 41 42 ...allThemes, 42 43 ], 44 + }), 45 + plugin(({ addVariant }) => { 46 + addVariant("dir-neutral", "[dir] &"); 43 47 }), 44 48 ], 45 49 };
+34 -26
vite.config.ts
··· 7 7 import { handlebars } from "./plugins/handlebars"; 8 8 import { loadEnv } from "vite"; 9 9 10 + import tailwind from "tailwindcss"; 11 + import rtl from "postcss-rtlcss"; 12 + 10 13 export default defineConfig(({ mode }) => { 11 14 const env = loadEnv(mode, process.cwd()); 12 15 return { ··· 18 21 env.VITE_APP_DOMAIN + 19 22 (env.VITE_NORMAL_ROUTER !== "true" ? "/#" : ""), 20 23 domain: env.VITE_APP_DOMAIN, 21 - env 22 - } 24 + env, 25 + }, 23 26 }), 24 27 react({ 25 28 babel: { ··· 31 34 modules: false, 32 35 useBuiltIns: "entry", 33 36 corejs: { 34 - version: "3.29" 35 - } 36 - } 37 - ] 38 - ] 39 - } 37 + version: "3.29", 38 + }, 39 + }, 40 + ], 41 + ], 42 + }, 40 43 }), 41 44 VitePWA({ 42 45 disable: env.VITE_PWA_ENABLED !== "true", 43 46 registerType: "autoUpdate", 44 47 workbox: { 45 48 maximumFileSizeToCacheInBytes: 4000000, // 4mb 46 - globIgnores: ["**ping.txt**"] 49 + globIgnores: ["**ping.txt**"], 47 50 }, 48 51 includeAssets: [ 49 52 "favicon.ico", 50 53 "apple-touch-icon.png", 51 - "safari-pinned-tab.svg" 54 + "safari-pinned-tab.svg", 52 55 ], 53 56 manifest: { 54 57 name: "movie-web", ··· 63 66 src: "android-chrome-192x192.png", 64 67 sizes: "192x192", 65 68 type: "image/png", 66 - purpose: "any" 69 + purpose: "any", 67 70 }, 68 71 { 69 72 src: "android-chrome-512x512.png", 70 73 sizes: "512x512", 71 74 type: "image/png", 72 - purpose: "any" 75 + purpose: "any", 73 76 }, 74 77 { 75 78 src: "android-chrome-192x192.png", 76 79 sizes: "192x192", 77 80 type: "image/png", 78 - purpose: "maskable" 81 + purpose: "maskable", 79 82 }, 80 83 { 81 84 src: "android-chrome-512x512.png", 82 85 sizes: "512x512", 83 86 type: "image/png", 84 - purpose: "maskable" 85 - } 86 - ] 87 - } 87 + purpose: "maskable", 88 + }, 89 + ], 90 + }, 88 91 }), 89 92 loadVersion(), 90 93 checker({ 91 94 overlay: { 92 - position: "tr" 95 + position: "tr", 93 96 }, 94 97 typescript: true, // check typescript build errors in dev server 95 98 eslint: { 96 99 // check lint errors in dev server 97 100 lintCommand: "eslint --ext .tsx,.ts src", 98 101 dev: { 99 - logLevel: ["error"] 100 - } 101 - } 102 - }) 102 + logLevel: ["error"], 103 + }, 104 + }, 105 + }), 103 106 ], 104 107 105 108 build: { 106 109 sourcemap: true, 107 110 }, 111 + css: { 112 + postcss: { 113 + plugins: [tailwind(), rtl()], 114 + }, 115 + }, 108 116 109 117 resolve: { 110 118 alias: { ··· 112 120 "@sozialhelden/ietf-language-tags": path.resolve( 113 121 __dirname, 114 122 "./node_modules/@sozialhelden/ietf-language-tags/dist/cjs" 115 - ) 116 - } 123 + ), 124 + }, 117 125 }, 118 126 119 127 test: { 120 - environment: "jsdom" 121 - } 128 + environment: "jsdom", 129 + }, 122 130 }; 123 131 });