1
0
mirror of https://github.com/elk-zone/elk synced 2024-11-30 15:58:06 +09:00

feat: wip

This commit is contained in:
三咲智子 2023-01-03 17:39:00 +08:00
parent f7df0e54f5
commit 313cafa23c
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
18 changed files with 146 additions and 129 deletions

View File

@ -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>

View 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>

View File

@ -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 () => {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -48,7 +48,7 @@ async function oauth() {
}
function explore() {
masto.loginTo({ server, guest: true })
masto.loginTo({ server, guestOnly: true })
}
async function handleInput() {

View File

@ -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"

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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)))

View File

@ -45,107 +45,133 @@ 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}@`))
const defaultUser: UserLogin<false> = {
server: DEFAULT_SERVER,
guest: true,
}
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)
export const currentUser = computed<UserLogin>(() => {
let user: UserLogin | undefined
if (!currentUserId.value) {
// Fallback to the first account
user = users.value[0]
}
return users.value.find(user => user.account?.id === currentUserId.value)
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(),
// if PWA is not enabled, don't get push subscription
useRuntimeConfig().public.pwaEnabled
// we get 404 response instead empty data
? masto.pushSubscriptions.fetch().catch(() => Promise.resolve(undefined))
: Promise.resolve(undefined),
])
const [me, instance, pushSubscription] = await Promise.all([
masto.accounts.verifyCredentials(),
masto.instances.fetch(),
// if PWA is not enabled, don't get push subscription
useRuntimeConfig().public.pwaEnabled
// we get 404 response instead empty data
? masto.pushSubscriptions.fetch().catch(() => Promise.resolve(undefined))
: Promise.resolve(undefined),
])
if (!me.acct.includes('@'))
me.acct = `${me.acct}@${instance.uri}`
if (!me.acct.includes('@'))
me.acct = `${me.acct}@${instance.uri}`
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))
users.value.push(user)
}
catch {
await signout()
}
user.account = me
user.pushSubscription = pushSubscription
instances.value[server] = instance
}
if (needPush)
users.value.push(user)
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,

View File

@ -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'

View File

@ -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>

View File

@ -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,
})
})

View File

@ -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" />

View File

@ -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>
}