111
extensions/extensions.js
Normal file
111
extensions/extensions.js
Normal file
@@ -0,0 +1,111 @@
|
||||
!function () {
|
||||
const ENGLISH_DISPLAY_NAMES = new Intl.DisplayNames(["en"], { type: "language" });
|
||||
|
||||
function simpleLanguageName(language) {
|
||||
return language === "all" ? "All" : ENGLISH_DISPLAY_NAMES.of(language);
|
||||
}
|
||||
|
||||
function languageName(language) {
|
||||
if (language === "all") {
|
||||
return "All";
|
||||
}
|
||||
|
||||
if (language === "en") {
|
||||
return "English"
|
||||
}
|
||||
|
||||
const localDisplayNames = new Intl.DisplayNames([language], { type: "language" });
|
||||
|
||||
return `${ENGLISH_DISPLAY_NAMES.of(language)} - ${localDisplayNames.of(language)}`;
|
||||
}
|
||||
|
||||
const LoadingStatus = {
|
||||
Loading: "loading",
|
||||
Loaded: "loaded",
|
||||
Error: "error",
|
||||
}
|
||||
|
||||
const NsfwOption = {
|
||||
All: "all",
|
||||
Safe: "safe",
|
||||
Nsfw: "nsfw",
|
||||
}
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.store("repoUrl", "https://raw.githubusercontent.com/keiyoushi/extensions/repo");
|
||||
|
||||
Alpine.data("extensionList", () => ({
|
||||
LoadingStatus,
|
||||
NsfwOption,
|
||||
simpleLanguageName,
|
||||
languageName,
|
||||
extensions: [],
|
||||
languages: [],
|
||||
loading: LoadingStatus.Loading,
|
||||
filtered: [],
|
||||
query: "",
|
||||
selectedLanguages: [],
|
||||
nsfw: NsfwOption.All,
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const index = await fetch(`${Alpine.store("repoUrl")}/index.min.json`).then((e) => e.json());
|
||||
|
||||
this.extensions = index.sort((a, b) => {
|
||||
if ("all" === a.lang && "all" !== b.lang) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ("all" !== a.lang && "all" === b.lang) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ("en" === a.lang && "en" !== b.lang) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if ("en" === b.lang && "en" !== a.lang) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const langA = simpleLanguageName(a.lang);
|
||||
const langB = simpleLanguageName(b.lang);
|
||||
|
||||
return langA.localeCompare(langB) || a.name.localeCompare(b.name);
|
||||
});
|
||||
this.languages = [...new Set(this.extensions.map((e) => e.lang))];
|
||||
this.loading = LoadingStatus.Loaded;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
this.loading = LoadingStatus.Error;
|
||||
}
|
||||
|
||||
if (this.filtered.length === 0) {
|
||||
this.updateFilteredList();
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
window.location.hash && window.location.replace(window.location.hash);
|
||||
});
|
||||
},
|
||||
|
||||
updateFilteredList() {
|
||||
this.filtered = this.extensions
|
||||
.filter(
|
||||
(e) => !this.query
|
||||
|| e.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
|| e.pkg.toLowerCase().includes(this.query.toLowerCase()),
|
||||
)
|
||||
.filter(
|
||||
(e) => this.nsfw === NsfwOption.All
|
||||
|| (this.nsfw === NsfwOption.Nsfw ? e.nsfw : !e.nsfw),
|
||||
)
|
||||
.filter(
|
||||
(e) =>
|
||||
!this.selectedLanguages.length || this.selectedLanguages.includes(e.lang)
|
||||
);
|
||||
},
|
||||
}))
|
||||
});
|
||||
}()
|
||||
106
extensions/index.html
Normal file
106
extensions/index.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="robots" content="noindex">
|
||||
<title>Keiyoushi</title>
|
||||
<meta name="description" content="A repository of unofficial Tachiyomi extensions.">
|
||||
<meta property="og:title" content="Keiyoushi">
|
||||
<meta property="og:description" content="A repository of unofficial Tachiyomi extensions.">
|
||||
<meta property="og:image" content="https://avatars.githubusercontent.com/u/113362897?v=4">
|
||||
<meta property="og:url" content="https://keiyoushi.github.io/extensions/">
|
||||
<meta name="theme-color" content="#2e84bf">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:light)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.5.2/cdn/themes/light.css"
|
||||
integrity="sha384-0usmJJJTG5wZwRFlxdECle5gNAqtRYVm8rHBHjGO0+Cpgp83KTGEANVIFUYafjaO"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.5.2/cdn/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');"
|
||||
integrity="sha384-2HpI1Tt4Zv7emgrwKyetd6ouDie+RKolEtNHOdD+KCVHLj1V2fjxryG48h50f0Rw"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link rel="stylesheet" href="../index.css">
|
||||
<script async type="module"
|
||||
src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.5.2/cdn/shoelace-autoloader.js"
|
||||
integrity="sha384-ILzDiPYY4je5i95gGzpVe0e88rFqVnoz3i7HPBsRbtEHxfkA7wR8E+PwjCgt1Bin"
|
||||
crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"
|
||||
integrity="sha384-mPO6U7t0sNHfI1UIWNf5U6FDzprqWgAMKfOGW86JVGCKoU/7HPdy6DwBaWOsi4eV"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="extensions.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="header">
|
||||
<p class="header__title">Keiyoushi</p>
|
||||
</header>
|
||||
<div class="description">
|
||||
Join the <a class="description__anchor" href="https://discord.gg/3FbCpdKbdY">Discord server</a> for support. Make sure to star <a class="description__anchor" href="https://github.com/keiyoushi/extensions">the GitHub repo</a>.
|
||||
</div>
|
||||
<div class="sources" x-data="extensionList">
|
||||
<h1 class="sources__title">Extensions</h1>
|
||||
<template x-if="loading === LoadingStatus.Loading">
|
||||
<sl-spinner class="sources__status" style="font-size:3rem"></sl-spinner>
|
||||
</template>
|
||||
<template x-if="loading === LoadingStatus.Error">
|
||||
<div class="sources__status">Could not load extension list.</div>
|
||||
</template>
|
||||
<template x-if="loading === LoadingStatus.Loaded">
|
||||
<div>
|
||||
<div class="source-search" x-effect="updateFilteredList">
|
||||
<div class="source-search__nsfw-wrapper">
|
||||
<span class="source-search__nsfw-label">Display mode: </span>
|
||||
<sl-radio-group size="small" name="nsfw" value="all" @sl-change="nsfw = $event.target.value">
|
||||
<sl-radio-button value="all">Show all</sl-radio-button>
|
||||
<sl-radio-button value="nsfw">NSFW</sl-radio-button>
|
||||
<sl-radio-button value="safe">SFW</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
</div>
|
||||
<sl-input class="source-search__title" placeholder="Search by name or ID..."
|
||||
x-model="query"></sl-input>
|
||||
<sl-select class="source-search__language" multiple clearable placeholder="Filter by languages"
|
||||
@sl-change="selectedLanguages = event.target.value">
|
||||
<template x-for="(language, index) in languages" :key="index">
|
||||
<sl-option :value="language" x-text="languageName(language)"></sl-option>
|
||||
</template>
|
||||
</sl-select>
|
||||
</div>
|
||||
<template x-for="extension in filtered" :key="extension.pkg">
|
||||
<div class="source" :id="extension.pkg.replace('eu.kanade.tachiyomi.extension.', '')">
|
||||
<a :href="`#${extension.pkg.replace('eu.kanade.tachiyomi.extension.', '')}`"
|
||||
class="source__anchor">#</a> <img class="source__icon" :alt="`Icon for ${extension.name}`"
|
||||
:src="`${$store.repoUrl}/icon/${extension.pkg}.png`" loading="lazy" width="42" height="42">
|
||||
<div class="source__info">
|
||||
<div class="source__name">
|
||||
<span x-text="extension.name.split(': ')[1]"></span> <span class="source__version"
|
||||
x-text="`v${extension.version}`"></span>
|
||||
<sl-tag x-cloak size="small" variant="danger"
|
||||
class="content-rating content-rating--nsfw"
|
||||
x-show="extension.nsfw === 1">18+</sl-tag>
|
||||
</div>
|
||||
<div class="source__version" x-text="simpleLanguageName(extension.lang)"></div>
|
||||
</div>
|
||||
<sl-button pill size="small" download class="download-button"
|
||||
:href="`${$store.repoUrl}/apk/${extension.apk}`"><sl-icon name="download"></sl-icon></sl-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<script>
|
||||
Promise.allSettled(
|
||||
["sl-button", "sl-select"].map((e) => customElements.whenDefined(e))
|
||||
)
|
||||
.then(() => document.body.classList.add("ready"))
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user