Merge pull request #1291 from Materialious/update/1.13.1

Update/1.13.1
This commit is contained in:
Ward
2026-01-05 20:22:08 +00:00
committed by GitHub
13 changed files with 139 additions and 74 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 184
versionName "1.13.0"
versionCode 185
versionName "1.13.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -81,7 +81,11 @@
<release version="1.13.0" date="2026-1-03">
<release version="1.13.1" date="2026-1-06">
<url>https://github.com/Materialious/Materialious/releases/tag/1.13.1</url>
</release>
<release version="1.13.0" date="2026-1-03">
<url>https://github.com/Materialious/Materialious/releases/tag/1.13.0</url>
</release>
<release version="1.12.11" date="2026-1-02">
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.13.0",
"version": "1.13.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.13.0",
"version": "1.13.1",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.13.0",
"version": "1.13.1",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.13.0",
"version": "1.13.1",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
@@ -77,4 +77,4 @@
"svelte-infinite-loading": "^1.4.0",
"youtubei.js": "^16.0.1"
}
}
}
+50 -14
View File
@@ -38,6 +38,7 @@
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockTimelineStore,
sponsorBlockUrlStore,
synciousInstanceStore,
synciousStore
@@ -71,7 +72,8 @@
showControls = false
}: Props = $props();
let segments: Segment[] = [];
let segments: Segment[] = $state([]);
let segmentManualSkip: Segment | undefined = $state();
let snackBarAlert = $state('');
let originalOrigination: ScreenOrientationResult | undefined;
@@ -264,20 +266,31 @@
document.addEventListener('fullscreenchange', onAndroidFullscreenChange);
}
function skipSegment(segment: Segment) {
if (!playerElement) return;
segmentManualSkip = undefined;
if (Math.round(playerElement.currentTime) >= Math.round(playerElement.duration)) {
return;
}
playerElement.currentTime = segment.endTime + 1;
if (!get(sponsorBlockDisplayToastStore)) {
snackBarAlert = `${get(_)('skipping')} ${segment.category}`;
ui('#snackbar-alert');
}
}
async function setupSponsorSkip() {
if (!$sponsorBlockUrlStore || !$sponsorBlockCategoriesStore || !$sponsorBlockStore) return;
if (
$sponsorBlockCategoriesStore.length > 0 &&
$sponsorBlockUrlStore &&
$sponsorBlockUrlStore !== ''
) {
if ($sponsorBlockCategoriesStore && $sponsorBlockUrlStore && $sponsorBlockUrlStore !== '') {
const sponsorBlock = new SponsorBlock('', { baseURL: $sponsorBlockUrlStore });
try {
segments = await sponsorBlock.getSegments(
data.video.videoId,
$sponsorBlockCategoriesStore as Category[]
Object.keys($sponsorBlockCategoriesStore) as Category[]
);
playerElement?.addEventListener('timeupdate', () => {
@@ -288,12 +301,13 @@
playerElement.currentTime >= segment.startTime &&
playerElement.currentTime <= segment.endTime
) {
if (Math.round(playerElement.currentTime) >= Math.round(playerElement.duration)) {
return;
}
playerElement.currentTime = segment.endTime + 1;
if (!get(sponsorBlockDisplayToastStore)) {
snackBarAlert = `${get(_)('skipping')} ${segment.category}`;
const segmentTrigger = $sponsorBlockCategoriesStore[segment.category];
if (segmentTrigger === 'automatic') {
skipSegment(segment);
} else if (segmentTrigger === 'manual') {
snackBarAlert = `${get(_)('upcomingSegment')} ${segment.category}`;
segmentManualSkip = segment;
ui('#snackbar-alert');
}
}
@@ -747,6 +761,12 @@
playerElement.currentTime = playerElement.currentTime - playerDoubleTapSeek;
return false;
});
Mousetrap.bind('enter', () => {
if (segmentManualSkip) {
skipSegment(segmentManualSkip);
}
});
}
Mousetrap.bind('c', () => {
@@ -1057,7 +1077,7 @@
window.removeEventListener('resize', updateVideoPlayerHeight);
Mousetrap.unbind(['left', 'right', 'space', 'c', 'f', 'shift+left', 'shift+right']);
Mousetrap.unbind(['left', 'right', 'space', 'c', 'f', 'shift+left', 'shift+right', 'enter']);
if (watchProgressInterval) clearTimeout(watchProgressInterval);
@@ -1170,6 +1190,15 @@
)}
></div>
{/each}
{#if !$sponsorBlockTimelineStore}
{#each segments as segment (segment)}
<div
class="chapter-marker segment-marker"
style:left="{(segment.startTime / playerMaxKnownTime) * 100}%"
style:width={getMarkerWidth(segment.startTime, segment.endTime)}
></div>
{/each}
{/if}
</label>
{#if playerTimelineTooltipVisible}
@@ -1443,6 +1472,9 @@
{#if !isEmbed}
<div class="snackbar" id="snackbar-alert">
<span class="bold" style="text-transform: capitalize;">{snackBarAlert}</span>
{#if segmentManualSkip}
<button onclick={() => skipSegment(segmentManualSkip as Segment)}>{$_('skip')}</button>
{/if}
</div>
{/if}
@@ -1547,6 +1579,10 @@
pointer-events: none;
}
.segment-marker {
background-color: var(--tertiary);
}
menu.mobile {
transform: scale(1) translateY(-40%) translateX(0);
width: 300px;
@@ -8,14 +8,10 @@
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockTimelineStore,
sponsorBlockUrlStore
} from '../../store';
let sponsorCategoriesList: string[] = $state([]);
sponsorBlockCategoriesStore.subscribe((value) => {
sponsorCategoriesList = value;
});
let sponsorBlockInstance = $state(get(sponsorBlockUrlStore));
const sponsorCategories = [
@@ -28,13 +24,21 @@
{ name: $_('layout.sponsors.tangentJokes'), category: 'filler' }
];
function toggleSponsor(category: string) {
if (sponsorCategoriesList.includes(category)) {
sponsorBlockCategoriesStore.set(sponsorCategoriesList.filter((value) => value !== category));
function onSponsorSet(
category: string,
event: Event & { currentTarget: EventTarget & HTMLSelectElement }
) {
const value = event.currentTarget.value as 'automatic' | 'manual' | 'timeline' | 'disabled';
const categories = get(sponsorBlockCategoriesStore);
if (value !== 'disabled') {
categories[category] = value;
} else {
sponsorCategoriesList.push(category);
sponsorBlockCategoriesStore.set(sponsorCategoriesList);
delete categories[category];
}
sponsorBlockCategoriesStore.set(categories);
}
</script>
@@ -89,7 +93,7 @@
</label>
</nav>
<!-- <nav class="no-padding">
<nav class="no-padding">
<div class="max">
<p>{$_('layout.sponsors.disableTimeline')}</p>
</div>
@@ -101,27 +105,36 @@
/>
<span></span>
</label>
</nav> -->
</nav>
<hr style="margin: 1em 0;" />
<p class="bold">{$_('layout.sponsors.Catagories')}</p>
{#each sponsorCategories as sponsor (sponsor)}
{@const currentCatergoryTrigger = $sponsorBlockCategoriesStore[sponsor.category]}
<div class="field middle-align no-margin">
<nav class="no-padding">
<div class="max">
<p>{sponsor.name}</p>
</div>
<label class="switch" tabindex="0">
<input
type="checkbox"
checked={sponsorCategoriesList.includes(sponsor.category)}
onclick={() => toggleSponsor(sponsor.category)}
role="switch"
/>
<span></span>
</label>
<div class="field suffix border">
<select onchange={(event) => onSponsorSet(sponsor.category, event)}>
<option selected={currentCatergoryTrigger === undefined} value="disabled"
>{$_('disabled')}</option
>
<option selected={currentCatergoryTrigger === 'automatic'} value="automatic"
>{$_('layout.sponsors.automatic')}</option
>
<option selected={currentCatergoryTrigger === 'manual'} value="manual"
>{$_('layout.sponsors.manual')}</option
>
<option selected={currentCatergoryTrigger === 'timeline'} value="timeline"
>{$_('layout.sponsors.timeline')}</option
>
</select>
<i>arrow_drop_down</i>
</div>
</nav>
</div>
{/each}
-6
View File
@@ -25,7 +25,6 @@ import {
returnYTDislikesInstanceStore,
returnYtDislikesStore,
showWarningStore,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockUrlStore,
@@ -109,11 +108,6 @@ const persistedStores: {
store: sponsorBlockUrlStore,
type: 'string'
},
{
name: 'sponsorBlockCategories',
store: sponsorBlockCategoriesStore,
type: 'array'
},
{
name: 'deArrowInstance',
store: deArrowInstanceStore,
+6 -1
View File
@@ -29,6 +29,8 @@
"donate": "Donate",
"deleteAllHistory": "Delete all history",
"skipping": "Skipping",
"skip": "Skip",
"upcomingSegment": "Upcoming ",
"comments": "comments",
"reply": "reply",
"replies": "replies",
@@ -203,7 +205,10 @@
"intermissionIntroAnimation": "Intermission/Intro Animation",
"credits": "Endcards/Credits",
"preViewRecapHook": "Preview/Recap/Hook",
"tangentJokes": "Filler Tangent/Jokes"
"tangentJokes": "Filler Tangent/Jokes",
"automatic": "Auto skip",
"manual": "Manual skip",
"timeline": "Timeline only"
},
"deArrow": {
"title": "DeArrow",
+31 -17
View File
@@ -8,6 +8,7 @@ import type {
VideoBase,
VideoPlay
} from '$lib/api/model';
import { convertToSeconds } from '$lib/numbers';
import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
import { Capacitor } from '@capacitor/core';
import { USER_AGENT } from 'bgutils-js';
@@ -165,30 +166,43 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
});
const recommendedVideos: VideoBase[] = [];
video.watch_next_feed?.forEach((recommended: Record<string, any>) => {
video.watch_next_feed?.forEach((recommended) => {
if (
!recommended.metadata?.title?.text ||
!recommended.is(YTNodes.LockupView) ||
recommended.content_type !== 'VIDEO' ||
!recommended.metadata.metadata.metadata_rows ||
!recommended.metadata ||
!recommended.content_image?.is(YTNodes.ThumbnailView) ||
!recommended.metadata.metadata ||
recommended.metadata.metadata.metadata_rows.length < 2
) {
)
return;
}
const durationOverlay = recommended.content_image.overlays
?.find(
(overlay) =>
overlay.is(YTNodes.ThumbnailOverlayBadgeView) &&
overlay.position === 'THUMBNAIL_OVERLAY_BADGE_POSITION_BOTTOM_END'
)
?.as(YTNodes.ThumbnailOverlayBadgeView);
recommendedVideos.push({
videoThumbnails: (recommended?.content_image?.image as Thumbnail[]) || [],
videoId: recommended?.content_id || '',
title: recommended.metadata?.title?.text || '',
viewCountText: recommended.metadata.metadata.metadata_rows[1]?.metadata_parts[0]
? recommended.metadata.metadata.metadata_rows[1]?.metadata_parts[0]?.text.text?.replace(
'views',
''
)
: '',
lengthSeconds: recommended?.duration?.seconds || 0,
author: recommended.metadata.metadata.metadata_rows[0]?.metadata_parts[0]?.text.text || '',
videoThumbnails: (recommended?.content_image.image as Thumbnail[]) || [],
videoId: recommended.content_id,
title: recommended.metadata.title.toString(),
viewCountText:
(recommended.metadata.metadata.metadata_rows[1]?.metadata_parts?.[0]?.text ?? '')
.toString()
.replace('views', '') || '',
author:
(
recommended.metadata.metadata.metadata_rows[0]?.metadata_parts?.[0]?.text ?? ''
).toString() || '',
lengthSeconds: durationOverlay?.badges[0].text
? convertToSeconds(durationOverlay?.badges[0].text)
: 0,
authorId:
recommended.metadata?.image?.renderer_context?.command_context?.on_tap.payload?.browseId ||
recommended.metadata?.image?.renderer_context?.command_context?.on_tap?.payload?.browseId ||
'',
type: 'video'
});
+4 -5
View File
@@ -279,11 +279,10 @@ export const sponsorBlockUrlStore: Writable<string | null | undefined> = persist
createStorage(),
'sponsorBlockUrl'
);
export const sponsorBlockCategoriesStore: Writable<string[]> = persist(
writable([]),
createStorage(),
'sponsorBlockCategories'
);
export const sponsorBlockCategoriesStore: Writable<
Record<string, 'automatic' | 'manual' | 'timeline' | undefined>
> = persist(writable({}), createStorage(), 'sponsorBlockCategoriesv2');
export const sponsorBlockDisplayToastStore: Writable<boolean> = persist(
writable(false),
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.13.0"
LATEST_VERSION = "1.13.1"
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")