Merge pull request #870 from Materialious/update/1.7.20

Update/1.7.20
This commit is contained in:
Ward
2025-04-08 20:20:22 +12:00
committed by GitHub
28 changed files with 195 additions and 218 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 101
versionName "1.7.19"
versionCode 102
versionName "1.7.20"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -14,7 +14,6 @@ dependencies {
implementation project(':capacitor-clipboard')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-status-bar')
implementation project(':capgo-inappbrowser')
implementation project(':hugotomazi-capacitor-navigation-bar')
implementation project(':capacitor-nodejs')
@@ -17,9 +17,6 @@ project(':capacitor-screen-orientation').projectDir = new File('../node_modules/
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':capgo-inappbrowser'
project(':capgo-inappbrowser').projectDir = new File('../node_modules/@capgo/inappbrowser/android')
include ':hugotomazi-capacitor-navigation-bar'
project(':hugotomazi-capacitor-navigation-bar').projectDir = new File('../node_modules/@hugotomazi/capacitor-navigation-bar/android')
@@ -62,7 +62,11 @@
<release version="1.7.19" date="2025-4-04">
<release version="1.7.20" date="2025-4-08">
<url>https://github.com/Materialious/Materialious/releases/tag/1.7.20</url>
</release>
<release version="1.7.19" date="2025-4-04">
<url>https://github.com/Materialious/Materialious/releases/tag/1.7.19</url>
</release>
<release version="1.7.18" date="2025-4-04">
+8 -8
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.7.19",
"version": "1.7.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.7.19",
"version": "1.7.20",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -2506,9 +2506,9 @@
}
},
"node_modules/electron": {
"version": "34.4.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.4.1.tgz",
"integrity": "sha512-iYzeLBdCrAR3i0RVSLa+mzuFZwH6HGxTGKsI+SS41sg2anZj4R5mHjOiHsxcZ50/ih47NJbuVRJgPIVlTF+USg==",
"version": "34.5.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.5.0.tgz",
"integrity": "sha512-GabFMG7r2P1NQf5DYp6mnCXo5CcatxXb8YQo54VTStql6weeEv7tsqvl3lAssGwDdd4iMc8QpTCFjErBSVRWeQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -5751,9 +5751,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.7.19",
"version": "1.7.20",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
+3 -7
View File
@@ -7,7 +7,7 @@ import {
import { USER_AGENT } from 'bgutils-js';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
import { app, BrowserWindow, Menu, MenuItem, nativeImage, session, Tray } from 'electron';
import { app, BrowserWindow, Menu, MenuItem, nativeImage, session, shell, Tray } from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
@@ -185,18 +185,14 @@ export class ElectronCapacitorApp {
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
if (!details.url.includes(this.customScheme)) {
return { action: 'deny' };
} else {
return { action: 'allow' };
}
shell.openExternal(details.url);
return { action: 'deny' };
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
+8 -8
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.7.19",
"version": "1.7.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.7.19",
"version": "1.7.20",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -3423,9 +3423,9 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.20.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz",
"integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==",
"version": "2.20.4",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.4.tgz",
"integrity": "sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12904,9 +12904,9 @@
}
},
"node_modules/vite": {
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
"integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
"integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
"dev": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.7.19",
"version": "1.7.20",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -1,6 +1,6 @@
<script lang="ts">
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/time';
import { cleanNumber } from '$lib/numbers';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
@@ -2,8 +2,8 @@
import { getComments } from '$lib/api';
import { type Comment, type Comments } from '$lib/api/model';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { numberWithCommas } from '$lib/numbers';
import { interfaceLowBandwidthMode } from '$lib/store';
import { numberWithCommas } from '$lib/time';
import CommentSelf from './Comment.svelte';
interface Props {
@@ -1,5 +1,5 @@
<script lang="ts">
import { cleanNumber } from '$lib/time';
import { cleanNumber } from '$lib/numbers';
import { _ } from 'svelte-i18n';
import type { HashTag } from '../api/model';
import { truncate } from '../misc';
@@ -2,7 +2,8 @@
import { page } from '$app/stores';
import '$lib/css/shaka-player-theme.css';
import { getBestThumbnail } from '$lib/images';
import { padTime, videoLength } from '$lib/time';
import { padTime, videoLength } from '$lib/numbers';
import { type PhasedDescription } from '$lib/timestamps';
import { Capacitor } from '@capacitor/core';
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
import { StatusBar, Style } from '@capacitor/status-bar';
@@ -19,7 +20,6 @@
import { get } from 'svelte/store';
import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '../api';
import type { VideoPlay } from '../api/model';
import { type PhasedDescription } from '../misc';
import {
authStore,
instanceStore,
@@ -42,7 +42,6 @@
selectedSuggestionIndex = -1;
goto(`/search/${encodeURIComponent(search)}`);
dispatch('searchSubmitted');
suggestionsForSearch = [];
showSearchBox = false;
+78 -142
View File
@@ -1,18 +1,17 @@
<script lang="ts">
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { cleanNumber, videoLength } from '$lib/time';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import { cleanNumber, videoLength } from '$lib/numbers';
import { onDestroy, onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { getChannel, getDeArrow, getThumbnail, getVideo, getVideoProgress } from '../api';
import { getChannel, getDeArrow, getThumbnail, getVideoProgress } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { proxyVideoUrl, truncate } from '../misc';
import { truncate } from '../misc';
import type { PlayerEvents } from '../player';
import {
authStore,
deArrowEnabledStore,
deArrowTitlesOnly,
interfaceDisplayThumbnailAvatars,
interfaceLowBandwidthMode,
playerSavePlaybackPositionStore,
@@ -22,8 +21,6 @@
synciousStore
} from '../store';
const dispatch = createEventDispatcher();
interface Props {
video: VideoBase | Video | Notification | PlaylistPageVideo;
playlistId?: string;
@@ -32,8 +29,6 @@
let { video = $bindable(), playlistId = '', sideways = $bindable(false) }: Props = $props();
let thumbnailHidden: boolean = $state(false);
let authorImg: HTMLImageElement | undefined = $state();
let placeholderHeight: number = $state(0);
@@ -88,15 +83,6 @@
}
onMount(async () => {
try {
thumbnailHidden = localStorage.getItem(`v_h_${video.videoId}`) === '1';
} catch {}
if (thumbnailHidden) {
dispatch('videoHidden');
return;
}
calcThumbnailPlaceholderHeight();
// Check if sideways should be enabled or disabled.
@@ -138,46 +124,6 @@
}
}
} catch {}
if (!locatedThumbnail && !get(deArrowTitlesOnly)) {
// Process thumbnail locally.
const canvas = document.getElementById('canvas') as HTMLCanvasElement | null;
function generateThumbnail(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
if (canvas) {
const context = canvas.getContext('2d');
const mockVideo = document.createElement('video');
const videoContainer = document.getElementById('video-container');
videoContainer?.appendChild(mockVideo);
mockVideo.preload = 'auto';
mockVideo.id = 'video';
mockVideo.crossOrigin = 'anonymous';
const videoDetails = (await getVideo(video.videoId)).formatStreams[0];
mockVideo.src = proxyVideoUrl(videoDetails.url);
mockVideo.addEventListener('loadeddata', () => {
mockVideo.currentTime = mockVideo.duration / 50;
mockVideo.addEventListener('seeked', () => {
if (context) {
context.drawImage(mockVideo, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL('image/png'));
mockVideo.preload = 'none';
mockVideo.pause();
videoContainer?.removeChild(mockVideo);
}
});
});
mockVideo.load();
}
});
}
imageSrc = await generateThumbnail();
}
}
const img = new Image();
@@ -233,99 +179,89 @@
}
</script>
{#if !thumbnailHidden}
<div class:sideways-root={sideways}>
<div onfocus={() => {}} id="thumbnail-container" role="region">
<a
class="wave thumbnail"
href={watchUrl.toString()}
data-sveltekit-preload-data="off"
onclick={syncChangeVideo}
>
{#if !$interfaceLowBandwidthMode}
{#if !thumbnail}
<div
class="secondary-container"
style="width: 100%;height: {placeholderHeight}px;"
></div>
{:else}
<img class="responsive" src={thumbnail.src} alt="Thumbnail for video" />
{/if}
{/if}
{#if progress}
<progress
class="absolute right bottom"
style="z-index: 1;"
value={progress}
max={video.lengthSeconds}
></progress>
{/if}
{#if !('liveVideo' in video) || !video.liveVideo}
{#if video.lengthSeconds !== 0}
{#if !$interfaceLowBandwidthMode}
<div
class="absolute right bottom small-margin black white-text small-text thumbnail-corner"
>
&nbsp;{videoLength(video.lengthSeconds)}&nbsp;
</div>
{:else}
<h3>{videoLength(video.lengthSeconds)}</h3>
{/if}
{/if}
{:else if video.lengthSeconds !== 0}
<div
class="absolute right bottom small-margin red white-text small-text thumbnail-corner"
>
{$_('thumbnail.live')}
</div>
<div class:sideways-root={sideways}>
<div onfocus={() => {}} id="thumbnail-container" role="region">
<a
class="wave thumbnail"
href={watchUrl.toString()}
data-sveltekit-preload-data="off"
onclick={syncChangeVideo}
>
{#if !$interfaceLowBandwidthMode}
{#if !thumbnail}
<div class="secondary-container" style="width: 100%;height: {placeholderHeight}px;"></div>
{:else}
<h3>{$_('thumbnail.live')}</h3>
<img class="responsive" src={thumbnail.src} alt="Thumbnail for video" />
{/if}
</a>
</div>
<div class="thumbnail-details video-title">
{#if !sideways && !$interfaceLowBandwidthMode && $interfaceDisplayThumbnailAvatars}
<div style="margin-right: 1em;">
{#if authorImg}
<img src={authorImg.src} alt="Author" class="circle small" />
{:else}
<progress style="padding: 15px;" class="circle small"></progress>
{/if}
</div>
{/if}
<div class="video-title">
<a
style="padding-left: 1px;"
class="video-title"
data-sveltekit-preload-data="off"
href={watchUrl.toString()}
>
<span class="bold">{letterCase(truncate(video.title.trimEnd(), 80))}</span>
</a>
<div>
<a class:author={!sideways} href={`/channel/${video.authorId}`}>{video.author}</a>
{#if !('publishedText' in video) && 'viewCountText' in video}
<span style="margin-top: 1em;">
{video.viewCountText}
{$_('views')}
</span>
{/if}
{#if 'publishedText' in video}
<div class="max">
{cleanNumber(video.viewCount)}{video.publishedText}
{#if progress}
<progress
class="absolute right bottom"
style="z-index: 1;"
value={progress}
max={video.lengthSeconds}
></progress>
{/if}
{#if !('liveVideo' in video) || !video.liveVideo}
{#if video.lengthSeconds !== 0}
{#if !$interfaceLowBandwidthMode}
<div
class="absolute right bottom small-margin black white-text small-text thumbnail-corner"
>
&nbsp;{videoLength(video.lengthSeconds)}&nbsp;
</div>
{:else}
<h3>{videoLength(video.lengthSeconds)}</h3>
{/if}
{/if}
{:else if video.lengthSeconds !== 0}
<div class="absolute right bottom small-margin red white-text small-text thumbnail-corner">
{$_('thumbnail.live')}
</div>
{:else}
<h3>{$_('thumbnail.live')}</h3>
{/if}
</a>
</div>
<div class="thumbnail-details video-title">
{#if !sideways && !$interfaceLowBandwidthMode && $interfaceDisplayThumbnailAvatars}
<div style="margin-right: 1em;">
{#if authorImg}
<img src={authorImg.src} alt="Author" class="circle small" />
{:else}
<progress style="padding: 15px;" class="circle small"></progress>
{/if}
</div>
{/if}
<div class="video-title">
<a
style="padding-left: 1px;"
class="video-title"
data-sveltekit-preload-data="off"
href={watchUrl.toString()}
>
<span class="bold">{letterCase(truncate(video.title.trimEnd(), 80))}</span>
</a>
<div>
<a class:author={!sideways} href={`/channel/${video.authorId}`}>{video.author}</a>
{#if !('publishedText' in video) && 'viewCountText' in video}
<span style="margin-top: 1em;">
{video.viewCountText}
{$_('views')}
</span>
{/if}
{#if 'publishedText' in video}
<div class="max">
{cleanNumber(video.viewCount)}{video.publishedText}
</div>
{/if}
</div>
</div>
</div>
{/if}
<canvas id="canvas" style="display: none;"></canvas>
<div id="video-container" style="display: none;"></div>
</div>
<style>
.thumbnail {
@@ -1,5 +1,5 @@
<script lang="ts">
import { videoLength } from '$lib/time';
import { videoLength } from '$lib/numbers';
import Fuse from 'fuse.js';
import { VTTCue, parseText, type ParsedCaptionsResult } from 'media-captions';
import { _ } from 'svelte-i18n';
@@ -15,13 +15,11 @@
let { videos = [], playlistId = '', playlistAuthor = '' }: Props = $props();
let hiddenVideos: string[] = $state([]);
let auth = get(authStore);
async function removePlaylistItem(indexId: string, videoId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
hiddenVideos = [...hiddenVideos, videoId];
}
</script>
@@ -29,11 +27,9 @@
<div class="space"></div>
<div class="grid">
{#each videos as video}
{#if !hiddenVideos.includes(video.videoId)}
<ContentColumn>
<article class="no-padding" style="height: 100%;">
<Thumbnail
on:videoHidden={() => (hiddenVideos = [...hiddenVideos, video.videoId])}
{video}
{playlistId}
/>
@@ -50,7 +46,6 @@
{/if}
</article>
</ContentColumn>
{/if}
{/each}
</div>
</div>
-5
View File
@@ -9,11 +9,6 @@ export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
}
export interface PhasedDescription {
description: string;
timestamps: { title: string; time: number; timePretty: string; }[];
}
export function decodeHtmlCharCodes(str: string): string {
const { decode } = he;
return decode(str);
+1 -1
View File
@@ -1,7 +1,7 @@
import { androidPoTokenMinter } from '$lib/android/youtube/minter';
import type { AdaptiveFormats, Captions, Image, StoryBoard, Thumbnail, VideoBase, VideoPlay } from '$lib/api/model';
import { numberWithCommas } from '$lib/numbers';
import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
import { numberWithCommas } from '$lib/time';
import { Capacitor } from '@capacitor/core';
import { USER_AGENT } from 'bgutils-js';
import { Buffer } from 'buffer';
+45 -9
View File
@@ -1,33 +1,69 @@
import { type PhasedDescription, decodeHtmlCharCodes } from "./misc";
import { convertToSeconds } from "./time";
import { decodeHtmlCharCodes } from "./misc";
import { convertToSeconds } from "./numbers";
export function phaseDescription(content: string, usingYoutubeJs: boolean = false): PhasedDescription {
export interface PhasedDescription {
description: string;
timestamps: { title: string; time: number; timePretty: string; }[];
}
export function extractActualLink(url: string): string {
const urlParams = new URLSearchParams(url.split('?')[1]);
const actualLink = urlParams.get('q');
if (actualLink) {
return decodeURIComponent(actualLink);
}
return url;
}
export function processYoutubeLink(line: string): string {
// Regex to match the <a> tag and extract the href (YouTube redirect link)
const urlRegex = /<a href="https:\/\/www\.youtube\.com\/redirect\?([^"]+)"/;
const urlMatch = urlRegex.exec(line);
if (urlMatch) {
// Extract the YouTube redirect URL and get the actual URL from the `q` parameter
const redirectUrl = urlMatch[0]; // the full redirect URL with the `q` parameter
const actualUrl = extractActualLink(redirectUrl);
return line.replace(urlRegex, `<a href="${actualUrl}"`);
} else {
// If no match found, just return the original line
return line;
}
}
export function phaseDescription(videoId: string, content: string, fallbackPatch?: 'youtubejs' | 'piped'): PhasedDescription {
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
const lines = content.split('\n');
// Regular expressions for different timestamp formats
const urlRegex = /<a href="([^"]+)"/;
const timestampRegexInvidious = /<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
const timestampRegexYtJs = /&(?:\S*?&)?t=(\d+)\s*s.*?<span[^>]*>([^<]*)<\/span>.*?>(.*?)<\/span>/;
const timestampRegexYtJs = new RegExp(
`href="https://www\\.youtube\\.com/watch\\?v=${videoId}(?:&t=(\\d+)s)?"[^>]*>\\s*<span[^>]*>\\s*([^<]+)\\s*</span>\\s*</a>\\s*<span[^>]*>\\s*([^<]+)\\s*</span>`,
'i'
);
let filteredLines: string[] = [];
lines.forEach((line) => {
const urlMatch = urlRegex.exec(line);
// Use appropriate regex based on the `usingYoutubeJs` flag
const timestampMatch = (usingYoutubeJs ? timestampRegexYtJs : timestampRegexInvidious).exec(usingYoutubeJs ? line + '</span>' : line);
const timestampMatch = (fallbackPatch === 'youtubejs' ? timestampRegexYtJs : timestampRegexInvidious).exec(fallbackPatch === 'youtubejs' ? line + '</span>' : line);
if (urlMatch !== null && timestampMatch === null) {
// If line contains a URL but not a timestamp, modify the URL
const modifiedLine = line.replace(
const modifiedLine = processYoutubeLink(line).replace(
/<a href="([^"]+)"/,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link"'
);
console.log(modifiedLine);
filteredLines.push(modifiedLine);
} else if (timestampMatch !== null) {
// If line contains a timestamp, extract details and push into timestamps array
const time = usingYoutubeJs ? timestampMatch[1] : timestampMatch[2];
const timestamp = usingYoutubeJs ? timestampMatch[2] : timestampMatch[3];
const title = usingYoutubeJs ? timestampMatch[3] || '' : timestampMatch[4] || '';
const time = (fallbackPatch === 'youtubejs' ? timestampMatch[1] : timestampMatch[2]) || '0';
const timestamp = fallbackPatch === 'youtubejs' ? timestampMatch[2] : timestampMatch[3];
const title = fallbackPatch === 'youtubejs' ? timestampMatch[3] || '' : timestampMatch[4] || '';
timestamps.push({
time: convertToSeconds(time),
// Remove any HTML in the timestamp title.
@@ -153,6 +153,8 @@
onMount(async () => {
ui();
document.addEventListener('click', linkClickOverwrite);
scrollableRoot = document.querySelector('.root');
loadSettingsFromEnv();
@@ -183,6 +185,21 @@
}
});
function linkClickOverwrite(event: MouseEvent) {
// Handles opening links in browser for android.
if (Capacitor.getPlatform() !== 'android') return;
const link = (event.target as HTMLElement).closest('a');
if (link && link.href) {
if (link.href && link.href.startsWith('http') && link.target === '_blank') {
event.preventDefault();
Browser.open({ url: link.href });
}
}
}
let webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
</script>
@@ -6,8 +6,8 @@
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import VideoList from '$lib/components/VideoList.svelte';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/numbers';
import { activePageStore, authStore, interfaceLowBandwidthMode } from '$lib/store';
import { cleanNumber } from '$lib/time';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -137,21 +137,24 @@
<span>{$_('player.share.title')}</span>
<menu class="no-wrap mobile">
{#if !Capacitor.isNativePlatform()}
<a
href="#share"
<button
class="row"
onclick={async () => {
await Clipboard.write({ string: location.href });
}}>{$_('player.share.materialiousLink')}</a
}}>{$_('player.share.materialiousLink')}</button
>
{/if}
<!--Ugly hack to get pass svelte error-->
{#if true}
<button
class="row"
onclick={async () => {
await Clipboard.write({
string: `https://www.youtube.com/channel/${data.channel.authorId}`
});
}}>{$_('player.share.youtubeLink')}</button
>
{/if}
<a
href="#share"
onclick={async () => {
await Clipboard.write({
string: `https://www.youtube.com/channel/${data.channel.authorId}`
});
}}>{$_('player.share.youtubeLink')}</a
>
</menu>
</button>
</div>
@@ -3,8 +3,8 @@
import type { PlaylistPageVideo } from '$lib/api/model';
import VideoList from '$lib/components/VideoList.svelte';
import { unsafeRandomItem } from '$lib/misc';
import { cleanNumber } from '$lib/numbers';
import { activePageStore, playlistSettingsStore } from '$lib/store';
import { cleanNumber } from '$lib/time';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -18,6 +18,7 @@
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { truncate, unsafeRandomItem } from '$lib/misc';
import { cleanNumber, humanizeSeconds, numberWithCommas } from '$lib/numbers';
import type { PlayerEvents } from '$lib/player.js';
import {
activePageStore,
@@ -31,7 +32,6 @@
syncPartyConnectionsStore,
syncPartyPeerStore
} from '$lib/store';
import { cleanNumber, humanizeSeconds, numberWithCommas } from '$lib/time';
import ui from 'beercss';
import type { DataConnection } from 'peerjs';
import { type Segment } from 'sponsorblock-api';
@@ -52,7 +52,7 @@ export async function load({ params, url }) {
return {
video: video,
content: phaseDescription(video.descriptionHtml, video.fallbackPatch === 'youtubejs'),
content: phaseDescription(video.videoId, video.descriptionHtml, video.fallbackPatch),
playlistId: url.searchParams.get('playlist'),
streamed: {
personalPlaylists: personalPlaylists,
@@ -1,6 +1,6 @@
import { getVideo } from '$lib/api/index';
import type { PhasedDescription } from '$lib/misc';
import { playerProxyVideosStore } from '$lib/store';
import type { PhasedDescription } from '$lib/timestamps';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.7.19"
LATEST_VERSION = "1.7.20"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")