Add initial support to Download page (#7)

Also optimize parts of Extensions page
This commit is contained in:
Alessandro Jean 2023-08-31 07:17:15 -03:00 committed by GitHub
parent 7412eeacd6
commit ea29dac3f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 423 additions and 102 deletions

View File

@ -40,6 +40,8 @@
"@mdit/plugin-img-size": "^0.4.8", "@mdit/plugin-img-size": "^0.4.8",
"@mdit/plugin-include": "^0.4.8", "@mdit/plugin-include": "^0.4.8",
"@mdit/plugin-tab": "^0.4.8", "@mdit/plugin-tab": "^0.4.8",
"@types/lodash.groupby": "^4.6.7",
"@types/markdown-it": "^13.0.1",
"@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0", "@typescript-eslint/parser": "^6.5.0",
"eslint": "^8.48.0", "eslint": "^8.48.0",
@ -70,9 +72,12 @@
}, },
"dependencies": { "dependencies": {
"@mdi/js": "^7.2.96", "@mdi/js": "^7.2.96",
"@tanstack/vue-query": "^4.34.0",
"axios": "^1.5.0", "axios": "^1.5.0",
"element-plus": "^2.3.12", "element-plus": "^2.3.12",
"lodash.groupby": "^4.6.0", "lodash.groupby": "^4.6.0",
"markdown-it": "^13.0.1",
"moment": "^2.29.4",
"vitepress-plugin-auto-sidebar": "^1.1.0" "vitepress-plugin-auto-sidebar": "^1.1.0"
} }
} }

94
website/pnpm-lock.yaml generated
View File

@ -1,13 +1,12 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies: dependencies:
'@mdi/js': '@mdi/js':
specifier: ^7.2.96 specifier: ^7.2.96
version: 7.2.96 version: 7.2.96
'@tanstack/vue-query':
specifier: ^4.34.0
version: 4.34.0(vue@3.3.4)
axios: axios:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.5.0 version: 1.5.0
@ -17,6 +16,12 @@ dependencies:
lodash.groupby: lodash.groupby:
specifier: ^4.6.0 specifier: ^4.6.0
version: 4.6.0 version: 4.6.0
markdown-it:
specifier: ^13.0.1
version: 13.0.1
moment:
specifier: ^2.29.4
version: 2.29.4
vitepress-plugin-auto-sidebar: vitepress-plugin-auto-sidebar:
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0 version: 1.1.0
@ -43,6 +48,12 @@ devDependencies:
'@mdit/plugin-tab': '@mdit/plugin-tab':
specifier: ^0.4.8 specifier: ^0.4.8
version: 0.4.8 version: 0.4.8
'@types/lodash.groupby':
specifier: ^4.6.7
version: 4.6.7
'@types/markdown-it':
specifier: ^13.0.1
version: 13.0.1
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^6.5.0 specifier: ^6.5.0
version: 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2) version: 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2)
@ -765,6 +776,33 @@ packages:
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
dev: false dev: false
/@tanstack/match-sorter-utils@8.8.4:
resolution: {integrity: sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==}
engines: {node: '>=12'}
dependencies:
remove-accents: 0.4.2
dev: false
/@tanstack/query-core@4.33.0:
resolution: {integrity: sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g==}
dev: false
/@tanstack/vue-query@4.34.0(vue@3.3.4):
resolution: {integrity: sha512-jXqvP5z+9mAuUVELH4A9j+KYhu84IpBFDGo60Z+9GV/td+CeDBLRoijBEixBg87SEilCCpJugc0yx2MOCwlwew==}
peerDependencies:
'@vue/composition-api': ^1.1.2
vue: ^2.5.0 || ^3.0.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
'@tanstack/match-sorter-utils': 8.8.4
'@tanstack/query-core': 4.33.0
'@vue/devtools-api': 6.5.0
vue: 3.3.4
vue-demi: 0.13.11(vue@3.3.4)
dev: false
/@types/eslint@8.44.2: /@types/eslint@8.44.2:
resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==}
dependencies: dependencies:
@ -794,9 +832,14 @@ packages:
'@types/lodash': 4.14.197 '@types/lodash': 4.14.197
dev: false dev: false
/@types/lodash.groupby@4.6.7:
resolution: {integrity: sha512-dFUR1pqdMgjIBbgPJ/8axJX6M1C7zsL+HF4qdYMQeJ7XOp0Qbf37I3zh9gpXr/ks6tgEYPDRqyZRAnFYvewYHQ==}
dependencies:
'@types/lodash': 4.14.197
dev: true
/@types/lodash@4.14.197: /@types/lodash@4.14.197:
resolution: {integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==} resolution: {integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==}
dev: false
/@types/markdown-it@12.2.3: /@types/markdown-it@12.2.3:
resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
@ -805,6 +848,13 @@ packages:
'@types/mdurl': 1.0.2 '@types/mdurl': 1.0.2
dev: true dev: true
/@types/markdown-it@13.0.1:
resolution: {integrity: sha512-SUEb8Frsxs3D5Gg9xek6i6EG6XQ5s+O+ZdQzIPESZVZw3Pv3CPQfjCJBI+RgqZd1IBeu18S0Rn600qpPnEK37w==}
dependencies:
'@types/linkify-it': 3.0.2
'@types/mdurl': 1.0.2
dev: true
/@types/mdurl@1.0.2: /@types/mdurl@1.0.2:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true dev: true
@ -996,7 +1046,6 @@ packages:
/@vue/devtools-api@6.5.0: /@vue/devtools-api@6.5.0:
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
dev: true
/@vue/reactivity-transform@3.3.4: /@vue/reactivity-transform@3.3.4:
resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
@ -1237,7 +1286,6 @@ packages:
/argparse@2.0.1: /argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/array-buffer-byte-length@1.0.0: /array-buffer-byte-length@1.0.0:
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
@ -1700,7 +1748,6 @@ packages:
/entities@3.0.1: /entities@3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
dev: true
/entities@4.5.0: /entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
@ -2732,7 +2779,6 @@ packages:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
dependencies: dependencies:
uc.micro: 1.0.6 uc.micro: 1.0.6
dev: true
/lint-staged@14.0.1: /lint-staged@14.0.1:
resolution: {integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==} resolution: {integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==}
@ -2875,7 +2921,6 @@ packages:
linkify-it: 4.0.1 linkify-it: 4.0.1
mdurl: 1.0.1 mdurl: 1.0.1
uc.micro: 1.0.6 uc.micro: 1.0.6
dev: true
/markdown-it@8.4.2: /markdown-it@8.4.2:
resolution: {integrity: sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==} resolution: {integrity: sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==}
@ -2947,7 +2992,6 @@ packages:
/mdurl@1.0.1: /mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: true
/memoize-one@6.0.0: /memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@ -3048,6 +3092,10 @@ packages:
resolution: {integrity: sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==} resolution: {integrity: sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==}
dev: true dev: true
/moment@2.29.4:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: false
/ms@2.1.2: /ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true dev: true
@ -3385,6 +3433,10 @@ packages:
functions-have-names: 1.2.3 functions-have-names: 1.2.3
dev: true dev: true
/remove-accents@0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
dev: false
/require-from-string@2.0.2: /require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3970,7 +4022,6 @@ packages:
/uc.micro@1.0.6: /uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: true
/unbox-primitive@1.0.2: /unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@ -4117,6 +4168,21 @@ packages:
resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
dev: true dev: true
/vue-demi@0.13.11(vue@3.3.4):
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-demi@0.14.6(vue@3.3.4): /vue-demi@0.14.6(vue@3.3.4):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4254,3 +4320,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -1,12 +1,12 @@
export function simpleLangName(code) { export function simpleLangName(code: string) {
if (code === "all") { if (code === "all") {
return "All"; return "All";
} }
const namesInEnglish = new Intl.DisplayNames(["en"], { type: "language" }); const namesInEnglish = new Intl.DisplayNames(["en"], { type: "language" });
return namesInEnglish.of(code); return namesInEnglish.of(code)!;
} }
export function langName(code) { export function langName(code: string) {
if (code === "all") { if (code === "all") {
return "All"; return "All";
} }

View File

@ -0,0 +1,75 @@
<script setup lang="ts">
import { computed } from "vue";
import useReleaseQuery from "../queries/useReleaseQuery";
const {
data: stableRelease,
isLoading: isLoadingStable,
isError: isErrorStable,
error: errorStable
} = useReleaseQuery("stable");
const {
data: previewRelease,
isLoading: isLoadingPreview,
isError: isErrorPreview,
error: errorPreview
} = useReleaseQuery("preview");
const downloadInformation = computed(() => ({
preview: {
tagName: previewRelease.value?.tag_name,
asset: (previewRelease.value?.assets ?? [])
.find(a => /^tachiyomi-r\d{4,}.apk/.test(a.name)),
},
stable: {
tagName: stableRelease.value?.tag_name?.slice(1),
asset: (stableRelease.value?.assets ?? [])
.find(a => /^tachiyomi-v\d+\.\d+\.\d+.apk/.test(a.name)),
}
}))
</script>
<template>
<div v-if="isLoadingStable || isLoadingPreview">
This is the fancy loading indicator
</div>
<div v-else-if="isErrorStable">
This is the error message: {{ errorStable }}
</div>
<div v-else-if="isErrorPreview">
This is the error message: {{ errorPreview }}
</div>
<div v-else>
<div class="buttons-wrapper">
<a
:download="downloadInformation.stable.asset?.name"
:href="downloadInformation.stable.asset?.browser_download_url"
>
<span>Stable</span>
<span>{{ downloadInformation.stable.tagName }}</span>
</a>
<a
:download="downloadInformation.preview.asset?.name"
:href="downloadInformation.preview.asset?.browser_download_url"
>
<span>Preview</span>
<span>{{ downloadInformation.preview.tagName }}</span>
</a>
</div>
<p>
Requires <strong>Android 6.0</strong> or higher.
</p>
</div>
</template>
<style lang="stylus">
.buttons-wrapper
display: flex
gap: 2rem
& > a
display: flex
flex-direction: column
align-items: center
</style>

View File

@ -1,102 +1,91 @@
<script> <script setup lang="ts">
import axios from "axios";
import groupBy from "lodash.groupby"; import groupBy from "lodash.groupby";
import { GITHUB_EXTENSION_JSON } from "../../../config/constants";
import { simpleLangName } from "../../../config/scripts/languages"; import { simpleLangName } from "../../../config/scripts/languages";
import ExtensionFilters from "./ExtensionFilters.vue"; import ExtensionFilters from "./ExtensionFilters.vue";
import ExtensionList from "./ExtensionList.vue"; import ExtensionList from "./ExtensionList.vue";
import useExtensionsRepositoryQuery from "../../queries/useExtensionsRepositoryQuery";
import { computed, onUpdated, ref } from 'vue';
import type { Extension } from "../../queries/useExtensionsRepositoryQuery";
export default { const { data: extensions, isLoading } = useExtensionsRepositoryQuery();
components: { ExtensionList, ExtensionFilters },
data() {
return {
extensions: [],
filters: {
search: "",
lang: [],
nsfw: "Show all",
sort: "Ascending",
},
loading: true,
observer: null,
};
},
computed: {
filteredExtensions() {
const { extensions, filters } = this;
const filtered = []; const filters = ref({
search: "",
lang: [] as string[],
nsfw: "Show all",
sort: "Ascending",
});
for (const group of extensions) { function languageComparator(a: Extension[], b: Extension[]) {
let filteredGroup = filters.lang.length ? (filters.lang.includes(group[0].lang) ? group : []) : group; const langA = simpleLangName(a[0].lang);
const langB = simpleLangName(b[0].lang);
if (langA === "All" && langB === "English") {
return -1;
}
if (langA === "English" && langB === "All") {
return 1;
}
if (langA === "English") {
return -1;
}
if (langB === "English") {
return 1;
}
if (langA < langB) {
return -1;
}
if (langA > langB) {
return 1;
}
return 0;
}
if (filters.search) { const groupedExtensions = computed(() => {
filteredGroup = filteredGroup.filter( const values: Extension[][] = Object.values(groupBy(extensions.value, "lang"));
(ext) => values.sort(languageComparator)
ext.name.toLowerCase().includes(filters.search.toLowerCase()) ||
ext.sources.some((source) => source.id.includes(filters.search))
);
}
filteredGroup = filteredGroup.filter((ext) =>
filters.nsfw === "Show all" ? true : ext.nsfw === (filters.nsfw === "NSFW" ? 1 : 0)
);
if (filters.sort && filters.sort === "Descending") { return values
filteredGroup = filteredGroup.reverse(); })
}
if (filteredGroup.length) {
filtered.push(filteredGroup);
}
}
return filtered; const filteredExtensions = computed(() => {
}, const filtered: Extension[][] = [];
},
async beforeMount() {
const { data } = await axios.get(GITHUB_EXTENSION_JSON);
const values = Object.values(groupBy(data, "lang"));
values.sort(this.sortLanguages);
this.$data.extensions = values;
this.$nextTick(() => { for (const group of groupedExtensions.value) {
this.loading = false; let filteredGroup = filters.value.lang.length
}); ? (filters.value.lang.includes(group[0].lang) ? group : [])
}, : group;
updated() {
if (window.location.hash) { if (filters.value.search) {
window.location.replace(window.location.hash); filteredGroup = filteredGroup.filter(
(ext) =>
ext.name.toLowerCase().includes(filters.value.search.toLowerCase()) ||
ext.sources.some((source) => source.id.includes(filters.value.search))
);
} }
}, filteredGroup = filteredGroup.filter((ext) =>
methods: { filters.value.nsfw === "Show all" ? true : ext.nsfw === (filters.value.nsfw === "NSFW" ? 1 : 0)
sortLanguages(a, b) { );
const langA = simpleLangName(a[0].lang);
const langB = simpleLangName(b[0].lang); if (filters.value.sort && filters.value.sort === "Descending") {
if (langA === "All" && langB === "English") { filteredGroup = filteredGroup.reverse();
return -1; }
} if (filteredGroup.length) {
if (langA === "English" && langB === "All") { filtered.push(filteredGroup);
return 1; }
} }
if (langA === "English") {
return -1; return filtered;
} });
if (langB === "English") {
return 1; onUpdated(() => {
} if (window.location.hash) {
if (langA < langB) { window.location.replace(window.location.hash);
return -1; }
} });
if (langA > langB) {
return 1;
}
return 0;
},
},
};
</script> </script>
<template> <template>
<ExtensionFilters :extensions="extensions" @filters="filters = $event" /> <ExtensionFilters :extensions="groupedExtensions" @filters="filters = $event" />
<div class="loading" v-if="loading" v-loading.lock="loading" style="min-height: 200px"></div> <div class="loading" v-if="isLoading" v-loading.lock="isLoading" style="min-height: 200px"></div>
<ExtensionList v-else :extensions="filteredExtensions" /> <ExtensionList v-else :extensions="filteredExtensions" />
</template> </template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed, toRefs } from "vue";
import moment from "moment";
import useReleaseQuery from "../queries/useReleaseQuery";
import type { ReleaseType } from "../queries/useReleaseQuery";
const props = defineProps<{ type: ReleaseType }>();
const { type } = toRefs(props);
const { data: release, isLoading, isError } = useReleaseQuery(type);
const momentInfo = computed(() => {
if (isLoading.value || isError.value || !release.value) {
return {
relative: "at an unknown time",
exact: null,
iso: null,
};
}
return {
relative: moment(release.value!.published_at).fromNow(),
exact: moment(release.value!.published_at).format("dddd, MMMM Do YYYY [at] HH:mm"),
iso: release.value!.published_at,
}
})
</script>
<template>
<time :datetime="momentInfo.iso">
{{ momentInfo.relative }}
</time>
</template>

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
import { computed, toRefs } from "vue";
import MarkdownIt from "markdown-it";
import useReleaseQuery from "../queries/useReleaseQuery";
import type { ReleaseType } from "../queries/useReleaseQuery";
const props = defineProps<{ type: ReleaseType }>();
const { type } = toRefs(props);
const { data: release, isLoading, isError, error } = useReleaseQuery(type);
const md = new MarkdownIt();
const whatsNew = computed(() => {
const flavoredString = (release.value?.body ?? "")
.replace(/(?<=\(|(, ))@(.*?)(?=\)|(, ))/g, "[@$2](https://github.com/$2)")
return md.render(flavoredString);
})
</script>
<template>
<div v-if="isLoading">
This is the fancy loading indicator
</div>
<div v-else-if="isError">
This is the error message: {{ error }}
</div>
<div v-else>
<h2>What's new</h2>
<div v-html="whatsNew"></div>
<div>
<p>
View the full release
<a href="https://github.com/tachiyomiorg/tachiyomi/releases/latest" target="_blank" rel="noopener">
here
</a>
</p>
</div>
</div>
</template>
<style lang="stylus">
.buttons-wrapper
display: flex
gap: 2rem
& > a
display: flex
flex-direction: column
align-items: center
</style>

View File

@ -7,6 +7,8 @@ import ElementPlus from "element-plus";
import "element-plus/dist/index.css"; import "element-plus/dist/index.css";
import "element-plus/theme-chalk/dark/css-vars.css"; import "element-plus/theme-chalk/dark/css-vars.css";
import { VueQueryPlugin } from "@tanstack/vue-query"
import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client"; import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client";
export default { export default {
@ -18,6 +20,7 @@ export default {
}, },
enhanceApp({ app, router, siteData }) { enhanceApp({ app, router, siteData }) {
app.use(ElementPlus); app.use(ElementPlus);
app.use(VueQueryPlugin);
enhanceAppWithTabs(app); enhanceAppWithTabs(app);
}, },
}; };

View File

@ -0,0 +1,47 @@
import { useQuery } from "@tanstack/vue-query";
import axios from "axios";
import { GITHUB_EXTENSION_JSON } from "../../config/constants";
export type ReleaseType = "stable" | "preview";
export interface Extension {
name: string
pkg: string
apk: string
lang: string
code: number
version: string
nsfw: number
hasReadme: number
hasChangelog: number
sources: Source[]
}
export interface Source {
name: string
lang: string
id: string
baseUrl: string
versionId: number
hasCloudflare: string
}
export interface GitHubAsset {
name: string
content_type: string
browser_download_url: string
}
export default function useExtensionsRepositoryQuery() {
return useQuery({
queryKey: ["extensions"],
queryFn: async () => {
const { data } = await axios.get<Extension[]>(GITHUB_EXTENSION_JSON);
return data;
},
initialData: () => [],
refetchOnWindowFocus: false,
})
}

View File

@ -0,0 +1,39 @@
import { useQuery } from "@tanstack/vue-query";
import axios from "axios";
import { GITHUB_STABLE_API, GITHUB_PREVIEW_API } from "../../config/constants";
import { unref, type Ref } from 'vue';
export type ReleaseType = "stable" | "preview";
export interface GitHubRelease {
body: string
tag_name: string
name: string
assets: GitHubAsset[]
published_at: string
}
export interface GitHubAsset {
name: string
content_type: string
browser_download_url: string
}
const apiUrls: Record<ReleaseType, string> = {
stable: GITHUB_STABLE_API,
preview: GITHUB_PREVIEW_API,
};
export default function useReleaseQuery(type: ReleaseType | Ref<ReleaseType>) {
return useQuery({
queryKey: ["release", type],
queryFn: async () => {
const typeKey = unref(type);
const { data } = await axios.get<GitHubRelease>(apiUrls[typeKey]);
return data;
},
initialData: () => null,
refetchOnWindowFocus: false,
})
}

View File

@ -3,8 +3,16 @@ title: Download
description: Download page that allows users to access and install the latest version of the app. description: Download page that allows users to access and install the latest version of the app.
--- ---
<script setup>
import DownloadButtons from "../.vitepress/theme/components/DownloadButtons.vue"
import ReleaseDate from "../.vitepress/theme/components/ReleaseDate.vue";
import WhatsNew from "../.vitepress/theme/components/WhatsNew.vue"
</script>
# Download # Download
Download page that allows users to access and install the latest version of the app. Download the latest stable version of **Tachiyomi** that released <ReleaseDate type="stable" /> or the preview version that released <ReleaseDate type="preview" />.
![GitHub all releases](https://img.shields.io/github/downloads/tachiyomiorg/tachiyomi/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat) <DownloadButtons />
<WhatsNew type="stable" />