-
-
Notifications
You must be signed in to change notification settings - Fork 20
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: use oauth for login #29
base: main
Are you sure you want to change the base?
Changes from all commits
2ba759e
af694e3
16e0c51
632cce9
87c0669
27e57eb
073cf98
50daf06
946e5e4
76af988
4d97151
9af083f
813b322
baf2450
765bcd4
891cabe
7f0e1cc
aa3f470
4f82b72
f2ed549
d590e14
e3607b8
88a17fb
bc0651c
3e406e7
2c19a2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review note: renamed oauth to signIn to not get confused with oauth-callback |
||
</script> | ||
|
||
<template> | ||
|
@@ -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" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,111 +1,30 @@ | ||
<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 } = useSignIn(input) | ||
|
||
async function handleInput() { | ||
const input = server.value.trim() | ||
if (input.startsWith('https://')) | ||
server.value = input.replace('https://', '') | ||
const input = handle.value.trim() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review note: removed quite some code here as you don't need to select a server with bsky as there is an integrated discovery system for a handle / did. |
||
|
||
if (input.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"> | ||
<div text-3xl> | ||
{{ $t('action.sign_in') }} | ||
</div> | ||
</div> | ||
<div> | ||
{{ $t('user.server_address_label') }} | ||
{{ $t('user.handle_label') }} | ||
</div> | ||
<div :class="error ? 'animate animate-shake-x animate-delay-100' : null"> | ||
<div | ||
|
@@ -116,44 +35,17 @@ 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" | ||
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"> | ||
|
@@ -163,15 +55,7 @@ onClickOutside(input, () => { | |
</Transition> | ||
</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> | ||
<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 || 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> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,17 @@ | ||
import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs' | ||
import type { mastodon } from 'masto' | ||
|
||
export function getDisplayName(account: mastodon.v1.Account, options?: { rich?: boolean }) { | ||
// TODO: remove once mastondon support was replaced | ||
export type Account = mastodon.v1.Account | ProfileViewDetailed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review note: to avoid too many changes I changed most of these functions to accept bsky and mastodon accounts for now. |
||
|
||
export function isBsykAccount(account: Account): account is ProfileViewDetailed { | ||
return !!(account as ProfileViewDetailed).did | ||
} | ||
|
||
export function getDisplayName(account: Account, options?: { rich?: boolean }) { | ||
if (isBsykAccount(account)) | ||
return account.displayName | ||
|
||
const displayName = account.displayName || account.username || account.acct || '' | ||
if (options?.rich) | ||
return displayName | ||
|
@@ -11,20 +22,29 @@ export function accountToShortHandle(acct: string) { | |
return `@${acct.includes('@') ? acct.split('@')[0] : acct}` | ||
} | ||
|
||
export function getShortHandle({ acct }: mastodon.v1.Account) { | ||
if (!acct) | ||
export function getShortHandle(account: Account) { | ||
if (isBsykAccount(account)) | ||
return account.handle | ||
|
||
if (!account.acct) | ||
return '' | ||
return accountToShortHandle(acct) | ||
return accountToShortHandle(account.acct) | ||
} | ||
|
||
export function getServerName(account: mastodon.v1.Account) { | ||
export function getServerName(account: Account) { | ||
if (isBsykAccount(account)) | ||
return '' | ||
|
||
if (account.acct?.includes('@')) | ||
return account.acct.split('@')[1] | ||
// We should only lack the server name if we're on the same server as the account | ||
return currentInstance.value ? getInstanceDomain(currentInstance.value) : '' | ||
} | ||
|
||
export function getFullHandle(account: mastodon.v1.Account) { | ||
export function getFullHandle(account: Account) { | ||
if (isBsykAccount(account)) | ||
return account.handle | ||
|
||
const handle = `@${account.acct}` | ||
if (!currentUser.value || account.acct.includes('@')) | ||
return handle | ||
|
@@ -40,7 +60,10 @@ export function toShortHandle(fullHandle: string) { | |
return fullHandle | ||
} | ||
|
||
export function extractAccountHandle(account: mastodon.v1.Account) { | ||
export function extractAccountHandle(account: Account) { | ||
if (isBsykAccount(account)) | ||
return account.handle | ||
|
||
let handle = getFullHandle(account).slice(1) | ||
const uri = currentInstance.value ? getInstanceDomain(currentInstance.value) : currentServer.value | ||
if (currentInstance.value && handle.endsWith(`@${uri}`)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review note: did is the globally unique id of a bsyk account.