Merge pull request #1624 from Materialious/update/1.16.24

Update/1.16.24
This commit is contained in:
Ward
2026-04-09 02:00:48 +12:00
committed by GitHub
31 changed files with 705 additions and 282 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 240
versionName "1.16.23"
versionCode 241
versionName "1.16.24"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -82,7 +82,11 @@
<release version="1.16.23" date="2026-3-25">
<release version="1.16.24" date="2026-4-07">
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.24</url>
</release>
<release version="1.16.23" date="2026-3-25">
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.23</url>
</release>
<release version="1.16.22" date="2026-3-17">
+30 -30
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.16.22",
"version": "1.16.23",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.16.22",
"version": "1.16.23",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -22,7 +22,7 @@
"youtubei.js": "^17.0.1"
},
"devDependencies": {
"electron": "^41.0.3",
"electron": "^41.1.0",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.9.3"
@@ -401,9 +401,9 @@
}
},
"node_modules/@electron/asar/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1652,9 +1652,9 @@
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
"integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -2183,9 +2183,9 @@
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2390,9 +2390,9 @@
}
},
"node_modules/cacache/node_modules/rimraf/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2956,9 +2956,9 @@
}
},
"node_modules/dir-compare/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3105,9 +3105,9 @@
}
},
"node_modules/electron": {
"version": "41.0.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-41.0.3.tgz",
"integrity": "sha512-IDjx8liW1q+r7+MOip5W1Eo1eMwJzVObmYrd9yz2dPCkS7XlgLq3qPVMR80TpiROFp73iY30kTzMdpA6fEVs3A==",
"version": "41.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-41.1.0.tgz",
"integrity": "sha512-0XRFyxRqetmqtkkBvV++wGbHYJ7bD++f6EgJW8y9kX4pPRagwlmKDtzqXZhKiu0DIQppm3sXxzHWK9GYP91OKQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -3886,9 +3886,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4856,9 +4856,9 @@
}
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
@@ -5376,9 +5376,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.16.23",
"version": "1.16.24",
"description": "Modern material design for YouTube and Invidious.",
"author": {
"name": "Ward Pearce",
@@ -35,7 +35,7 @@
"youtubei.js": "^17.0.1"
},
"devDependencies": {
"electron": "^41.0.3",
"electron": "^41.1.0",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.9.3"
+50 -50
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.16.22",
"version": "1.16.24",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.16.22",
"version": "1.16.24",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -91,7 +91,7 @@
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^7.3.1"
"vite": "^7.3.2"
}
},
"node_modules/@acemir/cssom": {
@@ -1972,9 +1972,9 @@
}
},
"node_modules/@capacitor/assets/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3037,9 +3037,9 @@
}
},
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5002,9 +5002,9 @@
}
},
"node_modules/@rollup/plugin-commonjs/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5107,9 +5107,9 @@
}
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6479,9 +6479,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7669,9 +7669,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -9792,9 +9792,9 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10167,9 +10167,9 @@
}
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10877,15 +10877,15 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/minimatch": {
@@ -11008,9 +11008,9 @@
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14856,9 +14856,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -17660,9 +17660,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -18301,9 +18301,9 @@
}
},
"node_modules/vite": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -18450,9 +18450,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -18929,16 +18929,16 @@
}
},
"node_modules/workbox-build/node_modules/brace-expansion": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"node": "18 || 20 || >=22"
}
},
"node_modules/workbox-build/node_modules/estree-walker": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.16.23",
"version": "1.16.24",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
@@ -44,7 +44,7 @@
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^7.3.1"
"vite": "^7.3.2"
},
"type": "module",
"dependencies": {
@@ -58,16 +58,16 @@
<nav>
<a href={resolve(`/channel/[authorId]`, { authorId: channel.authorId })}>
<nav style="gap: 0.5em;">
<img class="circle large" {...avatar.image} alt="Channel profile" />
<img class="circle" {...avatar.image} alt="Channel profile" />
<button
class="circle large secondary-container"
class="circle small secondary-container"
{...mergeAttrs(avatar.fallback, {
style: 'text-transform: uppercase;border-radius: 2.5rem !important;'
})}>{channel.author[0]}</button
>
<div>
<p style="margin: 0;" class="bold">
{$isAndroidTvStore ? channel.author : truncate(channel.author, 16)}
{$isAndroidTvStore ? channel.author : truncate(channel.author, 14)}
</p>
<p style="margin: 0;">{channel.subCountText}</p>
</div>
@@ -19,14 +19,20 @@
if (src) avatarSrc = proxyGoogleImage(src);
});
});
function goToChannel() {
goto(resolve('/channel/[authorId]', { authorId }));
}
</script>
<img class="circle small" {...avatar.image} alt="Channel profile" />
<img
class="circle small"
{...mergeAttrs(avatar.image, { onclick: goToChannel })}
alt="Channel profile"
/>
<button
class="circle secondary-container"
onclick={() => {
goto(resolve('/channel/[authorId]', { authorId }));
}}
onclick={goToChannel}
{...mergeAttrs(avatar.fallback, {
style: 'text-transform: uppercase;border-radius: 2.5rem !important;'
})}>{author[0]}</button
+1 -1
View File
@@ -14,7 +14,7 @@
let fillColor = $state('');
async function setFill() {
fillColor = (await getDynamicTheme())['--primary'];
fillColor = (await getDynamicTheme())['--primary'] ?? '#fff';
}
onMount(async () => {
@@ -448,6 +448,8 @@
transform: translateY(-50%);
z-index: 1;
pointer-events: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: var(--player-border-radius);
border-bottom-right-radius: var(--player-border-radius);
opacity: 0.5;
@@ -6,10 +6,21 @@
import { importSubscriptions } from '$lib/importExport';
import { postSubscribeInvidious } from '$lib/api/invidious/subscribe';
import { getSubscriptionsInvidious } from '$lib/api/invidious/feed';
import { backendInUseStore, invidiousAuthStore, invidiousInstanceStore } from '$lib/store';
import {
backendInUseStore,
interfaceAdvancedThemingStore,
interfaceBorderRadiusStore,
invidiousAuthStore,
invidiousInstanceStore
} from '$lib/store';
import { Clipboard } from '@capacitor/clipboard';
import { loadSettingsFromFile, settingsToJson } from '$lib/externalSettings';
import {
bookmarkletSaveToUrl,
loadSettingsFromFile,
settingsToJson
} from '$lib/externalSettings';
import { downloadStringAsFile } from '$lib/misc';
import { zNumber, zThemeColors } from '$lib/externalSettings/settings';
async function importInvidiousSubs() {
const importedSubs = await getSubscriptionsInvidious();
@@ -179,3 +190,79 @@
type="file"
/>
</div>
<h3>{$_('layout.theme.theme')}</h3>
<div class="space"></div>
<button
class="no-margin surface-container-highest"
onclick={async () => {
await Clipboard.write({
string: bookmarkletSaveToUrl([
{
name: 'advancedTheming',
store: interfaceAdvancedThemingStore,
schema: zThemeColors,
serialize: JSON.stringify
},
{
name: 'borderRadius',
store: interfaceBorderRadiusStore,
schema: zNumber
}
])
});
addToast({
data: {
text: $_('player.share.copiedSuccess')
}
});
}}
>
<i>content_copy</i>
<span>{$_('layout.export.exportToShareUrl')}</span>
</button>
<div class="space"></div>
<button
class="no-margin surface-container-highest"
onclick={async () => {
downloadStringAsFile(
settingsToJson([
{
name: 'advancedTheming',
store: interfaceAdvancedThemingStore,
schema: zThemeColors,
serialize: JSON.stringify
},
{
name: 'borderRadius',
store: interfaceBorderRadiusStore,
schema: zNumber
}
]),
'materialious-theme.json'
);
}}
>
<i>file_export</i>
<span>{$_('layout.export.exportToFile')}</span>
</button>
<div class="space"></div>
<div>
<button class="surface-container-highest">
<i>attach_file</i>
<span>{$_('layout.export.importFromFile')}</span>
</button>
<input
onchange={async (event: Event) => {
const files = (event.target as HTMLInputElement).files;
if (files?.length === 0 || !files) return;
await loadSettingsFromFile(files[0]);
}}
accept=".json"
type="file"
/>
</div>
@@ -1,24 +1,18 @@
<script lang="ts">
import { bookmarkletSaveToUrl } from '$lib/externalSettings/index';
import { letterCase, titleCases, type TitleCase } from '$lib/letterCasing';
import { setAmoledTheme } from '$lib/theme';
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 { isUnrestrictedPlatform, timeout, shareURL } from '$lib/misc';
import { getPages, type Pages } from '$lib/navPages';
import { setInvidiousInstance, goToInvidiousLogin, invidiousLogout } from '$lib/auth';
import ColorPicker from 'svelte-awesome-color-picker';
import {
invidiousAuthStore,
backendInUseStore,
darkModeStore,
invidiousInstanceStore,
interfaceAllowInsecureRequests,
interfaceAmoledTheme,
interfaceAndroidUseNativeShare,
interfaceAutoExpandChapters,
interfaceAutoExpandComments,
@@ -30,46 +24,14 @@
interfaceSearchHistoryEnabled,
interfaceSearchSuggestionsStore,
searchHistoryStore,
themeColorStore,
watchHistoryEnabledStore
} from '../../store';
import { tick } from 'svelte';
import { isOwnBackend } from '$lib/shared';
import ComboBox from '../ComboBox.svelte';
let invidiousInstance = $state(get(invidiousInstanceStore));
let invalidInstance = $state(false);
let colorPickerOpen = $state(false);
let colorPickerDebounce: ReturnType<typeof setTimeout>;
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() {
const isDark = get(darkModeStore);
if (isDark) {
ui('mode', 'light');
darkModeStore.set(false);
} else {
ui('mode', 'dark');
darkModeStore.set(true);
}
}
async function setInstance(event: Event) {
event.preventDefault();
@@ -130,7 +92,7 @@
<form onsubmit={setInstance}>
<nav>
<div
class="field prefix suffix label surface-container-highest max"
class="field prefix label surface-container-highest max"
class:invalid={invalidInstance}
>
<i>link</i>
@@ -144,17 +106,6 @@
{#if invalidInstance}
<span class="error">{$_('invalidInstance')}</span>
{/if}
{#if $invidiousInstanceStore}
<i
role="presentation"
class="front"
onclick={() => {
invidiousInstanceStore.set(undefined);
invidiousInstance = undefined;
invidiousLogout();
}}>close</i
>
{/if}
</div>
<button class="circle">
<i>done</i>
@@ -199,57 +150,6 @@
{/if}
{/if}
<button onclick={toggleDarkMode} class="no-margin surface-container-highest">
{#if !$darkModeStore}
<i>dark_mode</i>
<span>{$_('layout.theme.darkMode')}</span>
{:else}
<i>light_mode</i>
<span>{$_('layout.theme.lightMode')}</span>
{/if}
</button>
<button class="surface-container-highest" onclick={() => (colorPickerOpen = !colorPickerOpen)}>
<i>palette</i>
<span>{$_('layout.theme.color')}</span>
</button>
{#if colorPickerOpen}
<div class="space"></div>
<div class="color-picker">
<ColorPicker
isTextInput={false}
isDialog={false}
onInput={setColor}
position="responsive"
isAlpha={false}
hex={get(themeColorStore)}
sliderDirection="horizontal"
/>
</div>
{/if}
<div class="space"></div>
{#if $darkModeStore}
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.theme.AmoledTheme')}</div>
</div>
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAmoledTheme}
onclick={() => interfaceAmoledTheme.set(!$interfaceAmoledTheme)}
role="switch"
/>
<span></span>
</label>
</nav>
</div>
{/if}
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
@@ -447,22 +347,3 @@
</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: 500px;
width: 100%;
}
@media screen and (max-width: 640px) {
.color-picker {
--picker-width: 95vw;
}
}
</style>
@@ -16,6 +16,7 @@
import { mergeAttrs } from 'melt';
import Filters from './Filters.svelte';
import ExportImport from './ExportImport.svelte';
import Theme from './Theme.svelte';
type TabCategories =
| 'interface'
@@ -27,7 +28,8 @@
| 'engine'
| 'account'
| 'filters'
| 'export';
| 'export'
| 'theme';
const tabCategories: Tabs<TabCategories> = new Tabs({
value: 'interface',
@@ -42,6 +44,7 @@
let tabs: { id: TabCategories; label: string; icon: string; component: Component }[] = $state([
{ id: 'interface', label: $_('layout.interface'), icon: 'grid_view', component: Interface },
{ id: 'theme', label: $_('layout.theme.theme'), icon: 'colors', component: Theme },
{ id: 'player', label: $_('layout.player.title'), icon: 'smart_display', component: Player },
{ id: 'filters', label: $_('layout.filter.title'), icon: 'filter_alt', component: Filters },
{ id: 'ryd', label: 'Return YT Dislike', icon: 'thumb_down', component: Ryd },
@@ -113,7 +113,7 @@
{@const currentCategoryTrigger = $sponsorBlockCategoriesStore[sponsor.category]}
<div class="field middle-align">
<nav class="no-padding">
<nav class="no-padding combobox">
<div class="max">
<p>{sponsor.name}</p>
</div>
@@ -130,3 +130,12 @@
</nav>
</div>
{/each}
<style>
@media screen and (max-width: 640px) {
nav.combobox {
flex-direction: column;
align-items: start;
}
}
</style>
@@ -0,0 +1,239 @@
<script lang="ts">
import {
getDynamicTheme,
setAmoledTheme,
setThemeColor,
type ThemeColors,
type ThemeKey
} from '$lib/theme';
import ui from 'beercss';
import type { RgbaColor, HsvaColor, Colord } from 'colord';
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import ColorPicker from 'svelte-awesome-color-picker';
import {
darkModeStore,
interfaceAdvancedThemingStore,
interfaceAmoledTheme,
interfaceBorderRadiusStore,
themeColorStore
} from '../../store';
import { onMount, tick } from 'svelte';
import { titleCase } from '$lib/letterCasing';
let colorPickerOpen = $state(false);
let colorPickerDebounce: ReturnType<typeof setTimeout>;
let currentThemeColors: ThemeColors | undefined = $state();
onMount(async () => {
currentThemeColors = await getDynamicTheme();
});
async function setThemeColors() {
await tick();
currentThemeColors = await getDynamicTheme();
interfaceAdvancedThemingStore.set(currentThemeColors);
}
async function colorOnInput(
color: {
hsv: HsvaColor | null;
rgb: RgbaColor | null;
hex: string | null;
color: Colord | null;
},
propetyKey: ThemeKey
) {
if (colorPickerDebounce) clearTimeout(colorPickerDebounce);
colorPickerDebounce = setTimeout(async () => {
if (!color.hex) return;
setThemeColor(propetyKey, color.hex);
await setThemeColors();
}, 100);
}
async function themeColorOnInput(color: {
hsv: HsvaColor | null;
rgb: RgbaColor | null;
hex: string | null;
color: Colord | null;
}) {
if (!color.hex) return;
if (colorPickerDebounce) clearTimeout(colorPickerDebounce);
colorPickerDebounce = setTimeout(async () => {
interfaceAdvancedThemingStore.set({});
themeColorStore.set(color.hex);
await tick();
setAmoledTheme();
currentThemeColors = await getDynamicTheme();
}, 100);
}
async function toggleDarkMode() {
const isDark = get(darkModeStore);
interfaceAdvancedThemingStore.set({});
if (isDark) {
ui('mode', 'light');
darkModeStore.set(false);
} else {
ui('mode', 'dark');
darkModeStore.set(true);
}
currentThemeColors = await getDynamicTheme();
}
</script>
<button onclick={toggleDarkMode} class="no-margin surface-container-highest">
{#if !$darkModeStore}
<i>dark_mode</i>
<span>{$_('layout.theme.darkMode')}</span>
{:else}
<i>light_mode</i>
<span>{$_('layout.theme.lightMode')}</span>
{/if}
</button>
<button class="surface-container-highest" onclick={() => (colorPickerOpen = !colorPickerOpen)}>
<i>palette</i>
<span>{$_('layout.theme.color')}</span>
</button>
{#if colorPickerOpen}
<div class="space"></div>
<div class="color-picker global-color-picker">
<ColorPicker
isTextInput={false}
isDialog={false}
onInput={themeColorOnInput}
position="responsive"
isAlpha={false}
hex={get(themeColorStore)}
sliderDirection="horizontal"
/>
</div>
{/if}
<div class="space"></div>
{#if $darkModeStore}
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.theme.AmoledTheme')}</div>
</div>
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$interfaceAmoledTheme}
onclick={async () => {
interfaceAdvancedThemingStore.set({});
interfaceAmoledTheme.set(!$interfaceAmoledTheme);
await setThemeColors();
}}
role="switch"
/>
<span></span>
</label>
</nav>
</div>
{/if}
<h5>{$_('layout.theme.borderRadius.borderRadius')}</h5>
<div class="grid">
<div class="s6 m3 l3">
<button
onclick={() => {
interfaceBorderRadiusStore.set(0);
}}
class="surface-container-highest"
style="border-radius: 0 !important;width: 80%;"
>
<p>{$_('layout.theme.borderRadius.none')}</p>
</button>
</div>
<div class="s6 m3 l3">
<button
onclick={() => {
interfaceBorderRadiusStore.set(0.25);
}}
class="surface-container-highest"
style="border-radius: 0.25rem !important;width: 80%;"
>
<p>{$_('layout.theme.borderRadius.slight')}</p>
</button>
</div>
<div class="s6 m3 l3">
<button
onclick={() => {
interfaceBorderRadiusStore.set(0.5);
}}
class="surface-container-highest"
style="border-radius: 0.5rem !important;width: 80%;"
>
<p>{$_('layout.theme.borderRadius.modern')}</p>
</button>
</div>
<div class="s6 m3 l3">
<button
onclick={() => {
interfaceBorderRadiusStore.set(1);
}}
class="surface-container-highest"
style="border-radius: 1rem !important;width: 80%;"
>
<p>{$_('layout.theme.borderRadius.rounded')}</p>
</button>
</div>
</div>
<h5>{$_('layout.theme.advanced')}</h5>
<div class="space"></div>
{#if currentThemeColors}
{#each Object.entries(currentThemeColors) as [themeVar, themeColor] (themeVar)}
<div class="color-picker">
<ColorPicker
hex={themeColor}
label={titleCase(themeVar.replaceAll('--', '').replaceAll('-', ' '))}
position="responsive"
textInputModes={['hex']}
sliderDirection="vertical"
onInput={async (color) => await colorOnInput(color, themeVar as ThemeKey)}
/>
</div>
{/each}
{/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: 30px;
--picker-width: 350px;
width: 100%;
--input-size: 40px;
}
.global-color-picker {
--slider-width: 50px;
--picker-width: 500px;
}
@media screen and (max-width: 640px) {
.global-color-picker {
--picker-width: 95vw;
}
}
</style>
+14 -3
View File
@@ -43,6 +43,7 @@ body {
article {
box-shadow: none !important;
border-radius: var(--border-radius) !important;
}
.primary-border {
@@ -75,7 +76,17 @@ nav.bottom {
}
dialog {
border-radius: 0.25rem !important;
border-radius: var(--border-radius) !important;
}
:is(.button, button):not(.chip):active,
:is(.button, button):not(.chip).active {
border-radius: var(--border-radius) !important;
}
nav:not(.max):is(.left, .right, .top, .bottom) > a:not(.button, .chip) > i,
nav:not(.max):is(.left, .right, .top, .bottom) > :is(ol, ul) > li > a:not(.button, .chip) > i {
border-radius: var(--border-radius) !important;
}
main.root {
@@ -103,7 +114,7 @@ menu {
right: 0;
}
.color-picker .picker {
.global-color-picker .picker {
display: none !important;
}
@@ -183,7 +194,7 @@ menu.player-settings {
.player-slider [data-melt-slider-thumb] {
position: absolute;
border-radius: 1rem;
border-radius: var(--player-border-radius);
background: var(--primary);
left: var(--percentage);
top: 50%;
@@ -2,7 +2,7 @@ import { page } from '$app/state';
import { get } from 'svelte/store';
import { z } from 'zod';
import { persistedStores } from './settings';
import { persistedStores, type PersistedStore } from './settings';
import { isOwnBackend } from '$lib/shared';
import { addOrUpdateKeyValue, getKeyValue } from '$lib/api/backend/keyvalue';
@@ -121,10 +121,10 @@ export function loadSettingsFromEnv() {
}
}
export function bookmarkletSaveToUrl(): string {
export function bookmarkletSaveToUrl(stores: PersistedStore<any>[] = persistedStores): string {
const url = new URL(location.origin);
for (const { name, store, serialize, excludeFromBookmarklet } of persistedStores) {
for (const { name, store, serialize, excludeFromBookmarklet } of stores) {
const value = get(store);
const encoded = serialize ? serialize(value) : value?.toString();
@@ -136,10 +136,10 @@ export function bookmarkletSaveToUrl(): string {
return url.toString();
}
export function settingsToJson(): string {
export function settingsToJson(stores: PersistedStore<any>[] = persistedStores): string {
const settings: Record<string, string> = {};
for (const { name, store, excludeFromBookmarklet } of persistedStores) {
for (const { name, store, excludeFromBookmarklet } of stores) {
const value = get(store);
if (!excludeFromBookmarklet) {
settings[name] = value;
@@ -53,11 +53,14 @@ import {
playerPreferredVolumeStore,
watchHistoryEnabledStore,
filterContentUrlStore,
filterContentUrlAutoUpdateStore
filterContentUrlAutoUpdateStore,
interfaceAdvancedThemingStore,
interfaceBorderRadiusStore
} from '$lib/store';
import { isOwnBackend } from '$lib/shared';
import { SUPPORTED_THEME_KEYS } from '$lib/shared/theme';
type PersistedStore<T> = {
export type PersistedStore<T> = {
name: string;
store: Writable<T>;
schema: z.ZodType<T>;
@@ -66,17 +69,23 @@ type PersistedStore<T> = {
excludeFromBackendSync?: boolean; // Won't be sync'd to account cloud
};
const zBoolean = z.coerce.boolean();
const zNumber = z.coerce.number();
const zFloat = z.coerce.number().min(0).max(1);
const zString = z.string();
const zArray = z.array(z.string());
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
const zAuth = z.object({
export const zBoolean = z.coerce.boolean();
export const zNumber = z.coerce.number();
export const zFloat = z.coerce.number().min(0).max(1);
export const zString = z.string();
export const zArray = z.array(z.string());
export const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
export const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
export const zAuth = z.object({
username: z.string(),
token: z.string()
});
export const zThemeColors = z.record(
z.string().refine((val) => SUPPORTED_THEME_KEYS.includes(val), {
message: 'CSS property not allowed'
}),
z.string().regex(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)
);
export const persistedStores: PersistedStore<any>[] = [
{
@@ -270,6 +279,17 @@ export const persistedStores: PersistedStore<any>[] = [
name: 'filterContentUrlAutoUpdate',
store: filterContentUrlAutoUpdateStore,
schema: zBoolean
},
{
name: 'advancedTheming',
store: interfaceAdvancedThemingStore,
schema: zThemeColors,
serialize: JSON.stringify
},
{
name: 'borderRadius',
store: interfaceBorderRadiusStore,
schema: zNumber
}
];
+1
View File
@@ -51,6 +51,7 @@ const zFilterRootSchema = z.object({
});
export function isItemFiltered(item: FeedItem): boolean {
console.log(item);
const filteredContent = get(filterContentListStore);
if (!filteredContent) return false;
+11 -2
View File
@@ -199,7 +199,15 @@
"darkMode": "Dark mode",
"lightMode": "Light mode",
"AmoledTheme": "Amoled theme",
"color": "Color"
"color": "Color",
"advanced": "Advanced theming",
"borderRadius": {
"none": "None",
"slight": "Slight",
"modern": "Modern",
"rounded": "Rounded",
"borderRadius": "Border radius"
}
},
"searchSuggestions": "Search suggestions",
"previewVideoOnHover": "Preview video on hover",
@@ -287,7 +295,8 @@
"exportSub": "Export subscriptions",
"exportToClipboard": "Export to clipboard",
"exportToFile": "Export to file",
"importFromFile": "Import from file"
"importFromFile": "Import from file",
"exportToShareUrl": "Export to share URL"
},
"deArrow": {
"title": "DeArrow",
+1 -1
View File
@@ -221,7 +221,7 @@ export async function exportSubscriptionsAsFile(exportType: 'InvidiousJSON' | 'O
if (data) {
downloadStringAsFile(
data,
`materialious-export-${new Date().toDateString().replaceAll(' ', '')}.${exportType === 'InvidiousJSON' ? 'json' : 'opml'}`
`materialious-export.${exportType === 'InvidiousJSON' ? 'json' : 'opml'}`
);
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ export function downloadStringAsFile(content: string, filename: string) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.download = `${new Date().toDateString().replaceAll(' ', '')}-${filename}`;
document.body.appendChild(a);
a.click();
+38
View File
@@ -0,0 +1,38 @@
export const SUPPORTED_THEME_KEYS: string[] = [
'--primary',
'--on-primary',
'--primary-container',
'--on-primary-container',
'--secondary',
'--on-secondary',
'--secondary-container',
'--on-secondary-container',
'--tertiary',
'--on-tertiary',
'--tertiary-container',
'--on-tertiary-container',
'--error',
'--on-error',
'--error-container',
'--on-error-container',
'--background',
'--on-background',
'--surface',
'--on-surface',
'--surface-variant',
'--on-surface-variant',
'--outline',
'--outline-variant',
'--shadow',
'--scrim',
'--inverse-surface',
'--inverse-on-surface',
'--inverse-primary',
'--surface-dim',
'--surface-bright',
'--surface-container-lowest',
'--surface-container-low',
'--surface-container',
'--surface-container-high',
'--surface-container-highest'
] as const;
+11
View File
@@ -24,6 +24,7 @@ import { ensureNoTrailingSlash, getPublicEnv } from './misc';
import type { EngineFallback } from './api/misc';
import type z from 'zod';
import type { zFilterSchema } from './filtering/index';
import type { ThemeColors } from './theme';
function createListenerFunctions(): {
callListeners: (eventKey: string, newValue: any) => void;
@@ -260,6 +261,16 @@ export const interfaceAndroidUseNativeShare = persist(
createStorage(),
'androidUseNativeShare'
);
export const interfaceAdvancedThemingStore: Writable<ThemeColors> = persist(
writable({}),
createStorage(),
'advancedTheming'
);
export const interfaceBorderRadiusStore: Writable<number> = persist(
writable(0.5),
createStorage(),
'borderRadius'
);
export const sponsorBlockStore = persist(writable(true), createStorage(), 'sponsorBlock');
export const sponsorBlockUrlStore: Writable<string | null | undefined> = persist(
+62 -6
View File
@@ -4,16 +4,61 @@ import { get } from 'svelte/store';
import { SystemBars, SystemBarsStyle } from '@capacitor/core';
import { darkModeStore, interfaceAmoledTheme } from './store';
export async function getDynamicTheme(mode?: string): Promise<Record<string, string>> {
export type ThemeKey =
| '--primary'
| '--on-primary'
| '--primary-container'
| '--on-primary-container'
| '--secondary'
| '--on-secondary'
| '--secondary-container'
| '--on-secondary-container'
| '--tertiary'
| '--on-tertiary'
| '--tertiary-container'
| '--on-tertiary-container'
| '--error'
| '--on-error'
| '--error-container'
| '--on-error-container'
| '--background'
| '--on-background'
| '--surface'
| '--on-surface'
| '--surface-variant'
| '--on-surface-variant'
| '--outline'
| '--outline-variant'
| '--shadow'
| '--scrim'
| '--inverse-surface'
| '--inverse-on-surface'
| '--inverse-primary'
| '--surface-dim'
| '--surface-bright'
| '--surface-container-lowest'
| '--surface-container-low'
| '--surface-container'
| '--surface-container-high'
| '--surface-container-highest';
export type ThemeColors = Partial<Record<ThemeKey, string>>;
export async function getDynamicTheme(mode?: string): Promise<ThemeColors> {
const givenSettings = await ui('theme');
if (typeof givenSettings !== 'object') return {};
// @ts-expect-error Works as expected
const themes: string = givenSettings[mode ? mode : (ui('mode') as string)];
const themeVars: Record<string, string> = {};
themes.split(';').forEach((keyVar) => {
for (const keyVar of themes.split(';')) {
const [key, value] = keyVar.split(':');
themeVars[key] = value;
});
themeVars[key] = window.getComputedStyle(document.body).getPropertyValue(key) ?? value;
}
delete themeVars[''];
return themeVars;
}
@@ -35,7 +80,7 @@ export function setAmoledTheme() {
const isDark = get(darkModeStore);
if (isAmoled && isDark) {
const rootVars = [
const rootVars: ThemeKey[] = [
'--surface-container',
'--surface',
'--surface-container-lowest',
@@ -44,7 +89,7 @@ export function setAmoledTheme() {
'--surface-container-highest'
];
rootVars.forEach((varName) => {
document.body.style.setProperty(varName, '#000');
setThemeColor(varName, '#000');
});
} else {
setTheme();
@@ -70,3 +115,14 @@ export function setTheme() {
}
}
}
export function setThemeColors(theme: ThemeColors) {
for (const [themeKey, themeValue] of Object.entries(theme)) {
setThemeColor(themeKey as ThemeKey, themeValue.trim());
}
}
export function setThemeColor(theme: ThemeKey, color: string) {
document.documentElement.style.setProperty(theme, color.trim());
document.body.style.setProperty(theme, color.trim());
}
@@ -20,7 +20,6 @@
playerState,
playerTheatreModeIsActive,
rawMasterKeyStore,
themeColorStore,
backendInUseStore,
hideSearchStore
} from '$lib/store';
@@ -126,11 +125,6 @@
notifications = feed.notifications;
}
themeColorStore.subscribe(async (hex) => {
if (!hex) return;
await ui('theme', hex);
});
function resetScroll() {
const main = document.querySelector('main.root');
if (main) main.scrollTop = 0;
@@ -22,7 +22,8 @@
playerTheatreModeByDefaultStore,
playerTheatreModeIsActive,
playlistCacheStore,
type PlayerState
type PlayerState,
filterContentListStore
} from '$lib/store';
import ui from 'beercss';
import { onDestroy, onMount, tick } from 'svelte';
@@ -38,7 +39,7 @@
import { page } from '$app/state';
import Share from '$lib/components/Share.svelte';
import Playlist from '$lib/components/watch/Playlist.svelte';
import { isItemFiltered } from '$lib/filtering/index.js';
import { isItemFiltered } from '$lib/filtering/index';
let { data = $bindable() } = $props();
@@ -398,26 +399,55 @@
<div class="chapter-list" id="chapters">
<ul class="list">
{#each data.content.timestamps as timestamp (timestamp)}
<li
role="presentation"
onclick={() => {
if (playerElement) playerElement.currentTime = timestamp.time;
}}
>
<li>
<img
class="round large"
loading="lazy"
src={getBestThumbnail(data.video.videoThumbnails) as string}
alt="Thumbnail for current video"
role="presentation"
onclick={() => {
if (playerElement) playerElement.currentTime = timestamp.time;
}}
/>
<div class="max" style="white-space: pre-line; overflow-wrap: break-word;">
<p style="no-margin no-padding">{timestamp.title}</p>
<div
role="presentation"
onclick={() => {
if (playerElement) playerElement.currentTime = timestamp.time;
}}
class="max"
style="white-space: pre-line; overflow-wrap: break-word;"
>
<p style="margin: 0;">{timestamp.title}</p>
<span
class:primary={playerCurrentTime >= timestamp.time &&
(playerCurrentTime <= timestamp.endTime || timestamp.endTime === -1)}
class="chip no-margin">{timestamp.timePretty}</span
>
</div>
<Share
shares={[
{
type: 'materialious',
path: resolve(`/watch/[videoId]?time=${Math.round(timestamp.time)}`, {
videoId: data.video.videoId
})
},
{
type: 'invidious',
path: `/watch?v=${data.video.videoId}&t=${Math.round(timestamp.time)}`
},
{
type: 'invidious redirect',
path: `/watch?v=${data.video.videoId}&t=${Math.round(timestamp.time)}`
},
{
type: 'youtube',
path: `/watch?v=${data.video.videoId}&t=${Math.round(timestamp.time)}`
}
]}
iconOnly={true}
/>
</li>
{/each}
</ul>
@@ -461,15 +491,17 @@
{#if data.playlistId && data.playlistId in $playlistCacheStore}
<Playlist video={data.video} playlist={$playlistCacheStore[data.playlistId]} />
{:else if data.video.recommendedVideos}
{#each data.video.recommendedVideos as recommendedVideo (recommendedVideo.videoId)}
{#if !isItemFiltered(recommendedVideo)}
<article class="no-padding border">
{#key recommendedVideo.videoId}
<Thumbnail video={recommendedVideo} sideways={true} />
{/key}
</article>
{/if}
{/each}
{#key $filterContentListStore?.length}
{#each data.video.recommendedVideos as recommendedVideo (recommendedVideo.videoId)}
{#if !isItemFiltered(recommendedVideo)}
<article class="no-padding border">
{#key recommendedVideo.videoId}
<Thumbnail video={recommendedVideo} sideways={true} />
{/key}
</article>
{/if}
{/each}
{/key}
{/if}
</div>
{/if}
@@ -557,6 +589,10 @@
height: 50vh;
}
.video-actions button {
margin-left: 0;
}
@media screen and (max-width: 1200px) {
.video-actions {
align-items: flex-start;
+23 -7
View File
@@ -1,7 +1,9 @@
<script lang="ts">
import {
darkModeStore,
interfaceAdvancedThemingStore,
interfaceAmoledTheme,
interfaceBorderRadiusStore,
invidiousAuthStore,
isAndroidTvStore,
rawMasterKeyStore,
@@ -15,7 +17,7 @@
import 'beercss';
import 'material-dynamic-colors';
import { setAmoledTheme, setStatusBarColor, setTheme } from '$lib/theme';
import { setAmoledTheme, setStatusBarColor, setTheme, setThemeColors } from '$lib/theme';
import { pwaInfo } from 'virtual:pwa-info';
import { onMount } from 'svelte';
@@ -30,13 +32,30 @@
let { children } = $props();
interfaceAmoledTheme.subscribe(async () => {
setAmoledTheme();
themeColorStore.subscribe(async (hex) => {
if (!hex || Object.keys($interfaceAdvancedThemingStore).length > 0) return;
await ui('theme', hex);
});
interfaceAdvancedThemingStore.subscribe(async (colors) => {
setThemeColors(colors);
await setStatusBarColor();
});
interfaceBorderRadiusStore.subscribe((radius) => {
document.documentElement.style.setProperty('--border-radius', `${radius}rem`);
});
interfaceAmoledTheme.subscribe(async () => {
if (Object.keys($interfaceAdvancedThemingStore).length > 0) return;
setAmoledTheme();
await setStatusBarColor();
});
darkModeStore.subscribe(async () => {
if (Object.keys($interfaceAdvancedThemingStore).length > 0) return;
setTheme();
setAmoledTheme();
@@ -115,10 +134,7 @@
// So user preferences overwrite instance preferences.
bookmarkletLoadFromUrl();
await setStatusBarColor();
setTheme();
setAmoledTheme();
setThemeColors($interfaceAdvancedThemingStore);
});
let syncToSettingsInitialized = false;
@@ -25,7 +25,7 @@ export async function DELETE({ locals, params }) {
}
const zUpdateKeyStore = z.object({
valueCipher: z.string().max(1000),
valueCipher: z.string().max(3000),
valueNonce: z.string().max(255)
});
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.16.23"
LATEST_VERSION = "1.16.24"
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")