Merge pull request #1377 from Materialious/update/1.13.14
Update/1.13.14
This commit is contained in:
+3
-7
@@ -310,7 +310,7 @@ Specifies the SponsorBlock API instance.
|
||||
"sponsorBlockUrl": "https://sponsor.ajay.app"
|
||||
```
|
||||
|
||||
<!--### SponsorBlock categories
|
||||
### SponsorBlock categories
|
||||
|
||||
Defines which SponsorBlock categories should be skipped.
|
||||
|
||||
@@ -326,12 +326,8 @@ Available values include:
|
||||
* `filler`
|
||||
|
||||
```json
|
||||
"sponsorBlockCategories": [
|
||||
"intro",
|
||||
"outro",
|
||||
"interaction"
|
||||
]
|
||||
```-->
|
||||
"sponsorBlockCategoriesv2": {"sponsor":"manual","selfpromo":"automatic","preview":"timeline",}
|
||||
```
|
||||
|
||||
### Display toast
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 197
|
||||
versionName "1.13.13"
|
||||
versionCode 198
|
||||
versionName "1.13.14"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -96,7 +96,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.13.13" date="2026-2-07">
|
||||
|
||||
<release version="1.13.14" date="2026-2-09">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.13.14</url>
|
||||
</release>
|
||||
<release version="1.13.13" date="2026-2-07">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.13.13</url>
|
||||
</release>
|
||||
<release version="1.13.12" date="2026-2-06">
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.13.13",
|
||||
"version": "1.13.14",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Materialious",
|
||||
"version": "1.13.13",
|
||||
"version": "1.13.14",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.13.13",
|
||||
"version": "1.13.14",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
@@ -45,4 +45,4 @@
|
||||
"capacitor",
|
||||
"electron"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const CapacitorNodejs = require('../../../node_modules/capacitor-nodejs/electron/dist/plugin.js');
|
||||
|
||||
module.exports = {
|
||||
CapacitorNodejs,
|
||||
}
|
||||
@@ -190,6 +190,8 @@ export class ElectronCapacitorApp {
|
||||
globalShortcut.register('Control+Shift+I', () => {
|
||||
this.MainWindow.webContents.toggleDevTools();
|
||||
});
|
||||
this.MainWindow.webContents.toggleDevTools();
|
||||
|
||||
// Security
|
||||
this.MainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url);
|
||||
|
||||
Generated
+37
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.13.11",
|
||||
"version": "1.13.14",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "materialious",
|
||||
"version": "1.13.11",
|
||||
"version": "1.13.14",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
@@ -65,6 +65,7 @@
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.47.0",
|
||||
"svelte-awesome-color-picker": "^4.1.1",
|
||||
"svelte-check": "^4.3.3",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.9.3",
|
||||
@@ -5611,6 +5612,13 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -12540,6 +12548,33 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-awesome-color-picker": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-4.1.1.tgz",
|
||||
"integrity": "sha512-k4APFGBFN8EpvUS0UJfdlqj8xDekMbqGBwnDYBK1QCY8kcTXSO8c7SitEk70MkiYOIByOgXlHcjaipy6H7dpAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colord": "^2.9.3",
|
||||
"svelte-awesome-slider": "2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-awesome-slider": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-awesome-slider/-/svelte-awesome-slider-2.0.0.tgz",
|
||||
"integrity": "sha512-YBkOdYm1Feaqsn2JkJBRs+Kc/X3Qy/3GuVmI7GmoYDjBaHkjx9uH4khTuED22z57Hg3gGWeDhp/clIjWDdLNaw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.13.13",
|
||||
"version": "1.13.14",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run patch:github && vite dev",
|
||||
@@ -34,6 +34,7 @@
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.47.0",
|
||||
"svelte-awesome-color-picker": "^4.1.1",
|
||||
"svelte-check": "^4.3.3",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.9.3",
|
||||
@@ -79,4 +80,4 @@
|
||||
"youtubei.js": "^16.0.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +649,11 @@
|
||||
});
|
||||
playerElement = document.getElementById('player') as HTMLMediaElement;
|
||||
|
||||
// Enable AirPlay if supported
|
||||
if (hasWebkitShowPlaybackTargetPicker(playerElement)) {
|
||||
playerElement.setAttribute('x-webkit-airplay', 'allow');
|
||||
}
|
||||
|
||||
playerElement.loop = playerLoop;
|
||||
|
||||
if ($playerState) {
|
||||
@@ -832,6 +837,9 @@
|
||||
} else {
|
||||
playerElement.pause();
|
||||
}
|
||||
|
||||
showPlayerUI();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -839,44 +847,48 @@
|
||||
Mousetrap.bind('right', () => {
|
||||
if (!playerElement) return;
|
||||
playerElement.currentTime = playerElement.currentTime + playerDoubleTapSeek;
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('left', () => {
|
||||
if (!playerElement) return;
|
||||
|
||||
playerElement.currentTime = playerElement.currentTime - playerDoubleTapSeek;
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('enter', () => {
|
||||
if (segmentManualSkip) {
|
||||
skipSegment(segmentManualSkip);
|
||||
showPlayerUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Mousetrap.bind('c', () => {
|
||||
toggleSubtitles();
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('f', () => {
|
||||
toggleFullscreen();
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('shift+left', () => {
|
||||
if (!playerElement) return;
|
||||
|
||||
playerElement.playbackRate = playerElement.playbackRate - 0.25;
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('shift+right', () => {
|
||||
if (!playerElement) return;
|
||||
|
||||
playerElement.playbackRate = playerElement.playbackRate + 0.25;
|
||||
showPlayerUI();
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -886,6 +898,8 @@
|
||||
const currentTrack = player.getVariantTracks().find((track) => track.active);
|
||||
const frameTime = 1 / (currentTrack?.frameRate || 30);
|
||||
playerElement.currentTime -= frameTime;
|
||||
|
||||
showPlayerUI();
|
||||
});
|
||||
|
||||
Mousetrap.bind('.', () => {
|
||||
@@ -894,6 +908,8 @@
|
||||
const currentTrack = player.getVariantTracks().find((track) => track.active);
|
||||
const frameTime = 1 / (currentTrack?.frameRate || 30);
|
||||
playerElement.currentTime += frameTime;
|
||||
|
||||
showPlayerUI();
|
||||
});
|
||||
|
||||
playerVolumeElement?.addEventListener('mousewheel', (event) => {
|
||||
@@ -911,6 +927,7 @@
|
||||
playerCurrentPlaybackState = false;
|
||||
playerIsBuffering = false;
|
||||
savePlayerPos();
|
||||
showPlayerUI();
|
||||
});
|
||||
|
||||
playerElement?.addEventListener('ended', async () => {
|
||||
@@ -927,6 +944,7 @@
|
||||
playerElement?.addEventListener('waiting', () => {
|
||||
playerCurrentPlaybackState = false;
|
||||
playerIsBuffering = true;
|
||||
showPlayerUI();
|
||||
});
|
||||
|
||||
playerElement?.addEventListener('timeupdate', () => {
|
||||
@@ -988,6 +1006,18 @@
|
||||
playerTextTracks = player.getTextTracks();
|
||||
});
|
||||
|
||||
function hasWebkitShowPlaybackTargetPicker(
|
||||
el: HTMLMediaElement
|
||||
): el is HTMLMediaElement & { webkitShowPlaybackTargetPicker: () => void } {
|
||||
return typeof (el as any).webkitShowPlaybackTargetPicker === 'function';
|
||||
}
|
||||
|
||||
function handleAirPlayClick() {
|
||||
if (playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)) {
|
||||
playerElement.webkitShowPlaybackTargetPicker();
|
||||
}
|
||||
}
|
||||
|
||||
async function getLastPlayPos(): Promise<number> {
|
||||
if (loadTimeFromUrl($page) || !$playerSavePlaybackPositionStore) return 0;
|
||||
|
||||
@@ -1513,6 +1543,11 @@
|
||||
{/if}
|
||||
</menu>
|
||||
</button>
|
||||
{#if playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)}
|
||||
<button class="inverse-primary" onclick={handleAirPlayClick} title="AirPlay">
|
||||
<i>airplay</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if document.pictureInPictureEnabled}
|
||||
<button
|
||||
class="inverse-primary"
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import ui from 'beercss';
|
||||
import { iso31661 } from 'iso-3166';
|
||||
import type { RgbaColor, HsvaColor, Colord } from 'colord';
|
||||
import { _ } from '$lib/i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { ensureNoTrailingSlash } from '../../misc';
|
||||
import { ensureNoTrailingSlash, isMobile } from '../../misc';
|
||||
import { getPages, type Pages } from '../../navPages';
|
||||
import ColorPicker from 'svelte-awesome-color-picker';
|
||||
import {
|
||||
authStore,
|
||||
darkModeStore,
|
||||
@@ -32,6 +34,8 @@
|
||||
searchHistoryStore,
|
||||
themeColorStore
|
||||
} from '../../store';
|
||||
import { addToast } from '../Toast.svelte';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
let invidiousInstance = $state(get(instanceStore));
|
||||
let region = $state(get(interfaceRegionStore));
|
||||
@@ -39,12 +43,23 @@
|
||||
let defaultPage = $state(get(interfaceDefaultPage));
|
||||
|
||||
let invalidInstance = $state(false);
|
||||
let colorPickerOpen = $state(false);
|
||||
let colorPickerDebounce: ReturnType<typeof setTimeout>;
|
||||
|
||||
async function setColor(color: any) {
|
||||
const target = color.target;
|
||||
const hex = (target as { value: string }).value;
|
||||
setAmoledTheme();
|
||||
themeColorStore.set(hex);
|
||||
async function setColor(color: {
|
||||
hsv: HsvaColor | null;
|
||||
rgb: RgbaColor | null;
|
||||
hex: string | null;
|
||||
color: Colord | null;
|
||||
}) {
|
||||
if (!color.hex) return;
|
||||
if (colorPickerDebounce) clearTimeout(colorPickerDebounce);
|
||||
|
||||
colorPickerDebounce = setTimeout(async () => {
|
||||
themeColorStore.set(color.hex);
|
||||
await tick();
|
||||
setAmoledTheme();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
@@ -139,8 +154,11 @@
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
{#if invalidInstance}
|
||||
<div style="margin-bottom: 6em;"></div>
|
||||
|
||||
{#if isMobile()}
|
||||
{#if invalidInstance}
|
||||
<div style="margin-bottom: 6em;"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if Capacitor.isNativePlatform() && (invalidInstance || $interfaceAllowInsecureRequests)}
|
||||
@@ -172,13 +190,27 @@
|
||||
<span>{$_('layout.theme.lightMode')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button>
|
||||
<button onclick={() => (colorPickerOpen = !colorPickerOpen)}>
|
||||
<i>palette</i>
|
||||
<span>{$_('layout.theme.color')}</span>
|
||||
<input onchange={setColor} type="color" />
|
||||
</button>
|
||||
|
||||
<div class="margin"></div>
|
||||
{#if colorPickerOpen}
|
||||
<div class="space"></div>
|
||||
|
||||
<div class="color-picker">
|
||||
<ColorPicker
|
||||
isTextInput={false}
|
||||
isDialog={false}
|
||||
onInput={setColor}
|
||||
position="responsive"
|
||||
isAlpha={false}
|
||||
sliderDirection="horizontal"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
{#if $darkModeStore}
|
||||
<div class="field no-margin">
|
||||
@@ -396,12 +428,38 @@
|
||||
</div>
|
||||
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<div class="space"></div>
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.bookmarklet')}</h6>
|
||||
<button
|
||||
class="no-margin"
|
||||
onclick={async () => await Clipboard.write({ string: bookmarkletSaveToUrl() })}
|
||||
>{$_('copyUrl')}</button
|
||||
onclick={async () => {
|
||||
await Clipboard.write({ string: bookmarkletSaveToUrl() });
|
||||
addToast({
|
||||
data: {
|
||||
text: $_('player.share.copiedSuccess')
|
||||
}
|
||||
});
|
||||
}}>{$_('copyUrl')}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.color-picker {
|
||||
--cp-bg-color: var(--surface-container);
|
||||
--cp-border-color: transparent;
|
||||
--cp-text-color: var(--on-surface);
|
||||
--cp-input-color: var(--surface);
|
||||
--cp-button-hover-color: var(--surface-variant);
|
||||
--slider-width: 50px;
|
||||
--picker-width: 45vw;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.color-picker {
|
||||
--picker-width: 85vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:has(>main) {
|
||||
:has(> main) {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
@@ -96,6 +96,31 @@ menu {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.color-picker .picker {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.color-picker .wrapper {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.color-picker .thumb {
|
||||
height: 20px !important;
|
||||
width: 20px !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.color-picker .track {
|
||||
border-radius: 0.25rem !important;
|
||||
}
|
||||
|
||||
.color-picker .h {
|
||||
margin-top: 0 !important;
|
||||
justify-content: flex-start !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
menu.mobile {
|
||||
position: fixed !important;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
darkModeStore,
|
||||
deArrowEnabledStore,
|
||||
@@ -34,256 +37,275 @@ import {
|
||||
interfaceAutoExpandChapters,
|
||||
playerDefaultPlaybackSpeed,
|
||||
playerCCByDefault,
|
||||
playerMiniplayerEnabled
|
||||
playerMiniplayerEnabled,
|
||||
sponsorBlockCategoriesStore
|
||||
} from '$lib/store';
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
|
||||
const persistedStores: {
|
||||
type PersistedStore<T> = {
|
||||
name: string;
|
||||
store: Writable<any>;
|
||||
type: 'string' | 'boolean' | 'array' | 'number';
|
||||
}[] = [
|
||||
store: Writable<T>;
|
||||
schema: z.ZodType<T>;
|
||||
serialize?: (value: T) => string;
|
||||
};
|
||||
|
||||
function parseWithSchema<T>(schema: z.ZodType<T>, raw: unknown): T | undefined {
|
||||
try {
|
||||
if (typeof raw === 'string') {
|
||||
// Try JSON first (records / objects)
|
||||
try {
|
||||
return schema.parse(JSON.parse(raw));
|
||||
} catch {
|
||||
return schema.parse(raw);
|
||||
}
|
||||
}
|
||||
return schema.parse(raw);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const zBoolean = z.coerce.boolean();
|
||||
const zNumber = z.coerce.number();
|
||||
const zString = z.string();
|
||||
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
|
||||
const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
|
||||
|
||||
const persistedStores: PersistedStore<any>[] = [
|
||||
{
|
||||
name: 'returnYTDislikesInstance',
|
||||
store: returnYTDislikesInstanceStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'darkMode',
|
||||
store: darkModeStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'themeColor',
|
||||
store: themeColorStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'autoPlay',
|
||||
store: playerAutoPlayStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'alwaysLoop',
|
||||
store: playerAlwaysLoopStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'proxyVideos',
|
||||
store: playerProxyVideosStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'savePlaybackPosition',
|
||||
store: playerSavePlaybackPositionStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'theatreModeByDefault',
|
||||
store: playerTheatreModeByDefaultStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'autoplayNextByDefault',
|
||||
store: playerAutoplayNextByDefaultStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'returnYtDislikes',
|
||||
store: returnYtDislikesStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'searchSuggestions',
|
||||
store: interfaceSearchSuggestionsStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlock',
|
||||
store: sponsorBlockStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockUrl',
|
||||
store: sponsorBlockUrlStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'deArrowInstance',
|
||||
store: deArrowInstanceStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'deArrowEnabled',
|
||||
store: deArrowEnabledStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'deArrowThumbnailInstance',
|
||||
store: deArrowThumbnailInstanceStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'syncious',
|
||||
store: synciousStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'synciousInstance',
|
||||
store: synciousInstanceStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'region',
|
||||
store: interfaceRegionStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'forceCase',
|
||||
store: interfaceForceCase,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'autoExpandComments',
|
||||
store: interfaceAutoExpandComments,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'autoExpandDesc',
|
||||
store: interfaceAutoExpandDesc,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'deArrowTitlesOnly',
|
||||
store: deArrowTitlesOnly,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockDisplayToast',
|
||||
store: sponsorBlockDisplayToastStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'amoledTheme',
|
||||
store: interfaceAmoledTheme,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'showWarning',
|
||||
store: showWarningStore,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'lowBandwidthMode',
|
||||
store: interfaceLowBandwidthMode,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'displayThumbnailAvatars',
|
||||
store: interfaceDisplayThumbnailAvatars,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'defaultLanguage',
|
||||
store: playerDefaultLanguage,
|
||||
type: 'boolean'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'defaultPage',
|
||||
store: interfaceDefaultPage,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'defaultQuality',
|
||||
store: playerDefaultQualityStore,
|
||||
type: 'string'
|
||||
schema: zString
|
||||
},
|
||||
{
|
||||
name: 'autoExpandChapters',
|
||||
store: interfaceAutoExpandChapters,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'defaultPlaybackSpeed',
|
||||
store: playerDefaultPlaybackSpeed,
|
||||
type: 'number'
|
||||
schema: zNumber
|
||||
},
|
||||
{
|
||||
name: 'CCByDefault',
|
||||
store: playerCCByDefault,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'miniplayerEnabled',
|
||||
store: playerMiniplayerEnabled,
|
||||
type: 'boolean'
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockCategoriesv2',
|
||||
store: sponsorBlockCategoriesStore,
|
||||
schema: zChapterModeRecord,
|
||||
serialize: JSON.stringify
|
||||
}
|
||||
];
|
||||
|
||||
function setStores(toSet: Record<string, any>, overwriteExisting: boolean = false) {
|
||||
persistedStores.forEach((store) => {
|
||||
const paramValue = toSet[store.name];
|
||||
if (typeof paramValue !== 'undefined' && overwriteExisting) {
|
||||
let value: any;
|
||||
function setStores(toSet: Record<string, unknown>, overwriteExisting = false) {
|
||||
if (!overwriteExisting) return;
|
||||
|
||||
if (store.type === 'array') {
|
||||
value = paramValue.split(',');
|
||||
} else if (store.type === 'number') {
|
||||
value = Number(paramValue);
|
||||
} else if (store.type === 'boolean') {
|
||||
value = typeof paramValue === 'string' ? paramValue === 'true' : paramValue;
|
||||
} else {
|
||||
value = paramValue;
|
||||
}
|
||||
for (const { name, store, schema } of persistedStores) {
|
||||
const raw = toSet[name];
|
||||
if (raw === undefined) continue;
|
||||
|
||||
store.store.set(value);
|
||||
const parsed = parseWithSchema(schema, raw);
|
||||
if (parsed !== undefined) {
|
||||
store.set(parsed);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function loadSettingsFromEnv() {
|
||||
if (typeof import.meta.env.VITE_DEFAULT_SETTINGS !== 'string') return;
|
||||
|
||||
// If $VITE_DEFAULT_SETTINGS is passed via docker, it will be wrapped with quotations.
|
||||
let defaultSettingsJson = import.meta.env.VITE_DEFAULT_SETTINGS;
|
||||
let raw = import.meta.env.VITE_DEFAULT_SETTINGS;
|
||||
|
||||
if (defaultSettingsJson.startsWith('"')) {
|
||||
defaultSettingsJson = defaultSettingsJson.slice(1);
|
||||
}
|
||||
|
||||
if (defaultSettingsJson.endsWith('"')) {
|
||||
defaultSettingsJson = defaultSettingsJson.slice(0, -1);
|
||||
}
|
||||
// Docker wraps env vars in quotes
|
||||
if (raw.startsWith('"')) raw = raw.slice(1);
|
||||
if (raw.endsWith('"')) raw = raw.slice(0, -1);
|
||||
|
||||
let isInitialLoad = false;
|
||||
try {
|
||||
const isInitialLoadStorage = localStorage.getItem('initialLoadState');
|
||||
if (isInitialLoadStorage === null) {
|
||||
if (localStorage.getItem('initialLoadState') === null) {
|
||||
isInitialLoad = true;
|
||||
localStorage.setItem('initialLoadState', '0');
|
||||
}
|
||||
} catch {
|
||||
// In an environment where localstorage isn't allowed, treat as initial load.
|
||||
isInitialLoad = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const defaultSettings = JSON.parse(defaultSettingsJson);
|
||||
setStores(defaultSettings, isInitialLoad);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const parsed = JSON.parse(raw);
|
||||
setStores(parsed, isInitialLoad);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
export function bookmarkletSaveToUrl(): string {
|
||||
const url = new URL(location.origin);
|
||||
|
||||
persistedStores.forEach((store) => {
|
||||
const value = get(store.store);
|
||||
if (value !== null && value !== undefined) {
|
||||
url.searchParams.set(store.name, value.toString());
|
||||
for (const { name, store, serialize } of persistedStores) {
|
||||
const value = get(store);
|
||||
const encoded = serialize ? serialize(value) : value?.toString();
|
||||
|
||||
if (encoded !== undefined) {
|
||||
url.searchParams.set(name, encoded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.13.13"
|
||||
LATEST_VERSION = "1.13.14"
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user