forked from Mirrors/elk
feat: wip
This commit is contained in:
parent
f7df0e54f5
commit
313cafa23c
18 changed files with 146 additions and 129 deletions
4
app.vue
4
app.vue
|
@ -3,9 +3,7 @@ setupPageHeader()
|
|||
provideGlobalCommands()
|
||||
|
||||
// We want to trigger rerendering the page when account changes
|
||||
const key = computed(() =>
|
||||
`${currentServer.value}:${checkUser(currentUser.value) ? currentUser.value.account.id : GUEST_ID}`,
|
||||
)
|
||||
const key = computed(() => currentUser.value ? getUniqueUserId(currentUser.value) : '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
18
components/account/AccountGuest.vue
Normal file
18
components/account/AccountGuest.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import type { UserLogin } from '~/types'
|
||||
|
||||
defineProps<{
|
||||
user: UserLogin
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~ gap3" items-center>
|
||||
<div bg="gray/40" rounded-full w-54px h-54px flex shrink-0 items-center justify-center text-5>
|
||||
G
|
||||
</div>
|
||||
<div>
|
||||
Guest from <span font-bold>{{ user.server }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -7,7 +7,7 @@ const { account } = defineProps<{
|
|||
}>()
|
||||
let relationship = $(useRelationship(account))
|
||||
|
||||
const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
||||
const isSelf = $computed(() => checkUser(currentUser.value) && currentUser.value.account.id === account.id)
|
||||
|
||||
const masto = useMasto()
|
||||
const toggleMute = async () => {
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
w-8
|
||||
:draggable="false"
|
||||
/>
|
||||
<div v-else>
|
||||
TODO: Guest
|
||||
<div v-else bg="gray/40" rounded-full w-8 h-8 flex items-center justify-center text-5>
|
||||
G
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().accounts.iterateStatuses(currentUser.value!.account.id, { pinned: true })
|
||||
const paginator = useMasto().accounts.iterateStatuses(currentUser.value!.account!.id, { pinned: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -16,7 +16,9 @@ const masto = useMasto()
|
|||
@click="switchUser(user, masto)"
|
||||
>
|
||||
<AccountAvatar v-if="!user.guest" w-13 h-13 :account="user.account" />
|
||||
<span v-else>TODO: Guest from {{ user.server }}</span>
|
||||
<div v-else bg="gray/40" rounded-full w-13 h-13 flex shrink-0 items-center justify-center text-5>
|
||||
G
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -48,7 +48,7 @@ async function oauth() {
|
|||
}
|
||||
|
||||
function explore() {
|
||||
masto.loginTo({ server, guest: true })
|
||||
masto.loginTo({ server, guestOnly: true })
|
||||
}
|
||||
|
||||
async function handleInput() {
|
||||
|
|
|
@ -22,7 +22,7 @@ const masto = useMasto()
|
|||
@click="switchUser(user, masto)"
|
||||
>
|
||||
<AccountInfo v-if="!user.guest" :account="user.account" :hover-card="false" />
|
||||
<span v-else>TODO: Guest from {{ user.server }}</span>
|
||||
<AccountGuest v-else :user="user" />
|
||||
<div flex-auto />
|
||||
<div v-if="isSameUser(user, currentUser)" i-ri:check-line text-primary mya text-2xl />
|
||||
</button>
|
||||
|
@ -34,7 +34,7 @@ const masto = useMasto()
|
|||
@click="openSigninDialog"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-if="isMastoInitialised && currentUser"
|
||||
v-if="isMastoInitialised && canSignOut"
|
||||
:text="$t('user.sign_out_account', [getFullHandle(currentUser)])"
|
||||
icon="i-ri:logout-box-line rtl-flip"
|
||||
@click="signout"
|
||||
|
|
|
@ -248,7 +248,7 @@ export const provideGlobalCommands = () => {
|
|||
useCommand({
|
||||
scope: 'Actions',
|
||||
|
||||
visible: () => currentUser.value,
|
||||
visible: () => !isGuest.value,
|
||||
|
||||
name: () => t('action.compose'),
|
||||
icon: 'i-ri:quill-pen-line',
|
||||
|
@ -344,9 +344,9 @@ export const provideGlobalCommands = () => {
|
|||
parent: 'account-switch',
|
||||
scope: 'Switch account',
|
||||
|
||||
visible: () => !user.guest && user.account.id !== currentUser.value?.account?.id,
|
||||
visible: () => !isSameUser(user, currentUser.value),
|
||||
|
||||
name: () => t('command.switch_account', [getFullHandle(user)]),
|
||||
name: () => t('command.switch_account', [user.guest ? `guest from ${user.server}` : getFullHandle(user)]),
|
||||
icon: 'i-ri:user-shared-line',
|
||||
|
||||
onActivate() {
|
||||
|
@ -356,8 +356,8 @@ export const provideGlobalCommands = () => {
|
|||
useCommand({
|
||||
scope: 'Account',
|
||||
|
||||
visible: () => currentUser.value,
|
||||
name: () => currentUser.value ? t('user.sign_out_account', [getFullHandle(currentUser.value)]) : '',
|
||||
visible: () => canSignOut.value,
|
||||
name: () => t('user.sign_out_account', [getFullHandle(currentUser.value)]),
|
||||
icon: 'i-ri:logout-box-line',
|
||||
|
||||
onActivate() {
|
||||
|
|
|
@ -97,7 +97,7 @@ async function subscribe(
|
|||
|
||||
async function unsubscribeFromBackend(fromSWPushManager: boolean, removePushNotification = true) {
|
||||
const cu = currentUser.value
|
||||
if (cu) {
|
||||
if (checkUser(cu)) {
|
||||
await removePushNotifications(cu)
|
||||
removePushNotification && await removePushNotificationData(cu, fromSWPushManager)
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ async function unsubscribeFromBackend(fromSWPushManager: boolean, removePushNoti
|
|||
|
||||
async function removePushNotificationDataOnError(e: Error) {
|
||||
const cu = currentUser.value
|
||||
if (cu)
|
||||
if (checkUser(cu))
|
||||
await removePushNotificationData(cu, true)
|
||||
|
||||
throw e
|
||||
|
|
|
@ -117,6 +117,9 @@ export const usePushManager = () => {
|
|||
}
|
||||
|
||||
const saveSettings = async (policy?: SubscriptionPolicy) => {
|
||||
if (!checkUser(currentUser.value))
|
||||
return
|
||||
|
||||
if (policy)
|
||||
pushNotificationData.value.policy = policy
|
||||
|
||||
|
@ -133,6 +136,9 @@ export const usePushManager = () => {
|
|||
}
|
||||
|
||||
const undoChanges = () => {
|
||||
if (!checkUser(currentUser.value))
|
||||
return
|
||||
|
||||
const current = pushNotificationData.value
|
||||
const previous = history.value[0].snapshot
|
||||
current.favourite = previous.favourite
|
||||
|
|
|
@ -51,7 +51,7 @@ function mentionHTML(acct: string) {
|
|||
|
||||
export function getReplyDraft(status: Status) {
|
||||
const accountsToMention: string[] = []
|
||||
const userId = currentUser.value?.account.id
|
||||
const userId = currentUser.value!.account!.id
|
||||
if (status.account.id !== userId)
|
||||
accountsToMention.push(status.account.acct)
|
||||
accountsToMention.push(...(status.mentions.filter(mention => mention.id !== userId).map(mention => mention.acct)))
|
||||
|
|
|
@ -45,63 +45,87 @@ const users = await initializeUsers()
|
|||
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 isGuestId = computed(() => !currentUserId.value || currentUserId.value.startsWith(`${GUEST_ID}@`))
|
||||
|
||||
export const currentUser = computed<UserLogin | undefined>(() => {
|
||||
if (!currentUserId.value)
|
||||
// Fallback to the first account
|
||||
return users.value[0]
|
||||
|
||||
if (isGuestId.value) {
|
||||
const server = currentUserId.value.replace(`${GUEST_ID}@`, '')
|
||||
return users.value.find(user => user.guest && user.server === server)
|
||||
const defaultUser: UserLogin<false> = {
|
||||
server: DEFAULT_SERVER,
|
||||
guest: true,
|
||||
}
|
||||
|
||||
return users.value.find(user => user.account?.id === currentUserId.value)
|
||||
export const currentUser = computed<UserLogin>(() => {
|
||||
let user: UserLogin | undefined
|
||||
if (!currentUserId.value) {
|
||||
// Fallback to the first account
|
||||
user = users.value[0]
|
||||
}
|
||||
else if (isGuestId.value) {
|
||||
const server = currentUserId.value.replace(`${GUEST_ID}@`, '')
|
||||
user = users.value.find(user => user.guest && user.server === server)
|
||||
}
|
||||
else {
|
||||
user = users.value.find(user => user.account?.id === currentUserId.value)
|
||||
}
|
||||
return user || defaultUser
|
||||
})
|
||||
|
||||
export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER)
|
||||
export const currentServer = computed<string>(() => currentUser.value.server)
|
||||
export const currentInstance = computed<null | Instance>(() => {
|
||||
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 getUniqueUserId = (user: UserLogin) => user.guest ? `${GUEST_ID}@${user.server}` : user.account.id
|
||||
export const getUniqueUserId = (user: UserLogin) =>
|
||||
user.guest ? `${GUEST_ID}@${user.server}` : user.account.id
|
||||
export const isSameUser = (a: UserLogin | undefined, b: UserLogin | undefined) =>
|
||||
a && b && getUniqueUserId(a) === getUniqueUserId(b)
|
||||
|
||||
export const currentUserHandle = computed(() =>
|
||||
isGuestId.value ? GUEST_ID : currentUser.value!.account!.acct
|
||||
,
|
||||
currentUser.value.guest ? GUEST_ID : currentUser.value.account!.acct,
|
||||
)
|
||||
|
||||
export const useUsers = () => users
|
||||
|
||||
export const characterLimit = computed(() => currentInstance.value?.configuration.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)
|
||||
|
||||
async function loginTo(user?: UserLogin) {
|
||||
async function loginTo({ server, token, vapidKey, pushSubscription, guest = false }: { guest?: boolean } & Omit<UserLogin, 'guest'>) {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const server = user?.server || (route.params.server as string) || DEFAULT_SERVER
|
||||
|
||||
let user: UserLogin | undefined = token
|
||||
? users.value.find(u => u.server === server && u.token === token)
|
||||
: ((guest
|
||||
? undefined
|
||||
: users.value.find(u => u.server === server && u.token))
|
||||
|| users.value.find(u => u.server === server && u.guest))
|
||||
|
||||
const needPush = !user
|
||||
if (!user) {
|
||||
if (token) {
|
||||
user = {
|
||||
server,
|
||||
guest: false,
|
||||
token,
|
||||
vapidKey,
|
||||
pushSubscription,
|
||||
account: undefined as any, // to be assigned later
|
||||
}
|
||||
}
|
||||
else {
|
||||
user = { server, guest: true }
|
||||
}
|
||||
}
|
||||
|
||||
const masto = await loginMasto({
|
||||
url: `https://${server}`,
|
||||
accessToken: user?.token,
|
||||
url: `https://${user.server}`,
|
||||
accessToken: user.token,
|
||||
disableVersionCheck: true,
|
||||
// Suppress warning of `masto/fetch` usage
|
||||
disableExperimentalWarning: true,
|
||||
})
|
||||
|
||||
if (!user?.token) {
|
||||
if (user.guest) {
|
||||
const instance = await masto.instances.fetch()
|
||||
|
||||
instances.value[server] = instance
|
||||
if (!users.value.some(u => u.server === server && u.guest))
|
||||
users.value.push({ server, guest: true })
|
||||
|
||||
currentUserId.value = `${GUEST_ID}@${server}`
|
||||
}
|
||||
|
||||
else {
|
||||
try {
|
||||
const [me, instance, pushSubscription] = await Promise.all([
|
||||
masto.accounts.verifyCredentials(),
|
||||
masto.instances.fetch(),
|
||||
|
@ -117,35 +141,37 @@ async function loginTo(user?: UserLogin) {
|
|||
|
||||
user.account = me
|
||||
user.pushSubscription = pushSubscription
|
||||
currentUserId.value = me.id
|
||||
instances.value[server] = instance
|
||||
}
|
||||
|
||||
if (!users.value.some(u => u.server === user.server && u.token === user.token))
|
||||
if (needPush)
|
||||
users.value.push(user)
|
||||
}
|
||||
catch {
|
||||
await signout()
|
||||
}
|
||||
}
|
||||
|
||||
currentUserId.value = getUniqueUserId(user)
|
||||
|
||||
// This only cleans up the URL; page content should stay the same
|
||||
if (route.path === '/signin/callback') {
|
||||
await router.push('/home')
|
||||
}
|
||||
|
||||
else if ('server' in route.params && user?.token && !useNuxtApp()._processingMiddleware) {
|
||||
else if ('server' in route.params && user.server !== route.params.server) {
|
||||
await router.push({
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
server: user.server,
|
||||
},
|
||||
force: true,
|
||||
})
|
||||
}
|
||||
|
||||
return masto
|
||||
}
|
||||
export type LoginTo = typeof loginTo
|
||||
|
||||
export const switchUser = (user: UserLogin, masto: ElkMasto) => {
|
||||
const router = useRouter()
|
||||
if (!user.guest && !isGuest.value && user.account.id === currentUser.value!.account!.id)
|
||||
if (!user.guest && !isGuest.value && user.account.id === currentUser.value.account!.id)
|
||||
router.push(getAccountRoute(user.account))
|
||||
else
|
||||
masto.loginTo(user)
|
||||
|
@ -173,7 +199,7 @@ export function getUsersIndexByUserId(userId: string) {
|
|||
return users.value.findIndex(u => u.account?.id === userId)
|
||||
}
|
||||
|
||||
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
|
||||
export async function removePushNotificationData(user: UserLogin<true>, fromSWPushManager = true) {
|
||||
// clear push subscription
|
||||
user.pushSubscription = undefined
|
||||
const { acct } = user.account!
|
||||
|
@ -212,13 +238,18 @@ export async function removePushNotifications(user: UserLogin<true>) {
|
|||
}
|
||||
}
|
||||
|
||||
// do not sign out if there is only one guest user
|
||||
export const canSignOut = computed(() =>
|
||||
users.value.length > 1 || !users.value[0].guest,
|
||||
)
|
||||
|
||||
export async function signout() {
|
||||
// TODO: confirm
|
||||
if (!currentUser.value)
|
||||
|
||||
if (!canSignOut.value)
|
||||
return
|
||||
|
||||
const index = users.value.findIndex(u => isSameUser(u, currentUser.value))
|
||||
|
||||
if (index !== -1) {
|
||||
// Clear stale data
|
||||
clearUserLocalStorage()
|
||||
|
@ -235,11 +266,8 @@ export async function signout() {
|
|||
users.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// Set currentUserId to next user if available
|
||||
currentUserId.value = users.value[0]?.account?.id
|
||||
|
||||
if (!currentUserId.value)
|
||||
await useRouter().push('/')
|
||||
// Set currentUserId to next user
|
||||
currentUserId.value = getUniqueUserId(users.value[0] ? users.value[0] : defaultUser)
|
||||
|
||||
const masto = useMasto()
|
||||
await masto.loginTo(currentUser.value)
|
||||
|
@ -306,7 +334,7 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
|
|||
const all = storages.get(key) as Ref<Record<string, T>>
|
||||
|
||||
return computed(() => {
|
||||
const id = isGuestId.value
|
||||
const id = currentUser.value.guest
|
||||
? GUEST_ID
|
||||
: currentUser.value!.account!.acct
|
||||
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
||||
|
@ -343,10 +371,11 @@ export const createMasto = () => {
|
|||
|
||||
if (key === 'loginTo') {
|
||||
return (...args: any[]): Promise<MastoClient> => {
|
||||
return apiPromise.value = loginTo(...args).then((r) => {
|
||||
return apiPromise.value = (loginTo as any)(...args).then((r: any) => {
|
||||
api.value = r
|
||||
return masto
|
||||
}).catch(() => {
|
||||
}).catch((err: any) => {
|
||||
console.error(err)
|
||||
// Show error page when Mastodon server is down
|
||||
throw createError({
|
||||
fatal: true,
|
||||
|
|
|
@ -22,7 +22,7 @@ const reload = async () => {
|
|||
try {
|
||||
if (!masto.loggedIn.value)
|
||||
await masto.loginTo(currentUser.value)
|
||||
clearError({ redirect: currentUser.value ? '/home' : `/${currentServer.value}/public` })
|
||||
clearError({ redirect: !isGuest.value ? '/home' : `/${currentServer.value}/public` })
|
||||
}
|
||||
catch {
|
||||
state.value = 'error'
|
||||
|
|
|
@ -32,9 +32,8 @@ const wideLayout = computed(() => route.meta.wideLayout ?? false)
|
|||
>
|
||||
<AccountInfo :account="currentUser.account" md:break-words />
|
||||
</NuxtLink>
|
||||
<div v-else>
|
||||
TODO: guest {{ currentUser.server }} @ default.vue
|
||||
</div>
|
||||
<AccountGuest v-else :user="currentUser" />
|
||||
|
||||
<UserDropdown />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
if (process.server)
|
||||
return
|
||||
|
||||
|
@ -13,23 +13,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|||
|
||||
const user = currentUser.value
|
||||
|
||||
if (!user) {
|
||||
if (from.params.server !== to.params.server) {
|
||||
await masto.loginTo({
|
||||
server: to.params.server as string,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// No need to additionally resolve an id if we're already logged in
|
||||
if (user.server === to.params.server)
|
||||
return
|
||||
|
||||
// Tags don't need to be redirected to a local id
|
||||
if (to.params.tag)
|
||||
return
|
||||
|
||||
// Handle redirecting to new permalink structure for users with old links
|
||||
if (!to.params.server) {
|
||||
return {
|
||||
|
@ -41,28 +24,11 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// If we're already on an account page, we can search for this on the new instance
|
||||
if (to.params.account && to.name !== 'status' && to.params.account.includes('@')) {
|
||||
const account = await fetchAccountByHandle(to.params.account as string)
|
||||
if (account)
|
||||
return getAccountRoute(account)
|
||||
}
|
||||
// No need to additionally resolve an id if we're already logged in
|
||||
if (user.server === to.params.server)
|
||||
return
|
||||
|
||||
if (!masto.loggedIn.value)
|
||||
await masto.loginTo(currentUser.value)
|
||||
|
||||
// If we're logged in, search for the local id the account or status corresponds to
|
||||
const { value } = await masto.search({ q: `https:/${to.fullPath}`, resolve: true, limit: 1 }).next()
|
||||
|
||||
const { accounts, statuses } = value
|
||||
if (statuses[0])
|
||||
return getStatusRoute(statuses[0])
|
||||
|
||||
if (accounts[0])
|
||||
return getAccountRoute(accounts[0])
|
||||
}
|
||||
catch {}
|
||||
|
||||
return '/home'
|
||||
masto.loginTo({
|
||||
server: to.params.server as string,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -71,9 +71,7 @@ async function importTokens() {
|
|||
<div flex="~ col gap2">
|
||||
<div v-for="user of loggedInUsers" :key="getUniqueUserId(user)">
|
||||
<AccountInfo v-if="!user.guest" :account="user.account" :hover-card="false" />
|
||||
<div v-else>
|
||||
TODO: Guest @ settings/users/index.vue
|
||||
</div>
|
||||
<AccountGuest v-else :user="user" />
|
||||
</div>
|
||||
</div>
|
||||
<div my4 border="t base" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { Account, AccountCredentials, Attachment, CreateStatusParams, Emoji, Instance, MastoClient, Notification, PushSubscription, Status } from 'masto'
|
||||
import type { Ref } from 'vue'
|
||||
import type { MarkNonNullable, Mutable } from './utils'
|
||||
import type { LoginTo } from '~/composables/users'
|
||||
|
||||
export interface AppInfo {
|
||||
id: string
|
||||
|
@ -30,7 +31,7 @@ export type UserLogin<WithToken extends boolean = boolean> = {
|
|||
} & ((WithToken extends false ? UserLoginGuest : never) | (WithToken extends true ? UserLoginWithToken : never))
|
||||
|
||||
export interface ElkMasto extends MastoClient {
|
||||
loginTo(user?: UserLogin): Promise<MastoClient>
|
||||
loginTo: LoginTo
|
||||
loggedIn: Ref<boolean>
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue