forked from Mirrors/elk
feat: wip
This commit is contained in:
parent
1816530f84
commit
f2d125ed4e
14 changed files with 64 additions and 40 deletions
2
app.vue
2
app.vue
|
@ -3,7 +3,7 @@ setupPageHeader()
|
||||||
provideGlobalCommands()
|
provideGlobalCommands()
|
||||||
|
|
||||||
// We want to trigger rerendering the page when account changes
|
// 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(() => `${currentServer.value}:${isGuest.value ? '[anonymous]' : currentUser.value!.account!.id || ''}`)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -41,8 +41,8 @@ onMastoInit(async () => {
|
||||||
|
|
||||||
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items
|
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items
|
||||||
// when we know there is no user.
|
// when we know there is no user.
|
||||||
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value))
|
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && isGuest.value))
|
||||||
const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && !currentUser.value)
|
const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && isGuest.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
<VDropdown v-if="isMastoInitialised && currentUser" sm:hidden>
|
<VDropdown v-if="isMastoInitialised && currentUser" sm:hidden>
|
||||||
<div style="-webkit-touch-callout: none;">
|
<div style="-webkit-touch-callout: none;">
|
||||||
<AccountAvatar
|
<AccountAvatar
|
||||||
|
v-if="!currentUser.guest"
|
||||||
ref="avatar"
|
ref="avatar"
|
||||||
:account="currentUser.account"
|
:account="currentUser.account"
|
||||||
h-8
|
h-8
|
||||||
w-8
|
w-8
|
||||||
:draggable="false"
|
:draggable="false"
|
||||||
/>
|
/>
|
||||||
|
<div v-else>
|
||||||
|
TODO: Guest
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #popper="{ hide }">
|
<template #popper="{ hide }">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const disabled = computed(() => !isMastoInitialised.value || !currentUser.value)
|
const disabled = computed(() => !isMastoInitialised.value || isGuest.value)
|
||||||
const disabledVisual = computed(() => isMastoInitialised.value && !currentUser.value)
|
const disabledVisual = computed(() => isMastoInitialised.value && isGuest.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -181,7 +181,7 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isMastoInitialised && currentUser" flex="~ col gap-4" py3 px2 sm:px4>
|
<div v-if="isMastoInitialised && currentUser && currentUser.account && !isGuest" flex="~ col gap-4" py3 px2 sm:px4>
|
||||||
<template v-if="draft.editingStatus">
|
<template v-if="draft.editingStatus">
|
||||||
<div flex="~ col gap-1">
|
<div flex="~ col gap-1">
|
||||||
<div id="state-editing" text-secondary self-center>
|
<div id="state-editing" text-secondary self-center>
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// workaround: cannot use TSNonNull `!` in template
|
||||||
|
const account = $computed(() => currentUser.value!.account!)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDropdown :distance="0" placement="top-start">
|
<VDropdown :distance="0" placement="top-start">
|
||||||
<button btn-action-icon :aria-label="$t('action.switch_account')">
|
<button btn-action-icon :aria-label="$t('action.switch_account')">
|
||||||
<div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line />
|
<div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line />
|
||||||
<AccountAvatar v-if="currentUser" xl:hidden :account="currentUser.account" w-9 h-9 />
|
<AccountAvatar v-if="!isGuest" xl:hidden :account="account" w-9 h-9 />
|
||||||
|
<span v-else>TODO: Guest</span>
|
||||||
</button>
|
</button>
|
||||||
<template #popper="{ hide }">
|
<template #popper="{ hide }">
|
||||||
<UserSwitcher @click="hide" />
|
<UserSwitcher @click="hide" />
|
||||||
|
|
|
@ -6,7 +6,7 @@ const all = useUsers()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const masto = useMasto()
|
const masto = useMasto()
|
||||||
const switchUser = (user: UserLogin) => {
|
const switchUser = (user: UserLogin) => {
|
||||||
if (!user.guest && !currentUser.value?.guest && user.account.id === currentUser.value?.account.id)
|
if (!user.guest && !isGuest.value && user.account.id === currentUser.value!.account!.id)
|
||||||
router.push(getAccountRoute(user.account))
|
router.push(getAccountRoute(user.account))
|
||||||
else
|
else
|
||||||
masto.loginTo(user)
|
masto.loginTo(user)
|
||||||
|
@ -25,7 +25,8 @@ const switchUser = (user: UserLogin) => {
|
||||||
hover="filter-none op100"
|
hover="filter-none op100"
|
||||||
@click="switchUser(user)"
|
@click="switchUser(user)"
|
||||||
>
|
>
|
||||||
<AccountAvatar w-13 h-13 :account="user.account" />
|
<AccountAvatar v-if="!user.guest" w-13 h-13 :account="user.account" />
|
||||||
|
<span v-else>TODO: Guest</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@ export interface PushManagerSubscriptionInfo {
|
||||||
subscription: PushSubscription | null
|
subscription: PushSubscription | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequiredUserLogin extends Required<Omit<UserLogin, 'account' | 'pushSubscription'>> {
|
export interface RequiredUserLogin extends Required<Pick<UserLogin, 'server' | 'token' | 'vapidKey'>> {
|
||||||
pushSubscription?: MastoPushSubscription
|
pushSubscription?: MastoPushSubscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import type {
|
||||||
SubscriptionResult,
|
SubscriptionResult,
|
||||||
} from '~/composables/push-notifications/types'
|
} from '~/composables/push-notifications/types'
|
||||||
import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants'
|
import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants'
|
||||||
|
import type { UserLogin } from '~/types'
|
||||||
|
|
||||||
const supportsPushNotifications = typeof window !== 'undefined'
|
const supportsPushNotifications = typeof window !== 'undefined'
|
||||||
&& 'serviceWorker' in navigator
|
&& 'serviceWorker' in navigator
|
||||||
|
@ -68,10 +69,10 @@ export const usePushManager = () => {
|
||||||
if (!isSupported)
|
if (!isSupported)
|
||||||
return 'not-supported'
|
return 'not-supported'
|
||||||
|
|
||||||
if (!currentUser.value)
|
if (isGuest.value)
|
||||||
return 'no-user'
|
return 'no-user'
|
||||||
|
|
||||||
const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value
|
const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value as UserLogin<true>
|
||||||
|
|
||||||
if (!token || !server || !vapidKey)
|
if (!token || !server || !vapidKey)
|
||||||
return 'invalid-vapid-key'
|
return 'invalid-vapid-key'
|
||||||
|
@ -86,10 +87,8 @@ export const usePushManager = () => {
|
||||||
return 'notification-denied'
|
return 'notification-denied'
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUser.value.pushSubscription = await createPushSubscription(
|
currentUser.value!.pushSubscription = await createPushSubscription(
|
||||||
{
|
{ pushSubscription, server, token, vapidKey },
|
||||||
pushSubscription, server, token, vapidKey,
|
|
||||||
},
|
|
||||||
notificationData ?? {
|
notificationData ?? {
|
||||||
alerts: {
|
alerts: {
|
||||||
follow: true,
|
follow: true,
|
||||||
|
@ -110,7 +109,7 @@ export const usePushManager = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = async () => {
|
const unsubscribe = async () => {
|
||||||
if (!isSupported || !isSubscribed || !currentUser.value)
|
if (!isSupported || !isSubscribed || !checkUser(currentUser.value))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
await removePushNotifications(currentUser.value)
|
await removePushNotifications(currentUser.value)
|
||||||
|
|
|
@ -43,28 +43,30 @@ const initializeUsers = async (): Promise<Ref<UserLogin[]> | RemovableRef<UserLo
|
||||||
const users = await initializeUsers()
|
const users = await initializeUsers()
|
||||||
const instances = useLocalStorage<Record<string, Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
const instances = useLocalStorage<Record<string, Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||||
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
||||||
|
const isGuestId = computed(() => currentUserId.value.startsWith('[anonymous]@'))
|
||||||
|
|
||||||
export const currentUser = computed<UserLogin | undefined>(() => {
|
export const currentUser = computed<UserLogin | undefined>(() => {
|
||||||
if (!currentUserId.value)
|
if (!currentUserId.value)
|
||||||
// Fallback to the first account
|
// Fallback to the first account
|
||||||
return users.value[0]
|
return users.value[0]
|
||||||
|
|
||||||
const user = users.value.find(user => user.account?.id === currentUserId.value)
|
if (isGuestId.value) {
|
||||||
if (user)
|
const server = currentUserId.value.replace('[anonymous]@', '')
|
||||||
return user
|
return users.value.find(user => user.guest && user.server === server)
|
||||||
})
|
}
|
||||||
|
|
||||||
export const isGuest = computed(
|
return users.value.find(user => user.account?.id === currentUserId.value)
|
||||||
() => currentUserId.value.startsWith('[anonymous]@') || !currentUser.value?.account?.acct,
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER)
|
export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER)
|
||||||
export const currentInstance = computed<null | Instance>(() => {
|
export const currentInstance = computed<null | Instance>(() => {
|
||||||
return instances.value[currentServer.value] ?? null
|
return instances.value[currentServer.value] ?? null
|
||||||
})
|
})
|
||||||
|
export const checkUser = (val: UserLogin | undefined): val is UserLogin<true> => !!(val && !val.guest)
|
||||||
|
export const isGuest = computed(() => !checkUser(currentUser.value))
|
||||||
|
|
||||||
export const currentUserHandle = computed(() =>
|
export const currentUserHandle = computed(() =>
|
||||||
isGuest.value ? '[anonymous]' : currentUser.value!.account!.acct
|
isGuestId.value ? '[anonymous]' : currentUser.value!.account!.acct
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@ export const characterLimit = computed(() => currentInstance.value?.configuratio
|
||||||
async function loginTo(user?: UserLogin) {
|
async function loginTo(user?: UserLogin) {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const server = user?.server || (route.params.server as string)
|
const server = user?.server || (route.params.server as string) || DEFAULT_SERVER
|
||||||
const masto = await loginMasto({
|
const masto = await loginMasto({
|
||||||
url: `https://${server}`,
|
url: `https://${server}`,
|
||||||
accessToken: user?.token,
|
accessToken: user?.token,
|
||||||
|
@ -87,9 +89,11 @@ async function loginTo(user?: UserLogin) {
|
||||||
if (!user?.token) {
|
if (!user?.token) {
|
||||||
const instance = await masto.instances.fetch()
|
const instance = await masto.instances.fetch()
|
||||||
|
|
||||||
currentUserId.value = `[anonymous]@${server}`
|
|
||||||
instances.value[server] = instance
|
instances.value[server] = instance
|
||||||
|
if (!users.value.some(u => u.server === server && u.guest))
|
||||||
users.value.push({ server, guest: true })
|
users.value.push({ server, guest: true })
|
||||||
|
|
||||||
|
currentUserId.value = `[anonymous]@${server}`
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -183,7 +187,7 @@ export async function removePushNotificationData(user: UserLogin, fromSWPushMana
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePushNotifications(user: UserLogin) {
|
export async function removePushNotifications(user: UserLogin<true>) {
|
||||||
if (!user.pushSubscription)
|
if (!user.pushSubscription)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -213,9 +217,10 @@ export async function signout() {
|
||||||
if (!users.value.some((u, i) => u.server === currentUser.value!.server && i !== index))
|
if (!users.value.some((u, i) => u.server === currentUser.value!.server && i !== index))
|
||||||
delete instances.value[currentUser.value.server]
|
delete instances.value[currentUser.value.server]
|
||||||
|
|
||||||
|
if (checkUser(currentUser.value)) {
|
||||||
await removePushNotifications(currentUser.value)
|
await removePushNotifications(currentUser.value)
|
||||||
|
|
||||||
await removePushNotificationData(currentUser.value)
|
await removePushNotificationData(currentUser.value)
|
||||||
|
}
|
||||||
|
|
||||||
currentUserId.value = ''
|
currentUserId.value = ''
|
||||||
// Remove the current user from the users
|
// Remove the current user from the users
|
||||||
|
@ -234,7 +239,7 @@ export async function signout() {
|
||||||
const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, number]>>({})
|
const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, number]>>({})
|
||||||
|
|
||||||
export const useNotifications = () => {
|
export const useNotifications = () => {
|
||||||
const id = currentUser.value?.account!.id
|
const id = $computed(() => currentUser.value?.account?.id)
|
||||||
const masto = useMasto()
|
const masto = useMasto()
|
||||||
|
|
||||||
const clearNotifications = () => {
|
const clearNotifications = () => {
|
||||||
|
@ -292,7 +297,7 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
|
||||||
const all = storages.get(key) as Ref<Record<string, T>>
|
const all = storages.get(key) as Ref<Record<string, T>>
|
||||||
|
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
const id = isGuest.value
|
const id = isGuestId.value
|
||||||
? '[anonymous]'
|
? '[anonymous]'
|
||||||
: currentUser.value!.account!.acct
|
: currentUser.value!.account!.acct
|
||||||
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
||||||
|
|
|
@ -5,7 +5,7 @@ export default defineNuxtRouteMiddleware((to) => {
|
||||||
return
|
return
|
||||||
|
|
||||||
onMastoInit(() => {
|
onMastoInit(() => {
|
||||||
if (!currentUser.value)
|
if (isGuest.value)
|
||||||
return navigateTo(`/${currentServer.value}/public`)
|
return navigateTo(`/${currentServer.value}/public`)
|
||||||
if (to.path === '/')
|
if (to.path === '/')
|
||||||
return navigateTo('/home')
|
return navigateTo('/home')
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { invoke } from '@vueuse/shared'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const tabs = $computed(() => [
|
const tabs = $computed(() => [
|
||||||
|
@ -20,7 +18,7 @@ const tabs = $computed(() => [
|
||||||
{
|
{
|
||||||
to: `/${currentServer.value}/explore/users`,
|
to: `/${currentServer.value}/explore/users`,
|
||||||
display: t('tab.for_you'),
|
display: t('tab.for_you'),
|
||||||
disabled: !isMastoInitialised.value || !currentUser.value,
|
disabled: !isMastoInitialised.value || isGuest.value,
|
||||||
},
|
},
|
||||||
] as const)
|
] as const)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||||
server: query.server,
|
server: query.server,
|
||||||
token: query.token,
|
token: query.token,
|
||||||
vapidKey: typeof query.vapid_key === 'string' ? query.vapid_key : undefined,
|
vapidKey: typeof query.vapid_key === 'string' ? query.vapid_key : undefined,
|
||||||
|
guest: false,
|
||||||
}
|
}
|
||||||
: currentUser.value
|
: currentUser.value
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,22 @@ export interface AppInfo {
|
||||||
vapid_key: string
|
vapid_key: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserLogin = {
|
interface UserLoginWithToken {
|
||||||
|
account: AccountCredentials
|
||||||
|
guest: false
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserLoginGuest {
|
||||||
|
account?: undefined
|
||||||
|
guest: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserLogin<WithToken extends boolean = boolean> = {
|
||||||
server: string
|
server: string
|
||||||
token?: string
|
token?: string
|
||||||
vapidKey?: string
|
vapidKey?: string
|
||||||
pushSubscription?: PushSubscription
|
pushSubscription?: PushSubscription
|
||||||
} & ({ account: AccountCredentials; guest: false } | { account?: undefined; guest: true })
|
} & ((WithToken extends false ? UserLoginGuest : never) | (WithToken extends true ? UserLoginWithToken : never))
|
||||||
|
|
||||||
export interface ElkMasto extends MastoClient {
|
export interface ElkMasto extends MastoClient {
|
||||||
loginTo(user?: UserLogin): Promise<MastoClient>
|
loginTo(user?: UserLogin): Promise<MastoClient>
|
||||||
|
|
Loading…
Reference in a new issue