This commit is contained in:
三咲智子 2023-01-03 08:11:10 +08:00
parent 3468c13e75
commit 1816530f84
No known key found for this signature in database
GPG key ID: 69992F2250DFD93E
10 changed files with 70 additions and 44 deletions

View file

@ -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.account.id === currentUser.value?.account.id) if (!user.guest && !currentUser.value?.guest && 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)
@ -21,7 +21,7 @@ const switchUser = (user: UserLogin) => {
flex rounded flex rounded
cursor-pointer cursor-pointer
aria-label="Switch user" aria-label="Switch user"
:class="user.account.id === currentUser?.account.id ? '' : 'op25 grayscale'" :class="user.account?.id === currentUser?.account?.id ? '' : 'op25 grayscale'"
hover="filter-none op100" hover="filter-none op100"
@click="switchUser(user)" @click="switchUser(user)"
> >

View file

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import { DEFAULT_SERVER } from '~/constants'
const masto = useMasto()
const input = $ref<HTMLInputElement>() const input = $ref<HTMLInputElement>()
let server = $ref<string>('') let server = $ref<string>('')
@ -26,7 +27,7 @@ async function oauth() {
server = server.split('/')[0] server = server.split('/')[0]
try { try {
location.href = await $fetch<string>(`/api/${server || DEFAULT_SERVER}/login`, { location.href = await $fetch<string>(`/api/${server}/login`, {
method: 'POST', method: 'POST',
body: { body: {
origin: location.origin, origin: location.origin,
@ -46,6 +47,10 @@ async function oauth() {
} }
} }
function explore() {
masto.loginTo({ server, guest: true })
}
async function handleInput() { async function handleInput() {
if (server.startsWith('https://')) if (server.startsWith('https://'))
server = server.replace('https://', '') server = server.replace('https://', '')
@ -180,9 +185,19 @@ onClickOutside($$(input), () => {
</i18n-t> </i18n-t>
</span> </span>
</div> </div>
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy"> <div flex="~ gap2" mt2 items-center>
<button
type="button"
flex="~ row" gap-x-2 items-center btn-outline text-sm px2 py1 h-fit :disabled="!server || busy"
@click="explore"
>
<span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:user-4-line'" />
{{ $t('action.explore_as_a_guest') }}
</button>
<button flex="~ row" gap-x-2 items-center btn-solid :disabled="!server || busy">
<span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:login-circle-line'" class="rtl-flip" /> <span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:login-circle-line'" class="rtl-flip" />
{{ $t('action.sign_in') }} {{ $t('action.sign_in') }}
</button> </button>
</div>
</form> </form>
</template> </template>

View file

@ -344,9 +344,9 @@ export const provideGlobalCommands = () => {
parent: 'account-switch', parent: 'account-switch',
scope: 'Switch account', scope: 'Switch account',
visible: () => user.account.id !== currentUser.value?.account.id, visible: () => !user.guest && user.account.id !== currentUser.value?.account?.id,
name: () => t('command.switch_account', [getFullHandle(user.account)]), name: () => t('command.switch_account', [getFullHandle(user)]),
icon: 'i-ri:user-shared-line', icon: 'i-ri:user-shared-line',
onActivate() { onActivate() {
@ -357,8 +357,7 @@ export const provideGlobalCommands = () => {
scope: 'Account', scope: 'Account',
visible: () => currentUser.value, visible: () => currentUser.value,
name: () => currentUser.value ? t('user.sign_out_account', [getFullHandle(currentUser.value)]) : '',
name: () => t('user.sign_out_account', [getFullHandle(currentUser.value!.account)]),
icon: 'i-ri:logout-box-line', icon: 'i-ri:logout-box-line',
onActivate() { onActivate() {

View file

@ -1,7 +1,7 @@
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { Account, Relationship, Status } from 'masto' import type { Account, Relationship, Status } from 'masto'
import { withoutProtocol } from 'ufo' import { withoutProtocol } from 'ufo'
import type { ElkMasto } from '~/types' import type { ElkMasto, UserLogin } from '~/types'
export const useMasto = () => useNuxtApp().$masto as ElkMasto export const useMasto = () => useNuxtApp().$masto as ElkMasto
@ -33,7 +33,11 @@ export function getServerName(account: Account) {
return currentInstance.value?.uri || '' return currentInstance.value?.uri || ''
} }
export function getFullHandle(account: Account) { export function getFullHandle(_account: Account | UserLogin) {
if ('guest' in _account && _account.guest)
return `[anonymous]@${_account.server}`
const account = 'server' in _account ? _account.account : _account
const handle = `@${account.acct}` const handle = `@${account.acct}`
if (!currentUser.value || account.acct.includes('@')) if (!currentUser.value || account.acct.includes('@'))
return handle return handle

View file

@ -45,34 +45,37 @@ const instances = useLocalStorage<Record<string, Instance>>(STORAGE_KEY_SERVERS,
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 : '')
export const currentUser = computed<UserLogin | undefined>(() => { export const currentUser = computed<UserLogin | undefined>(() => {
if (currentUserId.value) { if (!currentUserId.value)
// Fallback to the first account
return users.value[0]
const user = users.value.find(user => user.account?.id === currentUserId.value) const user = users.value.find(user => user.account?.id === currentUserId.value)
if (user) if (user)
return user return user
}
// Fallback to the first account
return users.value[0]
}) })
const publicInstance = ref<Instance | null>(null) export const isGuest = computed(
export const currentInstance = computed<null | Instance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value) () => currentUserId.value.startsWith('[anonymous]@') || !currentUser.value?.account?.acct,
)
export const publicServer = ref(DEFAULT_SERVER) export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER)
export const currentServer = computed<string>(() => currentUser.value?.server || publicServer.value) export const currentInstance = computed<null | Instance>(() => {
return instances.value[currentServer.value] ?? null
})
export const currentUserHandle = computed(() => currentUser.value?.account.id export const currentUserHandle = computed(() =>
? `${currentUser.value.account.acct}@${currentInstance.value?.uri || currentServer.value}` isGuest.value ? '[anonymous]' : currentUser.value!.account!.acct
: '[anonymous]', ,
) )
export const useUsers = () => users export const useUsers = () => users
export const characterLimit = computed(() => currentInstance.value?.configuration.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT) export const characterLimit = computed(() => currentInstance.value?.configuration.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)
async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: AccountCredentials }) { 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 || publicServer.value const server = user?.server || (route.params.server as string)
const masto = await loginMasto({ const masto = await loginMasto({
url: `https://${server}`, url: `https://${server}`,
accessToken: user?.token, accessToken: user?.token,
@ -82,8 +85,11 @@ async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: AccountCr
}) })
if (!user?.token) { if (!user?.token) {
publicServer.value = server const instance = await masto.instances.fetch()
publicInstance.value = await masto.instances.fetch()
currentUserId.value = `[anonymous]@${server}`
instances.value[server] = instance
users.value.push({ server, guest: true })
} }
else { else {
@ -107,7 +113,7 @@ async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: AccountCr
instances.value[server] = instance instances.value[server] = instance
if (!users.value.some(u => u.server === user.server && u.token === user.token)) if (!users.value.some(u => u.server === user.server && u.token === user.token))
users.value.push(user as UserLogin) users.value.push(user)
} }
catch { catch {
await signout() await signout()
@ -154,7 +160,7 @@ export function getUsersIndexByUserId(userId: string) {
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) { export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
// clear push subscription // clear push subscription
user.pushSubscription = undefined user.pushSubscription = undefined
const { acct } = user.account const { acct } = user.account!
// clear request notification permission // clear request notification permission
delete useLocalStorage<PushNotificationRequest>(STORAGE_KEY_NOTIFICATION, {}).value[acct] delete useLocalStorage<PushNotificationRequest>(STORAGE_KEY_NOTIFICATION, {}).value[acct]
// clear push notification policy // clear push notification policy
@ -197,7 +203,7 @@ export async function signout() {
const masto = useMasto() const masto = useMasto()
const _currentUserId = currentUser.value.account.id const _currentUserId = currentUser.value.account!.id
const index = users.value.findIndex(u => u.account?.id === _currentUserId) const index = users.value.findIndex(u => u.account?.id === _currentUserId)
@ -228,7 +234,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 = currentUser.value?.account!.id
const masto = useMasto() const masto = useMasto()
const clearNotifications = () => { const clearNotifications = () => {
@ -286,9 +292,9 @@ 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 = currentUser.value?.account.id const id = isGuest.value
? currentUser.value.account.acct ? '[anonymous]'
: '[anonymous]' : currentUser.value!.account!.acct
all.value[id] = Object.assign(initial(), all.value[id] || {}) all.value[id] = Object.assign(initial(), all.value[id] || {})
return all.value[id] return all.value[id]
}) })

View file

@ -49,6 +49,7 @@
"confirm": "Confirm", "confirm": "Confirm",
"edit": "Edit", "edit": "Edit",
"enter_app": "Enter App", "enter_app": "Enter App",
"explore_as_a_guest": "Explore as a guest",
"favourite": "Favourite", "favourite": "Favourite",
"favourited": "Favourited", "favourited": "Favourited",
"more": "More", "more": "More",

View file

@ -50,6 +50,7 @@
"confirm": "Confirm", "confirm": "Confirm",
"edit": "Edit", "edit": "Edit",
"enter_app": "Enter App", "enter_app": "Enter App",
"explore_as_a_guest": "Explore as a guest",
"favourite": "Favorite", "favourite": "Favorite",
"favourite_count": "{0}", "favourite_count": "{0}",
"favourited": "Favorited", "favourited": "Favorited",

View file

@ -49,6 +49,7 @@
"confirm": "确认", "confirm": "确认",
"edit": "编辑", "edit": "编辑",
"enter_app": "进入应用", "enter_app": "进入应用",
"explore_as_a_guest": "游客浏览",
"favourite": "喜欢", "favourite": "喜欢",
"favourited": "已喜欢", "favourited": "已喜欢",
"more": "更多", "more": "更多",

View file

@ -12,16 +12,15 @@ export interface AppInfo {
vapid_key: string vapid_key: string
} }
export interface UserLogin { export type UserLogin = {
server: string server: string
token?: string token?: string
account: AccountCredentials
vapidKey?: string vapidKey?: string
pushSubscription?: PushSubscription pushSubscription?: PushSubscription
} } & ({ account: AccountCredentials; guest: false } | { account?: undefined; guest: true })
export interface ElkMasto extends MastoClient { export interface ElkMasto extends MastoClient {
loginTo (user?: Omit<UserLogin, 'account'> & { account?: AccountCredentials }): Promise<MastoClient> loginTo(user?: UserLogin): Promise<MastoClient>
loggedIn: Ref<boolean> loggedIn: Ref<boolean>
} }

View file

@ -35,7 +35,7 @@ export default defineConfig({
// buttons // buttons
'btn-base': 'cursor-pointer disabled:pointer-events-none disabled:bg-$c-bg-btn-disabled disabled:text-$c-text-btn-disabled', 'btn-base': 'cursor-pointer disabled:pointer-events-none disabled:bg-$c-bg-btn-disabled disabled:text-$c-text-btn-disabled',
'btn-solid': 'btn-base px-4 py-2 rounded text-$c-text-btn bg-$c-primary hover:bg-$c-primary-active', 'btn-solid': 'btn-base px-4 py-2 rounded text-$c-text-btn bg-$c-primary hover:bg-$c-primary-active',
'btn-outline': 'btn-base px-4 py-2 rounded text-$c-primary border border-$c-primary hover:bg-$c-primary hover:text-inverted', 'btn-outline': 'btn-base px-4 py-2 rounded text-$c-primary border border-$c-primary hover:bg-$c-primary hover:text-inverted disabled:border-$c-bg-btn-disabled',
'btn-text': 'btn-base px-4 py-2 text-$c-primary hover:text-$c-primary-active', 'btn-text': 'btn-base px-4 py-2 text-$c-primary hover:text-$c-primary-active',
'btn-action-icon': 'btn-base hover:bg-active rounded-full h9 w9 flex items-center justify-center disabled:bg-transparent disabled:text-$c-text-secondary', 'btn-action-icon': 'btn-base hover:bg-active rounded-full h9 w9 flex items-center justify-center disabled:bg-transparent disabled:text-$c-text-secondary',