Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ added password field #38

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (import.meta.server && !route.path.startsWith('/settings')) {
}

// We want to trigger rerendering the page when account changes
const key = computed(() => `${currentUser.value?.server ?? currentServer.value}:${currentUser.value?.account.id || ''}`)
const key = computed(() => currentUserDid.value)
</script>

<template>
Expand Down
4 changes: 2 additions & 2 deletions components/nav/NavUser.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
const { busy, oauth, singleInstanceServer } = useSignIn()
const { busy, signIn, singleInstanceServer } = useSignIn()
</script>

<template>
Expand All @@ -24,7 +24,7 @@ const { busy, oauth, singleInstanceServer } = useSignIn()
flex="~ row"
gap-x-1 items-center justify-center btn-solid text-sm px-2 py-1 xl:hidden
:disabled="busy"
@click="oauth()"
@click="signIn()"
>
<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" />
Expand Down
4 changes: 2 additions & 2 deletions components/user/UserPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const all = useUsers()
const router = useRouter()

function clickUser(user: UserLogin) {
if (user.account.acct === currentUser.value?.account.acct)
if (user.did === currentUser.value?.did)
router.push(getAccountRoute(user.account))
else
switchUser(user)
Expand All @@ -21,7 +21,7 @@ function clickUser(user: UserLogin) {
flex rounded
cursor-pointer
:aria-label="$t('action.switch_account')"
:class="user.account.acct === currentUser?.account.acct ? '' : 'op25 grayscale'"
:class="user.did === currentUser?.did ? '' : 'op25 grayscale'"
hover="filter-none op100"
@click="clickUser(user)"
>
Expand Down
173 changes: 39 additions & 134 deletions components/user/UserSignIn.vue
Original file line number Diff line number Diff line change
@@ -1,113 +1,28 @@
<script setup lang="ts">
import Fuse from 'fuse.js'

const input = ref<HTMLInputElement | undefined>()
const knownServers = ref<string[]>([])
const autocompleteIndex = ref(0)
const autocompleteShow = ref(false)

const { busy, error, displayError, server, oauth } = useSignIn(input)

const fuse = shallowRef(new Fuse([] as string[]))

const filteredServers = computed(() => {
if (!server.value)
return []

const results = fuse.value.search(server.value, { limit: 6 }).map(result => result.item)
if (results[0] === server.value)
return []

return results
})

function isValidUrl(str: string) {
try {
// eslint-disable-next-line no-new
new URL(str)
return true
}
catch {
return false
}
}
const { busy, error, displayError, handle, signIn, user_password } = useSignIn(input)

async function handleInput() {
const input = server.value.trim()
if (input.startsWith('https://'))
server.value = input.replace('https://', '')

if (input.length)
const user_handle = handle.value.trim()
const user_password = handle.value.trim()
if (user_handle.length && user_password.length)
displayError.value = false

if (
isValidUrl(`https://${input}`)
&& input.match(/^[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/i)
// Do not hide the autocomplete if a result has an exact substring match on the input
&& !filteredServers.value.some(s => s.includes(input))
) {
autocompleteShow.value = false
}

else {
autocompleteShow.value = true
}
}

function toSelector(server: string) {
return server.replace(/[^\w-]/g, '-')
}
function move(delta: number) {
if (filteredServers.value.length === 0) {
autocompleteIndex.value = 0
return
}
autocompleteIndex.value = ((autocompleteIndex.value + delta) + filteredServers.value.length) % filteredServers.value.length
document.querySelector(`#${toSelector(filteredServers.value[autocompleteIndex.value])}`)?.scrollIntoView(false)
}

function onEnter(e: KeyboardEvent) {
if (autocompleteShow.value === true && filteredServers.value[autocompleteIndex.value]) {
server.value = filteredServers.value[autocompleteIndex.value]
e.preventDefault()
autocompleteShow.value = false
}
}

function escapeAutocomplete(evt: KeyboardEvent) {
if (!autocompleteShow.value)
return
autocompleteShow.value = false
evt.stopPropagation()
}

function select(index: number) {
server.value = filteredServers.value[index]
}

onMounted(async () => {
input?.value?.focus()
knownServers.value = await (globalThis.$fetch as any)('/api/list-servers')
fuse.value = new Fuse(knownServers.value, { shouldSort: true })
})

onClickOutside(input, () => {
autocompleteShow.value = false
})
</script>

<template>
<form text-center justify-center items-center max-w-150 py6 flex="~ col gap-3" @submit.prevent="oauth">
<form text-center justify-center items-center max-w-150 py6 flex="~ col gap-3" @submit.prevent="signIn">
<div flex="~ center" items-end mb2 gap-x-2>
<img :src="`/${''}logo.svg`" w-12 h-12 mxa height="48" width="48" :alt="$t('app_logo')" class="rtl-flip">
<NavLogo />
<div text-3xl>
{{ $t('action.sign_in') }}
</div>
</div>
<div>
{{ $t('user.server_address_label') }}
</div>
<div :class="error ? 'animate animate-shake-x animate-delay-100' : null">
<div flex flex-col gap-5 pt-5 :class="error ? 'animate animate-shake-x animate-delay-100' : null">
<div
dir="ltr"
flex bg-gray:10 px4 py2 mxa rounded
Expand All @@ -116,62 +31,52 @@ onClickOutside(input, () => {
relative
:class="displayError ? 'border-red-600 dark:border-red-400' : null"
>
<span text-secondary-light me1>https://</span>

<input
ref="input"
v-model="server"
v-model="handle"
autocapitalize="off"
:placeholder="$t('user.handle_label')"
inputmode="url"
outline-none bg-transparent w-full max-w-50
spellcheck="false"
autocorrect="off"
autocomplete="off"
@input="handleInput"
@keydown.down="move(1)"
@keydown.up="move(-1)"
@keydown.enter="onEnter"
@keydown.esc.prevent="escapeAutocomplete"
@focus="autocompleteShow = true"
>
<div
v-if="autocompleteShow && filteredServers.length"
absolute left-6em right-0 top="100%"
bg-base rounded border="~ base"
z-10 shadow of-auto
overflow-y-auto
class="max-h-[8rem]"
>
<button
v-for="(name, idx) in filteredServers"
:id="toSelector(name)"
:key="name"
:value="name"
px-2 py1 font-mono w-full text-left
:class="autocompleteIndex === idx ? 'text-primary font-bold' : null"
@click="select(idx)"
>
{{ name }}
</button>
</div>
</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
dir="ltr"
flex bg-gray:10 px4 py2 mxa rounded
border="~ base" items-center font-mono
focus:outline-none focus:ring="2 primary inset"
relative
:class="displayError ? 'border-red-600 dark:border-red-400' : null"
>
<input
ref="input"
v-model="user_password"
autocapitalize="off"
inputmode="text"
type="password"
:placeholder="$t('user.user_password_label')"

outline-none bg-transparent w-full max-w-50
spellcheck="false"
autocorrect="off"
autocomplete="off"
@input="handleInput"
>
</div>
</div>
<div text-secondary text-sm flex>
<div i-ri:lightbulb-line me-1 />
<span>
<i18n-t keypath="user.tip_no_account">
<NuxtLink href="https://joinmastodon.org/servers" target="_blank" external class="text-primary" hover="underline">{{ $t('user.tip_register_account') }}</NuxtLink>
</i18n-t>
</span>

<div min-h-4>
<Transition v-if="error" 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>
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!handle || !user_password || busy">
<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>
Expand Down
4 changes: 2 additions & 2 deletions components/user/UserSignInEntry.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
const { busy, oauth, singleInstanceServer } = useSignIn()
const { busy, signIn, singleInstanceServer } = useSignIn()
</script>

<template>
Expand All @@ -16,7 +16,7 @@ const { busy, oauth, singleInstanceServer } = useSignIn()
v-if="singleInstanceServer"
flex="~ row" gap-x-2 items-center justify-center btn-solid text-center rounded-3
:disabled="busy"
@click="oauth()"
@click="signIn()"
>
<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" />
Expand Down
10 changes: 5 additions & 5 deletions components/user/UserSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ const emit = defineEmits<{
}>()

const all = useUsers()
const { singleInstanceServer, oauth } = useSignIn()
const { singleInstanceServer, signIn } = useSignIn()

const sorted = computed(() => {
return [
currentUser.value!,
...all.value.filter(account => account.token !== currentUser.value?.token),
...all.value.filter(account => account.did !== currentUser.value?.did),
].filter(Boolean)
})

const router = useRouter()
function clickUser(user: UserLogin) {
if (user.account.id === currentUser.value?.account.id)
if (user.did === currentUser.value?.did)
router.push(getAccountRoute(user.account))
else
switchUser(user)
}
function processSignIn() {
if (singleInstanceServer)
oauth()
signIn()
else
openSigninDialog()
}
Expand All @@ -41,7 +41,7 @@ function processSignIn() {
>
<AccountInfo :account="user.account" :hover-card="false" square />
<div flex-auto />
<div v-if="user.token === currentUser?.token" i-ri:check-line text-primary mya text-2xl />
<div v-if="user.did === currentUser?.did" i-ri:check-line text-primary mya text-2xl />
</button>
</template>
<div border="t base" pt2>
Expand Down
8 changes: 4 additions & 4 deletions composables/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export function provideGlobalCommands() {
const masto = useMasto()
const colorMode = useColorMode()
const userSettings = useUserSettings()
const { singleInstanceServer, oauth } = useSignIn()
const { singleInstanceServer, signIn } = useSignIn()

useCommand({
scope: 'Preferences',
Expand Down Expand Up @@ -305,7 +305,7 @@ export function provideGlobalCommands() {

onActivate() {
if (singleInstanceServer)
oauth()
signIn()
else
openSigninDialog()
},
Expand All @@ -328,13 +328,13 @@ export function provideGlobalCommands() {
parent: 'account-switch',
scope: 'Switch account',

visible: () => user.account.id !== currentUser.value?.account.id,
visible: () => user.did !== currentUser.value?.did,

name: () => t('command.switch_account', [getFullHandle(user.account)]),
icon: 'i-ri:user-shared-line',

onActivate() {
loginTo(masto, user)
loginTo(masto, user.did)
},
})))
useCommand({
Expand Down
Loading
Loading