Merge pull request #1125 from Materialious/update/1.10.12

Update/1.10.12
This commit is contained in:
Ward
2025-10-08 04:03:09 +13:00
committed by GitHub
33 changed files with 635 additions and 351 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: ./materialious
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
push: true
tags: |
wardpearce/materialious:latest
-16
View File
@@ -1,16 +0,0 @@
{
"i18n-ally.defaultNamespace": "",
"i18n-ally.enabledFrameworks": [
"svelte"
],
"i18n-ally.localesPaths": [
"materialious/src/lib/i18n",
"materialious/src/lib/i18n/locales"
],
"i18n-ally.extract.autoDetect": true,
"i18n-ally.keystyle": "nested",
"cSpell.words": [
"Materialious"
],
"java.configuration.updateBuildConfiguration": "interactive"
}
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-11T07:20:32.362551415Z">
<DropdownSelection timestamp="2025-10-07T14:49:15.931464993Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/ward/.android/avd/Pixel_7_Pro.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=/home/ward/.android/avd/Pixel_9.avd" />
</handle>
</Target>
</DropdownSelection>
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 152
versionName "1.10.11"
versionCode 153
versionName "1.10.12"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -69,7 +69,11 @@
<release version="1.10.11" date="2025-9-25">
<release version="1.10.12" date="2025-10-07">
<url>https://github.com/Materialious/Materialious/releases/tag/1.10.12</url>
</release>
<release version="1.10.11" date="2025-9-25">
<url>https://github.com/Materialious/Materialious/releases/tag/1.10.11</url>
</release>
<release version="1.10.10" date="2025-9-20">
+10 -10
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.10.11",
"version": "1.10.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.10.11",
"version": "1.10.12",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -22,10 +22,10 @@
"jsonfile": "^6.2.0"
},
"devDependencies": {
"electron": "^38.1.2",
"electron": "^38.2.1",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
},
"node_modules/@asamuzakjp/css-color": {
@@ -2558,9 +2558,9 @@
}
},
"node_modules/electron": {
"version": "38.1.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-38.1.2.tgz",
"integrity": "sha512-WXUcN3W8h8NTTZViA3KNX0rV2YBU0X0mEUM3ubupXTDY4QtIN7tmiqYVOKSKpR2LckTmBWGuEeY4D6xVoffwKQ==",
"version": "38.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-38.2.1.tgz",
"integrity": "sha512-P4pE2RpRg3kM8IeOK+heg6iAxR5wcXnNHrbVchn7M3GBnYAhjfJRkROusdOro5PlKzdtfKjesbbqaG4MqQXccg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -5831,9 +5831,9 @@
}
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.10.11",
"version": "1.10.12",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -35,10 +35,10 @@
"jsonfile": "^6.2.0"
},
"devDependencies": {
"electron": "^38.1.2",
"electron": "^38.2.1",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
},
"keywords": [
"capacitor",
+172 -136
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.10.11",
"version": "1.10.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.10.11",
"version": "1.10.12",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -39,7 +39,7 @@
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^7.0.0",
"@capacitor/cli": "^7.4.3",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.37.0",
@@ -56,13 +56,13 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.11.0",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.19.7",
"svelte-check": "^4.0.0",
"tslib": "^2.7.0",
"typescript": "^5.6.3",
"vite": "^6.3.6"
"vite": "^7.1.9"
}
},
"node_modules/@ampproject/remapping": {
@@ -1846,9 +1846,9 @@
}
},
"node_modules/@capacitor/cli": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.2.tgz",
"integrity": "sha512-ckcRbg/kBSnJsuCTCLzowBxvgDTI+dcIN7LK+Nt71fbaUG3judpF279ymJRrgRDJshFR5D5dfcvTFh5N1z8liA==",
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.3.tgz",
"integrity": "sha512-SWozpdDgrbQ/ry1nIapugDFvE9z+l22BmU/+fpgL2Zv5487hGdXvCX5+1SluuFBP3IPpx6b4LjsKnBigyJoUWg==",
"license": "MIT",
"dependencies": {
"@ionic/cli-framework-output": "^2.2.8",
@@ -2905,9 +2905,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
"integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
"cpu": [
"arm"
],
@@ -2919,9 +2919,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
"integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
"cpu": [
"arm64"
],
@@ -2933,9 +2933,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
"integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
"cpu": [
"arm64"
],
@@ -2947,9 +2947,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
"integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
"cpu": [
"x64"
],
@@ -2961,9 +2961,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
"integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
"cpu": [
"arm64"
],
@@ -2975,9 +2975,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
"integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
"cpu": [
"x64"
],
@@ -2989,9 +2989,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
"integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
"cpu": [
"arm"
],
@@ -3003,9 +3003,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
"integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
"cpu": [
"arm"
],
@@ -3017,9 +3017,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
"integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
"cpu": [
"arm64"
],
@@ -3031,9 +3031,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
"integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
"cpu": [
"arm64"
],
@@ -3044,10 +3044,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
"integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
"cpu": [
"loong64"
],
@@ -3058,10 +3058,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
"integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
"cpu": [
"ppc64"
],
@@ -3073,9 +3073,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
"integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
"cpu": [
"riscv64"
],
@@ -3087,9 +3087,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
"integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
"cpu": [
"riscv64"
],
@@ -3101,9 +3101,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
"integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
"cpu": [
"s390x"
],
@@ -3115,9 +3115,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
"integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
"cpu": [
"x64"
],
@@ -3129,9 +3129,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
"integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
"cpu": [
"x64"
],
@@ -3142,10 +3142,24 @@
"linux"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
"integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
"cpu": [
"arm64"
],
@@ -3157,9 +3171,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
"integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
"cpu": [
"ia32"
],
@@ -3170,10 +3184,24 @@
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
"integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
"cpu": [
"x64"
],
@@ -3488,9 +3516,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
"node_modules/@types/event-source-polyfill": {
@@ -9589,9 +9617,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
@@ -9609,7 +9637,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.8",
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -9800,9 +9828,9 @@
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -10686,13 +10714,13 @@
}
},
"node_modules/rollup": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
"integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.7"
"@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -10702,26 +10730,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.40.1",
"@rollup/rollup-android-arm64": "4.40.1",
"@rollup/rollup-darwin-arm64": "4.40.1",
"@rollup/rollup-darwin-x64": "4.40.1",
"@rollup/rollup-freebsd-arm64": "4.40.1",
"@rollup/rollup-freebsd-x64": "4.40.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
"@rollup/rollup-linux-arm-musleabihf": "4.40.1",
"@rollup/rollup-linux-arm64-gnu": "4.40.1",
"@rollup/rollup-linux-arm64-musl": "4.40.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
"@rollup/rollup-linux-riscv64-gnu": "4.40.1",
"@rollup/rollup-linux-riscv64-musl": "4.40.1",
"@rollup/rollup-linux-s390x-gnu": "4.40.1",
"@rollup/rollup-linux-x64-gnu": "4.40.1",
"@rollup/rollup-linux-x64-musl": "4.40.1",
"@rollup/rollup-win32-arm64-msvc": "4.40.1",
"@rollup/rollup-win32-ia32-msvc": "4.40.1",
"@rollup/rollup-win32-x64-msvc": "4.40.1",
"@rollup/rollup-android-arm-eabi": "4.52.4",
"@rollup/rollup-android-arm64": "4.52.4",
"@rollup/rollup-darwin-arm64": "4.52.4",
"@rollup/rollup-darwin-x64": "4.52.4",
"@rollup/rollup-freebsd-arm64": "4.52.4",
"@rollup/rollup-freebsd-x64": "4.52.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
"@rollup/rollup-linux-arm64-musl": "4.52.4",
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
"@rollup/rollup-linux-x64-gnu": "4.52.4",
"@rollup/rollup-linux-x64-musl": "4.52.4",
"@rollup/rollup-openharmony-arm64": "4.52.4",
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
"@rollup/rollup-win32-x64-gnu": "4.52.4",
"@rollup/rollup-win32-x64-msvc": "4.52.4",
"fsevents": "~2.3.2"
}
},
@@ -11957,14 +11987,14 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
@@ -11974,11 +12004,14 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -11989,9 +12022,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12474,24 +12507,24 @@
}
},
"node_modules/vite": {
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -12500,14 +12533,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "*",
"less": "^4.0.0",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -13037,11 +13070,14 @@
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -13052,9 +13088,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.10.11",
"version": "1.10.12",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -15,7 +15,7 @@
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^7.0.0",
"@capacitor/cli": "^7.4.3",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.37.0",
@@ -32,13 +32,13 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.11.0",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.19.7",
"svelte-check": "^4.0.0",
"tslib": "^2.7.0",
"typescript": "^5.6.3",
"vite": "^6.3.6"
"vite": "^7.1.9"
},
"type": "module",
"dependencies": {
@@ -70,4 +70,4 @@
"svelte-persisted-store": "^0.12.0",
"youtubei.js": "^15.1.1"
}
}
}
@@ -1,6 +1,8 @@
import { goto } from '$app/navigation';
import { isAndroidTvStore } from '$lib/store';
import { Capacitor } from '@capacitor/core';
import { NodeJS } from 'capacitor-nodejs';
import { get } from 'svelte/store';
const originalFetch = window.fetch;
const corsProxyUrl: string = 'http://localhost:3000/';
@@ -56,4 +58,13 @@ if (Capacitor.getPlatform() === 'android') {
NodeJS.whenReady().then(() => {
goto('/', { replaceState: true });
});
// Required for Android TV to load correctly.
let hasReloaded = false;
isAndroidTvStore.subscribe((isAndroidTv) => {
if (hasReloaded || !isAndroidTv) return;
hasReloaded = true;
setTimeout(() => goto('/', { replaceState: true }), 2000);
});
}
+6 -6
View File
@@ -14,6 +14,7 @@ export interface Thumbnail {
}
export interface VideoBase {
type: 'video' | 'shortVideo' | 'stream';
videoId: string;
title: string;
videoThumbnails: Thumbnail[];
@@ -30,7 +31,6 @@ export interface ResolvedUrl {
}
export interface Video extends VideoBase {
type: 'video';
title: string;
authorUrl: string;
authorVerified: boolean;
@@ -178,6 +178,7 @@ export interface Channel {
}
export interface PlaylistVideo {
type: 'playlist';
title: string;
videoId: string;
lengthSeconds: number;
@@ -196,7 +197,8 @@ export interface Playlist {
videos: PlaylistVideo[];
}
export interface PlaylistPageVideo extends PlaylistVideo {
export interface PlaylistPageVideo extends Omit<PlaylistVideo, 'type'> {
type: 'video';
author: string;
index: number;
indexId: string;
@@ -214,7 +216,7 @@ export interface ChannelContentPlaylists {
continuation: string;
}
export interface PlaylistPage extends Playlist {
export interface PlaylistPage extends Omit<Playlist, 'videos'> {
description: string;
descriptionHtml: string;
viewCount: number;
@@ -245,9 +247,7 @@ export interface HashTag {
videoCount: number;
}
export interface Notification extends VideoBase {
type: 'video' | 'shortVideo' | 'stream';
}
export interface Notification extends VideoBase {}
export interface Feed {
notifications: Notification[];
@@ -0,0 +1,292 @@
<script lang="ts">
import Thumbnail from '$lib/components/Thumbnail.svelte';
import { _ } from '$lib/i18n';
import { removePlaylistVideo } from '../api';
import type {
Channel,
HashTag,
Playlist,
PlaylistPage,
PlaylistPageVideo,
Video,
VideoBase
} from '../api/model';
import { authStore, feedLastItemId, isAndroidTvStore } from '../store';
import ContentColumn from './ContentColumn.svelte';
import { onMount, onDestroy } from 'svelte';
import Mousetrap from 'mousetrap';
import { extractUniqueId } from '$lib/misc';
import ChannelThumbnail from './ChannelThumbnail.svelte';
import PlaylistThumbnail from './PlaylistThumbnail.svelte';
import HashtagThumbnail from './HashtagThumbnail.svelte';
interface Props {
items?:
| (VideoBase | Video | PlaylistPageVideo | Channel | Playlist | HashTag)[]
| PlaylistPage[];
playlistId?: string;
playlistAuthor?: string;
}
let { items = [], playlistId = '', playlistAuthor = '' }: Props = $props();
let gridElement = $state<HTMLElement>();
let focusableItems = $state<HTMLElement[]>([]);
let currentFocusIndex = $state(0);
let lastFocusIndex = $state(0); // Remember position when leaving grid
let columns = $state(4); // Default columns for Android TV
let isComponentActive = $state(false); // Track if focus is within this component
async function removePlaylistItem(indexId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
}
function calculateColumns() {
if (!gridElement || !$isAndroidTvStore) return;
const gridItems = gridElement.querySelectorAll('article[role="presentation"]');
if (gridItems.length === 0) return;
// Count items in first row by checking top position
const firstItemTop = gridItems[0].getBoundingClientRect().top;
let cols = 1;
for (let i = 1; i < gridItems.length; i++) {
const itemTop = gridItems[i].getBoundingClientRect().top;
if (Math.abs(itemTop - firstItemTop) < 10) {
cols++;
} else {
break;
}
}
columns = Math.max(1, cols);
}
function setupAndroidTVNavigation() {
if (!$isAndroidTvStore || !gridElement) return;
focusableItems = Array.from(
gridElement.querySelectorAll('article[role="presentation"]')
) as HTMLElement[];
calculateColumns();
// Make items focusable and set current focus to stored position
const initialFocusIndex = Math.min(lastFocusIndex, focusableItems.length - 1);
currentFocusIndex = initialFocusIndex;
updateTabIndex(initialFocusIndex);
}
// Update tabindex when focus changes to maintain proper focus order
function updateTabIndex(focusIndex: number) {
if (!$isAndroidTvStore || focusableItems.length === 0) return;
focusableItems.forEach((focusableItem, index) => {
focusableItem.tabIndex = index === focusIndex ? 0 : -1;
});
}
// Check if focus is within the ItemsList component
function checkComponentFocus() {
if (!gridElement) return;
const activeElement = document.activeElement;
const isWithinComponent = gridElement.contains(activeElement);
isComponentActive = isWithinComponent;
// If focus left the component, store the position
if (!isWithinComponent && focusableItems.length > 0) {
lastFocusIndex = currentFocusIndex;
updateTabIndex(currentFocusIndex);
}
}
function handleNavigation(direction: 'up' | 'down' | 'left' | 'right', event: Event) {
if (!$isAndroidTvStore || focusableItems.length === 0 || !isComponentActive) return true;
let newIndex = currentFocusIndex;
switch (direction) {
case 'left':
newIndex = Math.max(0, currentFocusIndex - 1);
break;
case 'right':
newIndex = Math.min(focusableItems.length - 1, currentFocusIndex + 1);
break;
case 'up':
// Check if we're in the first row and trying to go up
if (currentFocusIndex < columns) {
// Store current position before leaving and ensure it stays focusable
lastFocusIndex = currentFocusIndex;
updateTabIndex(currentFocusIndex);
// Allow the event to bubble up to parent navigation
return true;
}
newIndex = Math.max(0, currentFocusIndex - columns);
break;
case 'down':
newIndex = Math.min(focusableItems.length - 1, currentFocusIndex + columns);
break;
}
if (newIndex !== currentFocusIndex) {
event.preventDefault();
// Update focus
updateTabIndex(newIndex);
focusableItems[newIndex].focus();
// Snap to view with instant behavior
focusableItems[newIndex].scrollIntoView({
behavior: 'auto',
block: 'center',
inline: 'center'
});
currentFocusIndex = newIndex;
return false;
}
return true;
}
onMount(() => {
if ($feedLastItemId && !$isAndroidTvStore) {
document
.getElementById($feedLastItemId)
?.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
feedLastItemId.set(undefined);
}
if ($isAndroidTvStore) {
// Setup Android TV navigation
setTimeout(() => {
setupAndroidTVNavigation();
// Focus the correct item initially (first time or restored position)
if (focusableItems.length > 0) {
let focusedItemIndex = -1;
if ($feedLastItemId) {
focusedItemIndex = focusableItems.findIndex((item) => item.id === $feedLastItemId);
feedLastItemId.set(undefined);
}
const focusIndex =
focusedItemIndex === -1
? Math.min(lastFocusIndex, focusableItems.length - 1)
: focusedItemIndex;
focusableItems[focusIndex]?.focus();
currentFocusIndex = focusIndex;
}
}, 200);
// Bind navigation keys
Mousetrap.bind('up', (e) => handleNavigation('up', e));
Mousetrap.bind('down', (e) => handleNavigation('down', e), 'keydown');
Mousetrap.bind('left', (e) => handleNavigation('left', e));
Mousetrap.bind('right', (e) => handleNavigation('right', e));
// Watch for window resize to recalculate columns
window.addEventListener('resize', calculateColumns);
// Watch for focus changes to detect when we're active
document.addEventListener('focusin', checkComponentFocus);
document.addEventListener('focusout', checkComponentFocus);
}
});
onDestroy(() => {
if ($isAndroidTvStore) {
Mousetrap.unbind(['up', 'down', 'left', 'right']);
window.removeEventListener('resize', calculateColumns);
document.removeEventListener('focusin', checkComponentFocus);
document.removeEventListener('focusout', checkComponentFocus);
}
});
// Update navigation when items change
$effect(() => {
if ($isAndroidTvStore && items.length > 0 && gridElement) {
setTimeout(() => {
setupAndroidTVNavigation();
}, 100);
}
});
</script>
<div class="page right active" class:android-container={$isAndroidTvStore}>
<div class="space"></div>
<div class="grid" bind:this={gridElement}>
{#each items as item, index}
<ContentColumn>
<article
class="no-padding android-tv-item"
class:android-tv-focused={$isAndroidTvStore}
style="height: 100%;"
id={extractUniqueId(item)}
role="presentation"
onclick={() => {
feedLastItemId.set(extractUniqueId(item));
// Required to pass click through to thumbnail component on Android TV
if ($isAndroidTvStore) {
const articleElement = document.getElementById(extractUniqueId(item));
if (articleElement) {
const clickable = articleElement.querySelector('a, button');
if (clickable instanceof HTMLElement) {
clickable.click();
}
}
}
}}
onfocus={() => {
if ($isAndroidTvStore) {
currentFocusIndex = index;
}
}}
>
{#if item.type === 'video' || item.type === 'shortVideo' || item.type === 'stream'}
<Thumbnail video={item} {playlistId} />
{#if $authStore && decodeURIComponent($authStore.username) === playlistAuthor && 'indexId' in item}
<div class="right-align" style="margin: 1em .5em;">
<button
onclick={async () => removePlaylistItem(item.indexId)}
class="tertiary circle small"
>
<i>delete</i>
<div class="tooltip">{$_('delete')}</div>
</button>
</div>
{/if}
{:else if item.type === 'channel'}
<ChannelThumbnail channel={item} />
{:else if item.type === 'playlist'}
<PlaylistThumbnail playlist={item} />
{:else if item.type === 'hashtag'}
<HashtagThumbnail hashtag={item} />
{/if}
</article>
</ContentColumn>
{/each}
</div>
</div>
<style>
.android-container {
padding: 0 1em;
}
.android-tv-item {
transition:
transform 0.15s ease,
box-shadow 0.15s ease;
}
.android-tv-item:focus {
transform: scale(1.05);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
z-index: 10;
position: relative;
outline: 4px solid var(--primary);
}
</style>
@@ -4,12 +4,12 @@
import { onMount } from 'svelte';
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import type { Playlist } from '../api/model';
import type { Playlist, PlaylistPage } from '../api/model';
import { insecureRequestImageHandler, truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
interface Props {
playlist: Playlist;
playlist: Playlist | PlaylistPage;
disabled?: boolean;
}
@@ -7,7 +7,7 @@
import { get } from 'svelte/store';
import { getDeArrow, getThumbnail } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { insecureRequestImageHandler } from '../misc';
import { createVideoUrl, insecureRequestImageHandler } from '../misc';
import type { PlayerEvents } from '../player';
import {
authStore,
@@ -34,13 +34,7 @@
let placeholderHeight: number = $state(0);
let watchUrl = new URL(
`${location.origin}/${$isAndroidTvStore ? 'tv' : 'watch'}/${video.videoId}`
);
if (playlistId !== '') {
watchUrl.searchParams.set('playlist', playlistId);
}
let watchUrl = createVideoUrl(video.videoId, playlistId);
syncPartyPeerStore.subscribe((peer) => {
if (peer) {
@@ -161,14 +155,7 @@
}
</script>
<div
class:sideways-root={sideways}
tabindex="0"
role="button"
onclick={async () => {
goto(watchUrl, { replaceState: $isAndroidTvStore && page.url.pathname.startsWith('/tv') });
}}
>
<div class:sideways-root={sideways} tabindex="0" role="button">
<div id="thumbnail-container">
<a
tabindex="-1"
@@ -232,10 +219,9 @@
</a>
{#if !('publishedText' in video) && 'viewCountText' in video}
<span>
{video.viewCountText}
{$_('views')}
</span>
{video.viewCountText}
{$_('views')}
{/if}
{#if 'publishedText' in video}
@@ -1,62 +0,0 @@
<script lang="ts">
import Thumbnail from '$lib/components/Thumbnail.svelte';
import { _ } from '$lib/i18n';
import { removePlaylistVideo } from '../api';
import type { PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { authStore, feedLastItemId } from '../store';
import ContentColumn from './ContentColumn.svelte';
import { onMount } from 'svelte';
interface Props {
videos?: (VideoBase | Video | PlaylistPageVideo)[];
playlistId?: string;
playlistAuthor?: string;
}
let { videos = [], playlistId = '', playlistAuthor = '' }: Props = $props();
async function removePlaylistItem(indexId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
}
onMount(() => {
if ($feedLastItemId) {
document
.getElementById($feedLastItemId)
?.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
feedLastItemId.set(undefined);
}
});
</script>
<div class="page right active">
<div class="space"></div>
<div class="grid">
{#each videos as video}
<ContentColumn>
<article
class="no-padding"
style="height: 100%;"
id={video.videoId}
role="presentation"
onclick={() => feedLastItemId.set(video.videoId)}
>
<Thumbnail {video} {playlistId} />
{#if $authStore && decodeURIComponent($authStore.username) === playlistAuthor && 'indexId' in video}
<div class="right-align" style="margin: 1em .5em;">
<button
onclick={async () => removePlaylistItem(video.indexId)}
class="tertiary circle small"
>
<i>delete</i>
<div class="tooltip">{$_('delete')}</div>
</button>
</div>
{/if}
</article>
</ContentColumn>
{/each}
</div>
</div>
@@ -8,6 +8,24 @@
color: var(--on-surface) !important;
}
.player-theme .shaka-overflow-menu,
.player-theme .shaka-settings-menu {
border-radius: 0.25rem;
background: var(--surface-container-low);
animation: none !important;
transition: none !important;
opacity: 1 !important;
visibility: visible !important;
z-index: 99999999 !important;
pointer-events: auto !important;
pointer-events: auto !important;
}
.shaka-overflow-menu button,
.shaka-settings-menu button {
color: var(--text) !important;
}
@media screen and (max-width: 650px) {
.player-theme .shaka-end-time {
display: none;
+1 -1
View File
@@ -68,7 +68,7 @@
"audioOnly": "Solo audio",
"theatreMode": "Modo teatro",
"pauseTimer": "Pausar temporizador",
"pauseVideoIn": "Pausar video en",
"pauseVideoIn": "Pausar video en ",
"share": {
"title": "Compartir",
"materialiousLink": "Copiar enlace de Materialious",
+9 -3
View File
@@ -72,7 +72,8 @@
"title": "Поделиться",
"materialiousLink": "Скопировать ссылку на Materialious",
"invidiousRedirect": "Скопировать ссылку на сервер Invidious",
"youtubeLink": "Скопировать ссылку на Youtube"
"youtubeLink": "Скопировать ссылку на Youtube",
"includeTimestamp": "С отметкой времени"
},
"download": "Скачать",
"downloadSteps": {
@@ -170,7 +171,8 @@
"searchHistory": "История поиска",
"allowInsecureRequests": "Разрешить небезопасные запросы",
"companionUrl": "URL-адрес компаньона",
"expandChapters": "Раскрывать главы по умолчанию"
"expandChapters": "Раскрывать главы по умолчанию",
"disableAutoUpdate": "Отключить автоматические обновления"
},
"subscribe": "Подписаться",
"invalidInstance": "Проверьте URL-адрес. Если он правильный, то возможно сервер сейчас недоступен или не поддерживает сторонние клиенты.",
@@ -179,5 +181,9 @@
"login": "Имя пользователя",
"invidiousLogin": "Пожалуйста, войдите со своей учётной записью Invidious",
"playlistVideos": "Видео из плейлиста",
"recommendedVideos": "Рекомендуемые видео"
"recommendedVideos": "Рекомендуемые видео",
"premium": "Премиум-контент YouTube нельзя просматривать в Materialious.",
"isUpcoming": "Видео ещё не готово к просмотру.",
"comments": "комментарии",
"reply": "ответить"
}
+31 -3
View File
@@ -3,8 +3,16 @@ import { page } from '$app/stores';
import he from 'he';
import type Peer from 'peerjs';
import { get } from 'svelte/store';
import { instanceStore, interfaceAllowInsecureRequests } from './store';
import type { Channel, HashTag, Playlist, PlaylistPageVideo, Video, VideoBase } from './api/model';
import { instanceStore, interfaceAllowInsecureRequests, isAndroidTvStore } from './store';
import type {
Channel,
HashTag,
Playlist,
PlaylistPage,
PlaylistPageVideo,
Video,
VideoBase
} from './api/model';
export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
@@ -74,7 +82,15 @@ export async function insecureRequestImageHandler(source: string): Promise<HTMLI
return img;
}
export type feedItem = VideoBase | Video | PlaylistPageVideo | Channel | Video | Playlist | HashTag;
export type feedItem =
| VideoBase
| Video
| PlaylistPageVideo
| Channel
| Video
| Playlist
| HashTag
| PlaylistPage;
export type feedItems = feedItem[];
export function extractUniqueId(item: feedItem): string {
@@ -112,3 +128,15 @@ export function expandSummery(id: string) {
element.click();
}
}
export function createVideoUrl(videoId: string, playlistId: string): URL {
const watchUrl = new URL(
`${location.origin}/${get(isAndroidTvStore) ? 'tv' : 'watch'}/${videoId}`
);
if (playlistId !== '') {
watchUrl.searchParams.set('playlist', playlistId);
}
return watchUrl;
}
+2 -1
View File
@@ -135,7 +135,8 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
author: recommended.metadata.metadata.metadata_rows[0]?.metadata_parts[0]?.text.text || '',
authorId:
recommended.metadata?.image?.renderer_context?.command_context?.on_tap.payload?.browseId ||
''
'',
type: 'video'
});
});
+1
View File
@@ -27,6 +27,7 @@ export async function loadEntirePlaylist(
}
newVideos = newVideos.filter((playlistVideo) => {
playlistVideo.type = 'video';
return playlistVideo.lengthSeconds > 0 && !ignoreVideos.includes(playlistVideo.videoId);
});
+1 -1
View File
@@ -55,7 +55,7 @@ export function phaseDescription(
// Handle youtubejs timestamps
if (
fallbackPatch === 'youtubejs' &&
href.includes(`https://www.youtube.com/watch?v=${videoId}`)
href.includes(`https://www.youtube.com/watch?v=${videoId}&t=`)
) {
const url = new URL(href);
const timeParam = url.searchParams.get('t') || '0';
+34 -19
View File
@@ -18,10 +18,13 @@
import {
authStore,
darkModeStore,
feedCacheStore,
instanceStore,
interfaceAmoledTheme,
interfaceDefaultPage,
isAndroidTvStore,
playlistCacheStore,
searchCacheStore,
syncPartyPeerStore,
themeColorStore
} from '$lib/store';
@@ -62,6 +65,14 @@
await setStatusBarColor();
});
App.addListener('backButton', async (data) => {
if (data.canGoBack) {
window.history.back();
} else {
await App.exitApp();
}
});
App.addListener('appUrlOpen', (data) => {
const url = new URL(data.url);
@@ -162,6 +173,9 @@
function logout() {
authStore.set(null);
feedCacheStore.set({});
searchCacheStore.set({});
playlistCacheStore.set({});
goto('/');
}
@@ -188,16 +202,30 @@
if ($isAndroidTvStore) {
const topContent = document.getElementById('top-content') as HTMLElement;
Mousetrap.bind('down', () => {
if (topContent.contains(document.activeElement)) {
document.getElementById('main-content')?.focus();
return false;
Mousetrap.bind(
'down',
() => {
if (topContent.contains(document.activeElement)) {
document.getElementById('main-content')?.focus();
return true;
}
},
'keyup'
);
}
if (Capacitor.getPlatform() === 'android') {
document.addEventListener('click', async (event: MouseEvent) => {
// Handles opening links in browser for android.
const link = (event.target as HTMLElement).closest('a');
if (link && link.href && link.href.startsWith('http') && link.target === '_blank') {
event.preventDefault();
await Browser.open({ url: link.href });
}
});
}
document.addEventListener('click', linkClickOverwrite);
loadSettingsFromEnv();
// Should always be loaded after env settings
// So user preferences overwrite instance preferences.
@@ -213,19 +241,6 @@
}
});
function linkClickOverwrite(event: MouseEvent) {
// Handles opening links in browser for android.
if (Capacitor.getPlatform() !== 'android') return;
const link = (event.target as HTMLElement).closest('a');
if (link && link.href && link.href.startsWith('http') && link.target === '_blank') {
event.preventDefault();
Browser.open({ url: link.href });
}
}
let webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
</script>
+3 -3
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import VideoList from '$lib/components/VideoList.svelte';
import { feedCacheStore } from '$lib/store.js';
import ItemsList from '$lib/components/ItemsList.svelte';
import { feedCacheStore } from '$lib/store';
let { data } = $props();
</script>
@@ -12,5 +12,5 @@
<p>Popular page has been disabled</p>
</nav>
{:else}
<VideoList videos={$feedCacheStore.popular ?? []} />
<ItemsList items={$feedCacheStore.popular ?? []} />
{/if}
@@ -4,7 +4,6 @@
import ContentColumn from '$lib/components/ContentColumn.svelte';
import PageLoading from '$lib/components/PageLoading.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import VideoList from '$lib/components/VideoList.svelte';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/numbers';
import { authStore, interfaceLowBandwidthMode, isAndroidTvStore } from '$lib/store';
@@ -14,6 +13,7 @@
import { _ } from '$lib/i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import { get } from 'svelte/store';
import ItemsList from '$lib/components/ItemsList.svelte';
let { data } = $props();
@@ -203,20 +203,9 @@
{#if displayContent}
{#if 'videos' in displayContent}
<VideoList videos={displayContent.videos} />
<ItemsList items={displayContent.videos} />
{:else}
<div class="page right active">
<div class="space"></div>
<div class="grid">
{#each displayContent.playlists as playlist}
<ContentColumn>
<article class="no-padding" style="height: 100%;">
<PlaylistThumbnail {playlist} />
</article>
</ContentColumn>
{/each}
</div>
</div>
<ItemsList items={displayContent.playlists} />
{/if}
<InfiniteLoading on:infinite={loadMore} />
@@ -1,7 +1,7 @@
<script lang="ts">
import { getHashtag } from '$lib/api';
import type { Video } from '$lib/api/model.js';
import VideoList from '$lib/components/VideoList.svelte';
import ItemsList from '$lib/components/ItemsList.svelte';
import { onMount } from 'svelte';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
@@ -26,6 +26,6 @@
}
</script>
<VideoList {videos} />
<ItemsList items={videos} />
<InfiniteLoading on:infinite={loadMore} />
@@ -2,13 +2,13 @@
import { deleteAllVideoProgress, deleteHistory, getHistory, getVideo } from '$lib/api';
import type { VideoPlay } from '$lib/api/model';
import PageLoading from '$lib/components/PageLoading.svelte';
import VideoList from '$lib/components/VideoList.svelte';
import { synciousStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { onMount } from 'svelte';
import { _ } from '$lib/i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import { get } from 'svelte/store';
import ItemsList from '$lib/components/ItemsList.svelte';
let history: VideoPlay[] = $state([]);
let loaded = $state(false);
@@ -82,7 +82,7 @@
</nav>
{#if loaded}
<VideoList videos={history} />
<ItemsList items={history} />
<InfiniteLoading on:infinite={loadMore} />
{:else}
@@ -1,11 +1,11 @@
<script lang="ts">
import VideoList from '$lib/components/VideoList.svelte';
import { unsafeRandomItem } from '$lib/misc';
import { cleanNumber } from '$lib/numbers';
import { isAndroidTvStore, playlistSettingsStore } from '$lib/store';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { _ } from '$lib/i18n';
import ItemsList from '$lib/components/ItemsList.svelte';
let { data } = $props();
</script>
@@ -93,8 +93,8 @@
</article>
{#if data.playlist.videos}
<VideoList
videos={data.playlist.videos}
<ItemsList
items={data.playlist.videos}
playlistAuthor={data.playlist.info.author}
playlistId={data.playlist.info.playlistId}
/>
@@ -11,6 +11,7 @@
import { onMount } from 'svelte';
import { _ } from '$lib/i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import ItemsList from '$lib/components/ItemsList.svelte';
let { data } = $props();
@@ -86,34 +87,7 @@
</div>
{#if $searchCacheStore[data.searchStoreId]}
<div class="page right active">
<div class="space"></div>
<div class="grid">
{#each $searchCacheStore[data.searchStoreId] as item}
<ContentColumn>
{#key item}
<article
class="no-padding"
style="height: 100%;"
role="presentation"
onclick={async () => feedLastItemId.set(await extractUniqueId(item))}
id={extractUniqueId(item)}
>
{#if item.type === 'video'}
<Thumbnail video={item} />
{:else if item.type === 'channel'}
<ChannelThumbnail channel={item} />
{:else if item.type === 'playlist'}
<PlaylistThumbnail playlist={item} />
{:else if item.type === 'hashtag'}
<HashtagThumbnail hashtag={item} />
{/if}
</article>
{/key}
</ContentColumn>
{/each}
</div>
</div>
<ItemsList items={$searchCacheStore[data.searchStoreId]} />
{:else}
<PageLoading />
{/if}
@@ -1,10 +1,10 @@
<script lang="ts">
import { getFeed } from '$lib/api/index';
import type { PlaylistPageVideo, Video, VideoBase } from '$lib/api/model.js';
import VideoList from '$lib/components/VideoList.svelte';
import { feedCacheStore } from '$lib/store';
import { _ } from '$lib/i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import ItemsList from '$lib/components/ItemsList.svelte';
let currentPage = 1;
let videos: (VideoBase | Video | PlaylistPageVideo)[] = $state($feedCacheStore.subscription);
@@ -30,6 +30,6 @@
</a>
</nav>
<VideoList videos={videos ?? []} />
<ItemsList items={videos ?? []} />
<InfiniteLoading on:infinite={loadMore} />
@@ -1,6 +1,6 @@
<script lang="ts">
import VideoList from '$lib/components/VideoList.svelte';
import ItemsList from '$lib/components/ItemsList.svelte';
import { feedCacheStore } from '$lib/store';
</script>
<VideoList videos={$feedCacheStore.trending ?? []} />
<ItemsList items={$feedCacheStore.trending ?? []} />
@@ -102,6 +102,7 @@
<article id="shown-info" transition:fade>
<h5>{letterCase(data.video.title)}</h5>
<Author bind:subscribed video={data.video} />
<div class="space"></div>
<LikesDislikes video={data.video} returnYTDislikes={data.streamed.returnYTDislikes} />
<article class="border">
<Description video={data.video} description={data.content.description} />
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.10.11"
LATEST_VERSION = "1.10.12"
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")