diff --git a/.eslintrc.js b/.eslintrc.js
index d75c57d9..c1a2b2a7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -8,26 +8,26 @@ const a11yOff = Object.keys(require("eslint-plugin-jsx-a11y").rules).reduce(
module.exports = {
env: {
- browser: true,
+ browser: true
},
extends: [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
- "plugin:prettier/recommended",
+ "plugin:prettier/recommended"
],
- ignorePatterns: ["public/*", "dist/*", "/*.js", "/*.ts"],
+ ignorePatterns: ["public/*", "dist/*", "/*.js", "/*.ts", "/plugins/*.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
- tsconfigRootDir: "./",
+ tsconfigRootDir: "./"
},
settings: {
"import/resolver": {
typescript: {
- project: "./tsconfig.json",
- },
- },
+ project: "./tsconfig.json"
+ }
+ }
},
plugins: ["@typescript-eslint", "import", "prettier"],
rules: {
@@ -55,15 +55,15 @@ module.exports = {
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"react/jsx-filename-extension": [
"error",
- { extensions: [".js", ".tsx", ".jsx"] },
+ { extensions: [".js", ".tsx", ".jsx"] }
],
"import/extensions": [
"error",
"ignorePackages",
{
ts: "never",
- tsx: "never",
- },
+ tsx: "never"
+ }
],
"import/order": [
"error",
@@ -74,14 +74,14 @@ module.exports = {
"internal",
["sibling", "parent"],
"index",
- "unknown",
+ "unknown"
],
"newlines-between": "always",
alphabetize: {
order: "asc",
- caseInsensitive: true,
- },
- },
+ caseInsensitive: true
+ }
+ }
],
"sort-imports": [
"error",
@@ -90,9 +90,9 @@ module.exports = {
ignoreDeclarationSort: true,
ignoreMemberSort: false,
memberSyntaxSortOrder: ["none", "all", "multiple", "single"],
- allowSeparatedGroups: true,
- },
+ allowSeparatedGroups: true
+ }
],
- ...a11yOff,
- },
+ ...a11yOff
+ }
};
diff --git a/example.env b/example.env
index d191d741..38c690a5 100644
--- a/example.env
+++ b/example.env
@@ -1,3 +1,8 @@
+VITE_TMDB_READ_API_KEY=...
+VITE_OPENSEARCH_ENABLED=false
+
# make sure the cors proxy url does NOT have a slash at the end
VITE_CORS_PROXY_URL=...
-VITE_TMDB_READ_API_KEY=...
+
+# make sure the domain does NOT have a slash at the end
+VITE_APP_DOMAIN=http://localhost:5173
diff --git a/index.html b/index.html
index 23063c5a..5c674b2f 100644
--- a/index.html
+++ b/index.html
@@ -33,6 +33,28 @@
movie-web
+
+ {{#if opensearchEnabled }}
+
+
+
+
+
+ {{/if}}
diff --git a/package.json b/package.json
index d315bd23..71fef462 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,6 @@
"@babel/core": "^7.21.3",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.21.0",
- "@tailwindcss/line-clamp": "^0.4.2",
"@types/chromecast-caf-sender": "^1.0.5",
"@types/crypto-js": "^4.1.1",
"@types/dompurify": "^2.4.0",
@@ -88,6 +87,8 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react-hooks": "4.3.0",
+ "glob": "^10.3.3",
+ "handlebars": "^4.7.7",
"jsdom": "^21.1.0",
"postcss": "^8.4.20",
"prettier": "^2.5.1",
@@ -99,6 +100,7 @@
"vite-plugin-checker": "^0.5.6",
"vite-plugin-package-version": "^1.0.2",
"vite-plugin-pwa": "^0.14.4",
+ "vite-plugin-static-copy": "^0.16.0",
"vitest": "^0.28.5",
"workbox-build": "^6.5.4",
"workbox-window": "^6.5.4"
diff --git a/plugins/handlebars.ts b/plugins/handlebars.ts
new file mode 100644
index 00000000..a8ab0170
--- /dev/null
+++ b/plugins/handlebars.ts
@@ -0,0 +1,41 @@
+import { globSync } from "glob";
+import { viteStaticCopy } from 'vite-plugin-static-copy'
+import { PluginOption } from "vite";
+import Handlebars from "handlebars";
+import path from "path";
+
+export const handlebars = (options: { vars?: Record } = {}): PluginOption[] => {
+ const files = globSync("src/assets/**/**.hbs");
+
+ function render(content: string): string {
+ const template = Handlebars.compile(content);
+ return template(options?.vars ?? {});
+ }
+
+ return [
+ {
+ name: 'hbs-templating',
+ enforce: "pre",
+ transformIndexHtml: {
+ order: 'pre',
+ handler(html) {
+ return render(html);
+ }
+ },
+ },
+ viteStaticCopy({
+ silent: true,
+ targets: files.map(file => ({
+ src: file,
+ dest: '',
+ rename: path.basename(file).slice(0, -4), // remove .hbs file extension
+ transform: {
+ encoding: 'utf8',
+ handler(content: string) {
+ return render(content);
+ }
+ }
+ }))
+ })
+ ]
+}
diff --git a/src/assets/opensearch.xml.hbs b/src/assets/opensearch.xml.hbs
new file mode 100644
index 00000000..e0ce3a5a
--- /dev/null
+++ b/src/assets/opensearch.xml.hbs
@@ -0,0 +1,6 @@
+
+ movie-web
+ The place for your favorite movies & shows
+ UTF-8
+
+
diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts
index 6081e5ad..199efccb 100644
--- a/src/backend/metadata/getmeta.ts
+++ b/src/backend/metadata/getmeta.ts
@@ -6,7 +6,6 @@ import {
TMDBMediaToMediaType,
formatTMDBMeta,
getEpisodes,
- getExternalIds,
getMediaDetails,
getMediaPoster,
getMovieFromExternalId,
@@ -19,6 +18,7 @@ import {
} from "./types/justwatch";
import { MWMediaMeta, MWMediaType } from "./types/mw";
import {
+ TMDBContentTypes,
TMDBMediaResult,
TMDBMovieData,
TMDBSeasonMetaResult,
@@ -74,8 +74,7 @@ export async function getMetaFromId(
if (!details) return null;
- const externalIds = await getExternalIds(id, mediaTypeToTMDB(type));
- const imdbId = externalIds.imdb_id ?? undefined;
+ const imdbId = details.external_ids.imdb_id ?? undefined;
let seasonData: TMDBSeasonMetaResult | undefined;
@@ -165,7 +164,13 @@ export async function getLegacyMetaFromId(
}
export function isLegacyUrl(url: string): boolean {
- if (url.startsWith("/media/JW")) return true;
+ if (url.startsWith("/media/JW") || url.startsWith("/media/tmdb-show"))
+ return true;
+ return false;
+}
+
+export function isLegacyMediaType(url: string): boolean {
+ if (url.startsWith("/media/tmdb-show")) return true;
return false;
}
@@ -177,7 +182,16 @@ export async function convertLegacyUrl(
const urlParts = url.split("/").slice(2);
const [, type, id] = urlParts[0].split("-", 3);
- const mediaType = TMDBMediaToMediaType(type);
+ if (isLegacyMediaType(url)) {
+ const details = await getMediaDetails(id, TMDBContentTypes.TV);
+ return `/media/${TMDBIdToUrlId(
+ MWMediaType.SERIES,
+ details.id.toString(),
+ details.name
+ )}`;
+ }
+
+ const mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
const meta = await getLegacyMetaFromId(mediaType, id);
if (!meta) return undefined;
diff --git a/src/backend/metadata/search.ts b/src/backend/metadata/search.ts
index 0d8f561f..6f6ecc99 100644
--- a/src/backend/metadata/search.ts
+++ b/src/backend/metadata/search.ts
@@ -1,26 +1,21 @@
import { SimpleCache } from "@/utils/cache";
-import {
- formatTMDBMeta,
- formatTMDBSearchResult,
- mediaTypeToTMDB,
- searchMedia,
-} from "./tmdb";
+import { formatTMDBMeta, formatTMDBSearchResult, multiSearch } from "./tmdb";
import { MWMediaMeta, MWQuery } from "./types/mw";
const cache = new SimpleCache();
cache.setCompare((a, b) => {
- return a.type === b.type && a.searchQuery.trim() === b.searchQuery.trim();
+ return a.searchQuery.trim() === b.searchQuery.trim();
});
cache.initialize();
export async function searchForMedia(query: MWQuery): Promise {
if (cache.has(query)) return cache.get(query) as MWMediaMeta[];
- const { searchQuery, type } = query;
+ const { searchQuery } = query;
- const data = await searchMedia(searchQuery, mediaTypeToTMDB(type));
- const results = data.results.map((v) => {
- const formattedResult = formatTMDBSearchResult(v, mediaTypeToTMDB(type));
+ const data = await multiSearch(searchQuery);
+ const results = data.map((v) => {
+ const formattedResult = formatTMDBSearchResult(v, v.media_type);
return formatTMDBMeta(formattedResult);
});
diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts
index 64304900..d4e75e57 100644
--- a/src/backend/metadata/tmdb.ts
+++ b/src/backend/metadata/tmdb.ts
@@ -7,33 +7,26 @@ import {
ExternalIdMovieSearchResult,
TMDBContentTypes,
TMDBEpisodeShort,
- TMDBExternalIds,
TMDBMediaResult,
TMDBMovieData,
- TMDBMovieExternalIds,
- TMDBMovieResponse,
- TMDBMovieResult,
TMDBMovieSearchResult,
TMDBSearchResult,
TMDBSeason,
TMDBSeasonMetaResult,
TMDBShowData,
- TMDBShowExternalIds,
- TMDBShowResponse,
- TMDBShowResult,
TMDBShowSearchResult,
} from "./types/tmdb";
import { mwFetch } from "../helpers/fetch";
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes {
- if (type === MWMediaType.MOVIE) return "movie";
- if (type === MWMediaType.SERIES) return "show";
+ if (type === MWMediaType.MOVIE) return TMDBContentTypes.MOVIE;
+ if (type === MWMediaType.SERIES) return TMDBContentTypes.TV;
throw new Error("unsupported type");
}
-export function TMDBMediaToMediaType(type: string): MWMediaType {
- if (type === "movie") return MWMediaType.MOVIE;
- if (type === "show") return MWMediaType.SERIES;
+export function TMDBMediaToMediaType(type: TMDBContentTypes): MWMediaType {
+ if (type === TMDBContentTypes.MOVIE) return MWMediaType.MOVIE;
+ if (type === TMDBContentTypes.TV) return MWMediaType.SERIES;
throw new Error("unsupported type");
}
@@ -103,7 +96,7 @@ export function decodeTMDBId(
if (prefix !== "tmdb") return null;
let mediaType;
try {
- mediaType = TMDBMediaToMediaType(type);
+ mediaType = TMDBMediaToMediaType(type as TMDBContentTypes);
} catch {
return null;
}
@@ -131,40 +124,10 @@ async function get(url: string, params?: object): Promise {
return res;
}
-export async function searchMedia(
- query: string,
- type: TMDBContentTypes
-): Promise {
- let data;
-
- switch (type) {
- case "movie":
- data = await get("search/movie", {
- query,
- include_adult: false,
- language: "en-US",
- page: 1,
- });
- break;
- case "show":
- data = await get("search/tv", {
- query,
- include_adult: false,
- language: "en-US",
- page: 1,
- });
- break;
- default:
- throw new Error("Invalid media type");
- }
-
- return data;
-}
-
export async function multiSearch(
query: string
): Promise<(TMDBMovieSearchResult | TMDBShowSearchResult)[]> {
- const data = await get(`search/multi`, {
+ const data = await get("search/multi", {
query,
include_adult: false,
language: "en-US",
@@ -172,7 +135,9 @@ export async function multiSearch(
});
// filter out results that aren't movies or shows
const results = data.results.filter(
- (r) => r.media_type === "movie" || r.media_type === "tv"
+ (r) =>
+ r.media_type === TMDBContentTypes.MOVIE ||
+ r.media_type === TMDBContentTypes.TV
);
return results;
}
@@ -183,32 +148,33 @@ export async function generateQuickSearchMediaUrl(
const data = await multiSearch(query);
if (data.length === 0) return undefined;
const result = data[0];
- const type = result.media_type === "movie" ? "movie" : "show";
- const title = result.media_type === "movie" ? result.title : result.name;
+ const title =
+ result.media_type === TMDBContentTypes.MOVIE ? result.title : result.name;
return `/media/${TMDBIdToUrlId(
- TMDBMediaToMediaType(type),
+ TMDBMediaToMediaType(result.media_type),
result.id.toString(),
title
)}`;
}
// Conditional type which for inferring the return type based on the content type
-type MediaDetailReturn = T extends "movie"
- ? TMDBMovieData
- : T extends "show"
- ? TMDBShowData
- : never;
+type MediaDetailReturn =
+ T extends TMDBContentTypes.MOVIE
+ ? TMDBMovieData
+ : T extends TMDBContentTypes.TV
+ ? TMDBShowData
+ : never;
export function getMediaDetails<
T extends TMDBContentTypes,
TReturn = MediaDetailReturn
>(id: string, type: T): Promise {
- if (type === "movie") {
- return get(`/movie/${id}`);
+ if (type === TMDBContentTypes.MOVIE) {
+ return get(`/movie/${id}`, { append_to_response: "external_ids" });
}
- if (type === "show") {
- return get(`/tv/${id}`);
+ if (type === TMDBContentTypes.TV) {
+ return get(`/tv/${id}`, { append_to_response: "external_ids" });
}
throw new Error("Invalid media type");
}
@@ -229,26 +195,6 @@ export async function getEpisodes(
}));
}
-export async function getExternalIds(
- id: string,
- type: TMDBContentTypes
-): Promise {
- let data;
-
- switch (type) {
- case "movie":
- data = await get(`/movie/${id}/external_ids`);
- break;
- case "show":
- data = await get(`/tv/${id}/external_ids`);
- break;
- default:
- throw new Error("Invalid media type");
- }
-
- return data;
-}
-
export async function getMovieFromExternalId(
imdbId: string
): Promise {
@@ -263,12 +209,12 @@ export async function getMovieFromExternalId(
}
export function formatTMDBSearchResult(
- result: TMDBShowResult | TMDBMovieResult,
+ result: TMDBMovieSearchResult | TMDBShowSearchResult,
mediatype: TMDBContentTypes
): TMDBMediaResult {
const type = TMDBMediaToMediaType(mediatype);
if (type === MWMediaType.SERIES) {
- const show = result as TMDBShowResult;
+ const show = result as TMDBShowSearchResult;
return {
title: show.name,
poster: getMediaPoster(show.poster_path),
@@ -277,7 +223,8 @@ export function formatTMDBSearchResult(
object_type: mediatype,
};
}
- const movie = result as TMDBMovieResult;
+
+ const movie = result as TMDBMovieSearchResult;
return {
title: movie.title,
diff --git a/src/backend/metadata/types/mw.ts b/src/backend/metadata/types/mw.ts
index e7cc26fe..3f0f452f 100644
--- a/src/backend/metadata/types/mw.ts
+++ b/src/backend/metadata/types/mw.ts
@@ -43,7 +43,6 @@ export type MWMediaMeta = MWMediaMetaBase & MWMediaMetaSpecific;
export interface MWQuery {
searchQuery: string;
- type: MWMediaType;
}
export interface DetailedMeta {
diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts
index 8f6bf14b..19a85c54 100644
--- a/src/backend/metadata/types/tmdb.ts
+++ b/src/backend/metadata/types/tmdb.ts
@@ -1,4 +1,7 @@
-export type TMDBContentTypes = "movie" | "show";
+export enum TMDBContentTypes {
+ MOVIE = "movie",
+ TV = "tv",
+}
export type TMDBSeasonShort = {
title: string;
@@ -121,6 +124,9 @@ export interface TMDBShowData {
type: string;
vote_average: number;
vote_count: number;
+ external_ids: {
+ imdb_id: string | null;
+ };
}
export interface TMDBMovieData {
@@ -169,6 +175,9 @@ export interface TMDBMovieData {
video: boolean;
vote_average: number;
vote_count: number;
+ external_ids: {
+ imdb_id: string | null;
+ };
}
export interface TMDBEpisodeResult {
@@ -183,54 +192,6 @@ export interface TMDBEpisodeResult {
};
}
-export interface TMDBShowResult {
- adult: boolean;
- backdrop_path: string | null;
- genre_ids: number[];
- id: number;
- origin_country: string[];
- original_language: string;
- original_name: string;
- overview: string;
- popularity: number;
- poster_path: string | null;
- first_air_date: string;
- name: string;
- vote_average: number;
- vote_count: number;
-}
-
-export interface TMDBShowResponse {
- page: number;
- results: TMDBShowResult[];
- total_pages: number;
- total_results: number;
-}
-
-export interface TMDBMovieResult {
- adult: boolean;
- backdrop_path: string | null;
- genre_ids: number[];
- id: number;
- original_language: string;
- original_title: string;
- overview: string;
- popularity: number;
- poster_path: string | null;
- release_date: string;
- title: string;
- video: boolean;
- vote_average: number;
- vote_count: number;
-}
-
-export interface TMDBMovieResponse {
- page: number;
- results: TMDBMovieResult[];
- total_pages: number;
- total_results: number;
-}
-
export interface TMDBEpisode {
air_date: string;
episode_number: number;
@@ -259,30 +220,6 @@ export interface TMDBSeason {
season_number: number;
}
-export interface TMDBShowExternalIds {
- id: number;
- imdb_id: null | string;
- freebase_mid: null | string;
- freebase_id: null | string;
- tvdb_id: number;
- tvrage_id: null | string;
- wikidata_id: null | string;
- facebook_id: null | string;
- instagram_id: null | string;
- twitter_id: null | string;
-}
-
-export interface TMDBMovieExternalIds {
- id: number;
- imdb_id: null | string;
- wikidata_id: null | string;
- facebook_id: null | string;
- instagram_id: null | string;
- twitter_id: null | string;
-}
-
-export type TMDBExternalIds = TMDBShowExternalIds | TMDBMovieExternalIds;
-
export interface ExternalIdMovieSearchResult {
movie_results: {
adult: boolean;
@@ -316,7 +253,7 @@ export interface TMDBMovieSearchResult {
original_title: string;
overview: string;
poster_path: string;
- media_type: "movie";
+ media_type: TMDBContentTypes.MOVIE;
genre_ids: number[];
popularity: number;
release_date: string;
@@ -334,7 +271,7 @@ export interface TMDBShowSearchResult {
original_name: string;
overview: string;
poster_path: string;
- media_type: "tv";
+ media_type: TMDBContentTypes.TV;
genre_ids: number[];
popularity: number;
first_air_date: string;
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx
index 431de337..1d2ce354 100644
--- a/src/components/SearchBar.tsx
+++ b/src/components/SearchBar.tsx
@@ -1,14 +1,9 @@
-import { useState } from "react";
-import { useTranslation } from "react-i18next";
+import { MWQuery } from "@/backend/metadata/types/mw";
-import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
-
-import { DropdownButton } from "./buttons/DropdownButton";
import { Icon, Icons } from "./Icon";
import { TextInputControl } from "./text-inputs/TextInputControl";
export interface SearchBarProps {
- buttonText?: string;
placeholder?: string;
onChange: (value: MWQuery, force: boolean) => void;
onUnFocus: () => void;
@@ -16,9 +11,6 @@ export interface SearchBarProps {
}
export function SearchBarInput(props: SearchBarProps) {
- const { t } = useTranslation();
-
- const [dropdownOpen, setDropdownOpen] = useState(false);
function setSearch(value: string) {
props.onChange(
{
@@ -28,15 +20,6 @@ export function SearchBarInput(props: SearchBarProps) {
false
);
}
- function setType(type: string) {
- props.onChange(
- {
- ...props.value,
- type: type as MWMediaType,
- },
- true
- );
- }
return (
@@ -51,31 +34,6 @@ export function SearchBarInput(props: SearchBarProps) {
className="w-full flex-1 bg-transparent px-4 py-4 pl-12 text-white placeholder-denim-700 focus:outline-none sm:py-4 sm:pr-2"
placeholder={props.placeholder}
/>
-
-
- setDropdownOpen(val)}
- selectedItem={props.value.type}
- setSelectedItem={(val) => setType(val)}
- options={[
- {
- id: MWMediaType.MOVIE,
- name: t("searchBar.movie"),
- icon: Icons.FILM,
- },
- {
- id: MWMediaType.SERIES,
- name: t("searchBar.series"),
- icon: Icons.CLAPPER_BOARD,
- },
- ]}
- onClick={() => setDropdownOpen((old) => !old)}
- >
- {props.buttonText || t("searchBar.search")}
-
-
);
}
diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx
index e05fbebb..828f6bd7 100644
--- a/src/components/media/MediaCard.tsx
+++ b/src/components/media/MediaCard.tsx
@@ -117,7 +117,7 @@ function MediaCardContent({
/>
-
+
{media.title}
diff --git a/src/hooks/useSearchQuery.ts b/src/hooks/useSearchQuery.ts
index cb8c3171..fe2b451e 100644
--- a/src/hooks/useSearchQuery.ts
+++ b/src/hooks/useSearchQuery.ts
@@ -1,14 +1,16 @@
import { useState } from "react";
-import { generatePath, useHistory, useRouteMatch } from "react-router-dom";
+import { generatePath, useHistory, useParams } from "react-router-dom";
-import { MWMediaType, MWQuery } from "@/backend/metadata/types/mw";
+import { MWQuery } from "@/backend/metadata/types/mw";
+import { useQueryParams } from "@/hooks/useQueryParams";
-function getInitialValue(params: { type: string; query: string }) {
- const type =
- Object.values(MWMediaType).find((v) => params.type === v) ||
- MWMediaType.MOVIE;
- const searchQuery = decodeURIComponent(params.query || "");
- return { type, searchQuery };
+function getInitialValue(
+ query: Record,
+ params: Record
+) {
+ let searchQuery = decodeURIComponent(params.query || "");
+ if (query.q) searchQuery = query.q;
+ return { searchQuery };
}
export function useSearchQuery(): [
@@ -17,28 +19,34 @@ export function useSearchQuery(): [
() => void
] {
const history = useHistory();
- const { path, params } = useRouteMatch<{ type: string; query: string }>();
- const [search, setSearch] = useState(getInitialValue(params));
+ const query = useQueryParams();
+ const params = useParams<{ query: string }>();
+ const [search, setSearch] = useState(getInitialValue(query, params));
const updateParams = (inp: Partial, force: boolean) => {
- const copySearch: MWQuery = { ...search };
+ const copySearch = { ...search };
Object.assign(copySearch, inp);
setSearch(copySearch);
if (!force) return;
+ if (copySearch.searchQuery.length === 0) {
+ history.replace("/");
+ return;
+ }
history.replace(
- generatePath(path, {
- query:
- copySearch.searchQuery.length === 0 ? undefined : inp.searchQuery,
- type: copySearch.type,
+ generatePath("/browse/:query", {
+ query: copySearch.searchQuery,
})
);
};
const onUnFocus = () => {
+ if (search.searchQuery.length === 0) {
+ history.replace("/");
+ return;
+ }
history.replace(
- generatePath(path, {
- query: search.searchQuery.length === 0 ? undefined : search.searchQuery,
- type: search.type,
+ generatePath("/browse/:query", {
+ query: search.searchQuery,
})
);
};
diff --git a/src/setup/App.tsx b/src/setup/App.tsx
index 53f3c131..6bb6b957 100644
--- a/src/setup/App.tsx
+++ b/src/setup/App.tsx
@@ -10,7 +10,6 @@ import {
import { convertLegacyUrl, isLegacyUrl } from "@/backend/metadata/getmeta";
import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb";
-import { MWMediaType } from "@/backend/metadata/types/mw";
import { BannerContextProvider } from "@/hooks/useBanner";
import { Layout } from "@/setup/Layout";
import { BookmarkContextProvider } from "@/state/bookmark";
@@ -64,9 +63,6 @@ function App() {
{/* functional routes */}
-
-
-
@@ -82,9 +78,15 @@ function App() {
+
+
+
+
+
+
diff --git a/src/setup/locales/cs/translation.json b/src/setup/locales/cs/translation.json
index a7e5e826..f2b1b3f1 100644
--- a/src/setup/locales/cs/translation.json
+++ b/src/setup/locales/cs/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Načítání Vašich oblíbených seriálů...",
- "loading_movie": "Načítání Vašich oblíbených filmů...",
"loading": "Načítání...",
"allResults": "To je vše co máme!",
"noResults": "Nemohli jsme nic najít!",
diff --git a/src/setup/locales/de/translation.json b/src/setup/locales/de/translation.json
index d58923c0..fa24c345 100644
--- a/src/setup/locales/de/translation.json
+++ b/src/setup/locales/de/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Auf der Suche nach deiner Lieblingsserie...",
- "loading_movie": "Auf der Suche nach deinen Lieblingsfilmen...",
"loading": "Wird geladen...",
"allResults": "Das ist alles, was wir haben!",
"noResults": "Wir haben nichts gefunden!",
diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json
index d90b568b..8f48a9d6 100644
--- a/src/setup/locales/en/translation.json
+++ b/src/setup/locales/en/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Fetching your favourite series...",
- "loading_movie": "Fetching your favourite movies...",
"loading": "Loading...",
"allResults": "That's all we have!",
"noResults": "We couldn't find anything!",
@@ -73,7 +71,7 @@
"sources": "Sources",
"close": "Close",
"seasons": {
- "title":"Seasons",
+ "title": "Seasons",
"other": "Other seasons",
"noSeason": "No season"
},
diff --git a/src/setup/locales/fr/translation.json b/src/setup/locales/fr/translation.json
index 408570df..84e81495 100644
--- a/src/setup/locales/fr/translation.json
+++ b/src/setup/locales/fr/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Recherche de votre série préférée...",
- "loading_movie": "Recherche de vos films préférés...",
"loading": "Chargement...",
"allResults": "C'est tout ce que nous avons!",
"noResults": "Nous n'avons rien trouvé!",
diff --git a/src/setup/locales/it/translation.json b/src/setup/locales/it/translation.json
index 7c28992c..fbd5bf3c 100644
--- a/src/setup/locales/it/translation.json
+++ b/src/setup/locales/it/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Recupero delle tue serie preferite...",
- "loading_movie": "Recupero dei tuoi film preferiti...",
"loading": "Caricamento...",
"allResults": "Ecco tutto ciò che abbiamo!",
"noResults": "Non abbiamo trovato nulla!",
@@ -88,7 +86,7 @@
"customCaption": "Sottotitolo personalizzato",
"uploadCustomCaption": "Carica sottotitolo",
"noEmbeds": "Nessun embed è stato trovato per questa fonte",
-
+
"errors": {
"loadingWentWong": "Si è verificato un problema durante il caricamento degli episodi per {{seasonTitle}}",
"embedsError": "Si è verificato un problema durante il caricamento degli embed per questa cosa che ti piace"
diff --git a/src/setup/locales/nl/translation.json b/src/setup/locales/nl/translation.json
index 23ecb618..5a97ae13 100644
--- a/src/setup/locales/nl/translation.json
+++ b/src/setup/locales/nl/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "We zoeken je favoriete series...",
- "loading_movie": "We zoeken je favoriete films...",
"loading": "Aan het zoeken...",
"allResults": "Dat is het!",
"noResults": "We konden helaas niets vinden.",
diff --git a/src/setup/locales/pirate/translation.json b/src/setup/locales/pirate/translation.json
index a4a92117..949d05ad 100644
--- a/src/setup/locales/pirate/translation.json
+++ b/src/setup/locales/pirate/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Fetchin' yer favorite series...",
- "loading_movie": "Fetchin' yer favorite movies...",
"loadin'": "Loadin'...",
"allResults": "That be all we 'ave, me hearty!",
"noResults": "We couldn't find anythin' that matches yer search!",
diff --git a/src/setup/locales/pl/translation.json b/src/setup/locales/pl/translation.json
index a85d9c70..b9fda283 100644
--- a/src/setup/locales/pl/translation.json
+++ b/src/setup/locales/pl/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Szukamy twoich ulubionych seriali...",
- "loading_movie": "Szukamy twoich ulubionych filmów...",
"loading": "Wczytywanie...",
"allResults": "To wszystko co mamy!",
"noResults": "Nie mogliśmy niczego znaleźć!",
@@ -71,7 +69,7 @@
"popouts": {
"close": "Zamknąć",
"seasons": {
- "title":"Sezony",
+ "title": "Sezony",
"other": "Inne sezony",
"noSeason": "Brak sezonu"
},
diff --git a/src/setup/locales/tr/translation.json b/src/setup/locales/tr/translation.json
index bab6cd1d..7b22a1c4 100644
--- a/src/setup/locales/tr/translation.json
+++ b/src/setup/locales/tr/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Favori dizileriniz aranıyor...",
- "loading_movie": "Favori filmleriniz aranıyor...",
"loading": "Yükleniyor...",
"allResults": "Bu kadarını bulabildik!",
"noResults": "Hiçbir şey bulamadık!",
@@ -71,9 +69,9 @@
"popouts": {
"back": "Geri git",
"sources": "Kaynaklar",
- "close":"Kapat",
+ "close": "Kapat",
"seasons": {
- "title":"Sezonlar",
+ "title": "Sezonlar",
"other": "Diğer sezonlar",
"noSeason": "Sezon yok"
},
diff --git a/src/setup/locales/vi/translation.json b/src/setup/locales/vi/translation.json
index d56b3595..d0c70768 100644
--- a/src/setup/locales/vi/translation.json
+++ b/src/setup/locales/vi/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "Đang tìm chương trình yêu thích của bạn...",
- "loading_movie": "Đang tìm bộ phim yêu thích của bạn...",
"loading": "Đang tải...",
"allResults": "Đó là tất cả chúng tôi có!",
"noResults": "Chúng tôi không thể tìm thấy gì!",
diff --git a/src/setup/locales/zh/translation.json b/src/setup/locales/zh/translation.json
index 39b0998d..83f7e8a7 100644
--- a/src/setup/locales/zh/translation.json
+++ b/src/setup/locales/zh/translation.json
@@ -3,8 +3,6 @@
"name": "movie-web"
},
"search": {
- "loading_series": "正在获取您最喜欢的连续剧……",
- "loading_movie": "正在获取您最喜欢的影片……",
"loading": "载入中……",
"allResults": "以上是我们能找到的所有结果!",
"noResults": "我们找不到任何结果!",
diff --git a/src/state/watched/migrations/v2.ts b/src/state/watched/migrations/v2.ts
index 94f1141b..2e92224e 100644
--- a/src/state/watched/migrations/v2.ts
+++ b/src/state/watched/migrations/v2.ts
@@ -48,7 +48,6 @@ async function getMetas(
const year = Number(item.year.toString().split("-")[0]);
const data = await searchForMedia({
searchQuery: `${item.title} ${year}`,
- type: item.mediaType,
});
const relevantItem = data.find(
(res) =>
diff --git a/src/state/watched/migrations/v3.ts b/src/state/watched/migrations/v3.ts
index dffae637..f546c943 100644
--- a/src/state/watched/migrations/v3.ts
+++ b/src/state/watched/migrations/v3.ts
@@ -5,6 +5,7 @@ import {
getMovieFromExternalId,
} from "@/backend/metadata/tmdb";
import { MWMediaType } from "@/backend/metadata/types/mw";
+import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
import { BookmarkStoreData } from "@/state/bookmark/types";
import { isNotNull } from "@/utils/typeguard";
@@ -59,7 +60,10 @@ export async function migrateV3Videos(
clone.item.meta.id = migratedId;
if (clone.item.series) {
const series = clone.item.series;
- const details = await getMediaDetails(migratedId, "show");
+ const details = await getMediaDetails(
+ migratedId,
+ TMDBContentTypes.TV
+ );
const season = details.seasons.find(
(v) => v.season_number === series.season
diff --git a/src/views/search/SearchLoadingView.tsx b/src/views/search/SearchLoadingView.tsx
index 45971860..0c59f5d9 100644
--- a/src/views/search/SearchLoadingView.tsx
+++ b/src/views/search/SearchLoadingView.tsx
@@ -1,19 +1,10 @@
import { useTranslation } from "react-i18next";
import { Loading } from "@/components/layout/Loading";
-import { useSearchQuery } from "@/hooks/useSearchQuery";
export function SearchLoadingView() {
const { t } = useTranslation();
- const [query] = useSearchQuery();
return (
-
+
);
}
diff --git a/tailwind.config.js b/tailwind.config.js
index e22b3b3a..3a6816a6 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -42,5 +42,5 @@ module.exports = {
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
}
},
- plugins: [require("tailwind-scrollbar"), require("@tailwindcss/line-clamp")]
+ plugins: [require("tailwind-scrollbar")]
};
diff --git a/vite.config.ts b/vite.config.ts
index 77a83ad2..ce5746ad 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,91 +4,104 @@ import loadVersion from "vite-plugin-package-version";
import { VitePWA } from "vite-plugin-pwa";
import checker from "vite-plugin-checker";
import path from "path";
+import { handlebars } from "./plugins/handlebars";
+import { loadEnv } from "vite";
-export default defineConfig({
- plugins: [
- react({
- babel: {
- presets: [
- "@babel/preset-typescript",
- [
- "@babel/preset-env",
- {
- modules: false,
- useBuiltIns: "entry",
- corejs: {
- version: "3.29",
+export default defineConfig(({ mode }) => {
+ const env = loadEnv(mode, process.cwd());
+ return {
+ plugins: [
+ handlebars({
+ vars: {
+ opensearchEnabled: env.VITE_OPENSEARCH_ENABLED === "true",
+ routeDomain: env.VITE_APP_DOMAIN + (env.VITE_NORMAL_ROUTER !== 'true' ? "/#" : ""),
+ domain: env.VITE_APP_DOMAIN,
+ env,
+ },
+ }),
+ react({
+ babel: {
+ presets: [
+ "@babel/preset-typescript",
+ [
+ "@babel/preset-env",
+ {
+ modules: false,
+ useBuiltIns: "entry",
+ corejs: {
+ version: "3.29",
+ },
},
+ ],
+ ],
+ },
+ }),
+ VitePWA({
+ registerType: "autoUpdate",
+ workbox: {
+ globIgnores: ["**ping.txt**"],
+ },
+ includeAssets: [
+ "favicon.ico",
+ "apple-touch-icon.png",
+ "safari-pinned-tab.svg",
+ ],
+ manifest: {
+ name: "movie-web",
+ short_name: "movie-web",
+ description: "The place for your favourite movies & shows",
+ theme_color: "#120f1d",
+ background_color: "#120f1d",
+ display: "standalone",
+ start_url: "/",
+ icons: [
+ {
+ src: "android-chrome-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "android-chrome-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "android-chrome-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ purpose: "maskable",
+ },
+ {
+ src: "android-chrome-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "maskable",
},
],
- ],
- },
- }),
- VitePWA({
- registerType: "autoUpdate",
- workbox: {
- globIgnores: ["**ping.txt**"],
- },
- includeAssets: [
- "favicon.ico",
- "apple-touch-icon.png",
- "safari-pinned-tab.svg",
- ],
- manifest: {
- name: "movie-web",
- short_name: "movie-web",
- description: "The place for your favourite movies & shows",
- theme_color: "#120f1d",
- background_color: "#120f1d",
- display: "standalone",
- start_url: "/",
- icons: [
- {
- src: "android-chrome-192x192.png",
- sizes: "192x192",
- type: "image/png",
- purpose: "any",
- },
- {
- src: "android-chrome-512x512.png",
- sizes: "512x512",
- type: "image/png",
- purpose: "any",
- },
- {
- src: "android-chrome-192x192.png",
- sizes: "192x192",
- type: "image/png",
- purpose: "maskable",
- },
- {
- src: "android-chrome-512x512.png",
- sizes: "512x512",
- type: "image/png",
- purpose: "maskable",
- },
- ],
- },
- }),
- loadVersion(),
- checker({
- 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"],
},
+ }),
+ loadVersion(),
+ checker({
+ 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"],
+ },
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
},
- }),
- ],
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "./src"),
},
- },
- test: {
- environment: "jsdom",
- },
+ test: {
+ environment: "jsdom",
+ },
+ };
});
diff --git a/yarn.lock b/yarn.lock
index 5d687f2a..8b7759b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1141,6 +1141,18 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
@@ -1207,6 +1219,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
"@react-spring/animated@~9.7.2":
version "9.7.2"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.2.tgz#0119db8075e91d693ec45c42575541e01b104a70"
@@ -1391,11 +1408,6 @@
magic-string "^0.25.0"
string.prototype.matchall "^4.0.6"
-"@tailwindcss/line-clamp@^0.4.2":
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz#767cf8e5d528a5d90c9740ca66eb079f5e87d423"
- integrity sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==
-
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
@@ -1836,7 +1848,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -1848,7 +1860,7 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
-ansi-styles@^6.0.0:
+ansi-styles@^6.0.0, ansi-styles@^6.1.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
@@ -2238,7 +2250,7 @@ core-js@^3.29.1, core-js@^3.6.5:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.1.tgz#fc9c5adcc541d8e9fa3e381179433cbf795628ba"
integrity sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==
-cross-spawn@^7.0.2:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -2453,6 +2465,11 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz#a98d838cdd79be4471cd04e9b4dffe891d037874"
integrity sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
@@ -2866,6 +2883,17 @@ fast-diff@^1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+fast-glob@^3.2.11:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0"
+ integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-glob@^3.2.12, fast-glob@^3.2.7, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
@@ -2958,6 +2986,14 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"
+foreground-child@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
+ integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@@ -3089,6 +3125,17 @@ glob@7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^10.3.3:
+ version "10.3.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b"
+ integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^2.0.3"
+ minimatch "^9.0.1"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+ path-scurry "^1.10.1"
+
glob@^7.1.3, glob@^7.1.6, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -3149,6 +3196,18 @@ grapheme-splitter@^1.0.4:
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+handlebars@^4.7.7:
+ version "4.7.7"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
+ integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
+ dependencies:
+ minimist "^1.2.5"
+ neo-async "^2.6.0"
+ source-map "^0.6.1"
+ wordwrap "^1.0.0"
+ optionalDependencies:
+ uglify-js "^3.1.4"
+
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@@ -3397,6 +3456,11 @@ is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-fullwidth-code-point@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
@@ -3541,6 +3605,15 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+jackspeak@^2.0.3:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6"
+ integrity sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@@ -3815,6 +3888,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+"lru-cache@^9.1.1 || ^10.0.0":
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61"
+ integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==
+
magic-string@^0.25.0, magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -3878,11 +3956,23 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
-minimist@^1.2.0, minimist@^1.2.6:
+minimatch@^9.0.1:
+ version "9.0.3"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
+ integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e"
+ integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==
+
mlly@^1.1.0, mlly@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.2.0.tgz#f0f6c2fc8d2d12ea6907cd869066689b5031b613"
@@ -3946,6 +4036,11 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+neo-async@^2.6.0:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
node-fetch-native@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.1.0.tgz#a530f5c4cadb49b382dcf81d8f5f19ed0f457fbe"
@@ -4151,6 +4246,14 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+path-scurry@^1.10.1:
+ version "1.10.1"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
+ integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
+ dependencies:
+ lru-cache "^9.1.1 || ^10.0.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
@@ -4751,6 +4854,11 @@ siginfo@^2.0.0:
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
+signal-exit@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
+ integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -4850,7 +4958,17 @@ stop-iteration-iterator@^1.0.0:
dependencies:
internal-slot "^1.0.4"
-string-width@^5.0.0:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
+ name string-width-cjs
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
@@ -4909,7 +5027,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -5241,6 +5359,11 @@ ufo@^1.1.0, ufo@^1.1.1:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.1.tgz#e70265e7152f3aba425bd013d150b2cdf4056d7c"
integrity sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==
+uglify-js@^3.1.4:
+ version "3.17.4"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
+ integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==
+
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@@ -5388,6 +5511,16 @@ vite-plugin-pwa@^0.14.4:
workbox-build "^6.5.4"
workbox-window "^6.5.4"
+vite-plugin-static-copy@^0.16.0:
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/vite-plugin-static-copy/-/vite-plugin-static-copy-0.16.0.tgz#2f65227037f17fc99c0782fd0b344e962935e69e"
+ integrity sha512-dMVEg5Z2SwYRgQnHZaeokvSKB4p/TOTf65JU4sP3U6ccSBsukqdtDOjpmT+xzTFHAA8WJjcS31RMLjUdWQCBzw==
+ dependencies:
+ chokidar "^3.5.3"
+ fast-glob "^3.2.11"
+ fs-extra "^11.1.0"
+ picocolors "^1.0.0"
+
"vite@^3.0.0 || ^4.0.0", vite@^4.0.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.1.tgz#9badb1377f995632cdcf05f32103414db6fbb95a"
@@ -5577,6 +5710,11 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+wordwrap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+ integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
+
workbox-background-sync@6.5.4:
version "6.5.4"
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
@@ -5735,6 +5873,24 @@ workbox-window@6.5.4, workbox-window@^6.5.4:
"@types/trusted-types" "^2.0.2"
workbox-core "6.5.4"
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"