2022-11-23 03:06:56 +00:00
|
|
|
<script setup lang="ts">
|
2022-12-24 00:40:51 +00:00
|
|
|
import Fuse from 'fuse.js'
|
2022-11-23 03:06:56 +00:00
|
|
|
|
2023-02-05 12:10:19 +00:00
|
|
|
const input = ref<HTMLInputElement | undefined>()
|
2024-02-21 15:20:08 +00:00
|
|
|
const knownServers = ref<string[]>([])
|
|
|
|
const autocompleteIndex = ref(0)
|
|
|
|
const autocompleteShow = ref(false)
|
2022-11-23 03:06:56 +00:00
|
|
|
|
2023-02-05 12:10:19 +00:00
|
|
|
const { busy, error, displayError, server, oauth } = useSignIn(input)
|
2022-11-23 09:06:32 +00:00
|
|
|
|
2024-02-21 15:20:08 +00:00
|
|
|
const fuse = shallowRef(new Fuse([] as string[]))
|
2022-12-24 00:40:51 +00:00
|
|
|
|
2024-02-21 15:20:08 +00:00
|
|
|
const filteredServers = computed(() => {
|
2023-02-05 12:10:19 +00:00
|
|
|
if (!server.value)
|
2022-12-24 00:40:51 +00:00
|
|
|
return []
|
|
|
|
|
2024-02-21 15:20:08 +00:00
|
|
|
const results = fuse.value.search(server.value, { limit: 6 }).map(result => result.item)
|
2023-02-05 12:10:19 +00:00
|
|
|
if (results[0] === server.value)
|
2022-12-24 00:40:51 +00:00
|
|
|
return []
|
|
|
|
|
|
|
|
return results
|
|
|
|
})
|
|
|
|
|
2023-01-12 18:14:34 +00:00
|
|
|
function isValidUrl(str: string) {
|
|
|
|
try {
|
|
|
|
// eslint-disable-next-line no-new
|
|
|
|
new URL(str)
|
|
|
|
return true
|
|
|
|
}
|
2024-08-16 15:52:08 +01:00
|
|
|
catch {
|
2023-01-12 18:14:34 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleInput() {
|
2023-02-05 12:10:19 +00:00
|
|
|
const input = server.value.trim()
|
2023-01-12 18:18:21 +00:00
|
|
|
if (input.startsWith('https://'))
|
2023-02-05 12:10:19 +00:00
|
|
|
server.value = input.replace('https://', '')
|
2023-01-12 18:14:34 +00:00
|
|
|
|
2023-01-12 18:18:21 +00:00
|
|
|
if (input.length)
|
2023-02-05 12:10:19 +00:00
|
|
|
displayError.value = false
|
2023-01-12 18:14:34 +00:00
|
|
|
|
|
|
|
if (
|
2023-01-12 18:18:21 +00:00
|
|
|
isValidUrl(`https://${input}`)
|
2024-08-16 15:52:08 +01:00
|
|
|
&& input.match(/^[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/i)
|
2023-01-12 18:14:34 +00:00
|
|
|
// Do not hide the autocomplete if a result has an exact substring match on the input
|
2024-02-21 15:20:08 +00:00
|
|
|
&& !filteredServers.value.some(s => s.includes(input))
|
2024-08-16 15:52:08 +01:00
|
|
|
) {
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteShow.value = false
|
2024-08-16 15:52:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteShow.value = true
|
2024-08-16 15:52:08 +01:00
|
|
|
}
|
2023-01-12 18:14:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-28 14:56:08 +00:00
|
|
|
function toSelector(server: string) {
|
|
|
|
return server.replace(/[^\w-]/g, '-')
|
|
|
|
}
|
2022-12-24 00:40:51 +00:00
|
|
|
function move(delta: number) {
|
2024-02-21 15:20:08 +00:00
|
|
|
if (filteredServers.value.length === 0) {
|
|
|
|
autocompleteIndex.value = 0
|
2023-01-13 06:23:24 +00:00
|
|
|
return
|
|
|
|
}
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteIndex.value = ((autocompleteIndex.value + delta) + filteredServers.value.length) % filteredServers.value.length
|
|
|
|
document.querySelector(`#${toSelector(filteredServers.value[autocompleteIndex.value])}`)?.scrollIntoView(false)
|
2022-12-24 00:40:51 +00:00
|
|
|
}
|
|
|
|
|
2022-12-24 11:42:54 +00:00
|
|
|
function onEnter(e: KeyboardEvent) {
|
2024-02-21 15:20:08 +00:00
|
|
|
if (autocompleteShow.value === true && filteredServers.value[autocompleteIndex.value]) {
|
|
|
|
server.value = filteredServers.value[autocompleteIndex.value]
|
2022-12-24 11:42:54 +00:00
|
|
|
e.preventDefault()
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteShow.value = false
|
2022-12-24 11:42:54 +00:00
|
|
|
}
|
2022-12-24 00:40:51 +00:00
|
|
|
}
|
|
|
|
|
2023-01-01 15:12:02 +00:00
|
|
|
function escapeAutocomplete(evt: KeyboardEvent) {
|
2024-03-11 10:53:25 +00:00
|
|
|
if (!autocompleteShow.value)
|
2023-01-01 15:12:02 +00:00
|
|
|
return
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteShow.value = false
|
2023-01-01 15:12:02 +00:00
|
|
|
evt.stopPropagation()
|
|
|
|
}
|
|
|
|
|
2022-12-28 14:56:08 +00:00
|
|
|
function select(index: number) {
|
2024-02-21 15:20:08 +00:00
|
|
|
server.value = filteredServers.value[index]
|
2022-12-28 14:56:08 +00:00
|
|
|
}
|
|
|
|
|
2022-12-24 00:40:51 +00:00
|
|
|
onMounted(async () => {
|
2023-02-05 12:10:19 +00:00
|
|
|
input?.value?.focus()
|
2024-02-21 15:20:08 +00:00
|
|
|
knownServers.value = await (globalThis.$fetch as any)('/api/list-servers')
|
|
|
|
fuse.value = new Fuse(knownServers.value, { shouldSort: true })
|
2022-11-29 12:04:16 +00:00
|
|
|
})
|
2023-01-01 15:12:02 +00:00
|
|
|
|
2023-02-05 12:10:19 +00:00
|
|
|
onClickOutside(input, () => {
|
2024-02-21 15:20:08 +00:00
|
|
|
autocompleteShow.value = false
|
2023-01-01 15:12:02 +00:00
|
|
|
})
|
2022-11-23 03:06:56 +00:00
|
|
|
</script>
|
|
|
|
|
2022-11-23 02:53:22 +00:00
|
|
|
<template>
|
2022-12-02 07:02:44 +00:00
|
|
|
<form text-center justify-center items-center max-w-150 py6 flex="~ col gap-3" @submit.prevent="oauth">
|
2023-01-03 00:14:53 +00:00
|
|
|
<div flex="~ center" items-end mb2 gap-x-2>
|
2023-02-12 11:56:29 +00:00
|
|
|
<img :src="`/${''}logo.svg`" w-12 h-12 mxa height="48" width="48" :alt="$t('app_logo')" class="rtl-flip">
|
2022-11-30 01:45:52 +00:00
|
|
|
<div text-3xl>
|
|
|
|
{{ $t('action.sign_in') }}
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-12-13 23:12:16 +00:00
|
|
|
<div>
|
|
|
|
{{ $t('user.server_address_label') }}
|
|
|
|
</div>
|
|
|
|
<div :class="error ? 'animate animate-shake-x animate-delay-100' : null">
|
|
|
|
<div
|
2023-01-01 14:29:11 +00:00
|
|
|
dir="ltr"
|
2022-12-13 23:12:16 +00:00
|
|
|
flex bg-gray:10 px4 py2 mxa rounded
|
|
|
|
border="~ base" items-center font-mono
|
|
|
|
focus:outline-none focus:ring="2 primary inset"
|
2022-12-24 00:40:51 +00:00
|
|
|
relative
|
2022-12-13 23:12:16 +00:00
|
|
|
:class="displayError ? 'border-red-600 dark:border-red-400' : null"
|
|
|
|
>
|
2023-01-01 14:29:11 +00:00
|
|
|
<span text-secondary-light me1>https://</span>
|
2022-12-24 00:40:51 +00:00
|
|
|
|
2022-12-13 23:12:16 +00:00
|
|
|
<input
|
|
|
|
ref="input"
|
|
|
|
v-model="server"
|
2023-01-01 19:21:09 +00:00
|
|
|
autocapitalize="off"
|
|
|
|
inputmode="url"
|
2022-12-13 23:12:16 +00:00
|
|
|
outline-none bg-transparent w-full max-w-50
|
2022-12-30 22:01:46 +00:00
|
|
|
spellcheck="false"
|
|
|
|
autocorrect="off"
|
|
|
|
autocomplete="off"
|
2022-12-13 23:12:16 +00:00
|
|
|
@input="handleInput"
|
2022-12-24 01:06:46 +00:00
|
|
|
@keydown.down="move(1)"
|
|
|
|
@keydown.up="move(-1)"
|
2022-12-24 00:40:51 +00:00
|
|
|
@keydown.enter="onEnter"
|
2023-01-01 15:12:02 +00:00
|
|
|
@keydown.esc.prevent="escapeAutocomplete"
|
2022-12-24 11:42:54 +00:00
|
|
|
@focus="autocompleteShow = true"
|
2022-12-24 00:40:51 +00:00
|
|
|
>
|
|
|
|
<div
|
2022-12-24 11:42:54 +00:00
|
|
|
v-if="autocompleteShow && filteredServers.length"
|
2022-12-24 00:40:51 +00:00
|
|
|
absolute left-6em right-0 top="100%"
|
|
|
|
bg-base rounded border="~ base"
|
2022-12-28 14:56:08 +00:00
|
|
|
z-10 shadow of-auto
|
|
|
|
overflow-y-auto
|
|
|
|
class="max-h-[8rem]"
|
2022-12-13 23:12:16 +00:00
|
|
|
>
|
2022-12-28 14:56:08 +00:00
|
|
|
<button
|
2023-01-01 14:29:11 +00:00
|
|
|
v-for="(name, idx) in filteredServers"
|
2022-12-28 14:56:08 +00:00
|
|
|
:id="toSelector(name)"
|
2022-12-28 01:06:54 +00:00
|
|
|
:key="name"
|
|
|
|
:value="name"
|
2022-12-28 14:56:08 +00:00
|
|
|
px-2 py1 font-mono w-full text-left
|
2022-12-24 11:42:54 +00:00
|
|
|
:class="autocompleteIndex === idx ? 'text-primary font-bold' : null"
|
2022-12-28 14:56:08 +00:00
|
|
|
@click="select(idx)"
|
2022-12-24 00:40:51 +00:00
|
|
|
>
|
2022-12-28 01:06:54 +00:00
|
|
|
{{ name }}
|
2022-12-28 14:56:08 +00:00
|
|
|
</button>
|
2022-12-24 00:40:51 +00:00
|
|
|
</div>
|
2022-12-13 23:12:16 +00:00
|
|
|
</div>
|
|
|
|
<div min-h-4>
|
|
|
|
<Transition css enter-active-class="animate animate-fade-in">
|
|
|
|
<p v-if="displayError" role="alert" p-0 m-0 text-xs text-red-600 dark:text-red-400>
|
|
|
|
{{ $t('error.sign_in_error') }}
|
|
|
|
</p>
|
|
|
|
</Transition>
|
|
|
|
</div>
|
2022-11-23 02:53:22 +00:00
|
|
|
</div>
|
2022-11-30 01:45:52 +00:00
|
|
|
<div text-secondary text-sm flex>
|
2023-01-01 14:29:11 +00:00
|
|
|
<div i-ri:lightbulb-line me-1 />
|
2022-11-30 01:45:52 +00:00
|
|
|
<span>
|
|
|
|
<i18n-t keypath="user.tip_no_account">
|
2023-01-12 18:03:39 +00:00
|
|
|
<NuxtLink href="https://joinmastodon.org/servers" target="_blank" external class="text-primary" hover="underline">{{ $t('user.tip_register_account') }}</NuxtLink>
|
2022-11-30 01:45:52 +00:00
|
|
|
</i18n-t>
|
|
|
|
</span>
|
2022-11-23 03:06:56 +00:00
|
|
|
</div>
|
2022-12-13 23:12:16 +00:00
|
|
|
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
|
2023-01-13 16:00:32 +00:00
|
|
|
<span v-if="busy" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip">
|
|
|
|
<span block i-ri:loader-2-fill aria-hidden="true" />
|
|
|
|
</span>
|
|
|
|
<span v-else aria-hidden="true" block i-ri:login-circle-line class="rtl-flip" />
|
2022-11-29 10:55:28 +00:00
|
|
|
{{ $t('action.sign_in') }}
|
2022-11-23 03:06:56 +00:00
|
|
|
</button>
|
2022-11-26 18:11:57 +00:00
|
|
|
</form>
|
2022-11-23 02:53:22 +00:00
|
|
|
</template>
|