Arm 64 support & liniting
This commit is contained in:
@@ -13,23 +13,20 @@ jobs:
|
||||
working-directory: ./materialious
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./materialious
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: wardpearce/materialious:latest
|
||||
|
||||
Vendored
+1
-2
@@ -10,5 +10,4 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export { };
|
||||
|
||||
export {};
|
||||
|
||||
+18
-17
@@ -1,20 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/style.css" />
|
||||
<meta name="description" content="Modern material design for Invidious." />
|
||||
<meta
|
||||
name="keywords"
|
||||
content="invidious,materialious,proxy,youtube,yt,theme,interface,modern"
|
||||
/>
|
||||
<meta name="theme-color" content="#141316" />
|
||||
<title>Materialious</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/style.css">
|
||||
<meta name="description" content="Modern material design for Invidious.">
|
||||
<meta name="keywords" content="invidious,materialious,proxy,youtube,yt,theme,interface,modern">
|
||||
<meta name="theme-color" content="#141316">
|
||||
<title>Materialious</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover" style="background-color: #141316">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<body data-sveltekit-preload-data="hover" style="background-color: #141316">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+202
-148
@@ -1,239 +1,293 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { auth, deArrowInstance, deArrowThumbnailInstance, returnYTDislikesInstance } from '../../store';
|
||||
import type { Channel, ChannelContentPlaylists, ChannelContentVideos, ChannelPage, Comments, DeArrow, Playlist, PlaylistPage, ReturnYTDislikes, SearchSuggestion, Subscription, Video, VideoPlay } from './model';
|
||||
import {
|
||||
auth,
|
||||
deArrowInstance,
|
||||
deArrowThumbnailInstance,
|
||||
returnYTDislikesInstance
|
||||
} from '../../store';
|
||||
import type {
|
||||
Channel,
|
||||
ChannelContentPlaylists,
|
||||
ChannelContentVideos,
|
||||
ChannelPage,
|
||||
Comments,
|
||||
DeArrow,
|
||||
Playlist,
|
||||
PlaylistPage,
|
||||
ReturnYTDislikes,
|
||||
SearchSuggestion,
|
||||
Subscription,
|
||||
Video,
|
||||
VideoPlay
|
||||
} from './model';
|
||||
|
||||
export function buildPath(path: string): string {
|
||||
return `${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/api/v1/${path}`;
|
||||
return `${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/api/v1/${path}`;
|
||||
}
|
||||
|
||||
export async function fetchErrorHandle(response: Response): Promise<Response> {
|
||||
if (!response.ok) {
|
||||
let message = 'Internal error';
|
||||
try {
|
||||
const json = await response.json();
|
||||
message = 'errorBacktrace' in json ? json.errorBacktrace : json.error;
|
||||
} catch { }
|
||||
throw Error(message);
|
||||
}
|
||||
if (!response.ok) {
|
||||
let message = 'Internal error';
|
||||
try {
|
||||
const json = await response.json();
|
||||
message = 'errorBacktrace' in json ? json.errorBacktrace : json.error;
|
||||
} catch { }
|
||||
throw Error(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
return response;
|
||||
}
|
||||
|
||||
export function buildAuthHeaders(): { headers: { Authorization: string; }; } {
|
||||
return { headers: { Authorization: `Bearer ${get(auth)?.token}` } };
|
||||
return { headers: { Authorization: `Bearer ${get(auth)?.token}` } };
|
||||
}
|
||||
|
||||
export async function getTrending(): Promise<Video[]> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('trending')));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('trending')));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getPopular(): Promise<Video[]> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('popular')));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('popular')));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getVideo(videoId: string, local: boolean = false): Promise<VideoPlay> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath(`videos/${videoId}?local=${local}`)));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath(`videos/${videoId}?local=${local}`)));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getDislikes(videoId: string): Promise<ReturnYTDislikes> {
|
||||
const resp = await fetchErrorHandle(await fetch(`${get(returnYTDislikesInstance)}/votes?videoId=${videoId}`));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(`${get(returnYTDislikesInstance)}/votes?videoId=${videoId}`)
|
||||
);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getComments(videoId: string, parameters: {
|
||||
sort_by?: "top" | "new",
|
||||
source?: "youtube" | "reddit",
|
||||
continuation?: string;
|
||||
}): Promise<Comments> {
|
||||
if (typeof parameters.sort_by === "undefined") {
|
||||
parameters.sort_by = "top";
|
||||
}
|
||||
export async function getComments(
|
||||
videoId: string,
|
||||
parameters: {
|
||||
sort_by?: 'top' | 'new';
|
||||
source?: 'youtube' | 'reddit';
|
||||
continuation?: string;
|
||||
}
|
||||
): Promise<Comments> {
|
||||
if (typeof parameters.sort_by === 'undefined') {
|
||||
parameters.sort_by = 'top';
|
||||
}
|
||||
|
||||
if (typeof parameters.source === "undefined") {
|
||||
parameters.source = "youtube";
|
||||
}
|
||||
if (typeof parameters.source === 'undefined') {
|
||||
parameters.source = 'youtube';
|
||||
}
|
||||
|
||||
const path = new URL(buildPath(`comments/${videoId}`));
|
||||
path.search = new URLSearchParams(parameters).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
const path = new URL(buildPath(`comments/${videoId}`));
|
||||
path.search = new URLSearchParams(parameters).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getChannel(channelId: string): Promise<ChannelPage> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`)));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`)));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getChannelContent(
|
||||
channelId: string,
|
||||
parameters: {
|
||||
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
|
||||
continuation?: string;
|
||||
}): Promise<ChannelContentVideos | ChannelContentPlaylists> {
|
||||
if (typeof parameters.type === 'undefined') parameters.type = 'videos';
|
||||
channelId: string,
|
||||
parameters: {
|
||||
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
|
||||
continuation?: string;
|
||||
}
|
||||
): Promise<ChannelContentVideos | ChannelContentPlaylists> {
|
||||
if (typeof parameters.type === 'undefined') parameters.type = 'videos';
|
||||
|
||||
const url = new URL(buildPath(`channels/${channelId}/${parameters.type}`));
|
||||
const url = new URL(buildPath(`channels/${channelId}/${parameters.type}`));
|
||||
|
||||
if (typeof parameters.continuation !== 'undefined') url.searchParams.set('continuation', parameters.continuation);
|
||||
if (typeof parameters.continuation !== 'undefined')
|
||||
url.searchParams.set('continuation', parameters.continuation);
|
||||
|
||||
const resp = await fetchErrorHandle(await fetch(url.toString()));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(url.toString()));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getSearchSuggestions(search: string): Promise<SearchSuggestion> {
|
||||
const path = new URL(buildPath("search/suggestions"));
|
||||
path.search = new URLSearchParams({ q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
const path = new URL(buildPath('search/suggestions'));
|
||||
path.search = new URLSearchParams({ q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getSearch(search: string, options: {
|
||||
sort_by?: "relevance" | "rating" | "upload_date" | "view_count",
|
||||
type?: "video" | "playlist" | "channel" | "all";
|
||||
page?: string;
|
||||
}): Promise<(Channel | Video | Playlist)[]> {
|
||||
if (typeof options.sort_by === "undefined") {
|
||||
options.sort_by = "relevance";
|
||||
}
|
||||
export async function getSearch(
|
||||
search: string,
|
||||
options: {
|
||||
sort_by?: 'relevance' | 'rating' | 'upload_date' | 'view_count';
|
||||
type?: 'video' | 'playlist' | 'channel' | 'all';
|
||||
page?: string;
|
||||
}
|
||||
): Promise<(Channel | Video | Playlist)[]> {
|
||||
if (typeof options.sort_by === 'undefined') {
|
||||
options.sort_by = 'relevance';
|
||||
}
|
||||
|
||||
if (typeof options.type === "undefined") {
|
||||
options.type = "video";
|
||||
}
|
||||
if (typeof options.type === 'undefined') {
|
||||
options.type = 'video';
|
||||
}
|
||||
|
||||
if (typeof options.page === "undefined") {
|
||||
options.page = "1";
|
||||
}
|
||||
if (typeof options.page === 'undefined') {
|
||||
options.page = '1';
|
||||
}
|
||||
|
||||
const path = new URL(buildPath("search"));
|
||||
path.search = new URLSearchParams({ ...options, q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
const path = new URL(buildPath('search'));
|
||||
path.search = new URLSearchParams({ ...options, q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getFeed(maxResults: number, page: number) {
|
||||
const path = new URL(buildPath("auth/feed"));
|
||||
path.search = new URLSearchParams({ max_results: maxResults.toString(), page: page.toString() }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path, buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
const path = new URL(buildPath('auth/feed'));
|
||||
path.search = new URLSearchParams({
|
||||
max_results: maxResults.toString(),
|
||||
page: page.toString()
|
||||
}).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path, buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getSubscriptions(): Promise<Subscription[]> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath("auth/subscriptions"), buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(buildPath('auth/subscriptions'), buildAuthHeaders())
|
||||
);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function amSubscribed(authorId: string): Promise<boolean> {
|
||||
try {
|
||||
const subscriptions = (await getSubscriptions()).filter(sub => sub.authorId === authorId);
|
||||
return subscriptions.length === 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const subscriptions = (await getSubscriptions()).filter((sub) => sub.authorId === authorId);
|
||||
return subscriptions.length === 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function postSubscribe(authorId: string) {
|
||||
await fetchErrorHandle(await fetch(buildPath(`auth/subscriptions/${authorId}`), {
|
||||
method: "POST",
|
||||
...buildAuthHeaders()
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/subscriptions/${authorId}`), {
|
||||
method: 'POST',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteUnsubscribe(authorId: string) {
|
||||
await fetchErrorHandle(await fetch(buildPath(`auth/subscriptions/${authorId}`), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/subscriptions/${authorId}`), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function getHistory(page: number = 1): Promise<string[]> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath(`auth/history?page=${page}`), buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/history?page=${page}`), buildAuthHeaders())
|
||||
);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function deleteHistory(videoId: string | undefined = undefined) {
|
||||
let url = '/api/v1/auth/history';
|
||||
if (typeof videoId !== 'undefined') {
|
||||
url += `/${videoId}`;
|
||||
}
|
||||
let url = '/api/v1/auth/history';
|
||||
if (typeof videoId !== 'undefined') {
|
||||
url += `/${videoId}`;
|
||||
}
|
||||
|
||||
await fetchErrorHandle(await fetch(buildPath(url), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(url), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function postHistory(videoId: string) {
|
||||
await fetchErrorHandle(await fetch(buildPath(`auth/history/${videoId}`), {
|
||||
method: 'POST',
|
||||
...buildAuthHeaders()
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/history/${videoId}`), {
|
||||
method: 'POST',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPlaylist(playlistId: string, page: number = 1): Promise<PlaylistPage> {
|
||||
let resp;
|
||||
let resp;
|
||||
|
||||
if (get(auth)) {
|
||||
resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), buildAuthHeaders());
|
||||
} else {
|
||||
resp = await fetch(buildPath(`playlists/${playlistId}?page=${page}`));
|
||||
}
|
||||
await fetchErrorHandle(resp);
|
||||
return await resp.json();
|
||||
if (get(auth)) {
|
||||
resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), buildAuthHeaders());
|
||||
} else {
|
||||
resp = await fetch(buildPath(`playlists/${playlistId}?page=${page}`));
|
||||
}
|
||||
await fetchErrorHandle(resp);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getPersonalPlaylists(): Promise<PlaylistPage[]> {
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), buildAuthHeaders()));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function deletePersonalPlaylist(playlistId: string) {
|
||||
await fetchErrorHandle(await fetch(buildPath(`auth/playlists/${playlistId}`), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/playlists/${playlistId}`), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function postPersonalPlaylist(title: string, privacy: 'public' | 'private' | 'unlisted') {
|
||||
let headers: Record<string, Record<string, string>> = buildAuthHeaders();
|
||||
headers['headers']['Content-type'] = 'application/json';
|
||||
export async function postPersonalPlaylist(
|
||||
title: string,
|
||||
privacy: 'public' | 'private' | 'unlisted'
|
||||
) {
|
||||
const headers: Record<string, Record<string, string>> = buildAuthHeaders();
|
||||
headers['headers']['Content-type'] = 'application/json';
|
||||
|
||||
await fetchErrorHandle(await fetch(buildPath('auth/playlists'), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: title,
|
||||
privacy: privacy
|
||||
}),
|
||||
...headers
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath('auth/playlists'), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: title,
|
||||
privacy: privacy
|
||||
}),
|
||||
...headers
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function addPlaylistVideo(playlistId: string, videoId: string) {
|
||||
let headers: Record<string, Record<string, string>> = buildAuthHeaders();
|
||||
headers['headers']['Content-type'] = 'application/json';
|
||||
const headers: Record<string, Record<string, string>> = buildAuthHeaders();
|
||||
headers['headers']['Content-type'] = 'application/json';
|
||||
|
||||
await fetchErrorHandle(await fetch(buildPath(`auth/playlists/${playlistId}/videos`), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
videoId: videoId
|
||||
}),
|
||||
...headers
|
||||
}));
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/playlists/${playlistId}/videos`), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
videoId: videoId
|
||||
}),
|
||||
...headers
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function getDeArrow(videoId: string): Promise<DeArrow> {
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(`${get(deArrowInstance)}/api/branding?videoID=${videoId}`
|
||||
)
|
||||
);
|
||||
return await resp.json();
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(`${get(deArrowInstance)}/api/branding?videoID=${videoId}`)
|
||||
);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getThumbnail(videoId: string, time: number): Promise<string> {
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(`${get(deArrowThumbnailInstance)}/api/v1/getThumbnail?videoID=${videoId}&time=${time}`)
|
||||
);
|
||||
return URL.createObjectURL(await resp.blob());
|
||||
}
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(
|
||||
`${get(deArrowThumbnailInstance)}/api/v1/getThumbnail?videoID=${videoId}&time=${time}`
|
||||
)
|
||||
);
|
||||
return URL.createObjectURL(await resp.blob());
|
||||
}
|
||||
|
||||
+182
-183
@@ -1,252 +1,251 @@
|
||||
export interface Image {
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
quality: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
quality: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
|
||||
export interface VideoBase {
|
||||
videoId: string;
|
||||
title: string;
|
||||
videoThumbnails: Thumbnail[];
|
||||
author: string;
|
||||
authorId: string;
|
||||
lengthSeconds: number;
|
||||
viewCountText: string;
|
||||
videoId: string;
|
||||
title: string;
|
||||
videoThumbnails: Thumbnail[];
|
||||
author: string;
|
||||
authorId: string;
|
||||
lengthSeconds: number;
|
||||
viewCountText: string;
|
||||
}
|
||||
|
||||
export interface Video extends VideoBase {
|
||||
type: "video";
|
||||
title: string;
|
||||
authorUrl: string;
|
||||
authorVerified: boolean;
|
||||
description: string;
|
||||
descriptionHtml: string;
|
||||
viewCount: number;
|
||||
published: number;
|
||||
publishedText: string;
|
||||
premiereTimestamp: number;
|
||||
liveNow: boolean;
|
||||
premium: boolean;
|
||||
isUpcoming: boolean;
|
||||
type: 'video';
|
||||
title: string;
|
||||
authorUrl: string;
|
||||
authorVerified: boolean;
|
||||
description: string;
|
||||
descriptionHtml: string;
|
||||
viewCount: number;
|
||||
published: number;
|
||||
publishedText: string;
|
||||
premiereTimestamp: number;
|
||||
liveNow: boolean;
|
||||
premium: boolean;
|
||||
isUpcoming: boolean;
|
||||
}
|
||||
|
||||
export interface AdaptiveFormats {
|
||||
index: string;
|
||||
bitrate: string;
|
||||
init: string;
|
||||
url: string;
|
||||
itag: string;
|
||||
type: string;
|
||||
clen: string;
|
||||
lmt: string;
|
||||
projectionType: number;
|
||||
container?: string;
|
||||
encoding?: string;
|
||||
qualityLabel?: string;
|
||||
resolution?: string;
|
||||
audioQuality?: string;
|
||||
index: string;
|
||||
bitrate: string;
|
||||
init: string;
|
||||
url: string;
|
||||
itag: string;
|
||||
type: string;
|
||||
clen: string;
|
||||
lmt: string;
|
||||
projectionType: number;
|
||||
container?: string;
|
||||
encoding?: string;
|
||||
qualityLabel?: string;
|
||||
resolution?: string;
|
||||
audioQuality?: string;
|
||||
}
|
||||
|
||||
export interface FormatStreams {
|
||||
url: string;
|
||||
itag: string;
|
||||
type: string;
|
||||
quality: string;
|
||||
container: string;
|
||||
encoding: string;
|
||||
qualityLabel: string;
|
||||
resolution: string;
|
||||
size: string;
|
||||
url: string;
|
||||
itag: string;
|
||||
type: string;
|
||||
quality: string;
|
||||
container: string;
|
||||
encoding: string;
|
||||
qualityLabel: string;
|
||||
resolution: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
export interface Captions {
|
||||
label: string;
|
||||
language_code: string;
|
||||
url: string;
|
||||
};
|
||||
label: string;
|
||||
language_code: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface VideoPlay extends Video {
|
||||
keywords: string[];
|
||||
likeCount: number;
|
||||
dislikeCount: number;
|
||||
subCountText: string;
|
||||
allowRatings: boolean;
|
||||
rating: number;
|
||||
isListed: number;
|
||||
isFamilyFriendly: boolean;
|
||||
allowedRegions: string[];
|
||||
genre: string;
|
||||
genreUrl: string;
|
||||
hlsUrl?: string;
|
||||
dashUrl: string;
|
||||
adaptiveFormats: AdaptiveFormats[];
|
||||
formatStreams: FormatStreams[];
|
||||
recommendedVideos: VideoBase[];
|
||||
authorThumbnails: Image[];
|
||||
captions: Captions[];
|
||||
storyboards?: {
|
||||
url: string;
|
||||
templateUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
count: number;
|
||||
interval: number;
|
||||
storyboardWidth: number;
|
||||
storyboardHeight: number;
|
||||
storyboardCount: number;
|
||||
}[];
|
||||
keywords: string[];
|
||||
likeCount: number;
|
||||
dislikeCount: number;
|
||||
subCountText: string;
|
||||
allowRatings: boolean;
|
||||
rating: number;
|
||||
isListed: number;
|
||||
isFamilyFriendly: boolean;
|
||||
allowedRegions: string[];
|
||||
genre: string;
|
||||
genreUrl: string;
|
||||
hlsUrl?: string;
|
||||
dashUrl: string;
|
||||
adaptiveFormats: AdaptiveFormats[];
|
||||
formatStreams: FormatStreams[];
|
||||
recommendedVideos: VideoBase[];
|
||||
authorThumbnails: Image[];
|
||||
captions: Captions[];
|
||||
storyboards?: {
|
||||
url: string;
|
||||
templateUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
count: number;
|
||||
interval: number;
|
||||
storyboardWidth: number;
|
||||
storyboardHeight: number;
|
||||
storyboardCount: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ReturnYTDislikes {
|
||||
id: string;
|
||||
dateCreated: string;
|
||||
likes: number;
|
||||
dislikes: number;
|
||||
rating: number;
|
||||
viewCount: number;
|
||||
deleted: boolean;
|
||||
id: string;
|
||||
dateCreated: string;
|
||||
likes: number;
|
||||
dislikes: number;
|
||||
rating: number;
|
||||
viewCount: number;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
author: string;
|
||||
authorThumbnails: Image[];
|
||||
authorID: string;
|
||||
authorUrl: string;
|
||||
isEdited: boolean;
|
||||
isPinned: boolean;
|
||||
content: string;
|
||||
contentHtml: string;
|
||||
published: number;
|
||||
publishedText: string;
|
||||
likeCount: number;
|
||||
authorIsChannelOwner: boolean;
|
||||
creatorHeart: {
|
||||
creatorThumbnail: string;
|
||||
creatorName: string;
|
||||
};
|
||||
replies: {
|
||||
replyCount: number;
|
||||
continuation: string;
|
||||
};
|
||||
author: string;
|
||||
authorThumbnails: Image[];
|
||||
authorID: string;
|
||||
authorUrl: string;
|
||||
isEdited: boolean;
|
||||
isPinned: boolean;
|
||||
content: string;
|
||||
contentHtml: string;
|
||||
published: number;
|
||||
publishedText: string;
|
||||
likeCount: number;
|
||||
authorIsChannelOwner: boolean;
|
||||
creatorHeart: {
|
||||
creatorThumbnail: string;
|
||||
creatorName: string;
|
||||
};
|
||||
replies: {
|
||||
replyCount: number;
|
||||
continuation: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Comments {
|
||||
commentCount: number;
|
||||
videoId: string;
|
||||
continuation?: string;
|
||||
comments: Comment[];
|
||||
commentCount: number;
|
||||
videoId: string;
|
||||
continuation?: string;
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
type: "channel";
|
||||
author: string;
|
||||
authorId: string;
|
||||
authorUrl: string;
|
||||
authorVerified: boolean;
|
||||
subCount: number;
|
||||
totalViews: number;
|
||||
autoGenerated: boolean;
|
||||
description: string;
|
||||
descriptionHml: string;
|
||||
authorThumbnails: Image[];
|
||||
type: 'channel';
|
||||
author: string;
|
||||
authorId: string;
|
||||
authorUrl: string;
|
||||
authorVerified: boolean;
|
||||
subCount: number;
|
||||
totalViews: number;
|
||||
autoGenerated: boolean;
|
||||
description: string;
|
||||
descriptionHml: string;
|
||||
authorThumbnails: Image[];
|
||||
}
|
||||
|
||||
export interface PlaylistVideo {
|
||||
title: string;
|
||||
videoId: string;
|
||||
lengthSeconds: number;
|
||||
videoThumbnails: Thumbnail[];
|
||||
title: string;
|
||||
videoId: string;
|
||||
lengthSeconds: number;
|
||||
videoThumbnails: Thumbnail[];
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
type: "playlist";
|
||||
title: string;
|
||||
playlistId: string;
|
||||
playlistThumbnail: string;
|
||||
author: string;
|
||||
authorId: string;
|
||||
authorVerified: boolean;
|
||||
videoCount: number;
|
||||
videos: PlaylistVideo[];
|
||||
type: 'playlist';
|
||||
title: string;
|
||||
playlistId: string;
|
||||
playlistThumbnail: string;
|
||||
author: string;
|
||||
authorId: string;
|
||||
authorVerified: boolean;
|
||||
videoCount: number;
|
||||
videos: PlaylistVideo[];
|
||||
}
|
||||
|
||||
export interface PlaylistPageVideo extends PlaylistVideo {
|
||||
author: string;
|
||||
index: number;
|
||||
authorId: string;
|
||||
viewCount: number;
|
||||
author: string;
|
||||
index: number;
|
||||
authorId: string;
|
||||
viewCount: number;
|
||||
}
|
||||
|
||||
export interface ChannelContentVideos {
|
||||
videos: Video[];
|
||||
continuation: string;
|
||||
videos: Video[];
|
||||
continuation: string;
|
||||
}
|
||||
|
||||
export interface ChannelContentPlaylists {
|
||||
playlists: PlaylistPage[];
|
||||
continuation: string;
|
||||
playlists: PlaylistPage[];
|
||||
continuation: string;
|
||||
}
|
||||
|
||||
export interface PlaylistPage extends Playlist {
|
||||
description: string;
|
||||
descriptionHtml: string;
|
||||
viewCount: number;
|
||||
updated: number;
|
||||
isListed: boolean;
|
||||
videos: PlaylistPageVideo[];
|
||||
description: string;
|
||||
descriptionHtml: string;
|
||||
viewCount: number;
|
||||
updated: number;
|
||||
isListed: boolean;
|
||||
videos: PlaylistPageVideo[];
|
||||
}
|
||||
|
||||
export interface ChannelPage extends Channel {
|
||||
allowedRegions: string[];
|
||||
tabs: string[];
|
||||
latestVideos: Video[];
|
||||
isFamilyFriendly: boolean;
|
||||
joined: number;
|
||||
authorBanners: Image[];
|
||||
allowedRegions: string[];
|
||||
tabs: string[];
|
||||
latestVideos: Video[];
|
||||
isFamilyFriendly: boolean;
|
||||
joined: number;
|
||||
authorBanners: Image[];
|
||||
}
|
||||
|
||||
export interface SearchSuggestion {
|
||||
query: string;
|
||||
suggestions: string[];
|
||||
query: string;
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
export interface Notification extends VideoBase {
|
||||
type: "video" | "shortVideo" | "stream";
|
||||
type: 'video' | 'shortVideo' | 'stream';
|
||||
}
|
||||
|
||||
export interface Feed {
|
||||
notifications: Notification[];
|
||||
videos: Video[];
|
||||
notifications: Notification[];
|
||||
videos: Video[];
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
author: string;
|
||||
authorId: string;
|
||||
author: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export interface DeArrow {
|
||||
titles: {
|
||||
title: string;
|
||||
original: boolean;
|
||||
votes: number;
|
||||
locked: boolean;
|
||||
UUID: string;
|
||||
}[];
|
||||
thumbnails: {
|
||||
timestamp: number | null;
|
||||
original: boolean;
|
||||
votes: number;
|
||||
locked: boolean;
|
||||
UUID: string;
|
||||
}[];
|
||||
randomTime: number;
|
||||
videoDuration: number;
|
||||
}
|
||||
titles: {
|
||||
title: string;
|
||||
original: boolean;
|
||||
votes: number;
|
||||
locked: boolean;
|
||||
UUID: string;
|
||||
}[];
|
||||
thumbnails: {
|
||||
timestamp: number | null;
|
||||
original: boolean;
|
||||
votes: number;
|
||||
locked: boolean;
|
||||
UUID: string;
|
||||
}[];
|
||||
randomTime: number;
|
||||
videoDuration: number;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
export let channel: Channel;
|
||||
|
||||
let loading = true;
|
||||
let loaded = false;
|
||||
let failed = false;
|
||||
|
||||
let img: HTMLImageElement;
|
||||
|
||||
@@ -17,12 +15,10 @@
|
||||
img.src = channel.authorThumbnails[0].url;
|
||||
|
||||
img.onload = () => {
|
||||
loaded = true;
|
||||
loading = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
loading = false;
|
||||
failed = true;
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
+140
-140
@@ -1,154 +1,154 @@
|
||||
import { page } from "$app/stores";
|
||||
import { get } from "svelte/store";
|
||||
import { page } from '$app/stores';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
darkMode,
|
||||
deArrowEnabled,
|
||||
deArrowInstance,
|
||||
deArrowThumbnailInstance,
|
||||
interfaceSearchSuggestions,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
playerAutoplayNextByDefault,
|
||||
playerDash,
|
||||
playerListenByDefault,
|
||||
playerProxyVideos,
|
||||
playerSavePlaybackPosition,
|
||||
playerTheatreModeByDefault,
|
||||
returnYTDislikesInstance,
|
||||
sponsorBlock,
|
||||
sponsorBlockCategories,
|
||||
sponsorBlockUrl,
|
||||
themeColor
|
||||
} from "../store";
|
||||
darkMode,
|
||||
deArrowEnabled,
|
||||
deArrowInstance,
|
||||
deArrowThumbnailInstance,
|
||||
interfaceSearchSuggestions,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
playerAutoplayNextByDefault,
|
||||
playerDash,
|
||||
playerListenByDefault,
|
||||
playerProxyVideos,
|
||||
playerSavePlaybackPosition,
|
||||
playerTheatreModeByDefault,
|
||||
returnYTDislikesInstance,
|
||||
sponsorBlock,
|
||||
sponsorBlockCategories,
|
||||
sponsorBlockUrl,
|
||||
themeColor
|
||||
} from '../store';
|
||||
|
||||
const persistedStores = [
|
||||
{
|
||||
name: 'returnYTDislikesInstance',
|
||||
store: returnYTDislikesInstance,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'darkMode',
|
||||
store: darkMode,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'themeColor',
|
||||
store: themeColor,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'autoPlay',
|
||||
store: playerAutoPlay,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'alwaysLoop',
|
||||
store: playerAlwaysLoop,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'proxyVideos',
|
||||
store: playerProxyVideos,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'listenByDefault',
|
||||
store: playerListenByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'savePlaybackPosition',
|
||||
store: playerSavePlaybackPosition,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'dashEnabled',
|
||||
store: playerDash,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'theatreModeByDefault',
|
||||
store: playerTheatreModeByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'autoplayNextByDefault',
|
||||
store: playerAutoplayNextByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'returnYtDislikes',
|
||||
store: returnYTDislikesInstance,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'searchSuggestions',
|
||||
store: interfaceSearchSuggestions,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlock',
|
||||
store: sponsorBlock,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockUrl',
|
||||
store: sponsorBlockUrl,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockCategories',
|
||||
store: sponsorBlockCategories,
|
||||
type: 'array'
|
||||
},
|
||||
{
|
||||
name: 'deArrowInstance',
|
||||
store: deArrowInstance,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'deArrowEnabled',
|
||||
store: deArrowEnabled,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'deArrowThumbnailInstance',
|
||||
store: deArrowThumbnailInstance,
|
||||
type: 'string'
|
||||
}
|
||||
{
|
||||
name: 'returnYTDislikesInstance',
|
||||
store: returnYTDislikesInstance,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'darkMode',
|
||||
store: darkMode,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'themeColor',
|
||||
store: themeColor,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'autoPlay',
|
||||
store: playerAutoPlay,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'alwaysLoop',
|
||||
store: playerAlwaysLoop,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'proxyVideos',
|
||||
store: playerProxyVideos,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'listenByDefault',
|
||||
store: playerListenByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'savePlaybackPosition',
|
||||
store: playerSavePlaybackPosition,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'dashEnabled',
|
||||
store: playerDash,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'theatreModeByDefault',
|
||||
store: playerTheatreModeByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'autoplayNextByDefault',
|
||||
store: playerAutoplayNextByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'returnYtDislikes',
|
||||
store: returnYTDislikesInstance,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'searchSuggestions',
|
||||
store: interfaceSearchSuggestions,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlock',
|
||||
store: sponsorBlock,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockUrl',
|
||||
store: sponsorBlockUrl,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockCategories',
|
||||
store: sponsorBlockCategories,
|
||||
type: 'array'
|
||||
},
|
||||
{
|
||||
name: 'deArrowInstance',
|
||||
store: deArrowInstance,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'deArrowEnabled',
|
||||
store: deArrowEnabled,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'deArrowThumbnailInstance',
|
||||
store: deArrowThumbnailInstance,
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
export function bookmarkletSaveToUrl(): string {
|
||||
const url = new URL(import.meta.env.VITE_DEFAULT_FRONTEND_URL);
|
||||
const url = new URL(import.meta.env.VITE_DEFAULT_FRONTEND_URL);
|
||||
|
||||
persistedStores.forEach(store => {
|
||||
let value = get(store.store);
|
||||
if (value !== null) {
|
||||
url.searchParams.set(store.name, value.toString());
|
||||
}
|
||||
});
|
||||
persistedStores.forEach((store) => {
|
||||
let value = get(store.store);
|
||||
if (value !== null) {
|
||||
url.searchParams.set(store.name, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return url.toString();
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export function bookmarkletLoadFromUrl() {
|
||||
const currentPage = get(page);
|
||||
const currentPage = get(page);
|
||||
|
||||
persistedStores.forEach(store => {
|
||||
let paramValue = currentPage.url.searchParams.get(store.name);
|
||||
if (paramValue) {
|
||||
let value: any;
|
||||
persistedStores.forEach((store) => {
|
||||
let paramValue = currentPage.url.searchParams.get(store.name);
|
||||
if (paramValue) {
|
||||
let value: any;
|
||||
|
||||
if (store.type === 'array') {
|
||||
value = paramValue.split(',');
|
||||
} else if (store.type === 'boolean') {
|
||||
value = paramValue === 'true';
|
||||
} else {
|
||||
value = paramValue;
|
||||
}
|
||||
if (store.type === 'array') {
|
||||
value = paramValue.split(',');
|
||||
} else if (store.type === 'boolean') {
|
||||
value = paramValue === 'true';
|
||||
} else {
|
||||
value = paramValue;
|
||||
}
|
||||
|
||||
store.store.set(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
store.store.set(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ const defaultLocale = 'en';
|
||||
register('en', () => import('./locales/en.json'));
|
||||
|
||||
init({
|
||||
fallbackLocale: defaultLocale,
|
||||
initialLocale: browser ? window.navigator.language : defaultLocale,
|
||||
fallbackLocale: defaultLocale,
|
||||
initialLocale: browser ? window.navigator.language : defaultLocale
|
||||
});
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
{
|
||||
"enabled": "Enabled",
|
||||
"copyUrl": "Copy URL",
|
||||
"loadMore": "Load more",
|
||||
"views": "views",
|
||||
"videos": "videos",
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"title": "Title",
|
||||
"searchPlaceholder": "Search...",
|
||||
"deleteAllHistory": "Delete all history",
|
||||
"skipping": "Skipping",
|
||||
"replies": "replies",
|
||||
"videoTabs": {
|
||||
"all": "All",
|
||||
"videos": "Videos",
|
||||
"playlists": "Playlists",
|
||||
"channels": "Channels",
|
||||
"shorts": "Shorts",
|
||||
"streams": "Streams"
|
||||
},
|
||||
"subscriptions": {
|
||||
"manageSubscriptions": "Manage subscriptions"
|
||||
},
|
||||
"playlist": {
|
||||
"createPlaylist": "Create playlist",
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
"private": "Private"
|
||||
},
|
||||
"thumbnail": {
|
||||
"live": "LIVE",
|
||||
"failedToLoadImage": "Failed to load image"
|
||||
},
|
||||
"pages": {
|
||||
"home": "Home",
|
||||
"trending": "Trending",
|
||||
"subscriptions": "Subscriptions",
|
||||
"playlists": "Playlists",
|
||||
"history": "History"
|
||||
},
|
||||
"subscribers": "subscribers",
|
||||
"subscribe": "Subscribe",
|
||||
"unsubscribe": "Unsubscribe",
|
||||
"loginRequired": "Login required",
|
||||
"player": {
|
||||
"audioOnly": "Audio only",
|
||||
"theatreMode": "Theatre mode",
|
||||
"share": {
|
||||
"title": "Share",
|
||||
"materialiousLink": "Copy Materialious link",
|
||||
"invidiousRedirect": "Copy Invidious redirect link",
|
||||
"youtubeLink": "Copy Youtube link"
|
||||
},
|
||||
"download": "Download",
|
||||
"addToPlaylist": "Add to playlist",
|
||||
"noPlaylists": "No playlists",
|
||||
"unableToLoadComments": "Unable to load comments"
|
||||
},
|
||||
"layout": {
|
||||
"star": "Star us on Github!",
|
||||
"syncParty": "Sync party",
|
||||
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
|
||||
"startSyncParty": "Start sync party",
|
||||
"endSyncParty": "End sync party",
|
||||
"notifications": "Notifications",
|
||||
"noNewNotifications": "No new notifications here",
|
||||
"settings": "Settings",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"customize": "Customize",
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"darkMode": "Dark mode",
|
||||
"lightMode": "Light mode",
|
||||
"color": "Color"
|
||||
},
|
||||
"searchSuggestions": "Search suggestions",
|
||||
"dataPreferences": {
|
||||
"dataPreferences": "Data preferences",
|
||||
"content": "Looking to import/export subscriptions, change password or delete account? Click here and scroll to the bottom of the page."
|
||||
},
|
||||
"player": {
|
||||
"title": "Player",
|
||||
"autoPlay": "Autoplay video",
|
||||
"alwaysLoopVideo": "Always loop video",
|
||||
"proxyVideos": "Proxy videos",
|
||||
"savePlaybackPosition": "Save playback position",
|
||||
"listenByDefault": "Listen by default",
|
||||
"theatreModeByDefault": "Theatre mode by default",
|
||||
"autoPlayNextByDefault": "Autoplay next by default",
|
||||
"dash": "Dash"
|
||||
},
|
||||
"instanceUrl": "Instance URL",
|
||||
"sponsors": {
|
||||
"sponsor": "Sponsor",
|
||||
"unpaidSelfPromotion": "Unpaid/Self Promotion",
|
||||
"interactionReminder": "Interaction Reminder (Subscribe)",
|
||||
"intermissionIntroAnimation": "Intermission/Intro Animation",
|
||||
"credits": "Endcards/Credits",
|
||||
"preViewRecapHook": "Preview/Recap/Hook",
|
||||
"tangentJokes": "Filler Tangent/Jokes"
|
||||
},
|
||||
"deArrow": {
|
||||
"title": "DeArrow",
|
||||
"thumbnailInstanceUrl": "Thumbnail instance URL"
|
||||
},
|
||||
"bookmarklet": "Bookmarklet"
|
||||
}
|
||||
}
|
||||
"enabled": "Enabled",
|
||||
"copyUrl": "Copy URL",
|
||||
"loadMore": "Load more",
|
||||
"views": "views",
|
||||
"videos": "videos",
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"title": "Title",
|
||||
"searchPlaceholder": "Search...",
|
||||
"deleteAllHistory": "Delete all history",
|
||||
"skipping": "Skipping",
|
||||
"replies": "replies",
|
||||
"videoTabs": {
|
||||
"all": "All",
|
||||
"videos": "Videos",
|
||||
"playlists": "Playlists",
|
||||
"channels": "Channels",
|
||||
"shorts": "Shorts",
|
||||
"streams": "Streams"
|
||||
},
|
||||
"subscriptions": {
|
||||
"manageSubscriptions": "Manage subscriptions"
|
||||
},
|
||||
"playlist": {
|
||||
"createPlaylist": "Create playlist",
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
"private": "Private"
|
||||
},
|
||||
"thumbnail": {
|
||||
"live": "LIVE",
|
||||
"failedToLoadImage": "Failed to load image"
|
||||
},
|
||||
"pages": {
|
||||
"home": "Home",
|
||||
"trending": "Trending",
|
||||
"subscriptions": "Subscriptions",
|
||||
"playlists": "Playlists",
|
||||
"history": "History"
|
||||
},
|
||||
"subscribers": "subscribers",
|
||||
"subscribe": "Subscribe",
|
||||
"unsubscribe": "Unsubscribe",
|
||||
"loginRequired": "Login required",
|
||||
"player": {
|
||||
"audioOnly": "Audio only",
|
||||
"theatreMode": "Theatre mode",
|
||||
"share": {
|
||||
"title": "Share",
|
||||
"materialiousLink": "Copy Materialious link",
|
||||
"invidiousRedirect": "Copy Invidious redirect link",
|
||||
"youtubeLink": "Copy Youtube link"
|
||||
},
|
||||
"download": "Download",
|
||||
"addToPlaylist": "Add to playlist",
|
||||
"noPlaylists": "No playlists",
|
||||
"unableToLoadComments": "Unable to load comments"
|
||||
},
|
||||
"layout": {
|
||||
"star": "Star us on Github!",
|
||||
"syncParty": "Sync party",
|
||||
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
|
||||
"startSyncParty": "Start sync party",
|
||||
"endSyncParty": "End sync party",
|
||||
"notifications": "Notifications",
|
||||
"noNewNotifications": "No new notifications here",
|
||||
"settings": "Settings",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"customize": "Customize",
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"darkMode": "Dark mode",
|
||||
"lightMode": "Light mode",
|
||||
"color": "Color"
|
||||
},
|
||||
"searchSuggestions": "Search suggestions",
|
||||
"dataPreferences": {
|
||||
"dataPreferences": "Data preferences",
|
||||
"content": "Looking to import/export subscriptions, change password or delete account? Click here and scroll to the bottom of the page."
|
||||
},
|
||||
"player": {
|
||||
"title": "Player",
|
||||
"autoPlay": "Autoplay video",
|
||||
"alwaysLoopVideo": "Always loop video",
|
||||
"proxyVideos": "Proxy videos",
|
||||
"savePlaybackPosition": "Save playback position",
|
||||
"listenByDefault": "Listen by default",
|
||||
"theatreModeByDefault": "Theatre mode by default",
|
||||
"autoPlayNextByDefault": "Autoplay next by default",
|
||||
"dash": "Dash"
|
||||
},
|
||||
"instanceUrl": "Instance URL",
|
||||
"sponsors": {
|
||||
"sponsor": "Sponsor",
|
||||
"unpaidSelfPromotion": "Unpaid/Self Promotion",
|
||||
"interactionReminder": "Interaction Reminder (Subscribe)",
|
||||
"intermissionIntroAnimation": "Intermission/Intro Animation",
|
||||
"credits": "Endcards/Credits",
|
||||
"preViewRecapHook": "Preview/Recap/Hook",
|
||||
"tangentJokes": "Filler Tangent/Jokes"
|
||||
},
|
||||
"deArrow": {
|
||||
"title": "DeArrow",
|
||||
"thumbnailInstanceUrl": "Thumbnail instance URL"
|
||||
},
|
||||
"bookmarklet": "Bookmarklet"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +1,107 @@
|
||||
import humanNumber from "human-number";
|
||||
import humanNumber from 'human-number';
|
||||
|
||||
export function truncate(value: string, maxLength: number = 50): string {
|
||||
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
|
||||
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
|
||||
}
|
||||
|
||||
export function numberWithCommas(number: number) {
|
||||
if (typeof number === "undefined") return;
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
if (typeof number === 'undefined') return;
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
export function cleanNumber(number: number): string {
|
||||
return humanNumber(number, (number: number) => Number.parseFloat(number.toString()).toFixed(1)).replace('.0', '');
|
||||
return humanNumber(number, (number: number) =>
|
||||
Number.parseFloat(number.toString()).toFixed(1)
|
||||
).replace('.0', '');
|
||||
}
|
||||
|
||||
export function videoLength(lengthSeconds: number): string {
|
||||
const hours = Math.floor(lengthSeconds / 3600);
|
||||
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
|
||||
let seconds: number | string = lengthSeconds % 60;
|
||||
const hours = Math.floor(lengthSeconds / 3600);
|
||||
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
|
||||
let seconds: number | string = lengthSeconds % 60;
|
||||
|
||||
if (minutes < 10) {
|
||||
minutes = `0${minutes}`;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
minutes = `0${minutes}`;
|
||||
}
|
||||
|
||||
if (seconds < 10) {
|
||||
seconds = `0${seconds}`;
|
||||
}
|
||||
if (seconds < 10) {
|
||||
seconds = `0${seconds}`;
|
||||
}
|
||||
|
||||
|
||||
if (hours !== 0) {
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
} else {
|
||||
return `${minutes}:${seconds}`;
|
||||
}
|
||||
if (hours !== 0) {
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
} else {
|
||||
return `${minutes}:${seconds}`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PhasedDescription {
|
||||
description: string, timestamps: { title: string; time: number; timePretty: string; }[];
|
||||
description: string;
|
||||
timestamps: { title: string; time: number; timePretty: string }[];
|
||||
}
|
||||
|
||||
export function phaseDescription(content: string): PhasedDescription {
|
||||
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
|
||||
const lines = content.split('\n');
|
||||
const timestamps: { title: string; time: number; timePretty: string }[] = [];
|
||||
const lines = content.split('\n');
|
||||
|
||||
const urlRegex = /<a href="([^"]+)"/;
|
||||
const timestampRegex = /<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
|
||||
const urlRegex = /<a href="([^"]+)"/;
|
||||
const timestampRegex =
|
||||
/<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
|
||||
|
||||
let filteredLines: string[] = [];
|
||||
lines.forEach(
|
||||
(line) => {
|
||||
const urlMatch = urlRegex.exec(line);
|
||||
const timestampMatch = timestampRegex.exec(line);
|
||||
let filteredLines: string[] = [];
|
||||
lines.forEach((line) => {
|
||||
const urlMatch = urlRegex.exec(line);
|
||||
const timestampMatch = timestampRegex.exec(line);
|
||||
|
||||
if (urlMatch !== null && timestampMatch === null) {
|
||||
// If line contains a URL but not a timestamp, modify the URL
|
||||
const modifiedLine = line.replace(/<a href="([^"]+)"/, '<a href="$1" target="_blank" rel="noopener noreferrer" class="link"');
|
||||
filteredLines.push(modifiedLine);
|
||||
} else if (timestampMatch !== null) {
|
||||
// If line contains a timestamp, extract details and push into timestamps array
|
||||
const time = timestampMatch[2];
|
||||
const timestamp = timestampMatch[3];
|
||||
const title = timestampMatch[4] || '';
|
||||
timestamps.push({
|
||||
time: convertToSeconds(time),
|
||||
title: title,
|
||||
timePretty: timestamp
|
||||
});
|
||||
} else {
|
||||
filteredLines.push(line);
|
||||
}
|
||||
}
|
||||
);
|
||||
if (urlMatch !== null && timestampMatch === null) {
|
||||
// If line contains a URL but not a timestamp, modify the URL
|
||||
const modifiedLine = line.replace(
|
||||
/<a href="([^"]+)"/,
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link"'
|
||||
);
|
||||
filteredLines.push(modifiedLine);
|
||||
} else if (timestampMatch !== null) {
|
||||
// If line contains a timestamp, extract details and push into timestamps array
|
||||
const time = timestampMatch[2];
|
||||
const timestamp = timestampMatch[3];
|
||||
const title = timestampMatch[4] || '';
|
||||
timestamps.push({
|
||||
time: convertToSeconds(time),
|
||||
title: title,
|
||||
timePretty: timestamp
|
||||
});
|
||||
} else {
|
||||
filteredLines.push(line);
|
||||
}
|
||||
});
|
||||
|
||||
const filteredContent = filteredLines.join('\n');
|
||||
|
||||
const filteredContent = filteredLines.join('\n');
|
||||
|
||||
return { description: filteredContent, timestamps: timestamps };
|
||||
return { description: filteredContent, timestamps: timestamps };
|
||||
}
|
||||
|
||||
function convertToSeconds(time: string): number {
|
||||
const parts = time.split(':').map(part => parseInt(part));
|
||||
let seconds = 0;
|
||||
if (parts.length === 3) {
|
||||
// hh:mm:ss
|
||||
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
||||
} else if (parts.length === 2) {
|
||||
// hh:ss or m:ss
|
||||
seconds = parts[0] * 60 + parts[1];
|
||||
} else if (parts.length === 1) {
|
||||
// s
|
||||
seconds = parts[0];
|
||||
}
|
||||
return seconds;
|
||||
const parts = time.split(':').map((part) => parseInt(part));
|
||||
let seconds = 0;
|
||||
if (parts.length === 3) {
|
||||
// hh:mm:ss
|
||||
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
||||
} else if (parts.length === 2) {
|
||||
// hh:ss or m:ss
|
||||
seconds = parts[0] * 60 + parts[1];
|
||||
} else if (parts.length === 1) {
|
||||
// s
|
||||
seconds = parts[0];
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
export function proxyVideoUrl(source: string): string {
|
||||
const rawSrc = new URL(source);
|
||||
rawSrc.host = import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE.replace(
|
||||
'http://',
|
||||
''
|
||||
).replace('https://', '');
|
||||
const rawSrc = new URL(source);
|
||||
rawSrc.host = import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE.replace('http://', '').replace(
|
||||
'https://',
|
||||
''
|
||||
);
|
||||
|
||||
return rawSrc.toString();
|
||||
}
|
||||
return rawSrc.toString();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export interface PlayerEvent {
|
||||
type: "pause" | "seek" | "change-video" | "play" | "playlist";
|
||||
time?: number;
|
||||
videoId?: string;
|
||||
playlistId?: string;
|
||||
type: 'pause' | 'seek' | 'change-video' | 'play' | 'playlist';
|
||||
time?: number;
|
||||
videoId?: string;
|
||||
playlistId?: string;
|
||||
}
|
||||
|
||||
export interface PlayerEvents {
|
||||
events: PlayerEvent[];
|
||||
}
|
||||
events: PlayerEvent[];
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export async function getDynamicTheme(mode?: string): Promise<Record<string, string>> {
|
||||
const givenSettings = await window.ui("theme") as IBeerCssTheme;
|
||||
const givenSettings = (await window.ui('theme')) as IBeerCssTheme;
|
||||
|
||||
// @ts-ignore
|
||||
const themes: string = givenSettings[mode ? mode : (ui("mode") as string)];
|
||||
let themeVars: Record<string, string> = {};
|
||||
themes.split(";").forEach(keyVar => {
|
||||
let [key, value] = keyVar.split(":");
|
||||
themeVars[key] = value;
|
||||
});
|
||||
return themeVars;
|
||||
}
|
||||
// @ts-ignore
|
||||
const themes: string = givenSettings[mode ? mode : (ui('mode') as string)];
|
||||
let themeVars: Record<string, string> = {};
|
||||
themes.split(';').forEach((keyVar) => {
|
||||
let [key, value] = keyVar.split(':');
|
||||
themeVars[key] = value;
|
||||
});
|
||||
return themeVars;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { LayoutLoad } from './$types';
|
||||
export let ssr = false;
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
if (browser) {
|
||||
locale.set(window.navigator.language);
|
||||
}
|
||||
await waitLocale();
|
||||
};
|
||||
if (browser) {
|
||||
locale.set(window.navigator.language);
|
||||
}
|
||||
await waitLocale();
|
||||
};
|
||||
|
||||
@@ -2,20 +2,20 @@ import { getPopular } from '$lib/Api/index.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
let popular = undefined;
|
||||
let popularDisabled: boolean = false;
|
||||
let popular = undefined;
|
||||
let popularDisabled: boolean = false;
|
||||
|
||||
try {
|
||||
popular = await getPopular();
|
||||
} catch (errorMessage: any) {
|
||||
if (errorMessage.toString() === 'Error: Administrator has disabled this endpoint.') {
|
||||
popularDisabled = true;
|
||||
} else {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
}
|
||||
return {
|
||||
popular: popular,
|
||||
popularDisabled: popularDisabled
|
||||
};
|
||||
}
|
||||
try {
|
||||
popular = await getPopular();
|
||||
} catch (errorMessage: any) {
|
||||
if (errorMessage.toString() === 'Error: Administrator has disabled this endpoint.') {
|
||||
popularDisabled = true;
|
||||
} else {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
}
|
||||
return {
|
||||
popular: popular,
|
||||
popularDisabled: popularDisabled
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { getChannel } from '$lib/Api/index.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ params }) {
|
||||
let channel;
|
||||
let channel;
|
||||
|
||||
try {
|
||||
channel = await getChannel(params.slug);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
try {
|
||||
channel = await getChannel(params.slug);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
return {
|
||||
channel: channel
|
||||
};
|
||||
}
|
||||
return {
|
||||
channel: channel
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { goto } from '$app/navigation';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ url }) {
|
||||
const playlistId = url.searchParams.get('list');
|
||||
if (playlistId) {
|
||||
goto(`/playlist/${playlistId}`);
|
||||
} else {
|
||||
error(404);
|
||||
}
|
||||
}
|
||||
const playlistId = url.searchParams.get('list');
|
||||
if (playlistId) {
|
||||
goto(`/playlist/${playlistId}`);
|
||||
} else {
|
||||
error(404);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getPlaylist } from '$lib/Api/index.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ params }) {
|
||||
let playlist;
|
||||
let playlist;
|
||||
|
||||
try {
|
||||
playlist = await getPlaylist(params.slug);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
playlist: playlist
|
||||
};
|
||||
}
|
||||
try {
|
||||
playlist = await getPlaylist(params.slug);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
playlist: playlist
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { getPersonalPlaylists } from "$lib/Api";
|
||||
import { error } from "@sveltejs/kit";
|
||||
import { getPersonalPlaylists } from '$lib/Api';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
let playlists;
|
||||
try {
|
||||
playlists = await getPersonalPlaylists();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
playlists: playlists
|
||||
};
|
||||
}
|
||||
let playlists;
|
||||
try {
|
||||
playlists = await getPersonalPlaylists();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
playlists: playlists
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,26 +2,26 @@ import { getSearch } from '$lib/Api/index';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ params, url }) {
|
||||
let type: "playlist" | "all" | "video" | "channel";
|
||||
let type: 'playlist' | 'all' | 'video' | 'channel';
|
||||
|
||||
const queryFlag = url.searchParams.get('type');
|
||||
if (queryFlag && ['playlist', 'video', 'channel', 'all'].includes(queryFlag)) {
|
||||
type = queryFlag;
|
||||
} else {
|
||||
type = 'all';
|
||||
}
|
||||
const queryFlag = url.searchParams.get('type');
|
||||
if (queryFlag && ['playlist', 'video', 'channel', 'all'].includes(queryFlag)) {
|
||||
type = queryFlag;
|
||||
} else {
|
||||
type = 'all';
|
||||
}
|
||||
|
||||
let search;
|
||||
let search;
|
||||
|
||||
try {
|
||||
search = await getSearch(params.slug, { type: type });
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
try {
|
||||
search = await getSearch(params.slug, { type: type });
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
return {
|
||||
search: search,
|
||||
slug: params.slug,
|
||||
searchType: type
|
||||
};
|
||||
return {
|
||||
search: search,
|
||||
slug: params.slug,
|
||||
searchType: type
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getFeed } from '$lib/Api/index.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ params }) {
|
||||
let feed;
|
||||
let feed;
|
||||
|
||||
try {
|
||||
feed = await getFeed(100, 1);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
feed: feed
|
||||
};
|
||||
}
|
||||
try {
|
||||
feed = await getFeed(100, 1);
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
return {
|
||||
feed: feed
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { getSubscriptions } from "$lib/Api";
|
||||
import { error } from "@sveltejs/kit";
|
||||
import { getSubscriptions } from '$lib/Api';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
let subscriptions;
|
||||
let subscriptions;
|
||||
|
||||
try {
|
||||
subscriptions = await getSubscriptions();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
try {
|
||||
subscriptions = await getSubscriptions();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
return {
|
||||
subscriptions: subscriptions
|
||||
};
|
||||
}
|
||||
return {
|
||||
subscriptions: subscriptions
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { Video } from '$lib/Api/model';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
let trending: Video[];
|
||||
try {
|
||||
trending = await getTrending();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
let trending: Video[];
|
||||
try {
|
||||
trending = await getTrending();
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
return { trending: trending };
|
||||
}
|
||||
return { trending: trending };
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ import { goto } from '$app/navigation';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ url }) {
|
||||
const videoId = url.searchParams.get('v');
|
||||
const playlistId = url.searchParams.get('list');
|
||||
if (videoId) {
|
||||
let goToUrl = `/watch/${videoId}`;
|
||||
const videoId = url.searchParams.get('v');
|
||||
const playlistId = url.searchParams.get('list');
|
||||
if (videoId) {
|
||||
let goToUrl = `/watch/${videoId}`;
|
||||
|
||||
if (playlistId) {
|
||||
goToUrl += `?playlist=${playlistId}`;
|
||||
}
|
||||
goto(goToUrl);
|
||||
} else {
|
||||
error(404);
|
||||
}
|
||||
}
|
||||
if (playlistId) {
|
||||
goToUrl += `?playlist=${playlistId}`;
|
||||
}
|
||||
goto(goToUrl);
|
||||
} else {
|
||||
error(404);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { amSubscribed, getComments, getDislikes, getPersonalPlaylists, getVideo, postHistory } from '$lib/Api/index.js';
|
||||
import {
|
||||
amSubscribed,
|
||||
getComments,
|
||||
getDislikes,
|
||||
getPersonalPlaylists,
|
||||
getVideo,
|
||||
postHistory
|
||||
} from '$lib/Api/index.js';
|
||||
import type { PlaylistPage } from '$lib/Api/model';
|
||||
import { phaseDescription } from '$lib/misc';
|
||||
import { error } from '@sveltejs/kit';
|
||||
@@ -6,66 +13,68 @@ import { get } from 'svelte/store';
|
||||
import { auth, playerProxyVideos, returnYtDislikes } from '../../../store';
|
||||
|
||||
export async function load({ params, url }) {
|
||||
let video;
|
||||
try {
|
||||
video = await getVideo(params.slug, get(playerProxyVideos));
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
let video;
|
||||
try {
|
||||
video = await getVideo(params.slug, get(playerProxyVideos));
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
let downloadOptions: { title: string; url: string; }[] = [];
|
||||
let downloadOptions: { title: string; url: string }[] = [];
|
||||
|
||||
if (!video.hlsUrl) {
|
||||
video.formatStreams.forEach(format => {
|
||||
downloadOptions.push({
|
||||
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel} (With audio)`,
|
||||
url: format.url
|
||||
});
|
||||
});
|
||||
if (!video.hlsUrl) {
|
||||
video.formatStreams.forEach((format) => {
|
||||
downloadOptions.push({
|
||||
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel} (With audio)`,
|
||||
url: format.url
|
||||
});
|
||||
});
|
||||
|
||||
video.adaptiveFormats.forEach(format => {
|
||||
downloadOptions.push({
|
||||
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel || format.bitrate + ' bitrate'}`,
|
||||
url: format.url
|
||||
});
|
||||
});
|
||||
}
|
||||
video.adaptiveFormats.forEach((format) => {
|
||||
downloadOptions.push({
|
||||
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel || format.bitrate + ' bitrate'}`,
|
||||
url: format.url
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let personalPlaylists: PlaylistPage[] | null;
|
||||
let personalPlaylists: PlaylistPage[] | null;
|
||||
|
||||
if (get(auth)) {
|
||||
postHistory(video.videoId);
|
||||
personalPlaylists = await getPersonalPlaylists();
|
||||
} else {
|
||||
personalPlaylists = null;
|
||||
}
|
||||
if (get(auth)) {
|
||||
postHistory(video.videoId);
|
||||
personalPlaylists = await getPersonalPlaylists();
|
||||
} else {
|
||||
personalPlaylists = null;
|
||||
}
|
||||
|
||||
let comments;
|
||||
try {
|
||||
comments = video.liveNow ? null : await getComments(params.slug, { sort_by: "top", source: "youtube" });
|
||||
} catch {
|
||||
comments = null;
|
||||
}
|
||||
let comments;
|
||||
try {
|
||||
comments = video.liveNow
|
||||
? null
|
||||
: await getComments(params.slug, { sort_by: 'top', source: 'youtube' });
|
||||
} catch {
|
||||
comments = null;
|
||||
}
|
||||
|
||||
if (comments && 'errorBacktrace' in comments) {
|
||||
comments = null;
|
||||
}
|
||||
if (comments && 'errorBacktrace' in comments) {
|
||||
comments = null;
|
||||
}
|
||||
|
||||
let returnYTDislikes;
|
||||
try {
|
||||
returnYTDislikes = get(returnYtDislikes) ? await getDislikes(params.slug) : null;
|
||||
} catch {
|
||||
returnYTDislikes = null;
|
||||
}
|
||||
let returnYTDislikes;
|
||||
try {
|
||||
returnYTDislikes = get(returnYtDislikes) ? await getDislikes(params.slug) : null;
|
||||
} catch {
|
||||
returnYTDislikes = null;
|
||||
}
|
||||
|
||||
return {
|
||||
video: video,
|
||||
returnYTDislikes: returnYTDislikes,
|
||||
comments: comments,
|
||||
subscribed: await amSubscribed(video.authorId),
|
||||
content: phaseDescription(video.descriptionHtml),
|
||||
playlistId: url.searchParams.get('playlist'),
|
||||
personalPlaylists: personalPlaylists,
|
||||
downloadOptions: downloadOptions
|
||||
};
|
||||
};
|
||||
return {
|
||||
video: video,
|
||||
returnYTDislikes: returnYTDislikes,
|
||||
comments: comments,
|
||||
subscribed: await amSubscribed(video.authorId),
|
||||
content: phaseDescription(video.descriptionHtml),
|
||||
playlistId: url.searchParams.get('playlist'),
|
||||
personalPlaylists: personalPlaylists,
|
||||
downloadOptions: downloadOptions
|
||||
};
|
||||
}
|
||||
|
||||
+37
-22
@@ -3,34 +3,49 @@ import type { DataConnection } from 'peerjs';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
export const returnYTDislikesInstance = persisted('returnYTDislikesInstance', import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE);
|
||||
export const darkMode: Writable<null | boolean> = persisted("darkMode", null);
|
||||
export const themeColor: Writable<null | string> = persisted("themeColor", null);
|
||||
export const returnYTDislikesInstance = persisted(
|
||||
'returnYTDislikesInstance',
|
||||
import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE
|
||||
);
|
||||
export const darkMode: Writable<null | boolean> = persisted('darkMode', null);
|
||||
export const themeColor: Writable<null | string> = persisted('themeColor', null);
|
||||
|
||||
export const activePage: Writable<string | null> = writable("home");
|
||||
export const activePage: Writable<string | null> = writable('home');
|
||||
|
||||
export const playerAutoPlay = persisted("autoPlay", true);
|
||||
export const playerAlwaysLoop = persisted("alwaysLoop", false);
|
||||
export const playerProxyVideos = persisted("proxyVideos", false);
|
||||
export const playerListenByDefault = persisted("listenByDefault", false);
|
||||
export const playerSavePlaybackPosition = persisted("savePlaybackPosition", true);
|
||||
export const playerDash = persisted("dashEnabled", false);
|
||||
export const playerTheatreModeByDefault = persisted("theatreModeByDefault", false);
|
||||
export const playerAutoplayNextByDefault = persisted("autoplayNextByDefault", false);
|
||||
export const playerAutoPlay = persisted('autoPlay', true);
|
||||
export const playerAlwaysLoop = persisted('alwaysLoop', false);
|
||||
export const playerProxyVideos = persisted('proxyVideos', false);
|
||||
export const playerListenByDefault = persisted('listenByDefault', false);
|
||||
export const playerSavePlaybackPosition = persisted('savePlaybackPosition', true);
|
||||
export const playerDash = persisted('dashEnabled', false);
|
||||
export const playerTheatreModeByDefault = persisted('theatreModeByDefault', false);
|
||||
export const playerAutoplayNextByDefault = persisted('autoplayNextByDefault', false);
|
||||
|
||||
export const returnYtDislikes = persisted("returnYtDislikes", true);
|
||||
export const returnYtDislikes = persisted('returnYtDislikes', true);
|
||||
|
||||
export const interfaceSearchSuggestions = persisted("searchSuggestions", true);
|
||||
export const interfaceSearchSuggestions = persisted('searchSuggestions', true);
|
||||
|
||||
export const auth: Writable<null | { username: string, token: string; }> = persisted("authToken", null);
|
||||
export const auth: Writable<null | { username: string; token: string }> = persisted(
|
||||
'authToken',
|
||||
null
|
||||
);
|
||||
|
||||
export const sponsorBlock = persisted("sponsorBlock", true);
|
||||
export const sponsorBlockUrl = persisted("sponsorBlockUrl", import.meta.env.VITE_DEFAULT_SPONSERBLOCK_INSTANCE);
|
||||
export const sponsorBlockCategories: Writable<string[]> = persisted("sponsorBlockCategories", []);
|
||||
export const sponsorBlock = persisted('sponsorBlock', true);
|
||||
export const sponsorBlockUrl = persisted(
|
||||
'sponsorBlockUrl',
|
||||
import.meta.env.VITE_DEFAULT_SPONSERBLOCK_INSTANCE
|
||||
);
|
||||
export const sponsorBlockCategories: Writable<string[]> = persisted('sponsorBlockCategories', []);
|
||||
|
||||
export const deArrowInstance = persisted("deArrowInstance", import.meta.env.VITE_DEFAULT_DEARROW_INSTANCE);
|
||||
export const deArrowEnabled = persisted("deArrowEnabled", false);
|
||||
export const deArrowThumbnailInstance = persisted("deArrowThumbnailInstance", import.meta.env.VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE);
|
||||
export const deArrowInstance = persisted(
|
||||
'deArrowInstance',
|
||||
import.meta.env.VITE_DEFAULT_DEARROW_INSTANCE
|
||||
);
|
||||
export const deArrowEnabled = persisted('deArrowEnabled', false);
|
||||
export const deArrowThumbnailInstance = persisted(
|
||||
'deArrowThumbnailInstance',
|
||||
import.meta.env.VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE
|
||||
);
|
||||
|
||||
export const syncPartyPeer: Writable<Peer | null> = writable(null);
|
||||
export const syncPartyConnections: Writable<DataConnection[] | null> = writable();
|
||||
export const syncPartyConnections: Writable<DataConnection[] | null> = writable();
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
.thumbnail-corner {
|
||||
padding: .1em .4em;
|
||||
padding: 0.1em 0.4em;
|
||||
}
|
||||
|
||||
.vds-chapter-radio-label {
|
||||
word-wrap: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
white-space: normal !important;
|
||||
word-wrap: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.vds-chapter-radio-content {
|
||||
max-width: 200px !important;
|
||||
max-width: 200px !important;
|
||||
}
|
||||
|
||||
.vds-chapter-radio {
|
||||
align-items: start !important;
|
||||
align-items: start !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 580px) {
|
||||
main {
|
||||
padding-left: .1em !important;
|
||||
padding-right: .1em !important;
|
||||
}
|
||||
main {
|
||||
padding-left: 0.1em !important;
|
||||
padding-right: 0.1em !important;
|
||||
}
|
||||
}
|
||||
|
||||
main.root {
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": [
|
||||
"vidstack/svelte"
|
||||
]
|
||||
"types": ["vidstack/svelte"]
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
}
|
||||
|
||||
+228
-228
@@ -8,462 +8,462 @@ export default defineConfig({
|
||||
SvelteKitPWA({
|
||||
injectRegister: 'inline',
|
||||
manifest: {
|
||||
description: "Modern material design for Invidious.",
|
||||
description: 'Modern material design for Invidious.',
|
||||
icons: [
|
||||
{
|
||||
"src": "windows11/SmallTile.scale-100.png",
|
||||
"sizes": "71x71"
|
||||
src: 'windows11/SmallTile.scale-100.png',
|
||||
sizes: '71x71'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SmallTile.scale-125.png",
|
||||
"sizes": "89x89"
|
||||
src: 'windows11/SmallTile.scale-125.png',
|
||||
sizes: '89x89'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SmallTile.scale-150.png",
|
||||
"sizes": "107x107"
|
||||
src: 'windows11/SmallTile.scale-150.png',
|
||||
sizes: '107x107'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SmallTile.scale-200.png",
|
||||
"sizes": "142x142"
|
||||
src: 'windows11/SmallTile.scale-200.png',
|
||||
sizes: '142x142'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SmallTile.scale-400.png",
|
||||
"sizes": "284x284"
|
||||
src: 'windows11/SmallTile.scale-400.png',
|
||||
sizes: '284x284'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square150x150Logo.scale-100.png",
|
||||
"sizes": "150x150"
|
||||
src: 'windows11/Square150x150Logo.scale-100.png',
|
||||
sizes: '150x150'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square150x150Logo.scale-125.png",
|
||||
"sizes": "188x188"
|
||||
src: 'windows11/Square150x150Logo.scale-125.png',
|
||||
sizes: '188x188'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square150x150Logo.scale-150.png",
|
||||
"sizes": "225x225"
|
||||
src: 'windows11/Square150x150Logo.scale-150.png',
|
||||
sizes: '225x225'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square150x150Logo.scale-200.png",
|
||||
"sizes": "300x300"
|
||||
src: 'windows11/Square150x150Logo.scale-200.png',
|
||||
sizes: '300x300'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square150x150Logo.scale-400.png",
|
||||
"sizes": "600x600"
|
||||
src: 'windows11/Square150x150Logo.scale-400.png',
|
||||
sizes: '600x600'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Wide310x150Logo.scale-100.png",
|
||||
"sizes": "310x150"
|
||||
src: 'windows11/Wide310x150Logo.scale-100.png',
|
||||
sizes: '310x150'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Wide310x150Logo.scale-125.png",
|
||||
"sizes": "388x188"
|
||||
src: 'windows11/Wide310x150Logo.scale-125.png',
|
||||
sizes: '388x188'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Wide310x150Logo.scale-150.png",
|
||||
"sizes": "465x225"
|
||||
src: 'windows11/Wide310x150Logo.scale-150.png',
|
||||
sizes: '465x225'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Wide310x150Logo.scale-200.png",
|
||||
"sizes": "620x300"
|
||||
src: 'windows11/Wide310x150Logo.scale-200.png',
|
||||
sizes: '620x300'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Wide310x150Logo.scale-400.png",
|
||||
"sizes": "1240x600"
|
||||
src: 'windows11/Wide310x150Logo.scale-400.png',
|
||||
sizes: '1240x600'
|
||||
},
|
||||
{
|
||||
"src": "windows11/LargeTile.scale-100.png",
|
||||
"sizes": "310x310"
|
||||
src: 'windows11/LargeTile.scale-100.png',
|
||||
sizes: '310x310'
|
||||
},
|
||||
{
|
||||
"src": "windows11/LargeTile.scale-125.png",
|
||||
"sizes": "388x388"
|
||||
src: 'windows11/LargeTile.scale-125.png',
|
||||
sizes: '388x388'
|
||||
},
|
||||
{
|
||||
"src": "windows11/LargeTile.scale-150.png",
|
||||
"sizes": "465x465"
|
||||
src: 'windows11/LargeTile.scale-150.png',
|
||||
sizes: '465x465'
|
||||
},
|
||||
{
|
||||
"src": "windows11/LargeTile.scale-200.png",
|
||||
"sizes": "620x620"
|
||||
src: 'windows11/LargeTile.scale-200.png',
|
||||
sizes: '620x620'
|
||||
},
|
||||
{
|
||||
"src": "windows11/LargeTile.scale-400.png",
|
||||
"sizes": "1240x1240"
|
||||
src: 'windows11/LargeTile.scale-400.png',
|
||||
sizes: '1240x1240'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.scale-100.png",
|
||||
"sizes": "44x44"
|
||||
src: 'windows11/Square44x44Logo.scale-100.png',
|
||||
sizes: '44x44'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.scale-125.png",
|
||||
"sizes": "55x55"
|
||||
src: 'windows11/Square44x44Logo.scale-125.png',
|
||||
sizes: '55x55'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.scale-150.png",
|
||||
"sizes": "66x66"
|
||||
src: 'windows11/Square44x44Logo.scale-150.png',
|
||||
sizes: '66x66'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.scale-200.png",
|
||||
"sizes": "88x88"
|
||||
src: 'windows11/Square44x44Logo.scale-200.png',
|
||||
sizes: '88x88'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.scale-400.png",
|
||||
"sizes": "176x176"
|
||||
src: 'windows11/Square44x44Logo.scale-400.png',
|
||||
sizes: '176x176'
|
||||
},
|
||||
{
|
||||
"src": "windows11/StoreLogo.scale-100.png",
|
||||
"sizes": "50x50"
|
||||
src: 'windows11/StoreLogo.scale-100.png',
|
||||
sizes: '50x50'
|
||||
},
|
||||
{
|
||||
"src": "windows11/StoreLogo.scale-125.png",
|
||||
"sizes": "63x63"
|
||||
src: 'windows11/StoreLogo.scale-125.png',
|
||||
sizes: '63x63'
|
||||
},
|
||||
{
|
||||
"src": "windows11/StoreLogo.scale-150.png",
|
||||
"sizes": "75x75"
|
||||
src: 'windows11/StoreLogo.scale-150.png',
|
||||
sizes: '75x75'
|
||||
},
|
||||
{
|
||||
"src": "windows11/StoreLogo.scale-200.png",
|
||||
"sizes": "100x100"
|
||||
src: 'windows11/StoreLogo.scale-200.png',
|
||||
sizes: '100x100'
|
||||
},
|
||||
{
|
||||
"src": "windows11/StoreLogo.scale-400.png",
|
||||
"sizes": "200x200"
|
||||
src: 'windows11/StoreLogo.scale-400.png',
|
||||
sizes: '200x200'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SplashScreen.scale-100.png",
|
||||
"sizes": "620x300"
|
||||
src: 'windows11/SplashScreen.scale-100.png',
|
||||
sizes: '620x300'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SplashScreen.scale-125.png",
|
||||
"sizes": "775x375"
|
||||
src: 'windows11/SplashScreen.scale-125.png',
|
||||
sizes: '775x375'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SplashScreen.scale-150.png",
|
||||
"sizes": "930x450"
|
||||
src: 'windows11/SplashScreen.scale-150.png',
|
||||
sizes: '930x450'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SplashScreen.scale-200.png",
|
||||
"sizes": "1240x600"
|
||||
src: 'windows11/SplashScreen.scale-200.png',
|
||||
sizes: '1240x600'
|
||||
},
|
||||
{
|
||||
"src": "windows11/SplashScreen.scale-400.png",
|
||||
"sizes": "2480x1200"
|
||||
src: 'windows11/SplashScreen.scale-400.png',
|
||||
sizes: '2480x1200'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-16.png",
|
||||
"sizes": "16x16"
|
||||
src: 'windows11/Square44x44Logo.targetsize-16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-20.png",
|
||||
"sizes": "20x20"
|
||||
src: 'windows11/Square44x44Logo.targetsize-20.png',
|
||||
sizes: '20x20'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-24.png",
|
||||
"sizes": "24x24"
|
||||
src: 'windows11/Square44x44Logo.targetsize-24.png',
|
||||
sizes: '24x24'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-30.png",
|
||||
"sizes": "30x30"
|
||||
src: 'windows11/Square44x44Logo.targetsize-30.png',
|
||||
sizes: '30x30'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-32.png",
|
||||
"sizes": "32x32"
|
||||
src: 'windows11/Square44x44Logo.targetsize-32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-36.png",
|
||||
"sizes": "36x36"
|
||||
src: 'windows11/Square44x44Logo.targetsize-36.png',
|
||||
sizes: '36x36'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-40.png",
|
||||
"sizes": "40x40"
|
||||
src: 'windows11/Square44x44Logo.targetsize-40.png',
|
||||
sizes: '40x40'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-44.png",
|
||||
"sizes": "44x44"
|
||||
src: 'windows11/Square44x44Logo.targetsize-44.png',
|
||||
sizes: '44x44'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-48.png",
|
||||
"sizes": "48x48"
|
||||
src: 'windows11/Square44x44Logo.targetsize-48.png',
|
||||
sizes: '48x48'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-60.png",
|
||||
"sizes": "60x60"
|
||||
src: 'windows11/Square44x44Logo.targetsize-60.png',
|
||||
sizes: '60x60'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-64.png",
|
||||
"sizes": "64x64"
|
||||
src: 'windows11/Square44x44Logo.targetsize-64.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-72.png",
|
||||
"sizes": "72x72"
|
||||
src: 'windows11/Square44x44Logo.targetsize-72.png',
|
||||
sizes: '72x72'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-80.png",
|
||||
"sizes": "80x80"
|
||||
src: 'windows11/Square44x44Logo.targetsize-80.png',
|
||||
sizes: '80x80'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-96.png",
|
||||
"sizes": "96x96"
|
||||
src: 'windows11/Square44x44Logo.targetsize-96.png',
|
||||
sizes: '96x96'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.targetsize-256.png",
|
||||
"sizes": "256x256"
|
||||
src: 'windows11/Square44x44Logo.targetsize-256.png',
|
||||
sizes: '256x256'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-16.png",
|
||||
"sizes": "16x16"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-20.png",
|
||||
"sizes": "20x20"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-20.png',
|
||||
sizes: '20x20'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-24.png",
|
||||
"sizes": "24x24"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-24.png',
|
||||
sizes: '24x24'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-30.png",
|
||||
"sizes": "30x30"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-30.png',
|
||||
sizes: '30x30'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-32.png",
|
||||
"sizes": "32x32"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-36.png",
|
||||
"sizes": "36x36"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-36.png',
|
||||
sizes: '36x36'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-40.png",
|
||||
"sizes": "40x40"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-40.png',
|
||||
sizes: '40x40'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-44.png",
|
||||
"sizes": "44x44"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-44.png',
|
||||
sizes: '44x44'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-48.png",
|
||||
"sizes": "48x48"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-48.png',
|
||||
sizes: '48x48'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-60.png",
|
||||
"sizes": "60x60"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-60.png',
|
||||
sizes: '60x60'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-64.png",
|
||||
"sizes": "64x64"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-64.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-72.png",
|
||||
"sizes": "72x72"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-72.png',
|
||||
sizes: '72x72'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-80.png",
|
||||
"sizes": "80x80"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-80.png',
|
||||
sizes: '80x80'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-96.png",
|
||||
"sizes": "96x96"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-96.png',
|
||||
sizes: '96x96'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-256.png",
|
||||
"sizes": "256x256"
|
||||
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-256.png',
|
||||
sizes: '256x256'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png",
|
||||
"sizes": "16x16"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png",
|
||||
"sizes": "20x20"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png',
|
||||
sizes: '20x20'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png",
|
||||
"sizes": "24x24"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png',
|
||||
sizes: '24x24'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png",
|
||||
"sizes": "30x30"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png',
|
||||
sizes: '30x30'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png",
|
||||
"sizes": "32x32"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png",
|
||||
"sizes": "36x36"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png',
|
||||
sizes: '36x36'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png",
|
||||
"sizes": "40x40"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png',
|
||||
sizes: '40x40'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png",
|
||||
"sizes": "44x44"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png',
|
||||
sizes: '44x44'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png",
|
||||
"sizes": "48x48"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png',
|
||||
sizes: '48x48'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png",
|
||||
"sizes": "60x60"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png',
|
||||
sizes: '60x60'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png",
|
||||
"sizes": "64x64"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png",
|
||||
"sizes": "72x72"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png',
|
||||
sizes: '72x72'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png",
|
||||
"sizes": "80x80"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png',
|
||||
sizes: '80x80'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png",
|
||||
"sizes": "96x96"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png',
|
||||
sizes: '96x96'
|
||||
},
|
||||
{
|
||||
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png",
|
||||
"sizes": "256x256"
|
||||
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png',
|
||||
sizes: '256x256'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-512-512.png",
|
||||
"sizes": "512x512"
|
||||
src: 'android/android-launchericon-512-512.png',
|
||||
sizes: '512x512'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-192-192.png",
|
||||
"sizes": "192x192"
|
||||
src: 'android/android-launchericon-192-192.png',
|
||||
sizes: '192x192'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-144-144.png",
|
||||
"sizes": "144x144"
|
||||
src: 'android/android-launchericon-144-144.png',
|
||||
sizes: '144x144'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-96-96.png",
|
||||
"sizes": "96x96"
|
||||
src: 'android/android-launchericon-96-96.png',
|
||||
sizes: '96x96'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-72-72.png",
|
||||
"sizes": "72x72"
|
||||
src: 'android/android-launchericon-72-72.png',
|
||||
sizes: '72x72'
|
||||
},
|
||||
{
|
||||
"src": "android/android-launchericon-48-48.png",
|
||||
"sizes": "48x48"
|
||||
src: 'android/android-launchericon-48-48.png',
|
||||
sizes: '48x48'
|
||||
},
|
||||
{
|
||||
"src": "ios/16.png",
|
||||
"sizes": "16x16"
|
||||
src: 'ios/16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
"src": "ios/20.png",
|
||||
"sizes": "20x20"
|
||||
src: 'ios/20.png',
|
||||
sizes: '20x20'
|
||||
},
|
||||
{
|
||||
"src": "ios/29.png",
|
||||
"sizes": "29x29"
|
||||
src: 'ios/29.png',
|
||||
sizes: '29x29'
|
||||
},
|
||||
{
|
||||
"src": "ios/32.png",
|
||||
"sizes": "32x32"
|
||||
src: 'ios/32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
"src": "ios/40.png",
|
||||
"sizes": "40x40"
|
||||
src: 'ios/40.png',
|
||||
sizes: '40x40'
|
||||
},
|
||||
{
|
||||
"src": "ios/50.png",
|
||||
"sizes": "50x50"
|
||||
src: 'ios/50.png',
|
||||
sizes: '50x50'
|
||||
},
|
||||
{
|
||||
"src": "ios/57.png",
|
||||
"sizes": "57x57"
|
||||
src: 'ios/57.png',
|
||||
sizes: '57x57'
|
||||
},
|
||||
{
|
||||
"src": "ios/58.png",
|
||||
"sizes": "58x58"
|
||||
src: 'ios/58.png',
|
||||
sizes: '58x58'
|
||||
},
|
||||
{
|
||||
"src": "ios/60.png",
|
||||
"sizes": "60x60"
|
||||
src: 'ios/60.png',
|
||||
sizes: '60x60'
|
||||
},
|
||||
{
|
||||
"src": "ios/64.png",
|
||||
"sizes": "64x64"
|
||||
src: 'ios/64.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
"src": "ios/72.png",
|
||||
"sizes": "72x72"
|
||||
src: 'ios/72.png',
|
||||
sizes: '72x72'
|
||||
},
|
||||
{
|
||||
"src": "ios/76.png",
|
||||
"sizes": "76x76"
|
||||
src: 'ios/76.png',
|
||||
sizes: '76x76'
|
||||
},
|
||||
{
|
||||
"src": "ios/80.png",
|
||||
"sizes": "80x80"
|
||||
src: 'ios/80.png',
|
||||
sizes: '80x80'
|
||||
},
|
||||
{
|
||||
"src": "ios/87.png",
|
||||
"sizes": "87x87"
|
||||
src: 'ios/87.png',
|
||||
sizes: '87x87'
|
||||
},
|
||||
{
|
||||
"src": "ios/100.png",
|
||||
"sizes": "100x100"
|
||||
src: 'ios/100.png',
|
||||
sizes: '100x100'
|
||||
},
|
||||
{
|
||||
"src": "ios/114.png",
|
||||
"sizes": "114x114"
|
||||
src: 'ios/114.png',
|
||||
sizes: '114x114'
|
||||
},
|
||||
{
|
||||
"src": "ios/120.png",
|
||||
"sizes": "120x120"
|
||||
src: 'ios/120.png',
|
||||
sizes: '120x120'
|
||||
},
|
||||
{
|
||||
"src": "ios/128.png",
|
||||
"sizes": "128x128"
|
||||
src: 'ios/128.png',
|
||||
sizes: '128x128'
|
||||
},
|
||||
{
|
||||
"src": "ios/144.png",
|
||||
"sizes": "144x144"
|
||||
src: 'ios/144.png',
|
||||
sizes: '144x144'
|
||||
},
|
||||
{
|
||||
"src": "ios/152.png",
|
||||
"sizes": "152x152"
|
||||
src: 'ios/152.png',
|
||||
sizes: '152x152'
|
||||
},
|
||||
{
|
||||
"src": "ios/167.png",
|
||||
"sizes": "167x167"
|
||||
src: 'ios/167.png',
|
||||
sizes: '167x167'
|
||||
},
|
||||
{
|
||||
"src": "ios/180.png",
|
||||
"sizes": "180x180"
|
||||
src: 'ios/180.png',
|
||||
sizes: '180x180'
|
||||
},
|
||||
{
|
||||
"src": "ios/192.png",
|
||||
"sizes": "192x192"
|
||||
src: 'ios/192.png',
|
||||
sizes: '192x192'
|
||||
},
|
||||
{
|
||||
"src": "ios/256.png",
|
||||
"sizes": "256x256"
|
||||
src: 'ios/256.png',
|
||||
sizes: '256x256'
|
||||
},
|
||||
{
|
||||
"src": "ios/512.png",
|
||||
"sizes": "512x512"
|
||||
src: 'ios/512.png',
|
||||
sizes: '512x512'
|
||||
},
|
||||
{
|
||||
"src": "ios/1024.png",
|
||||
"sizes": "1024x1024"
|
||||
src: 'ios/1024.png',
|
||||
sizes: '1024x1024'
|
||||
}
|
||||
],
|
||||
background_color: "#22005d",
|
||||
theme_color: "#efb0ff"
|
||||
background_color: '#22005d',
|
||||
theme_color: '#efb0ff'
|
||||
}
|
||||
}),
|
||||
vidstack(),
|
||||
sveltekit()
|
||||
],
|
||||
]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user