(add) server page

This commit is contained in:
kakkokari-gtyih 2023-07-10 03:09:50 +09:00
parent 66c32554c5
commit 5a692f54e7
20 changed files with 379 additions and 101 deletions

View File

@ -54,6 +54,7 @@ const getLdJson = (additionalGraphes: Thing[] = []): string => {
useHead((): Record<string, any> => ({
htmlAttrs: {
lang: locale.value,
'data-bs-theme': colorMode.value,
},
title: getTitle(),
meta: [
@ -84,7 +85,7 @@ useHead((): Record<string, any> => ({
}));
</script>
<template>
<div class="text-slate-700 dark:text-slate-200 bg-slate-100 dark:bg-gray-900">
<div class="text-slate-800 dark:text-slate-200 bg-slate-100 dark:bg-gray-900">
<noscript class="block bg-accent-800 text-white text-center py-1.5 px-3 keep-all relative z-[10005]">Please turn on Javascript from your browser's settings.</noscript>
<NuxtLayout>
<NuxtPage />

View File

@ -9,23 +9,23 @@ export default [
},
{
"lang": "es",
"label": "español"
"label": "Español"
},
{
"lang": "es-419",
"label": "español (Latinoamérica)"
"label": "Español (Latinoamérica)"
},
{
"lang": "fr",
"label": "français"
"label": "Français"
},
{
"lang": "hr",
"label": "hrvatski"
"label": "Hrvatski"
},
{
"lang": "it",
"label": "italiano"
"label": "Italiano"
},
{
"lang": "nl",
@ -33,15 +33,15 @@ export default [
},
{
"lang": "pl",
"label": "polski"
"label": "Polski"
},
{
"lang": "pt-BR",
"label": "português (Brasil)"
"label": "Português (Brasil)"
},
{
"lang": "pt-PT",
"label": "português (Portugal)"
"label": "Português (Portugal)"
},
{
"lang": "vi",

14
assets/js/misc/index.ts Normal file
View File

@ -0,0 +1,14 @@
export function resolveObjPath(o: object, s: string): any {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}

View File

@ -1,7 +1,7 @@
<template>
<div>
</div>
<footer>
</footer>
</template>
<script setup lang="ts">

View File

@ -1,38 +1,15 @@
<template>
<nav class="sticky top-0 z-[9900] md:relative container mx-auto max-w-screen-xl h-16 lg:h-20 grid items-center grid-cols-2 md:grid-cols-4 lg:grid-cols-6 p-4">
<div class="">
<GNuxtLink :to="localePath('/')" class="flex items-center space-x-2 hover:opacity-80">
<MiIcon class="h-8 w-8" />
<div class="font-title font-bold text-lg">{{ $t('_seo.siteName') }}</div>
</GNuxtLink>
</div>
<ul class="hidden lg:col-span-4 lg:space-x-8 xl:space-x-10 lg:flex justify-center">
<li v-for="item in NavData.center">
<GNuxtLink :to="localePath(item.to)" :class="['rounded-full px-4 py-1.5 hover:bg-slate-300 dark:hover:bg-slate-700 hover:bg-opacity-50 bg-blend-multiply', { 'bg-slate-200 dark:bg-slate-800': path.includes(item.to) }]">
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
<template v-else>
{{ $t(item.i18n) }}
</template>
<div :class="['bg-slate-100 dark:bg-gray-900 bg-opacity-80 backdrop-blur-lg sticky top-0 z-[9900]', { 'shadow': scrollPos >= 40 }]">
<nav class="container mx-auto max-w-screen-xl h-16 lg:h-20 grid items-center grid-cols-2 md:grid-cols-4 lg:grid-cols-6 p-4">
<div class="">
<GNuxtLink :to="localePath('/')" class="flex items-center space-x-2 hover:opacity-80">
<MiIcon class="h-8 w-8" />
<div class="font-title font-bold text-lg">{{ $t('_seo.siteName') }}</div>
</GNuxtLink>
</li>
</ul>
<div>
<ul class="hidden lg:col-span-4 lg:space-x-4 lg:flex justify-center">
<li class="relative group">
<a class="hover:opacity-80" href="#"><I18nIcon class="h-5 w-5" /><span class="sr-only">{{ $t('_nav.switchLang') }}</span></a>
<div class="absolute top-6 right-0 hidden group-hover:block z-[9955]">
<ul class="px-4 py-2 bg-slate-50 dark:bg-slate-700 rounded-lg shadow-lg space-y-1">
<li v-for="locale in locales">
<GNuxtLink :to="switchLocalePath(locale.code)" class="hover:text-accent-600">
{{ locale.name }}
</GNuxtLink>
</li>
</ul>
</div>
</li>
<li class="border-l"></li>
<li v-for="item in NavData.right">
<GNuxtLink :to="item.to" class="hover:opacity-80">
</div>
<ul class="hidden lg:col-span-4 lg:space-x-8 xl:space-x-10 lg:flex justify-center">
<li v-for="item in NavData.center">
<GNuxtLink :to="localePath(item.to)" :class="['rounded-full px-4 py-1.5 hover:bg-slate-300 dark:hover:bg-slate-800 hover:bg-opacity-50 bg-blend-multiply', { 'bg-slate-200 dark:bg-slate-800': path.includes(item.to) }]">
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
<template v-else>
{{ $t(item.i18n) }}
@ -40,17 +17,82 @@
</GNuxtLink>
</li>
</ul>
</div>
</nav>
<div>
<ul class="hidden lg:col-span-4 lg:space-x-4 lg:flex justify-center">
<li>
<button class="hover:opacity-80" @click="rotateColorMode()" aria-label="Change Color Mode">
<ClientOnly>
<SunIcon class="h-5 w-5" v-if="colorMode.preference === 'light'" />
<MoonIcon class="h-5 w-5" v-else-if="colorMode.preference === 'dark'" />
<DisplayIcon class="h-5 w-5" v-else />
</ClientOnly>
</button>
</li>
<li class="relative group">
<a class="hover:opacity-80" href="#"><I18nIcon class="h-5 w-5" /><span class="sr-only">{{ $t('_nav.switchLang') }}</span></a>
<div class="absolute top-6 right-0 hidden group-hover:block z-[9955]">
<ul class="px-4 py-2 bg-slate-50 dark:bg-slate-800 rounded-lg shadow-lg space-y-1">
<li v-for="locale in locales">
<GNuxtLink :to="switchLocalePath(locale.code)" :class="['hover:text-accent-600 py-1', {'text-accent-600 font-bold': currentLocale === locale.code}]">
{{ locale.name }}
</GNuxtLink>
</li>
</ul>
</div>
</li>
<li class="border-l"></li>
<li v-for="item in NavData.right">
<GNuxtLink :to="item.to" class="hover:opacity-80">
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
<template v-else>
{{ $t(item.i18n) }}
</template>
</GNuxtLink>
</li>
</ul>
</div>
</nav>
</div>
</template>
<script setup>
import MiIcon from '@/assets/svg/misskey_mi_bi.svg';
import I18nIcon from 'bi/translate.svg';
import SunIcon from 'bi/sun.svg';
import MoonIcon from 'bi/moon-stars.svg';
import DisplayIcon from 'bi/display.svg';
import NavData from '@/assets/data/nav';
const { locales } = useI18n();
const { locales, locale: currentLocale } = useI18n();
const { path } = useRoute();
const switchLocalePath = useSwitchLocalePath();
const localePath = useLocalePath();
const colorMode = useColorMode();
function rotateColorMode() {
const values = ['system', 'light', 'dark'];
const index = values.indexOf(colorMode.preference);
const next = (index + 1) % values.length;
colorMode.preference = values[next];
}
const scrollPos = ref(0);
function updatePos() {
scrollPos.value = window.screenY;
}
onMounted(() => {
if (process.client) {
window.addEventListener('scroll', updatePos());
window.addEventListener('resize', updatePos());
}
});
onUnmounted(() => {
if (process.client) {
window.removeEventListener('scroll', updatePos());
window.removeEventListener('resize', updatePos());
}
});
</script>

View File

@ -69,7 +69,7 @@ import { vFadeIn } from '@/assets/js/fadein';
}
.item > .content {
@apply p-5 bg-white dark:bg-slate-700 rounded-xl;
@apply p-5 bg-white dark:bg-slate-800 rounded-xl;
}
.item .img {

View File

@ -38,7 +38,7 @@ const localePath = useLocalePath();
<style scoped>
.item {
@apply rounded-2xl py-12 px-4 text-center bg-white dark:bg-slate-700;
@apply rounded-2xl py-12 px-4 text-center bg-white dark:bg-slate-800;
}
.find > .item {
@ -54,10 +54,10 @@ const localePath = useLocalePath();
}
.item > .link {
@apply px-6 py-3 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors font-bold text-lg;
@apply px-6 py-3 rounded-lg bg-gray-100 dark:bg-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors font-bold text-lg;
}
.find .link {
@apply text-accent-600 bg-white hover:bg-accent-100;
@apply text-accent-600 dark:text-slate-200 bg-white dark:bg-accent-950 hover:bg-accent-100 dark:hover:bg-accent-900;
}
</style>

View File

@ -18,12 +18,21 @@
</ul>
<div>
<ul class="hidden lg:col-span-4 lg:space-x-4 lg:flex justify-center">
<li>
<button class="text-white hover:opacity-80" @click="rotateColorMode()" aria-label="Change Color Mode">
<ClientOnly>
<SunIcon class="h-5 w-5" v-if="colorMode.preference === 'light'" />
<MoonIcon class="h-5 w-5" v-else-if="colorMode.preference === 'dark'" />
<DisplayIcon class="h-5 w-5" v-else />
</ClientOnly>
</button>
</li>
<li class="relative group">
<a class="text-white hover:opacity-80" href="#"><I18nIcon class="h-5 w-5" /><span class="sr-only">{{ $t('_nav.switchLang') }}</span></a>
<div class="absolute top-6 right-0 hidden group-hover:block z-[9955]">
<ul class="px-4 py-2 bg-slate-50 dark:bg-slate-700 rounded-lg shadow-lg space-y-1">
<ul class="px-4 py-2 bg-slate-50 dark:bg-slate-800 rounded-lg shadow-lg space-y-2">
<li v-for="locale in locales">
<GNuxtLink :to="switchLocalePath(locale.code)" class="hover:text-accent-600">
<GNuxtLink :to="switchLocalePath(locale.code)" :class="['hover:text-accent-600 py-1', {'text-accent-600 font-bold': currentLocale === locale.code}]">
{{ locale.name }}
</GNuxtLink>
</li>
@ -48,9 +57,21 @@
<script setup>
import MiIcon from '@/assets/svg/misskey_mi_bi.svg';
import I18nIcon from 'bi/translate.svg';
import SunIcon from 'bi/sun.svg';
import MoonIcon from 'bi/moon-stars.svg';
import DisplayIcon from 'bi/display.svg';
import NavData from '@/assets/data/nav';
const { locales } = useI18n();
const { locales, locale: currentLocale } = useI18n();
const switchLocalePath = useSwitchLocalePath();
const localePath = useLocalePath();
const colorMode = useColorMode();
function rotateColorMode() {
const values = ['system', 'light', 'dark'];
const index = values.indexOf(colorMode.preference);
const next = (index + 1) % values.length;
colorMode.preference = values[next];
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>
<h2 class="mb-12 text-2xl lg:text-3xl text-center font-bold font-title">{{ $t('_landing._sponsors.title') }}</h2>
<div class="mx-auto max-w-[240px] space-y-8 [&>*]:block">
<!-- スポンサーを入れるときはリンクとアイコンを適当に並べるだけでいい感じになります -->
<NuxtLink to="https://rss3.io/" target="_blank">
<img src="/img/sponsors/rss3.svg" />
</NuxtLink>
<NuxtLink to="https://www.dotchain.ltd/advirth" target="_blank">
<img src="/img/sponsors/dcadvirth.png" />
</NuxtLink>
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -8,7 +8,7 @@
<script setup lang="ts">
import { Loader } from '@/assets/js/particles/loader';
import tailwindConfig from 'tailwindcss/resolveConfig';
import resolveConfig from 'tailwindcss/resolveConfig';
const colorMode = useColorMode();
const container = ref<HTMLElement>();
@ -16,9 +16,11 @@ const isMounted = ref<boolean>(false);
const particleEnabled = ref<boolean>(true);
const colorVars = computed<string>(() => {
const out = [`--c-brand: ${tailwindConfig.theme?.extend.colors.accent['600'] || '#86b300'}`]
if (colorMode.preference == 'dark') {
out.join(`--c-text: `)
const out = [`--c-brand: #86b300;`];
if (colorMode.value == 'dark') {
out.push(`--c-text: rgb(226 232 240);`);
} else {
out.push(`--c-text: rgb(51 65 85);`);
}
return out.join(' ');
});
@ -27,9 +29,11 @@ onMounted(() => {
isMounted.value = true;
});
let loader: Loader;
watch(container, (to) => {
if (isMounted.value && process.client && to) {
const loader = new Loader(to);
loader = new Loader(to);
window.addEventListener('scroll', () => {
particleEnabled.value = false;
@ -38,14 +42,15 @@ watch(container, (to) => {
once: true,
});
onUnmounted(() => {
setTimeout(() => {
loader.destroy();
})
})
}
})
onUnmounted(() => {
setTimeout(() => {
loader.destroy();
}, 1);
});
</script>
<style>

View File

@ -1,6 +1,36 @@
<template>
<div>
<div class="border border-gray-300 dark:border-gray-800 dark:bg-slate-800 rounded-lg shadow-lg overflow-hidden focus-within:ring-2 ring-accent-500 ring-offset-2">
<NuxtLink :to="`https://${instance.url}`" target="_blank">
<div class="relative aspect-video bg-gray-200 dark:bg-gray-600">
<img v-if="instance.banner" :src="`https://instanceapp.misskey.page/instance-banners/${instance.url}.webp`" class="w-full h-full object-cover" />
<div class="absolute h-1/2 bottom-0 left-0 w-full bg-gradient-to-b from-transparent to-black text-white p-4 flex items-end">
<div class="h-14 w-14 min-w-0 flex-shrink-0 mr-4">
<img v-if="instance.icon" :src="`https://instanceapp.misskey.page/instance-icons/${instance.url}.webp`" class="w-full h-full" />
</div>
<div class="min-w-0 flex flex-col justify-end">
<h2 class="font-bold text-2xl whitespace-nowrap truncate">{{ instance.name }}</h2>
<p class="opacity-90 text-sm truncate">{{ instance.url }} / v.{{ instance.nodeinfo?.software.version }}</p>
</div>
</div>
</div>
<div class="p-4">
<p class="description h-12 mb-2">{{ instance.description }}</p>
<div class="grid grid-cols-3 text-center">
<dl>
<dt class="text-xs opacity-90">{{ $t('_servers._statistics.notes') }}</dt>
<dd class="font-bold text-accent-600">{{ instance.stats?.originalNotesCount.toLocaleString() }}</dd>
</dl>
<dl>
<dt class="text-xs opacity-90">{{ $t('_servers._statistics.users') }}</dt>
<dd class="font-bold text-accent-600">{{ instance.stats?.originalUsersCount.toLocaleString() }}</dd>
</dl>
<dl>
<dt class="text-xs opacity-90">{{ $t('_servers._registerAcceptance.title') }}</dt>
<dd class="font-bold text-accent-600">{{ instance.meta?.disableRegistration ? $t('_servers._registerAcceptance.inviteOnly') : $t('_servers._registerAcceptance.public')}}</dd>
</dl>
</div>
</div>
</NuxtLink>
</div>
</template>
@ -13,5 +43,12 @@ defineProps<{
</script>
<style scoped>
.description {
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
}
</style>

View File

@ -4,7 +4,7 @@ const isNavOpen = ref<boolean>(false);
<template>
<div>
<GNav @toggleNav="isNavOpen = !isNavOpen" :is-open="isNavOpen" />
<div class="main-content overflow-x-hidden">
<div class="main-content">
<slot></slot>
</div>
<GFooter />

View File

@ -73,6 +73,8 @@ _landing:
_donation:
title: "寄付のお願い"
description: "Misskeyは非営利なため、開発資金は皆様からの寄付に頼っています。Misskeyを気に入られたら、今後も開発を続けられるようにぜひ支援をお願いします。"
_sponsors:
title: "スポンサー"
_servers:
title: "サーバー一覧"
description: "Misskeyは単一のサービスではなく、各々がサービスを提供する分散型ネットワークとなっています。Misskeyを利用するには、サービスを提供しているサーバーでアカウントを作成する必要があります。"
@ -87,14 +89,15 @@ _servers:
_search:
title: "絞り込み検索"
all: "すべて"
query: "キーワードで検索"
lang: "言語"
orderBy: "並び替え"
recomendded: "デフォルト"
notesCount: "ノート数"
usersCount: "ユーザー数"
_registerAcceptance:
title: "新規登録"
public: "開放"
inviteOnly: "招待のみ"
_registerAcceptance:
title: "新規登録"
public: "開放"
inviteOnly: "招待のみ"
_list:
showMore: "もっと見る"

View File

@ -1,5 +1,5 @@
<template>
<div class="relative min-h-full">
<div class="relative min-h-full pb-24">
<IndexHeroBg />
<IndexHeroParticles />
<IndexNav />
@ -16,6 +16,7 @@
<IndexGetStarted id="getStarted" />
<GDots class="w-[95%] mx-auto text-accent-600" :space="30" />
<IndexDonation />
<IndexSponsors />
</main>
</div>
</template>

View File

@ -29,51 +29,71 @@
</div>
</template>
</GHero>
<div class="mt-12 pt-6 bg-white dark:bg-slate-950">
<div class="my-12 pt-6 bg-white dark:bg-slate-950">
<div class="container mx-auto max-w-screen-xl px-6 grid server-list gap-8">
<aside class="hidden lg:block">
<div class="sticky top-6 py-2 space-y-4">
<h3 class="text-xl font-bold">絞り込み検索</h3>
<div class="sticky top-24 py-2 space-y-4">
<h3 class="text-xl font-bold">{{ $t('_servers._search.title') }}</h3>
<form @submit.prevent="() => { f_query = f_query_partial }">
<label class="form-label" for="query">{{ $t('_servers._search.query') }}</label>
<div class="input-group">
<input class="form-control" type="search" autocomplete="off" id="query" v-model="f_query_partial" />
<button type="submit" class="btn btn-outline-primary hover:!text-white">
<SearchIco class="stroke-[0.5] stroke-current" />
</button>
</div>
</form>
<div>
<label class="form-label" for="languages">{{ $t('_servers._search.lang') }}</label>
<select id="languages" class="form-select">
<select id="languages" v-model="f_langs" class="form-select">
<option :value="null">{{ $t('_servers._search.all') }}</option>
<option v-for="lang in langs" :value="lang.lang">{{ lang.label }}</option>
</select>
</div>
<div>
<label class="form-label" for="orderBy">{{ $t('_servers._search.orderBy') }}</label>
<select id="orderBy" class="form-select">
<option value="recomendded">{{ $t('_servers._search.recomendded') }}</option>
<option value="notesCount">{{ $t('_servers._search.notesCount') }}</option>
<option value="usersCount">{{ $t('_servers._search.usersCount') }}</option>
</select>
<div class="input-group">
<select id="orderBy" v-model="f_orderBy" class="form-select">
<option value="recomendded">{{ $t('_servers._search.recomendded') }}</option>
<option value="notesCount">{{ $t('_servers._search.notesCount') }}</option>
<option value="usersCount">{{ $t('_servers._search.usersCount') }}</option>
</select>
<button class="btn btn-outline-primary hover:!text-white" @click="switchOrder()">
<SortDownIco v-if="f_order === 'desc'" class="stroke-[0.5] stroke-current" />
<SortUpIco v-else class="stroke-[0.5] stroke-current" />
</button>
</div>
</div>
<div>
<div class="mb-1">{{ $t('_servers._search._registerAcceptance.title') }}</div>
<div class="mb-1">{{ $t('_servers._registerAcceptance.title') }}</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="registerAcceptance" :value="null" id="registerAcceptance0">
<input class="form-check-input" type="radio" name="registerAcceptance" v-model="f_registerAcceptance" :value="null" id="registerAcceptance0">
<label class="form-check-label" for="registerAcceptance0">
{{ $t('_servers._search.all') }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="registerAcceptance" value="public" id="registerAcceptance1">
<input class="form-check-input" type="radio" name="registerAcceptance" v-model="f_registerAcceptance" value="public" id="registerAcceptance1">
<label class="form-check-label" for="registerAcceptance1">
{{ $t('_servers._search._registerAcceptance.public') }}
{{ $t('_servers._registerAcceptance.public') }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="registerAcceptance" value="inviteOnly" id="registerAcceptance2">
<input class="form-check-input" type="radio" name="registerAcceptance" v-model="f_registerAcceptance" value="inviteOnly" id="registerAcceptance2">
<label class="form-check-label" for="registerAcceptance2">
{{ $t('_servers._search._registerAcceptance.inviteOnly') }}
{{ $t('_servers._registerAcceptance.inviteOnly') }}
</label>
</div>
</div>
</div>
</aside>
<div class="grid gap-4 grid-cols-1 lg:grid-cols-3 xl:grid-cols-4">
<div>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-2">
<ServersItem v-for="item in filteredInstances.slice(0, f_limit)" :instance="item" />
<button v-if="f_limit < filteredInstances.length" @click="f_limit += 20" class="btn btn-outline-primary btn-lg hover:!text-white block sm:col-span-2 md:col-span-2 lg:col-span-2 px-4">
<ArrowIco class="mr-1" />{{ $t('_servers._list.showMore') }}
</button>
</div>
</div>
</div>
</div>
@ -81,12 +101,59 @@
</template>
<script setup lang="ts">
import type { InstanceInfo } from '@/types/instances-info';
import SearchIco from 'bi/search.svg';
import SortUpIco from 'bi/sort-down-alt.svg';
import SortDownIco from 'bi/sort-down.svg';
import ArrowIco from 'bi/arrow-down-circle.svg';
import type { InstanceInfo, InstanceItem } from '@/types/instances-info';
import { resolveObjPath } from '@/assets/js/misc';
import langs from '@/assets/data/lang';
const { t, locale } = useI18n();
const route = useRoute();
//
type MiHubSFStorage = {
f_langs: string;
f_orderBy: 'recomendded' | 'notesCount' | 'usersCount';
f_order: 'asc' | 'desc';
f_registerAcceptance: 'public' | 'inviteOnly' | null;
};
let savedSettings: MiHubSFStorage | null = null;
if (process.client) {
savedSettings = JSON.parse(window.localStorage.getItem('miHub_server_finder') ?? 'null') as MiHubSFStorage | null;
}
const f_query_partial = ref<string>("");
const f_query = ref<string>("");
const f_langs = ref<MiHubSFStorage['f_langs']>(savedSettings?.f_langs ?? locale.value);
const f_orderBy = ref<MiHubSFStorage['f_orderBy']>(savedSettings?.f_orderBy ?? 'recomendded');
const f_order = ref<MiHubSFStorage['f_order']>(savedSettings?.f_order ?? 'desc');
const f_registerAcceptance = ref<MiHubSFStorage['f_registerAcceptance']>(savedSettings?.f_registerAcceptance || null);
const f_limit = ref<number>(20);
//
//
watch([f_langs, f_orderBy, f_order, f_registerAcceptance], (to, from) => {
f_limit.value = 20;
const newSettings: MiHubSFStorage = {
f_langs: to[0],
f_orderBy: to[1],
f_order: to[2],
f_registerAcceptance: to[3],
};
if (process.client) {
window.localStorage.setItem('miHub_server_finder', JSON.stringify(newSettings));
}
});
//
route.meta.title = t('_servers.title');
route.meta.description = t('_servers.description');
@ -96,6 +163,58 @@ const { data } = await useFetch<InstanceInfo>('https://instanceapp.misskey.page/
}
});
const filteredInstances = computed<InstanceItem[]>(() => {
let instances = data.value?.instancesInfos ?? [];
if (instances.length === 0) {
return [];
}
if (f_query.value) {
instances = instances.filter((instance) => instance.name.includes(f_query.value) || instance.description?.includes(f_query.value));
}
if (f_langs.value) {
instances = instances.filter((instance) => instance.langs.includes(f_langs.value));
}
if (f_registerAcceptance.value) {
instances = instances.filter((instance) => {
if (f_registerAcceptance.value === 'inviteOnly') {
return instance.meta?.disableRegistration;
} else {
return !instance.meta?.disableRegistration ?? true;
}
});
}
if (f_orderBy.value) {
instances.sort((a, b) => {
let orderKey: string;
switch(f_orderBy.value) {
case 'recomendded':
orderKey = 'value';
break;
case 'notesCount':
orderKey = 'stats.originalNotesCount';
break;
case 'usersCount':
orderKey = 'stats.originalUsersCount';
break;
}
if (f_order.value === 'desc') {
return resolveObjPath(a, orderKey) > resolveObjPath(b, orderKey) ? -1 : 1;
} else {
return resolveObjPath(a, orderKey) < resolveObjPath(b, orderKey) ? -1 : 1;
}
});
}
return instances;
});
function switchOrder() {
f_order.value = f_order.value === 'asc' ? 'desc' : 'asc';
}
</script>
<style scoped>

View File

@ -1,4 +1,16 @@
/** Japanese (GenjyuuGothicX) */
@import url('./GenJyuuGothicX-Bold/GenJyuuGothicX-Bold.css');
@import url('./GenJyuuGothicX-P-Bold/GenJyuuGothicX-P-Bold.css');
@import url('./GenJyuuGothicX-P-Regular/GenJyuuGothicX-P-Regular.css');
@import url('./GenJyuuGothicX-Regular/GenJyuuGothicX-Regular.css');
/** Korean (Pretendard) */
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/static/pretendard-dynamic-subset.css");
html[lang='ja'] {
--mi-localized-font: 'GenJyuuGothicX';
--mi-localized-font-p: 'GenJyuuGothicXP';
}
html[lang='ko'] {
--mi-localized-font: 'Pretendard';
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 884.33 884.33"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#0b70ff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="图层_1" data-name="图层 1"><circle class="cls-1" cx="442.16" cy="442.16" r="442.16"/><path d="M566,456.24c-8.5-12.61-29-19.34-43.67-23.3-1.71-.45-3.49-.9-5.38-1.37l-.14,0c-7.91-2-16.87-4.21-23.85-8-8-4.38-11.93-10.06-11.93-17.36a17.71,17.71,0,0,1,.33-3.53l.05-.21c.11-.52.25-1.05.41-1.59l.07-.21,0-.08a20.46,20.46,0,0,1,5.06-8c5.93-6,15.4-9.37,26-9.37h.34a56,56,0,0,1,22.56,5.31,61,61,0,0,1,18.81,13.34L554.8,417v.17H568V371.4H554.8v10.38a.46.46,0,0,1-.2.39c-.12,0-.28-.09-.36-.17a67.94,67.94,0,0,0-18.4-9.91,71,71,0,0,0-22-3.82c-8.58,0-15.6,1-21.47,2.91A41.42,41.42,0,0,0,479,378.61,35,35,0,0,0,469,391.24a35.83,35.83,0,0,0-3.45,15.18,31.8,31.8,0,0,0,1.15,9.21,34.13,34.13,0,0,0,4.49,9.29,9.21,9.21,0,0,0,.55.79l.28.38.11.16c8.67,11.92,28,17.24,42.08,21.13l.38.11,3.13.86,3.4.91a138.94,138.94,0,0,1,21.47,7.17,25.41,25.41,0,0,1,9.36,7.08,22,22,0,0,1,4.59,9.83,21.52,21.52,0,0,1-.79,10.77A27.35,27.35,0,0,1,549,494.66a32,32,0,0,1-11.42,6.51,47.65,47.65,0,0,1-12.67,2.27,63.86,63.86,0,0,1-20.42-2.31c-11.24-3.56-21.52-10.43-28.18-18.82a2.71,2.71,0,0,1-.48-.92l-.13-13.08v-.17H462.5V516h13.21V502A57.47,57.47,0,0,0,494.55,513a78.07,78.07,0,0,0,25.27,4.82h1.07c7.32,0,24.48,0,37.92-11.39A38.91,38.91,0,0,0,567.9,495a40,40,0,0,0,4.25-13.38,37.44,37.44,0,0,0-.62-13.51A31.06,31.06,0,0,0,566,456.24Z"/><path d="M435.69,456.23c-8.34-12.56-28.91-19.31-43.67-23.29-1.68-.44-3.42-.87-5.27-1.32-16-4-35.93-8.89-36.16-25.46a17.52,17.52,0,0,1,1.59-7.27,21.78,21.78,0,0,1,4.51-6.4c6-5.92,15.42-9.32,25.94-9.32H383a55.78,55.78,0,0,1,22.54,5.31,59.83,59.83,0,0,1,18.7,13.34l.26,15.21v.17h13.2V371.4H424.47v10.38c0,.14-.08.34-.2.38s-.21,0-.36-.16a67.81,67.81,0,0,0-18.4-9.91,71,71,0,0,0-22-3.82c-8.63,0-15.67,1-21.52,2.91a41.45,41.45,0,0,0-13.29,7.43,35,35,0,0,0-10.06,12.63,35.85,35.85,0,0,0-3.46,15.18,32.27,32.27,0,0,0,1.14,9.21,35.44,35.44,0,0,0,4.37,9.29c8.29,12.92,28.58,18.47,43.39,22.53l.34.09,2.94.81,3.1.83.3.08a138.94,138.94,0,0,1,21.47,7.17,25.84,25.84,0,0,1,9.4,7.13,22.09,22.09,0,0,1,4.61,9.92,21.55,21.55,0,0,1-.81,10.78,27.2,27.2,0,0,1-6.73,10.53,31.22,31.22,0,0,1-11.37,6.49,47.21,47.21,0,0,1-12.68,2.22c-.9,0-1.83.07-2.77.07A65.27,65.27,0,0,1,374,501.13c-.67-.21-1.35-.44-2-.68l-.11,0c-1.35-.48-2.7-1-4-1.6A60.43,60.43,0,0,1,350.26,487l-.26-.25c-.81-.79-1.61-1.65-2.4-2.54l-.6-.68c-.34-.41-.69-.82-1-1.24a2.71,2.71,0,0,1-.47-.91l-.13-13.08v-.17H332V516h13.35V502A57.47,57.47,0,0,0,364.22,513a78.07,78.07,0,0,0,25.27,4.82h1.06c7.29,0,24.36,0,37.93-11.39A39.07,39.07,0,0,0,437.57,495a40,40,0,0,0,4.25-13.38,37.44,37.44,0,0,0-.62-13.51A31.17,31.17,0,0,0,435.69,456.23Z"/><path d="M300.3,504.67l-36.09-54.55A39.75,39.75,0,0,0,282,446.68c7.89-4,14.23-9.08,18.12-16.58a43.67,43.67,0,0,0,4.77-20,41.62,41.62,0,0,0-12-29.21c-8.31-7.57-18.71-11.26-31.8-11.26H191.13v15.1H206v120.1H189.85v12.62h46.84V504.85H221.6V450.34h25.25l35.36,54.51H266.09v12.62h50.43V504.85H300.8A.64.64,0,0,1,300.3,504.67ZM221.6,384.06h38.68c11.88,0,21,4.72,25.62,12.83a26.83,26.83,0,0,1-.43,26.47c-4.73,8.05-10.33,12.42-22.75,12.42H221.6Z"/><path d="M692.61,473.77c0-18.12-9.16-31.2-24.34-36.81,12.9-6.78,18.56-17.88,18.56-29.33,0-26.41-22.17-40.43-51.08-40.43-15.79.12-32.65,5.38-44.1,21.5l12.05,7.83c6.38-8.65,15.54-15.31,32-15.31,21,0,35.9,8.53,35.9,24.78,0,15.54-14.94,24.18-30.24,24.18H620.57v15h22c19.52,0,34.94,10.51,34.45,28.39-1.08,19.05-17.82,29.21-39.75,29.21-14.09,0-29.76-4.44-41.44-16.94l-.61-.23L585,496.78l.49.12c14.69,13.79,32.16,20.92,51.8,20.92C667.55,517.82,692.61,501.46,692.61,473.77Z"/><rect class="cls-2" x="142.14" y="435.68" width="37.98" height="15.51"/><rect class="cls-2" x="708.4" y="435.68" width="37.98" height="15.51"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -31,9 +31,9 @@ export default <Config> {
},
},
fontFamily: {
'title': ['Capriola', 'GenJyuuGothicX', ...defaultTheme.fontFamily.sans],
'sans': ['Nunito', 'GenJyuuGothicX', ...defaultTheme.fontFamily.sans],
'content-sans': ['Nunito', 'GenJyuuGothicXP', ...defaultTheme.fontFamily.sans],
'title': ['Capriola', 'var(--mi-localized-font, \'\')', ...defaultTheme.fontFamily.sans],
'sans': ['Nunito', 'var(--mi-localized-font, \'\')', ...defaultTheme.fontFamily.sans],
'content-sans': ['Nunito', 'var(--mi-localized-font-p, var(--mi-localized-font))', ...defaultTheme.fontFamily.sans],
}
},
plugins: [],

View File

@ -19,11 +19,11 @@ export type InstanceItem = {
/** Icon Image existance */
icon: boolean;
/** nodeinfo */
nodeinfo: Object | null,
nodeinfo: Record<string, any> | null,
/** result of api/meta */
meta: Object | null,
meta: Record<string, any> | null,
stats?: Object, // deprecated (result of api/stats)
stats?: Record<string, any>, // deprecated (result of api/stats)
};
/** JSON Object Returned from `joinmisskey/api`. */