Merge pull request #924 from Materialious/update/1.8.6

Update/1.8.6
This commit is contained in:
Ward
2025-05-29 19:09:02 +12:00
committed by GitHub
18 changed files with 179 additions and 110 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 109
versionName "1.8.5"
versionCode 110
versionName "1.8.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -48,6 +48,22 @@
android:scheme="https"
android:host="youtu.be"
android:pathPrefix="/watch" />
<data
android:scheme="https"
android:host="youtube.com"
android:pathPrefix="/shorts" />
<data
android:scheme="https"
android:host="www.youtube.com"
android:pathPrefix="/shorts" />
<data
android:scheme="https"
android:host="m.youtube.com"
android:pathPrefix="/shorts" />
<data
android:scheme="https"
android:host="youtu.be"
android:pathPrefix="/shorts" />
</intent-filter>
</activity>
@@ -54,7 +54,11 @@
</content_rating>
<releases>
<release version="1.8.5" date="2025-5-29">
<release version="1.8.6" date="2025-5-29">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.6</url>
</release>
<release version="1.8.5" date="2025-5-29">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.5</url>
</release>
<release version="1.8.4" date="2025-5-05">
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.8.5",
"version": "1.8.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.8.5",
"version": "1.8.6",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -2492,9 +2492,9 @@
}
},
"node_modules/electron": {
"version": "35.5.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.5.0.tgz",
"integrity": "sha512-16ScwDuKgnuL7tSrEgBvQe1Hm4CSK0vbOusPFrDs4oIs3QOdEFtrP9i8+4yKQGXpszj4f4F0MQjKv1tu9E4Gvg==",
"version": "35.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.2.1.tgz",
"integrity": "sha512-LO4xXLpzkPPUVLvAbdHMlW7N9Z+Qqz+7QsmSWXluTIQMeJk+v7o36nUTZghyA2I1s//tlMvuaCtle6Qo2W0Ktg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.8.5",
"version": "1.8.6",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -44,4 +44,4 @@
"capacitor",
"electron"
]
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.8.1",
"version": "1.8.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.8.1",
"version": "1.8.6",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.8.5",
"version": "1.8.6",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -1,6 +1,4 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation';
import { bookmarkletSaveToUrl } from '$lib/externalSettings';
import { letterCase, titleCases } from '$lib/letterCasing';
@@ -73,8 +71,16 @@
if (invalidInstance) return;
const resp = await fetch(`${instance}/api/v1/trending`);
if (!resp.ok) {
let resp;
try {
resp = await fetch(`${instance}/api/v1/trending`);
} catch {
invalidInstance = true;
}
if (invalidInstance) return;
if (resp && !resp.ok) {
invalidInstance = true;
return;
}
@@ -87,7 +93,7 @@
</script>
{#if Capacitor.isNativePlatform()}
<form style="margin-bottom: 2em;" onsubmit={setInstance}>
<form onsubmit={setInstance}>
<nav>
<div class="field label border max" class:invalid={invalidInstance}>
<input bind:value={invidiousInstance} name="invidious-instance" type="text" />
@@ -101,6 +107,9 @@
</button>
</nav>
</form>
{#if invalidInstance}
<div style="margin-bottom: 4em;"></div>
{/if}
{/if}
<button onclick={toggleDarkMode} class="no-margin">
@@ -3,19 +3,6 @@
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import {
deArrowInstanceStore,
deArrowThumbnailInstanceStore,
instanceStore,
interfaceDefaultPage,
interfaceForceCase,
interfaceRegionStore,
playerDefaultLanguage,
returnYTDislikesInstanceStore,
sponsorBlockCategoriesStore,
sponsorBlockUrlStore,
synciousInstanceStore
} from '../../store';
import ApiExtended from './ApiExtended.svelte';
import DataPreferences from './DataPreferences.svelte';
import DeArrow from './DeArrow.svelte';
@@ -24,43 +11,6 @@
import Ryd from './RYD.svelte';
import SponsorBlock from './SponsorBlock.svelte';
let sponsorCategoriesList: string[] = [];
sponsorBlockCategoriesStore.subscribe((value) => (sponsorCategoriesList = value));
let sponsorBlockInstance = get(sponsorBlockUrlStore);
let synciousInstance = get(synciousInstanceStore);
let returnYTInstance = get(returnYTDislikesInstanceStore);
let invidiousInstance = get(instanceStore);
let deArrowUrl = get(deArrowInstanceStore);
let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore);
let region = get(interfaceRegionStore);
let forceCase = get(interfaceForceCase);
let defaultLanguage = get(playerDefaultLanguage);
let defaultPage = get(interfaceDefaultPage);
const languageNames = ISO6391.getAllCodes().map((code) =>
ISO6391.getName(code).toLocaleLowerCase()
);
const sponsorCategories = [
{ name: $_('layout.sponsors.sponsor'), category: 'sponsor' },
{ name: $_('layout.sponsors.unpaidSelfPromotion'), category: 'selfpromo' },
{ name: $_('layout.sponsors.interactionReminder'), category: 'interaction' },
{ name: $_('layout.sponsors.intermissionIntroAnimation'), category: 'intro' },
{ name: $_('layout.sponsors.credits'), category: 'outro' },
{ name: $_('layout.sponsors.preViewRecapHook'), category: 'preview' },
{ name: $_('layout.sponsors.tangentJokes'), category: 'filler' }
];
function toggleSponsor(category: string) {
if (sponsorCategoriesList.includes(category)) {
sponsorBlockCategoriesStore.set(sponsorCategoriesList.filter((value) => value !== category));
} else {
sponsorCategoriesList.push(category);
sponsorBlockCategoriesStore.set(sponsorCategoriesList);
}
}
let activeTab:
| 'interface'
| 'player'
@@ -84,15 +34,6 @@
checkWidth();
addEventListener('resize', () => checkWidth());
// Set default language if not set already based off browser.
if (!get(playerDefaultLanguage)) {
const languageCode = navigator.language || navigator.languages[0];
const languageName = ISO6391.getName(languageCode.split('-')[0]);
if (languageName) {
playerDefaultLanguage.set(languageName.toLowerCase());
}
}
});
</script>
@@ -181,12 +122,11 @@
@media screen and (max-width: 1320px) {
.tabbed {
display: flex !important;
flex-direction: column !important;
gap: 0 !important;
flex: 0 !important;
margin-bottom: 1em !important;
align-items: start !important;
padding: 1em 0 !important;
overflow-x: scroll !important;
background-color: transparent;
border-bottom: solid 1px var(--outline-variant);
}
@@ -274,7 +274,7 @@
}
.sideways-root {
height: 115px;
height: 110px;
display: flex;
align-items: flex-start;
}
@@ -40,6 +40,118 @@
}
});
export function dedupeAndMergeOverlappingCues(cues: VTTCue[], timeOffset = -1): VTTCue[] {
const merged: VTTCue[] = [];
for (const cue of cues) {
const trimmedText = cue.text.trim();
if (!trimmedText) {
continue;
}
let wasMerged = false;
for (let i = 0; i < merged.length; i++) {
const existing = merged[i];
const timeGap = Math.abs(cue.startTime - existing.endTime);
const isTimeAdjacent = timeGap <= 1.5;
if (isTimeAdjacent && areCuesTextSimilar(existing.text, cue.text)) {
const mergedText = mergeCueText(existing.text, cue.text);
const newStart = Math.max(0, Math.min(existing.startTime, cue.startTime) + timeOffset);
const newEnd = Math.max(existing.endTime, cue.endTime) + timeOffset;
const mergedCue = new VTTCue(newStart, newEnd, mergedText);
copyCueMeta(mergedCue, cue);
merged[i] = mergedCue;
wasMerged = true;
break;
}
}
if (!wasMerged) {
const offsetCue = new VTTCue(
Math.max(0, cue.startTime + timeOffset),
cue.endTime + timeOffset,
cue.text
);
copyCueMeta(offsetCue, cue);
merged.push(offsetCue);
}
}
// Ensure no overlapping cues (each cue ends before the next starts)
for (let i = 0; i < merged.length - 1; i++) {
const current = merged[i];
const next = merged[i + 1];
if (current.endTime > next.startTime) {
current.endTime = Math.max(current.startTime, next.startTime - 0.01); // 10ms gap
}
}
return merged;
}
function areCuesTextSimilar(a: string, b: string): boolean {
if (a === b) return true;
// Normalize and compare overlap ratio
const cleanA = normalizeText(a);
const cleanB = normalizeText(b);
const overlap = getTextOverlap(cleanA, cleanB);
const minLength = Math.min(cleanA.length, cleanB.length);
// Consider similar if 50% or more overlap
return overlap / minLength >= 0.5;
}
function getTextOverlap(a: string, b: string): number {
const maxOverlap = Math.min(a.length, b.length);
for (let i = maxOverlap; i > 0; i--) {
if (a.endsWith(b.slice(0, i)) || b.endsWith(a.slice(0, i))) {
return i;
}
}
return 0;
}
function normalizeText(text: string): string {
return text
.trim()
.replace(/\s+/g, ' ')
.replace(/[.,?!"'”“]/g, '')
.toLowerCase();
}
function mergeCueText(a: string, b: string): string {
// Avoid duplicating overlap
const overlapLen = getTextOverlap(a, b);
if (a.endsWith(b.slice(0, overlapLen))) {
return a + b.slice(overlapLen);
}
if (b.endsWith(a.slice(0, overlapLen))) {
return b + a.slice(overlapLen);
}
return `${a} ${b}`;
}
function copyCueMeta(target: VTTCue, source: VTTCue): void {
target.region = source.region;
target.vertical = source.vertical;
target.snapToLines = source.snapToLines;
target.line = source.line;
target.lineAlign = source.lineAlign;
target.position = source.position;
target.positionAlign = source.positionAlign;
target.size = source.size;
target.align = source.align;
target.style = source.style;
}
async function loadTranscript() {
if (!url) {
transcript = null;
@@ -48,9 +160,11 @@
isLoading = true;
transcript = null;
const resp = await fetch(`${!video.fallbackPatch ? get(instanceStore) : ''}${url}`);
transcript = await parseText(await resp.text(), { strict: false });
transcriptCues = transcript.cues;
transcriptCues = dedupeAndMergeOverlappingCues(transcript.cues);
isLoading = false;
}
@@ -58,7 +172,7 @@
if (!transcript) return;
if (search.trim() === '') {
transcriptCues = transcript.cues;
transcriptCues = dedupeAndMergeOverlappingCues(transcript.cues);
return;
}
@@ -66,7 +180,8 @@
keys: ['text']
});
transcriptCues = fuse.search(search).map((item) => item.item);
const results = fuse.search(search).map((item) => item.item);
transcriptCues = dedupeAndMergeOverlappingCues(results);
}
</script>
-10
View File
@@ -59,16 +59,6 @@ button.row:hover {
}
}
@media screen and (max-width: 650px) {
::-webkit-scrollbar {
display: none !important;
}
html {
scrollbar-width: none !important;
}
}
@media screen and (max-width: 1000px) {
menu.mobile {
z-index: 9999 !important;
@@ -285,7 +285,6 @@
height: 50vh !important;
transform: translate(-50%, -50%) !important;
pointer-events: all !important;
z-index: 99999999 !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
@@ -320,20 +319,18 @@
width: 100vw !important;
height: 100vh !important;
background: transparent !important;
z-index: 99999998 !important;
pointer-events: auto !important;
}
.youtube-theme .shaka-overflow-menu:hover,
.youtube-theme .shaka-settings-menu:hover {
z-index: 99999998 !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
}
#shaka-container {
z-index: 99999998 !important;
z-index: 98 !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
+1 -1
View File
@@ -42,7 +42,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
: window.electronAPI.generatePoToken;
poTokenCacheStore.set(
await platformMinter(challengeResponse.bg_challenge, requestKey, visitorData)
await platformMinter(challengeResponse.bg_challenge, requestKey, visitorData)
);
const extraArgs: Record<string, any> = {
+3 -5
View File
@@ -21,6 +21,7 @@
darkModeStore,
instanceStore,
interfaceAmoledTheme,
interfaceDefaultPage,
showWarningStore,
syncPartyPeerStore,
themeColorStore
@@ -36,11 +37,8 @@
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { pwaInfo } from 'virtual:pwa-info';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
let { children } = $props();
let mobileSearchShow = $state(false);
@@ -238,7 +236,7 @@
</nav>
{/if}
<nav onclick={() => goto('/')} style="cursor: pointer;" class="m l">
<nav onclick={() => goto($interfaceDefaultPage)} style="cursor: pointer;" class="m l">
<Logo />
<h6 class="l">Materialious</h6>
</nav>
@@ -443,7 +443,7 @@
<div class="s12 m12 l5">
<nav>
<a href={`/channel/${data.video.authorId}`}>
<nav>
<nav style="gap: 0.5em;">
{#if !$interfaceLowBandwidthMode}
<img
class="circle large"
@@ -482,18 +482,18 @@
<div class="s12 m12 l7 video-actions">
{#await data.streamed.returnYTDislikes then returnYTDislikes}
{#if returnYTDislikes}
<nav class="no-space" style="margin-right: .5em;">
<nav class="no-space">
<button style="cursor: default;" class="border left-round">
<i class="small">thumb_up</i>
<span>{cleanNumber(returnYTDislikes.likes)}</span>
</button>
<button style="cursor: default;" class="border right-round">
<button style="cursor: default;margin-right: 0.5em;" class="border right-round">
<i class="small">thumb_down_alt</i>
<span>{cleanNumber(returnYTDislikes.dislikes)}</span>
</button>
</nav>
{:else}
<button style="cursor: default;" class="border">
<button style="cursor: default;margin-right: 0.5em;" class="border">
<i class="small">thumb_up</i>
<span>{cleanNumber(data.video.likeCount)}</span>
</button>
+2 -2
View File
@@ -7,7 +7,7 @@ import { authStore, interfaceDefaultPage } from '$lib/store.js';
import { locale, waitLocale } from 'svelte-i18n';
import { get } from 'svelte/store';
export let ssr = false;
export const ssr = false;
export async function load({ url }) {
if (url.pathname.startsWith('/@')) {
@@ -18,7 +18,7 @@ export async function load({ url }) {
if (resolvedUrl.pageType === 'WEB_PAGE_TYPE_CHANNEL') {
goto(`/channel/${resolvedUrl.ucid}`);
}
} catch { }
} catch {}
}
if (browser) {
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.8.5"
LATEST_VERSION = "1.8.6"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(