diff --git a/package.json b/package.json index 0e9b69df..3bf268a7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@react-spring/web": "^9.7.1", "@use-gesture/react": "^10.2.24", "crypto-js": "^4.1.1", + "dompurify": "^3.0.1", "fscreen": "^1.2.0", "fuse.js": "^6.4.6", "hls.js": "^1.0.7", @@ -17,6 +18,7 @@ "json5": "^2.2.0", "lodash.throttle": "^4.1.1", "nanoid": "^4.0.0", + "node-webvtt": "^1.9.4", "ofetch": "^1.0.0", "pako": "^2.1.0", "react": "^17.0.2", @@ -56,6 +58,7 @@ "@tailwindcss/line-clamp": "^0.4.2", "@types/chromecast-caf-sender": "^1.0.5", "@types/crypto-js": "^4.1.1", + "@types/dompurify": "^2.4.0", "@types/fscreen": "^1.0.1", "@types/lodash.throttle": "^4.1.7", "@types/node": "^17.0.15", diff --git a/src/@types/node_webtt.d.ts b/src/@types/node_webtt.d.ts new file mode 100644 index 00000000..5662d89f --- /dev/null +++ b/src/@types/node_webtt.d.ts @@ -0,0 +1,27 @@ +declare module "node-webvtt" { + interface Cue { + identifier: string; + start: number; + end: number; + text: string; + styles: string; + } + interface Options { + meta?: boolean; + strict?: boolean; + } + type ParserError = Error; + interface ParseResult { + valid: boolean; + strict: boolean; + cues: Cue[]; + errors: ParserError[]; + meta?: Map; + } + interface Segment { + duration: number; + cues: Cue[]; + } + function parse(text: string, options: Options): ParseResult; + function segment(input: string, segmentLength?: number): Segment[]; +} diff --git a/src/backend/helpers/captions.ts b/src/backend/helpers/captions.ts index 61adbdc9..4bd11052 100644 --- a/src/backend/helpers/captions.ts +++ b/src/backend/helpers/captions.ts @@ -1,7 +1,9 @@ import { mwFetch, proxiedFetch } from "@/backend/helpers/fetch"; import { MWCaption, MWCaptionType } from "@/backend/helpers/streams"; import toWebVTT from "srt-webvtt"; +import DOMPurify from "dompurify"; +export const sanitize = DOMPurify.sanitize; export const CUSTOM_CAPTION_ID = "customCaption"; export async function getCaptionUrl(caption: MWCaption): Promise { if (caption.type === MWCaptionType.SRT) { diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts index 0de96b21..da2910c0 100644 --- a/src/backend/providers/flixhq.ts +++ b/src/backend/providers/flixhq.ts @@ -18,15 +18,14 @@ interface FLIXMediaBase { title: string; url: string; image: string; + type: "Movie" | "TV Series"; } interface FLIXTVSerie extends FLIXMediaBase { - type: "TV Series"; seasons: number | null; } interface FLIXMovie extends FLIXMediaBase { - type: "Movie"; releaseDate: string; } @@ -66,22 +65,28 @@ registerProvider({ baseURL: flixHqBase, } ); + const foundItem = searchResults.results.find((v: FLIXMediaBase) => { if (media.meta.type === MWMediaType.MOVIE) { + if (v.type !== "Movie") return false; const movie = v as FLIXMovie; return ( compareTitle(movie.title, media.meta.title) && movie.releaseDate === media.meta.year ); } - const serie = v as FLIXTVSerie; - if (serie.seasons && media.meta.seasons) { - return ( - compareTitle(serie.title, media.meta.title) && - serie.seasons === media.meta.seasons.length - ); + if (media.meta.type === MWMediaType.SERIES) { + if (v.type !== "TV Series") return false; + const serie = v as FLIXTVSerie; + if (serie.seasons && media.meta.seasons) { + return ( + compareTitle(serie.title, media.meta.title) && + serie.seasons === media.meta.seasons.length + ); + } + return false; } - return compareTitle(serie.title, media.meta.title); + return false; }); if (!foundItem) throw new Error("No watchable item found"); const flixId = foundItem.id; @@ -110,6 +115,7 @@ registerProvider({ )?.id; } if (!episodeId) throw new Error("No watchable item found"); + const watchInfo = await proxiedFetch("/watch", { baseURL: flixHqBase, params: { diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index ae33aad7..96a7e430 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -39,6 +39,7 @@ export enum Icons { GEAR = "gear", WATCH_PARTY = "watch_party", PICTURE_IN_PICTURE = "pictureInPicture", + CHECKMARK = "checkmark", } export interface IconProps { @@ -85,6 +86,7 @@ const iconList: Record = { gear: ``, watch_party: ``, pictureInPicture: ``, + checkmark: ``, }; function ChromeCastButton() { diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 331337db..f2c63d91 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -1,6 +1,7 @@ import { Redirect, Route, Switch } from "react-router-dom"; import { BookmarkContextProvider } from "@/state/bookmark"; import { WatchedContextProvider } from "@/state/watched"; +import { SettingsProvider } from "@/state/settings"; import { NotFoundPage } from "@/views/notfound/NotFoundView"; import { MediaView } from "@/views/media/MediaView"; @@ -17,46 +18,48 @@ import { TestView } from "@/views/developer/TestView"; function App() { return ( - - - - - - {/* functional routes */} - - - - + + + + + + + {/* functional routes */} + + + + - {/* pages */} - - - + {/* pages */} + + + - {/* other */} - - - - - - - - - - - + {/* other */} + + + + + + + + + + + + ); } diff --git a/src/setup/index.css b/src/setup/index.css index ebb56bec..aecde89f 100644 --- a/src/setup/index.css +++ b/src/setup/index.css @@ -54,3 +54,123 @@ body[data-no-select] { .google-cast-button:not(.casting) google-cast-launcher { @apply brightness-[500]; } + +/*generated with Input range slider CSS style generator (version 20211225) +https://toughengineer.github.io/demo/slider-styler*/ +:root { + --slider-height: 0.25rem; + --slider-border-radius: 1em; + --slider-progress-background: #8652bb; +} +input[type=range].styled-slider { + height: var(--slider-height); + -webkit-appearance: none; + appearance: none; + border-radius: var(--slider-border-radius); + background: #1C161B; +} + +/*progress support*/ +input[type=range].styled-slider.slider-progress { + --range: calc(var(--max) - var(--min)); + --ratio: calc((var(--value) - var(--min)) / var(--range)); + --sx: calc(0.5 * 1rem + var(--ratio) * (100% - 1rem)); +} + +/*webkit*/ +input[type=range].styled-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 1rem; + height: 1rem; + border-radius: var(--slider-border-radius); + background: #FFFFFF; + border: none; + box-shadow: 0 0 2px #000000; + margin-top: calc(0.25em * 0.5 - 1rem * 0.5); +} + +input[type=range].styled-slider::-webkit-slider-runnable-track { + height: var(--slider-height); + border: none; + box-shadow: none; + border-radius: var(--slider-border-radius); + margin-bottom: 1.1em; +} + +input[type=range].styled-slider::-webkit-slider-thumb:hover { + background: #DCDCDC; +} + +input[type=range].styled-slider.slider-progress::-webkit-slider-runnable-track { + background: linear-gradient(var(--slider-progress-background),var(--slider-progress-background)) 0/var(--sx) 100% no-repeat, #1C161B; +} + +/*mozilla*/ +input[type=range].styled-slider::-moz-range-thumb { + width: 1rem; + height: 1rem; + border-radius: var(--slider-border-radius); + background: #FFFFFF; + border: none; + box-shadow: 0 0 2px #000000; +} + +input[type=range].styled-slider::-moz-range-track { + height: var(--slider-height); + border: none; + border-radius: var(--slider-border-radius); + background: #1C161B; + box-shadow: none; +} + +input[type=range].styled-slider::-moz-range-thumb:hover { + background: #DCDCDC; +} + +input[type=range].styled-slider.slider-progress::-moz-range-track { + background: linear-gradient(var(--slider-progress-background),var(--slider-progress-background)) 0/var(--sx) 100% no-repeat, #1C161B; +} + +/*ms*/ +input[type=range].styled-slider::-ms-fill-upper { + background: transparent; + border-color: transparent; +} + +input[type=range].styled-slider::-ms-fill-lower { + background: transparent; + border-color: transparent; +} + +input[type=range].styled-slider::-ms-thumb { + width: 1rem; + height: 1rem; + border-radius: var(--slider-border-radius); + background: #FFFFFF; + border: none; + box-shadow: 0 0 2px #000000; + margin-top: 0; + box-sizing: border-box; +} + +input[type=range].styled-slider::-ms-track { + height: var(--slider-height); + border-radius: var(--slider-border-radius); + background: #1C161B; + border: none; + box-shadow: none; + box-sizing: border-box; +} + +input[type=range].styled-slider::-ms-thumb:hover { + background: #DCDCDC; +} + +input[type=range].styled-slider.slider-progress::-ms-fill-lower { + height: var(--slider-height); + border-radius: var(--slider-border-radius) 0 0 5px; + margin: -undefined 0 -undefined -undefined; + background: var(--slider-progress-background); + border: none; + border-right-width: 0; +} diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index b4dd8ce5..d1408148 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -69,6 +69,13 @@ "sources": "Sources", "seasons": "Seasons", "captions": "Captions", + "captionPreferences": { + "title": "Customize", + "delay": "Delay", + "fontSize": "Size", + "opacity": "Opacity", + "color": "Color" + }, "episode": "E{{index}} - {{title}}", "noCaptions": "No captions", "linkedCaptions": "Linked captions", @@ -84,7 +91,8 @@ "embeds": "Choose which video to view", "seasons": "Choose which season you want to watch", "episode": "Pick an episode", - "captions": "Choose a subtitle language" + "captions": "Choose a subtitle language", + "captionPreferences": "Make subtitles look how you want it" } }, "errors": { diff --git a/src/state/settings/context.tsx b/src/state/settings/context.tsx new file mode 100644 index 00000000..030833cc --- /dev/null +++ b/src/state/settings/context.tsx @@ -0,0 +1,78 @@ +import { useStore } from "@/utils/storage"; +import { createContext, ReactNode, useContext, useMemo } from "react"; +import { SettingsStore } from "./store"; +import { MWSettingsData } from "./types"; + +interface MWSettingsDataSetters { + setLanguage(language: string): void; + setCaptionDelay(delay: number): void; + setCaptionColor(color: string): void; + setCaptionFontSize(size: number): void; + setCaptionBackgroundColor(backgroundColor: string): void; +} +type MWSettingsDataWrapper = MWSettingsData & MWSettingsDataSetters; +const SettingsContext = createContext(null as any); +export function SettingsProvider(props: { children: ReactNode }) { + function enforceRange(min: number, value: number, max: number) { + return Math.max(min, Math.min(value, max)); + } + const [settings, setSettings] = useStore(SettingsStore); + + const context: MWSettingsDataWrapper = useMemo(() => { + const settingsContext: MWSettingsDataWrapper = { + ...settings, + setLanguage(language) { + setSettings((oldSettings) => { + return { + ...oldSettings, + language, + }; + }); + }, + setCaptionDelay(delay: number) { + setSettings((oldSettings) => { + const captionSettings = oldSettings.captionSettings; + captionSettings.delay = enforceRange(-10, delay, 10); + const newSettings = oldSettings; + return newSettings; + }); + }, + setCaptionColor(color) { + setSettings((oldSettings) => { + const style = oldSettings.captionSettings.style; + style.color = color; + const newSettings = oldSettings; + return newSettings; + }); + }, + setCaptionFontSize(size) { + setSettings((oldSettings) => { + const style = oldSettings.captionSettings.style; + style.fontSize = enforceRange(10, size, 60); + const newSettings = oldSettings; + return newSettings; + }); + }, + setCaptionBackgroundColor(backgroundColor) { + setSettings((oldSettings) => { + const style = oldSettings.captionSettings.style; + style.backgroundColor = backgroundColor; + const newSettings = oldSettings; + return newSettings; + }); + }, + }; + return settingsContext; + }, [settings, setSettings]); + return ( + + {props.children} + + ); +} + +export function useSettings() { + return useContext(SettingsContext); +} + +export default SettingsContext; diff --git a/src/state/settings/index.ts b/src/state/settings/index.ts new file mode 100644 index 00000000..2edd280c --- /dev/null +++ b/src/state/settings/index.ts @@ -0,0 +1 @@ +export * from "./context"; diff --git a/src/state/settings/store.ts b/src/state/settings/store.ts new file mode 100644 index 00000000..c854dc04 --- /dev/null +++ b/src/state/settings/store.ts @@ -0,0 +1,22 @@ +import { createVersionedStore } from "@/utils/storage"; +import { MWSettingsData } from "./types"; + +export const SettingsStore = createVersionedStore() + .setKey("mw-settings") + .addVersion({ + version: 0, + create(): MWSettingsData { + return { + language: "en", + captionSettings: { + delay: 0, + style: { + color: "#ffffff", + fontSize: 25, + backgroundColor: "#00000096", + }, + }, + }; + }, + }) + .build(); diff --git a/src/state/settings/types.ts b/src/state/settings/types.ts new file mode 100644 index 00000000..b793308d --- /dev/null +++ b/src/state/settings/types.ts @@ -0,0 +1,21 @@ +export interface CaptionStyleSettings { + color: string; + /** + * Range is [10, 30] + */ + fontSize: number; + backgroundColor: string; +} + +export interface CaptionSettings { + /** + * Range is [-10, 10]s + */ + delay: number; + style: CaptionStyleSettings; +} + +export interface MWSettingsData { + language: string; + captionSettings: CaptionSettings; +} diff --git a/src/video/components/VideoPlayer.tsx b/src/video/components/VideoPlayer.tsx index 0dc9628c..22d96502 100644 --- a/src/video/components/VideoPlayer.tsx +++ b/src/video/components/VideoPlayer.tsx @@ -27,9 +27,10 @@ import { ReactNode, useCallback, useState } from "react"; import { PopoutProviderAction } from "@/video/components/popouts/PopoutProviderAction"; import { ChromecastAction } from "@/video/components/actions/ChromecastAction"; import { CastingTextAction } from "@/video/components/actions/CastingTextAction"; +import { PictureInPictureAction } from "@/video/components/actions/PictureInPictureAction"; +import { CaptionRendererAction } from "./actions/CaptionRendererAction"; import { SettingsAction } from "./actions/SettingsAction"; import { DividerAction } from "./actions/DividerAction"; -import { PictureInPictureAction } from "./actions/PictureInPictureAction"; type Props = VideoPlayerBaseProps; @@ -165,6 +166,7 @@ export function VideoPlayer(props: Props) { {show ? : null} + {props.children} diff --git a/src/video/components/actions/CaptionRendererAction.tsx b/src/video/components/actions/CaptionRendererAction.tsx new file mode 100644 index 00000000..6abc18c9 --- /dev/null +++ b/src/video/components/actions/CaptionRendererAction.tsx @@ -0,0 +1,92 @@ +import { Transition } from "@/components/Transition"; +import { useSettings } from "@/state/settings"; +import { sanitize } from "@/backend/helpers/captions"; +import { parse, Cue } from "node-webvtt"; +import { useRef } from "react"; +import { useAsync } from "react-use"; +import { useVideoPlayerDescriptor } from "../../state/hooks"; +import { useProgress } from "../../state/logic/progress"; +import { useSource } from "../../state/logic/source"; + +function CaptionCue({ text }: { text?: string }) { + const { captionSettings } = useSettings(); + const textWithNewlines = (text || "").replaceAll(/\r?\n/g, "
"); + + // https://www.w3.org/TR/webvtt1/#dom-construction-rules + // added a
for newlines + const html = sanitize(textWithNewlines, { + ALLOWED_TAGS: ["c", "b", "i", "u", "span", "ruby", "rt", "br"], + ADD_TAGS: ["v", "lang"], + ALLOWED_ATTR: ["title", "lang"], + }); + + return ( +

+ +

+ ); +} + +export function CaptionRendererAction({ + isControlsShown, +}: { + isControlsShown: boolean; +}) { + const descriptor = useVideoPlayerDescriptor(); + const source = useSource(descriptor).source; + const videoTime = useProgress(descriptor).time; + const { captionSettings } = useSettings(); + const captions = useRef([]); + + useAsync(async () => { + const url = source?.caption?.url; + if (url) { + // Is there a better way? + const result = await fetch(url); + // Uses UTF-8 by default + const text = await result.text(); + captions.current = parse(text, { strict: false }).cues; + } else { + captions.current = []; + } + }, [source?.caption?.url]); + + if (!captions.current.length) return null; + const isVisible = (start: number, end: number): boolean => { + const delayedStart = start + captionSettings.delay; + const delayedEnd = end + captionSettings.delay; + return ( + Math.max(0, delayedStart) <= videoTime && + Math.max(0, delayedEnd) >= videoTime + ); + }; + return ( + + {captions.current.map( + ({ identifier, end, start, text }) => + isVisible(start, end) && ( + + ) + )} + + ); +} diff --git a/src/video/components/internal/VideoElementInternal.tsx b/src/video/components/internal/VideoElementInternal.tsx index 335e8a9f..593f50c8 100644 --- a/src/video/components/internal/VideoElementInternal.tsx +++ b/src/video/components/internal/VideoElementInternal.tsx @@ -1,7 +1,6 @@ import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useMediaPlaying } from "@/video/state/logic/mediaplaying"; import { useMisc } from "@/video/state/logic/misc"; -import { useSource } from "@/video/state/logic/source"; import { setProvider, unsetStateProvider } from "@/video/state/providers/utils"; import { createVideoStateProvider } from "@/video/state/providers/videoStateProvider"; import { useEffect, useMemo, useRef } from "react"; @@ -13,7 +12,6 @@ interface Props { function VideoElement(props: Props) { const descriptor = useVideoPlayerDescriptor(); const mediaPlaying = useMediaPlaying(descriptor); - const source = useSource(descriptor); const misc = useMisc(descriptor); const ref = useRef(null); @@ -44,11 +42,7 @@ function VideoElement(props: Props) { muted={mediaPlaying.volume === 0} playsInline className="h-full w-full" - > - {source.source?.caption ? ( - - ) : null} - + /> ); } diff --git a/src/video/components/popouts/CaptionSelectionPopout.tsx b/src/video/components/popouts/CaptionSelectionPopout.tsx index 7d3b9a98..9caaca96 100644 --- a/src/video/components/popouts/CaptionSelectionPopout.tsx +++ b/src/video/components/popouts/CaptionSelectionPopout.tsx @@ -70,7 +70,6 @@ export function CaptionSelectionPopout(props: { const captionFile = e.target.files[0]; setCustomCaption(captionFile); } - return ( props.router.navigate("/")} + action={ + + } /> diff --git a/src/video/components/popouts/CaptionSettingsPopout.tsx b/src/video/components/popouts/CaptionSettingsPopout.tsx new file mode 100644 index 00000000..09bf6eea --- /dev/null +++ b/src/video/components/popouts/CaptionSettingsPopout.tsx @@ -0,0 +1,150 @@ +import { FloatingCardView } from "@/components/popout/FloatingCard"; +import { FloatingView } from "@/components/popout/FloatingView"; +import { useFloatingRouter } from "@/hooks/useFloatingRouter"; +import { useSettings } from "@/state/settings"; +import { useTranslation } from "react-i18next"; +import { ChangeEventHandler, useEffect, useRef } from "react"; +import { Icon, Icons } from "@/components/Icon"; + +export type SliderProps = { + label: string; + min: number; + max: number; + step: number; + value: number; + valueDisplay?: string; + onChange: ChangeEventHandler; +}; + +export function Slider(props: SliderProps) { + const ref = useRef(null); + useEffect(() => { + const e = ref.current as HTMLInputElement; + e.style.setProperty("--value", e.value); + e.style.setProperty("--min", e.min === "" ? "0" : e.min); + e.style.setProperty("--max", e.max === "" ? "100" : e.max); + e.addEventListener("input", () => e.style.setProperty("--value", e.value)); + }, [ref]); + + return ( +
+
+ + +
+
+
+ {props.valueDisplay ?? props.value} +
+
+
+ ); +} + +export function CaptionSettingsPopout(props: { + router: ReturnType; + prefix: string; +}) { + // For now, won't add label texts to language files since options are prone to change + const { t } = useTranslation(); + const { + captionSettings, + setCaptionBackgroundColor, + setCaptionColor, + setCaptionDelay, + setCaptionFontSize, + } = useSettings(); + const colors = ["#ffffff", "#00ffff", "#ffff00"]; + return ( + + props.router.navigate("/captions")} + /> + + setCaptionDelay(e.target.valueAsNumber)} + /> + setCaptionFontSize(e.target.valueAsNumber)} + /> + + setCaptionBackgroundColor( + `${captionSettings.style.backgroundColor.substring( + 0, + 7 + )}${e.target.valueAsNumber.toString(16)}` + ) + } + /> +
+ +
+ {colors.map((color) => ( +
setCaptionColor(color)} + > +
+ +
+ ))} +
+
+ + + ); +} diff --git a/src/video/components/popouts/SettingsPopout.tsx b/src/video/components/popouts/SettingsPopout.tsx index 9640397d..20e6736f 100644 --- a/src/video/components/popouts/SettingsPopout.tsx +++ b/src/video/components/popouts/SettingsPopout.tsx @@ -7,6 +7,7 @@ import { CaptionsSelectionAction } from "@/video/components/actions/list-entries import { SourceSelectionAction } from "@/video/components/actions/list-entries/SourceSelectionAction"; import { CaptionSelectionPopout } from "./CaptionSelectionPopout"; import { SourceSelectionPopout } from "./SourceSelectionPopout"; +import { CaptionSettingsPopout } from "./CaptionSettingsPopout"; export function SettingsPopout() { const floatingRouter = useFloatingRouter(); @@ -24,6 +25,10 @@ export function SettingsPopout() { + ); } diff --git a/tsconfig.json b/tsconfig.json index e1004c43..a00c1a1f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "noEmit": true, "jsx": "react-jsx", "baseUrl": "./src", + "typeRoots": ["./src/@types"], "paths": { "@/*": ["./*"] }, diff --git a/yarn.lock b/yarn.lock index 936000aa..85ee7063 100644 --- a/yarn.lock +++ b/yarn.lock @@ -944,7 +944,7 @@ "@esbuild/darwin-arm64@0.16.5": version "0.16.5" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.5.tgz" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.5.tgz#6e553f4be193d25a5e7cb6a73077d407a64bd6ad" integrity sha512-4HlbUMy50cRaHGVriBjShs46WRPshtnVOqkxEGhEuDuJhgZ3regpWzaQxXOcDXFvVwue8RiqDAAcOi/QlVLE6Q== "@esbuild/darwin-x64@0.16.5": @@ -1034,7 +1034,7 @@ "@esbuild/win32-x64@0.16.5": version "0.16.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.5.tgz#9398d079a83b309b44021634ae6b4f7bc6a0cad0" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.5.tgz" integrity sha512-q00Jasz6/wCOD2XxRj4GEwj27u1zfpiBniL1ip3/YGGcYtvOoGKCNSS47sufO/8ixEgrSYDlkglSd6CxcS7m0g== "@eslint/eslintrc@^1.3.3": @@ -1053,9 +1053,9 @@ strip-json-comments "^3.1.1" "@formkit/auto-animate@^1.0.0-beta.5": - version "1.0.0-beta.6" - resolved "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.6.tgz" - integrity sha512-PVDhLAlr+B4Xb7e+1wozBUWmXa6BFU8xUPR/W/E+TsQhPS1qkAdAsJ25keEnFrcePSnXHrOsh3tiFbEToOzV9w== + version "1.0.0-beta.5" + resolved "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.5.tgz" + integrity sha512-WoSwyhAZPOe6RB/IgicOtCHtrWwEpfKIZ/H/nxpKfnZL9CB6hhhBGU5bCdMRw7YpAUF2CDlQa+WWh+gCqz5lDg== "@headlessui/react@^1.5.0": version "1.7.5" @@ -1264,7 +1264,7 @@ "@swc/core-darwin-arm64@1.3.22": version "1.3.22" - resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.22.tgz" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.22.tgz#296db00b567d7fab0fc438eccc7334b53d54e2f2" integrity sha512-MMhtPsuXp8gpUgr9bs+RZQ2IyFGiUNDG93usCDAFgAF+6VVp+YaAVjET/3/Bx5Lk2WAt0RxT62C9KTEw1YMo3w== "@swc/core-darwin-x64@1.3.22": @@ -1309,7 +1309,7 @@ "@swc/core-win32-x64-msvc@1.3.22": version "1.3.22" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.22.tgz#fb820b1aa03605363d141c9656d966a25000790f" + resolved "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.22.tgz" integrity sha512-ESyn4lZXAKEE3mcTaDfXatsolCiEfVGstsXdgBmZYa6o1IE1bDW8FE7Ob/Y+82WTpm9+A9ZYXYjZ62t67POHZg== "@swc/core@^1.3.21": @@ -1351,9 +1351,9 @@ integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== "@types/chrome@*": - version "0.0.217" - resolved "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.217.tgz" - integrity sha512-q8fLzCCoHiR9gYRoqvrx12+HaJjRTqUom5Ks/wLSR8Ac83qAqWaA4NgUBUcDjM1O1ACczygxIHCEENXs1zmbqQ== + version "0.0.210" + resolved "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.210.tgz" + integrity sha512-VSjQu1k6a/rAfuqR1Gi/oxHZj4+t6+LG+GobNI3ZWI6DQ+fmphNSF6TrLHG6BYK2bXc9Gb4c1uXFKRRVLaGl5Q== dependencies: "@types/filesystem" "*" "@types/har-format" "*" @@ -1370,6 +1370,13 @@ resolved "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz" integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== +"@types/dompurify@^2.4.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz" + integrity sha512-IDBwO5IZhrKvHFUl+clZxgf3hn2b/lU6H1KaBShPkQyGJUQ0xwebezIPSuiyGwfz1UzJWQl4M7BDxtHtCCPlTg== + dependencies: + "@types/trusted-types" "*" + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" @@ -1520,7 +1527,7 @@ resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== -"@types/trusted-types@^2.0.2": +"@types/trusted-types@*", "@types/trusted-types@^2.0.2": version "2.0.3" resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz" integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== @@ -1707,7 +1714,7 @@ acorn-walk@^8.0.2, acorn-walk@^8.2.0: acorn@^7.0.0: version "7.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1, acorn@^8.8.2: @@ -1992,9 +1999,9 @@ camelcase-css@^2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449: - version "1.0.30001458" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz" - integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w== + version "1.0.30001457" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz" + integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== chai@^4.3.7: version "4.3.7" @@ -2100,6 +2107,11 @@ commander@^2.20.0: resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^8.0.0: version "8.3.0" resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" @@ -2133,9 +2145,9 @@ copy-to-clipboard@^3.3.1: toggle-selection "^1.0.6" core-js-compat@^3.25.1: - version "3.29.0" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz" - integrity sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ== + version "3.28.0" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.28.0.tgz" + integrity sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg== dependencies: browserslist "^4.21.5" @@ -2145,9 +2157,9 @@ core-js-pure@^3.25.1: integrity sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ== core-js@^3.6.5: - version "3.28.0" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz" - integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw== + version "3.27.1" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz" + integrity sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww== cross-spawn@^7.0.2: version "7.0.3" @@ -2285,7 +2297,7 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -destr@^1.2.2: +destr@^1.2.1: version "1.2.2" resolved "https://registry.npmjs.org/destr/-/destr-1.2.2.tgz" integrity sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA== @@ -2350,6 +2362,11 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +dompurify@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz" + integrity sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" @@ -2872,7 +2889,7 @@ fs-extra@^9.0.1: fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fscreen@^1.2.0: @@ -2882,7 +2899,7 @@ fscreen@^1.2.0: fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: @@ -2970,13 +2987,13 @@ glob@^7.1.3, glob@^7.1.6, glob@^7.2.0: globals@^11.1.0: version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0: - version "13.19.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz" - integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" @@ -3156,7 +3173,7 @@ imurmurhash@^0.1.4: inflight@^1.0.4: version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -3164,7 +3181,7 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inline-style-prefixer@^6.0.0: @@ -3707,16 +3724,23 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-fetch-native@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.0.2.tgz" - integrity sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ== +node-fetch-native@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.0.1.tgz" + integrity sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg== node-releases@^2.0.8: version "2.0.10" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz" integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-webvtt@^1.9.4: + version "1.9.4" + resolved "https://registry.npmjs.org/node-webvtt/-/node-webvtt-1.9.4.tgz" + integrity sha512-EjrJdKdxSyd8j4LMLW6s2Ah4yNoeVXp18Ob04CQl1In18xcUmKzEE8pcsxxnFVqanTyjbGYph2VnvtwIXR4EjA== + dependencies: + commander "^7.1.0" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -3805,17 +3829,17 @@ object.values@^1.1.5: es-abstract "^1.20.4" ofetch@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/ofetch/-/ofetch-1.0.1.tgz" - integrity sha512-icBz2JYfEpt+wZz1FRoGcrMigjNKjzvufE26m9+yUiacRQRHwnNlGRPiDnW4op7WX/MR6aniwS8xw8jyVelF2g== + version "1.0.0" + resolved "https://registry.npmjs.org/ofetch/-/ofetch-1.0.0.tgz" + integrity sha512-d40aof8czZFSQKJa4+F7Ch3UC5D631cK1TTUoK+iNEut9NoiCL+u0vykl/puYVUS2df4tIQl5upQcolIcEzQjQ== dependencies: - destr "^1.2.2" - node-fetch-native "^1.0.2" - ufo "^1.1.0" + destr "^1.2.1" + node-fetch-native "^1.0.1" + ufo "^1.0.0" once@^1.3.0: version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" @@ -3891,7 +3915,7 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: @@ -4916,7 +4940,7 @@ typescript@^4.6.4: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz" integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== -ufo@^1.1.0: +ufo@^1.0.0, ufo@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/ufo/-/ufo-1.1.0.tgz" integrity sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q== @@ -5396,7 +5420,7 @@ workbox-window@6.5.4, workbox-window@^6.5.4: wrappy@1: version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^8.11.0: