1
0
mirror of https://github.com/elk-zone/elk synced 2024-11-23 14:46:08 +09:00

feat: upgrade to masto.js v6 (#2530)

This commit is contained in:
patak 2024-01-09 09:56:15 +01:00 committed by GitHub
parent d8ea685803
commit 6c5bb83ac3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 262 additions and 263 deletions

View File

@ -19,7 +19,7 @@ const { client } = $(useMasto())
async function unblock() {
relationship!.blocking = false
try {
const newRel = await client.v1.accounts.unblock(account.id)
const newRel = await client.v1.accounts.$select(account.id).unblock()
Object.assign(relationship!, newRel)
}
catch (err) {
@ -32,7 +32,7 @@ async function unblock() {
async function unmute() {
relationship!.muting = false
try {
const newRel = await client.v1.accounts.unmute(account.id)
const newRel = await client.v1.accounts.$select(account.id).unmute()
Object.assign(relationship!, newRel)
}
catch (err) {

View File

@ -12,7 +12,7 @@ async function authorizeFollowRequest() {
relationship!.requestedBy = false
relationship!.followedBy = true
try {
const newRel = await client.v1.followRequests.authorize(account.id)
const newRel = await client.v1.followRequests.$select(account.id).authorize()
Object.assign(relationship!, newRel)
}
catch (err) {
@ -25,7 +25,7 @@ async function authorizeFollowRequest() {
async function rejectFollowRequest() {
relationship!.requestedBy = false
try {
const newRel = await client.v1.followRequests.reject(account.id)
const newRel = await client.v1.followRequests.$select(account.id).reject()
Object.assign(relationship!, newRel)
}
catch (err) {

View File

@ -53,7 +53,7 @@ function previewAvatar() {
async function toggleNotifications() {
relationship!.notifying = !relationship?.notifying
try {
const newRel = await client.v1.accounts.follow(account.id, { notify: relationship?.notifying })
const newRel = await client.v1.accounts.$select(account.id).follow({ notify: relationship?.notifying })
Object.assign(relationship!, newRel)
}
catch {
@ -97,7 +97,7 @@ async function editNote(event: Event) {
if (relationship.note?.trim() === newNote.trim())
return
const newNoteApiResult = await client.v1.accounts.createNote(account.id, { comment: newNote })
const newNoteApiResult = await client.v1.accounts.$select(account.id).note.create({ comment: newNote })
relationship.note = newNoteApiResult.note
personalNoteDraft.value = relationship.note ?? ''
}

View File

@ -33,7 +33,7 @@ async function toggleReblogs() {
return
const showingReblogs = !relationship?.showingReblogs
relationship = await client.v1.accounts.follow(account.id, { reblogs: showingReblogs })
relationship = await client.v1.accounts.$select(account.id).follow({ reblogs: showingReblogs })
}
async function addUserNote() {
@ -44,7 +44,7 @@ async function removeUserNote() {
if (!relationship!.note || relationship!.note.length === 0)
return
const newNote = await client.v1.accounts.createNote(account.id, { comment: '' })
const newNote = await client.v1.accounts.$select(account.id).note.create({ comment: '' })
relationship!.note = newNote.note
emit('removeNote')
}

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Paginator, mastodon } from 'masto'
import type { mastodon } from 'masto'
const { paginator, account, context } = defineProps<{
paginator: Paginator<mastodon.v1.Account[], mastodon.DefaultPaginationParams>
paginator: mastodon.Paginator<mastodon.v1.Account[], mastodon.DefaultPaginationParams | undefined>
context?: 'following' | 'followers'
account?: mastodon.v1.Account
relationshipContext?: 'followedBy' | 'following'

View File

@ -2,7 +2,7 @@
// @ts-expect-error missing types
import { DynamicScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import type { Paginator, WsEvents } from 'masto'
import type { mastodon } from 'masto'
import type { UnwrapRef } from 'vue'
const {
@ -14,10 +14,10 @@ const {
preprocess,
endMessage = true,
} = defineProps<{
paginator: Paginator<T[], O>
paginator: mastodon.Paginator<T[], O>
keyProp?: keyof T
virtualScroller?: boolean
stream?: Promise<WsEvents>
stream?: mastodon.streaming.Subscription
eventType?: 'notification' | 'update'
preprocess?: (items: (U | T)[]) => U[]
endMessage?: boolean | string

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Paginator, mastodon } from 'masto'
import type { mastodon } from 'masto'
const { paginator } = defineProps<{
paginator: Paginator<mastodon.v1.Conversation[], mastodon.DefaultPaginationParams>
paginator: mastodon.Paginator<mastodon.v1.Conversation[], mastodon.DefaultPaginationParams>
}>()
function preprocess(items: mastodon.v1.Conversation[]): mastodon.v1.Conversation[] {

View File

@ -16,8 +16,8 @@ const isRemoved = ref(false)
async function edit() {
try {
isRemoved.value
? await client.v1.lists.addAccount(list, { accountIds: [account.id] })
: await client.v1.lists.removeAccount(list, { accountIds: [account.id] })
? await client.v1.lists.$select(list).accounts.create({ accountIds: [account.id] })
: await client.v1.lists.$select(list).accounts.remove({ accountIds: [account.id] })
isRemoved.value = !isRemoved.value
}
catch (err) {

View File

@ -40,7 +40,7 @@ async function cancelEdit() {
const { submit, submitting } = submitter(async () => {
try {
list.value = await client.v1.lists.update(form.id, {
list.value = await client.v1.lists.$select(form.id).update({
title: form.title,
})
cancelEdit()
@ -70,7 +70,7 @@ async function removeList() {
if (confirmDelete === 'confirm') {
await nextTick()
try {
await client.v1.lists.remove(list.value.id)
await client.v1.lists.$select(list.value.id).remove()
emit('listRemoved', list.value.id)
}
catch (err) {

View File

@ -5,7 +5,7 @@ const { userId } = defineProps<{
const { client } = $(useMasto())
const paginator = client.v1.lists.list()
const listsWithUser = ref((await client.v1.accounts.listLists(userId)).map(list => list.id))
const listsWithUser = ref((await client.v1.accounts.$select(userId).lists.list()).map(list => list.id))
function indexOfUserInList(listId: string) {
return listsWithUser.value.indexOf(listId)
@ -15,11 +15,11 @@ async function edit(listId: string) {
try {
const index = indexOfUserInList(listId)
if (index === -1) {
await client.v1.lists.addAccount(listId, { accountIds: [userId] })
await client.v1.lists.$select(listId).accounts.create({ accountIds: [userId] })
listsWithUser.value.push(listId)
}
else {
await client.v1.lists.removeAccount(listId, { accountIds: [userId] })
await client.v1.lists.$select(listId).accounts.remove({ accountIds: [userId] })
listsWithUser.value = listsWithUser.value.filter(id => id !== listId)
}
}

View File

@ -15,7 +15,6 @@ const { notification } = defineProps<{
ps-3 pe-4 inset-is-0
rounded-ie-be-3
py-3 bg-base top-0
:lang="notification.status?.language ?? undefined"
>
<div i-ri-user-3-line text-xl me-3 color-blue />
<AccountDisplayName :account="notification.account" text-primary me-1 font-bold line-clamp-1 ws-pre-wrap break-all />
@ -26,7 +25,6 @@ const { notification } = defineProps<{
<AccountBigCard
ms10
:account="notification.account"
:lang="notification.status?.language ?? undefined"
/>
</NuxtLink>
</template>

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
// @ts-expect-error missing types
import { DynamicScrollerItem } from 'vue-virtual-scroller'
import type { Paginator, WsEvents, mastodon } from 'masto'
import type { mastodon } from 'masto'
import type { GroupedAccountLike, NotificationSlot } from '~/types'
const { paginator, stream } = defineProps<{
paginator: Paginator<mastodon.v1.Notification[], mastodon.v1.ListNotificationsParams>
stream?: Promise<WsEvents>
paginator: mastodon.Paginator<mastodon.v1.Notification[], mastodon.rest.v1.ListNotificationsParams>
stream?: mastodon.streaming.Subscription
}>()
const virtualScroller = false // TODO: fix flickering issue with virtual scroll

View File

@ -75,12 +75,13 @@ function trimPollOptions() {
}
function editPollOptionDraft(event: Event, index: number) {
draft.params.poll!.options[index] = (event.target as HTMLInputElement).value
draft.params.poll!.options = Object.assign(draft.params.poll!.options.slice(), { [index]: (event.target as HTMLInputElement).value })
trimPollOptions()
}
function deletePollOption(index: number) {
draft.params.poll!.options.splice(index, 1)
draft.params.poll!.options = draft.params.poll!.options.slice().splice(index, 1)
trimPollOptions()
}

View File

@ -34,11 +34,11 @@ function categoryChosen() {
async function loadStatuses() {
if (status) {
// Load the 5 statuses before and after the reported status
const prevStatuses = await client.value.v1.accounts.listStatuses(account.id, {
const prevStatuses = await client.value.v1.accounts.$select(account.id).statuses.list({
maxId: status.id,
limit: 5,
})
const nextStatuses = await client.value.v1.accounts.listStatuses(account.id, {
const nextStatuses = await client.value.v1.accounts.$select(account.id).statuses.list({
minId: status.id,
limit: 5,
})
@ -48,7 +48,7 @@ async function loadStatuses() {
else {
// Reporting an account directly
// Load the 10 most recent statuses
const mostRecentStatuses = await client.value.v1.accounts.listStatuses(account.id, {
const mostRecentStatuses = await client.value.v1.accounts.$select(account.id).statuses.list({
limit: 10,
})
availableStatuses.value = mostRecentStatuses

View File

@ -2,7 +2,7 @@
import type { mastodon } from 'masto'
const form = defineModel<{
fieldsAttributes: NonNullable<mastodon.v1.UpdateCredentialsParams['fieldsAttributes']>
fieldsAttributes: NonNullable<mastodon.rest.v1.UpdateCredentialsParams['fieldsAttributes']>
}>({ required: true })
const dropdown = $ref<any>()

View File

@ -72,7 +72,7 @@ async function deleteStatus() {
return
removeCachedStatus(status.id)
await client.v1.statuses.remove(status.id)
await client.v1.statuses.$select(status.id).remove()
if (route.name === 'status')
router.back()
@ -96,7 +96,7 @@ async function deleteAndRedraft() {
}
removeCachedStatus(status.id)
await client.v1.statuses.remove(status.id)
await client.v1.statuses.$select(status.id).remove()
await openPublishDialog('dialog', await getDraftFromStatus(status), true)
// Go to the new status, if the page is the old status

View File

@ -6,7 +6,7 @@ const type = ref<'favourited-by' | 'boosted-by'>('favourited-by')
const { client } = $(useMasto())
function load() {
return client.v1.statuses[type.value === 'favourited-by' ? 'listFavouritedBy' : 'listRebloggedBy'](favouritedBoostedByStatusId.value!)
return client.v1.statuses.$select(favouritedBoostedByStatusId.value!)[type.value === 'favourited-by' ? 'favouritedBy' : 'rebloggedBy'].list()
}
const paginator = $computed(() => load())

View File

@ -36,7 +36,7 @@ async function vote(e: Event) {
cacheStatus({ ...status, poll }, undefined, true)
await client.v1.polls.vote(poll.id, { choices })
await client.v1.polls.$select(poll.id).votes.create({ choices })
}
const votersCount = $computed(() => poll.votersCount ?? poll.votesCount ?? 0)

View File

@ -6,7 +6,7 @@ const { status } = defineProps<{
status: mastodon.v1.Status
}>()
const paginator = useMastoClient().v1.statuses.listHistory(status.id)
const paginator = useMastoClient().v1.statuses.$select(status.id).history.list()
function showHistory(edit: mastodon.v1.StatusEdit) {
openEditHistoryDialog(edit)

View File

@ -20,9 +20,9 @@ async function toggleFollowTag() {
try {
if (previousFollowingState)
await client.v1.tags.unfollow(tag.name)
await client.v1.tags.$select(tag.name).unfollow()
else
await client.v1.tags.follow(tag.name)
await client.v1.tags.$select(tag.name).follow()
emit('change')
}

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Paginator, mastodon } from 'masto'
import type { mastodon } from 'masto'
const { paginator } = defineProps<{
paginator: Paginator<mastodon.v1.Tag[], mastodon.DefaultPaginationParams>
paginator: mastodon.Paginator<mastodon.v1.Tag[], mastodon.DefaultPaginationParams>
}>()
</script>

View File

@ -3,7 +3,7 @@ const { client } = $(useMasto())
const paginator = client.v1.domainBlocks.list()
async function unblock(domain: string) {
await client.v1.domainBlocks.unblock(domain)
await client.v1.domainBlocks.remove({ domain })
}
</script>

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const paginator = useMastoClient().v1.timelines.listHome({ limit: 30 })
const stream = $(useStreaming(client => client.v1.stream.streamUser()))
const paginator = useMastoClient().v1.timelines.home.list({ limit: 30 })
const stream = useStreaming(client => client.user.subscribe())
function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'home')
}

View File

@ -9,7 +9,7 @@ const options = { limit: 30, types: filter ? [filter] : [] }
// Default limit is 20 notifications, and servers are normally caped to 30
const paginator = useMastoClient().v1.notifications.list(options)
const stream = useStreaming(client => client.v1.stream.streamUser())
const stream = useStreaming(client => client.user.subscribe())
const { clearNotifications } = useNotifications()
onActivated(clearNotifications)

View File

@ -2,11 +2,11 @@
// @ts-expect-error missing types
import { DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import type { Paginator, WsEvents, mastodon } from 'masto'
import type { mastodon } from 'masto'
const { paginator, stream, account, buffer = 10, endMessage = true } = defineProps<{
paginator: Paginator<mastodon.v1.Status[], mastodon.v1.ListAccountStatusesParams>
stream?: Promise<WsEvents>
paginator: mastodon.Paginator<mastodon.v1.Status[], mastodon.rest.v1.ListAccountStatusesParams>
stream?: mastodon.streaming.Subscription
context?: mastodon.v2.FilterContext
account?: mastodon.v1.Account
preprocess?: (items: mastodon.v1.Status[]) => mastodon.v1.Status[]

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
const paginator = useMastoClient().v1.accounts.listStatuses(currentUser.value!.account.id, { pinned: true })
const paginator = useMastoClient().v1.accounts.$select(currentUser.value!.account.id).statuses.list({ pinned: true })
</script>
<template>

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const paginator = useMastoClient().v1.timelines.listPublic({ limit: 30 })
const stream = useStreaming(client => client.v1.stream.streamPublicTimeline())
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30 })
const stream = useStreaming(client => client.public.subscribe())
function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'public')
}

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
const paginator = useMastoClient().v1.timelines.listPublic({ limit: 30, local: true })
const stream = useStreaming(client => client.v1.stream.streamCommunityTimeline())
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true })
const stream = useStreaming(client => client.direct.subscribe())
</script>
<template>

View File

@ -24,7 +24,7 @@ export function fetchStatus(id: string, force = false): Promise<mastodon.v1.Stat
const cached = cache.get(key)
if (cached && !force)
return cached
const promise = useMastoClient().v1.statuses.fetch(id)
const promise = useMastoClient().v1.statuses.$select(id).fetch()
.then((status) => {
cacheStatus(status)
return status
@ -44,7 +44,7 @@ export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Accoun
if (cached)
return cached
const domain = getInstanceDomainFromServer(server)
const promise = useMastoClient().v1.accounts.fetch(id)
const promise = useMastoClient().v1.accounts.$select(id).fetch()
.then((r) => {
if (r.acct && !r.acct.includes('@') && domain)
r.acct = `${r.acct}@${domain}`
@ -74,7 +74,7 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
}
else {
const userAcctDomain = userAcct.includes('@') ? userAcct : `${userAcct}@${domain}`
account = (await client.v1.search({ q: `@${userAcctDomain}`, type: 'accounts' })).accounts[0]
account = (await client.v1.search.fetch({ q: `@${userAcctDomain}`, type: 'accounts' })).accounts[0]
}
if (account.acct && !account.acct.includes('@') && domain)

View File

@ -1,27 +1,14 @@
import type { Pausable } from '@vueuse/core'
import type { CreateClientParams, WsEvents, mastodon } from 'masto'
import { createClient, fetchV1Instance } from 'masto'
import type { mastodon } from 'masto'
import { createRestAPIClient, createStreamingAPIClient } from 'masto'
import type { Ref } from 'vue'
import type { ElkInstance } from '../users'
import type { Mutable } from '~/types/utils'
import type { UserLogin } from '~/types'
export function createMasto() {
let client = $shallowRef<mastodon.Client>(undefined as never)
let params = $ref<Mutable<CreateClientParams>>()
const canStreaming = $computed(() => !!params?.streamingApiUrl)
const setParams = (newParams: Partial<CreateClientParams>) => {
const p = { ...params, ...newParams } as CreateClientParams
client = createClient(p)
params = p
}
return {
client: $$(client),
params: readonly($$(params)),
canStreaming: $$(canStreaming),
setParams,
client: shallowRef<mastodon.rest.Client>(undefined as never),
streamingClient: shallowRef<mastodon.streaming.Client | undefined>(),
}
}
export type ElkMasto = ReturnType<typeof createMasto>
@ -34,23 +21,25 @@ export function useMastoClient() {
}
export function mastoLogin(masto: ElkMasto, user: Pick<UserLogin, 'server' | 'token'>) {
const { setParams } = $(masto)
const server = user.server
const url = `https://${server}`
const instance: ElkInstance = reactive(getInstanceCache(server) || { uri: server, accountDomain: server })
setParams({
url,
accessToken: user?.token,
disableVersionCheck: true,
streamingApiUrl: instance?.urls?.streamingApi,
})
const accessToken = user.token
fetchV1Instance({ url }).then((newInstance) => {
const createStreamingClient = (streamingApiUrl: string | undefined) => {
return streamingApiUrl ? createStreamingAPIClient({ streamingApiUrl, accessToken, implementation: globalThis.WebSocket }) : undefined
}
const streamingApiUrl = instance?.urls?.streamingApi
masto.client.value = createRestAPIClient({ url, accessToken })
masto.streamingClient.value = createStreamingClient(streamingApiUrl)
// Refetch instance info in the background on login
masto.client.value.v1.instance.fetch().then((newInstance) => {
Object.assign(instance, newInstance)
setParams({
streamingApiUrl: newInstance.urls.streamingApi,
})
if (newInstance.urls.streamingApi !== streamingApiUrl)
masto.streamingClient.value = createStreamingClient(newInstance.urls.streamingApi)
instanceStorage.value[server] = newInstance
})
@ -73,21 +62,21 @@ interface UseStreamingOptions<Controls extends boolean> {
}
export function useStreaming(
cb: (client: mastodon.Client) => Promise<WsEvents>,
cb: (client: mastodon.streaming.Client) => mastodon.streaming.Subscription,
options: UseStreamingOptions<true>,
): { stream: Ref<Promise<WsEvents> | undefined> } & Pausable
): { stream: Ref<mastodon.streaming.Subscription | undefined> } & Pausable
export function useStreaming(
cb: (client: mastodon.Client) => Promise<WsEvents>,
cb: (client: mastodon.streaming.Client) => mastodon.streaming.Subscription,
options?: UseStreamingOptions<false>,
): Ref<Promise<WsEvents> | undefined>
): Ref<mastodon.streaming.Subscription | undefined>
export function useStreaming(
cb: (client: mastodon.Client) => Promise<WsEvents>,
cb: (client: mastodon.streaming.Client) => mastodon.streaming.Subscription,
{ immediate = true, controls }: UseStreamingOptions<boolean> = {},
): ({ stream: Ref<Promise<WsEvents> | undefined> } & Pausable) | Ref<Promise<WsEvents> | undefined> {
const { canStreaming, client } = useMasto()
): ({ stream: Ref<mastodon.streaming.Subscription | undefined> } & Pausable) | Ref<mastodon.streaming.Subscription | undefined> {
const { streamingClient } = useMasto()
const isActive = ref(immediate)
const stream = ref<Promise<WsEvents>>()
const stream = ref<mastodon.streaming.Subscription>()
function pause() {
isActive.value = false
@ -99,15 +88,15 @@ export function useStreaming(
function cleanup() {
if (stream.value) {
stream.value.then(s => s.disconnect()).catch(() => Promise.resolve())
stream.value.unsubscribe()
stream.value = undefined
}
}
watchEffect(() => {
cleanup()
if (canStreaming.value && isActive.value)
stream.value = cb(client.value)
if (streamingClient.value && isActive.value)
stream.value = cb(streamingClient.value)
})
if (process.client && !process.test)

View File

@ -1,11 +1,11 @@
import type { WsEvents } from 'masto'
import type { mastodon } from 'masto'
const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, string[]]>>({})
const notifications = reactive<Record<string, undefined | [Promise<mastodon.streaming.Subscription>, string[]]>>({})
export function useNotifications() {
const id = currentUser.value?.account.id
const { client, canStreaming } = $(useMasto())
const { client, streamingClient } = $(useMasto())
async function clearNotifications() {
if (!id || !notifications[id])
@ -19,21 +19,27 @@ export function useNotifications() {
}
}
async function processNotifications(stream: mastodon.streaming.Subscription, id: string) {
for await (const entry of stream) {
if (entry.event === 'notification' && notifications[id])
notifications[id]![1].unshift(entry.payload.id)
}
}
async function connect(): Promise<void> {
if (!isHydrated.value || !id || notifications[id] || !currentUser.value?.token)
if (!isHydrated.value || !id || notifications[id] !== undefined || !currentUser.value?.token)
return
let resolveStream
const stream = new Promise<WsEvents>(resolve => resolveStream = resolve)
notifications[id] = [stream, []]
const streamPromise = new Promise<mastodon.streaming.Subscription>(resolve => resolveStream = resolve)
notifications[id] = [streamPromise, []]
await until($$(canStreaming)).toBe(true)
await until($$(streamingClient)).toBe(true)
client.v1.stream.streamUser().then(resolveStream)
stream.then(s => s.on('notification', (n) => {
if (notifications[id])
notifications[id]![1].unshift(n.id)
}))
const stream = streamingClient!.user.subscribe()
resolveStream!(stream)
processNotifications(stream, id)
const position = await client.v1.markers.fetch({ timeline: ['notifications'] })
const paginator = client.v1.notifications.list({ limit: 30 })
@ -55,7 +61,7 @@ export function useNotifications() {
function disconnect(): void {
if (!id || !notifications[id])
return
notifications[id]![0].then(stream => stream.disconnect())
notifications[id]![0].then(stream => stream.unsubscribe())
notifications[id] = undefined
}
@ -67,7 +73,6 @@ export function useNotifications() {
return {
notifications: computed(() => id ? notifications[id]?.[1].length ?? 0 : 0),
disconnect,
clearNotifications,
}
}

View File

@ -93,7 +93,7 @@ export function usePublish(options: {
language: draft.params.language || preferredLanguage,
poll,
...(isGlitchEdition.value ? { 'content-type': 'text/markdown' } : {}),
} as mastodon.v1.CreateStatusParams
} as mastodon.rest.v1.CreateStatusParams
if (process.dev) {
// eslint-disable-next-line no-console
@ -116,14 +116,13 @@ export function usePublish(options: {
}
else {
const updatePayload = {
status = await client.v1.statuses.$select(draft.editingStatus.id).update({
...payload,
mediaAttributes: draft.attachments.map(media => ({
id: media.id,
description: media.description,
})),
} as mastodon.v1.UpdateStatusParams
status = await client.v1.statuses.update(draft.editingStatus.id, updatePayload)
})
}
if (draft.params.inReplyToId)
navigateToStatus({ status })
@ -232,7 +231,7 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
if (draft.attachments.length < limit) {
isExceedingAttachmentLimit = false
try {
const attachment = await client.v1.mediaAttachments.create({
const attachment = await client.v1.media.create({
file: await processFile(file),
})
draft.attachments.push(attachment)
@ -266,7 +265,7 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
att.description = description
if (!draft.editingStatus)
await client.v1.mediaAttachments.update(att.id, { description: att.description })
await client.v1.media.$select(att.id).update({ description: att.description })
}
function removeAttachment(index: number) {

View File

@ -27,7 +27,7 @@ export function useRelationship(account: mastodon.v1.Account): Ref<mastodon.v1.R
async function fetchRelationships() {
const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value)
const relationships = await useMastoClient().v1.accounts.fetchRelationships(requested.map(([id]) => id))
const relationships = await useMastoClient().v1.accounts.relationships.fetch({ id: requested.map(([id]) => id) })
for (let i = 0; i < requested.length; i++)
requested[i][1].value = relationships[i]
}
@ -58,7 +58,7 @@ export async function toggleFollowAccount(relationship: mastodon.v1.Relationship
relationship!.following = true
}
relationship = await client.v1.accounts[unfollow ? 'unfollow' : 'follow'](account.id)
relationship = await client.v1.accounts.$select(account.id)[unfollow ? 'unfollow' : 'follow']()
}
export async function toggleMuteAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
@ -74,10 +74,10 @@ export async function toggleMuteAccount(relationship: mastodon.v1.Relationship,
relationship!.muting = !relationship!.muting
relationship = relationship!.muting
? await client.v1.accounts.mute(account.id, {
? await client.v1.accounts.$select(account.id).mute({
// TODO support more options
})
: await client.v1.accounts.unmute(account.id)
: await client.v1.accounts.$select(account.id).unmute()
}
export async function toggleBlockAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
@ -92,7 +92,7 @@ export async function toggleBlockAccount(relationship: mastodon.v1.Relationship,
return
relationship!.blocking = !relationship!.blocking
relationship = await client.v1.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
relationship = await client.v1.accounts.$select(account.id)[relationship!.blocking ? 'block' : 'unblock']()
}
export async function toggleBlockDomain(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
@ -107,5 +107,5 @@ export async function toggleBlockDomain(relationship: mastodon.v1.Relationship,
return
relationship!.domainBlocking = !relationship!.domainBlocking
await client.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
await client.v1.domainBlocks[relationship!.domainBlocking ? 'create' : 'remove']({ domain: getServerName(account) })
}

View File

@ -1,9 +1,9 @@
import type { MaybeRefOrGetter } from '@vueuse/core'
import type { Paginator, mastodon } from 'masto'
import type { mastodon } from 'masto'
import type { RouteLocation } from 'vue-router'
export type UseSearchOptions = MaybeRefOrGetter<
Partial<Omit<mastodon.v1.SearchParams, keyof mastodon.DefaultPaginationParams | 'q'>>
Partial<Omit<mastodon.rest.v2.SearchParams, keyof mastodon.DefaultPaginationParams | 'q'>>
>
export interface BuildSearchResult<K extends keyof any, T> {
@ -30,7 +30,7 @@ export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOpt
const q = $computed(() => resolveUnref(query).trim())
let paginator: Paginator<mastodon.v2.Search, mastodon.v2.SearchParams> | undefined
let paginator: mastodon.Paginator<mastodon.v2.Search, mastodon.rest.v2.SearchParams> | undefined
const appendResults = (results: mastodon.v2.Search, empty = false) => {
if (empty) {
@ -72,7 +72,7 @@ export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOpt
* Based on the source it seems like modifying the params when calling next would result in a new search,
* but that doesn't seem to be the case. So instead we just create a new paginator with the new params.
*/
paginator = client.v2.search({
paginator = client.v2.search.list({
q,
...resolveUnref(options),
resolve: !!currentUser.value,

View File

@ -61,7 +61,7 @@ export function useStatusActions(props: StatusActionsProps) {
const toggleReblog = () => toggleStatusAction(
'reblogged',
() => client.v1.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
() => client.v1.statuses.$select(status.id)[status.reblogged ? 'unreblog' : 'reblog']().then((res) => {
if (status.reblogged)
// returns the original status
return res.reblog!
@ -72,23 +72,23 @@ export function useStatusActions(props: StatusActionsProps) {
const toggleFavourite = () => toggleStatusAction(
'favourited',
() => client.v1.statuses[status.favourited ? 'unfavourite' : 'favourite'](status.id),
() => client.v1.statuses.$select(status.id)[status.favourited ? 'unfavourite' : 'favourite'](),
'favouritesCount',
)
const toggleBookmark = () => toggleStatusAction(
'bookmarked',
() => client.v1.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
() => client.v1.statuses.$select(status.id)[status.bookmarked ? 'unbookmark' : 'bookmark'](),
)
const togglePin = async () => toggleStatusAction(
'pinned',
() => client.v1.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
() => client.v1.statuses.$select(status.id)[status.pinned ? 'unpin' : 'pin'](),
)
const toggleMute = async () => toggleStatusAction(
'muted',
() => client.v1.statuses[status.muted ? 'unmute' : 'mute'](status.id),
() => client.v1.statuses.$select(status.id)[status.muted ? 'unmute' : 'mute'](),
)
return {

View File

@ -25,7 +25,7 @@ function getDefaultVisibility(currentVisibility: mastodon.v1.StatusVisibility) {
: preferredVisibility
}
export function getDefaultDraft(options: Partial<Mutable<mastodon.v1.CreateStatusParams> & Omit<Draft, 'params'>> = {}): Draft {
export function getDefaultDraft(options: Partial<Mutable<mastodon.rest.v1.CreateStatusParams> & Omit<Draft, 'params'>> = {}): Draft {
const {
attachments = [],
initialText = '',

View File

@ -1,10 +1,10 @@
import type { Paginator, WsEvents, mastodon } from 'masto'
import type { mastodon } from 'masto'
import type { Ref } from 'vue'
import type { PaginatorState } from '~/types'
export function usePaginator<T, P, U = T>(
_paginator: Paginator<T[], P>,
stream: Ref<Promise<WsEvents> | undefined>,
_paginator: mastodon.Paginator<T[], P>,
stream: Ref<mastodon.streaming.Subscription | undefined>,
eventType: 'notification' | 'update' = 'update',
preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[],
buffer = 10,
@ -30,10 +30,15 @@ export function usePaginator<T, P, U = T>(
prevItems.value = []
}
watch(stream, (stream) => {
stream?.then((s) => {
s.on(eventType, (status) => {
if ('uri' in status)
watch(stream, async (stream) => {
if (!stream)
return
for await (const entry of stream) {
if (entry.event === 'update') {
const status = entry.payload
if ('uri' in entry)
cacheStatus(status, undefined, true)
const index = prevItems.value.findIndex((i: any) => i.id === status.id)
@ -41,27 +46,27 @@ export function usePaginator<T, P, U = T>(
prevItems.value.splice(index, 1)
prevItems.value.unshift(status as any)
})
// TODO: update statuses
s.on('status.update', (status) => {
}
else if (entry.event === 'status.update') {
const status = entry.payload
cacheStatus(status, undefined, true)
const data = items.value as mastodon.v1.Status[]
const index = data.findIndex(s => s.id === status.id)
if (index >= 0)
data[index] = status
})
}
s.on('delete', (id) => {
else if (entry.event === 'delete') {
const id = entry.payload
removeCachedStatus(id)
const data = items.value as mastodon.v1.Status[]
const index = data.findIndex(s => s.id === id)
if (index >= 0)
data.splice(index, 1)
})
})
}
}
}, { immediate: true })
async function loadNext() {

View File

@ -8,7 +8,7 @@ import { PushSubscriptionError } from '~/composables/push-notifications/types'
export async function createPushSubscription(user: RequiredUserLogin,
notificationData: CreatePushNotification,
policy: mastodon.v1.SubscriptionPolicy = 'all',
policy: mastodon.v1.WebPushSubscriptionPolicy = 'all',
force = false): Promise<mastodon.v1.WebPushSubscription | undefined> {
const { server: serverEndpoint, vapidKey } = user
@ -115,10 +115,10 @@ async function removePushNotificationDataOnError(e: Error) {
async function sendSubscriptionToBackend(
subscription: PushSubscription,
data: CreatePushNotification,
policy: mastodon.v1.SubscriptionPolicy,
policy: mastodon.v1.WebPushSubscriptionPolicy,
): Promise<mastodon.v1.WebPushSubscription> {
const { endpoint, keys } = subscription.toJSON()
const params: mastodon.v1.CreateWebPushSubscriptionParams = {
return await useMastoClient().v1.push.subscription.create({
policy,
subscription: {
endpoint: endpoint!,
@ -128,7 +128,5 @@ async function sendSubscriptionToBackend(
},
},
data,
}
return await useMastoClient().v1.webPushSubscriptions.create(params)
})
}

View File

@ -14,11 +14,11 @@ export interface RequiredUserLogin extends Required<Omit<UserLogin, 'account' |
export interface CreatePushNotification {
alerts?: Partial<mastodon.v1.WebPushSubscriptionAlerts> | null
policy?: mastodon.v1.SubscriptionPolicy
policy?: mastodon.v1.WebPushSubscriptionPolicy
}
export type PushNotificationRequest = Record<string, boolean>
export type PushNotificationPolicy = Record<string, mastodon.v1.SubscriptionPolicy>
export type PushNotificationPolicy = Record<string, mastodon.v1.WebPushSubscriptionPolicy>
export interface CustomEmojisInfo {
lastUpdate: number

View File

@ -61,7 +61,7 @@ export function usePushManager() {
const subscribe = async (
notificationData?: CreatePushNotification,
policy?: mastodon.v1.SubscriptionPolicy,
policy?: mastodon.v1.WebPushSubscriptionPolicy,
force?: boolean,
): Promise<SubscriptionResult> => {
if (!isSupported)
@ -116,7 +116,7 @@ export function usePushManager() {
await removePushNotificationData(currentUser.value)
}
const saveSettings = async (policy?: mastodon.v1.SubscriptionPolicy) => {
const saveSettings = async (policy?: mastodon.v1.WebPushSubscriptionPolicy) => {
if (policy)
pushNotificationData.value.policy = policy
@ -173,7 +173,7 @@ export function usePushManager() {
if (policyChanged)
await subscribe(data, policy, true)
else
currentUser.value.pushSubscription = await client.v1.webPushSubscriptions.update({ data })
currentUser.value.pushSubscription = await client.v1.push.subscription.update({ data })
policyChanged && await nextTick()
@ -198,7 +198,7 @@ export function usePushManager() {
function createRawSettings(
pushSubscription?: mastodon.v1.WebPushSubscription,
subscriptionPolicy?: mastodon.v1.SubscriptionPolicy,
subscriptionPolicy?: mastodon.v1.WebPushSubscriptionPolicy,
) {
return {
follow: pushSubscription?.alerts.follow ?? true,

View File

@ -27,8 +27,8 @@ export const TiptapMentionSuggestion: Partial<SuggestionOptions> = process.serve
if (query.length === 0)
return []
const results = await useMastoClient().v2.search({ q: query, type: 'accounts', limit: 25, resolve: true })
return results.accounts
const paginator = useMastoClient().v2.search.list({ q: query, type: 'accounts', limit: 25, resolve: true })
return (await paginator.next()).value?.accounts ?? []
},
render: createSuggestionRenderer(TiptapMentionList),
}
@ -40,14 +40,14 @@ export const TiptapHashtagSuggestion: Partial<SuggestionOptions> = {
if (query.length === 0)
return []
const results = await useMastoClient().v2.search({
const paginator = useMastoClient().v2.search.list({
q: query,
type: 'hashtags',
limit: 25,
resolve: false,
excludeUnreviewed: true,
})
return results.hashtags
return (await paginator.next()).value?.hashtags ?? []
},
render: createSuggestionRenderer(TiptapHashtagList),
}

View File

@ -149,7 +149,7 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
// if PWA is not enabled, don't get push subscription
useAppConfig().pwaEnabled
// we get 404 response instead empty data
? client.v1.webPushSubscriptions.fetch().catch(() => Promise.resolve(undefined))
? client.v1.push.subscription.fetch().catch(() => Promise.resolve(undefined))
: Promise.resolve(undefined),
])
@ -172,6 +172,7 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
const accountPreferencesMap = new Map<string, Partial<mastodon.v1.Preference>>()
/**
* @param account
* @returns `true` when user ticked the preference to always expand posts with content warnings
*/
export function getExpandSpoilersByDefault(account: mastodon.v1.AccountCredentials) {
@ -179,6 +180,7 @@ export function getExpandSpoilersByDefault(account: mastodon.v1.AccountCredentia
}
/**
* @param account
* @returns `true` when user selected "Always show media" as Media Display preference
*/
export function getExpandMediaByDefault(account: mastodon.v1.AccountCredentials) {
@ -186,13 +188,14 @@ export function getExpandMediaByDefault(account: mastodon.v1.AccountCredentials)
}
/**
* @param account
* @returns `true` when user selected "Always hide media" as Media Display preference
*/
export function getHideMediaByDefault(account: mastodon.v1.AccountCredentials) {
return accountPreferencesMap.get(account.acct)?.['reading:expand:media'] === 'hide_all' ?? false
}
export async function fetchAccountInfo(client: mastodon.Client, server: string) {
export async function fetchAccountInfo(client: mastodon.rest.Client, server: string) {
// Try to fetch user preferences if the backend supports it.
const fetchPrefs = async (): Promise<Partial<mastodon.v1.Preference>> => {
try {
@ -267,7 +270,7 @@ export async function removePushNotifications(user: UserLogin) {
return
// unsubscribe push notifications
await useMastoClient().v1.webPushSubscriptions.remove().catch(() => Promise.resolve())
await useMastoClient().v1.push.subscription.remove().catch(() => Promise.resolve())
}
export async function switchUser(user: UserLogin) {
@ -336,6 +339,8 @@ interface UseUserLocalStorageCache {
/**
* Create reactive storage for the current user
* @param key
* @param initial
*/
export function useUserLocalStorage<T extends object>(key: string, initial: () => T): Ref<T> {
if (process.server || process.test)
@ -382,6 +387,7 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
/**
* Clear all storages for the given account
* @param account
*/
export function clearUserLocalStorage(account?: mastodon.v1.Account) {
if (!account)

View File

@ -43,7 +43,9 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
}
// If we're logged in, search for the local id the account or status corresponds to
const { accounts, statuses } = await masto.client.value.v2.search({ q: `https:/${to.fullPath}`, resolve: true, limit: 1 })
const paginator = masto.client.value.v2.search.list({ q: `https:/${to.fullPath}`, resolve: true, limit: 1 })
const { accounts, statuses } = (await paginator.next()).value ?? { accounts: [], statuses: [] }
if (statuses[0])
return getStatusRoute(statuses[0])

View File

@ -82,7 +82,7 @@
"iso-639-1": "^3.0.0",
"js-yaml": "^4.1.0",
"lru-cache": "^10.0.0",
"masto": "^5.11.3",
"masto": "^6.5.2",
"node-emoji": "^2.1.3",
"nuxt-security": "^0.13.1",
"page-lifecycle": "^0.1.2",
@ -108,7 +108,8 @@
"vue-advanced-cropper": "^2.8.8",
"vue-virtual-scroller": "2.0.0-beta.8",
"workbox-build": "^7.0.0",
"workbox-window": "^7.0.0"
"workbox-window": "^7.0.0",
"ws": "^8.15.1"
},
"devDependencies": {
"@antfu/eslint-config": "^0.41.3",
@ -121,6 +122,7 @@
"@types/js-yaml": "^4.0.5",
"@types/prettier": "^2.7.3",
"@types/wicg-file-system-access": "^2020.9.6",
"@types/ws": "^8.5.10",
"@unlazy/nuxt": "^0.9.3",
"@vue/test-utils": "^2.4.3",
"bumpp": "^9.2.0",

View File

@ -22,7 +22,7 @@ const { data: status, pending, refresh: refreshStatus } = useAsyncData(
const { client } = $(useMasto())
const { data: context, pending: pendingContext, refresh: refreshContext } = useAsyncData(
`context:${id}`,
async () => client.v1.statuses.fetchContext(id),
async () => client.v1.statuses.$select(id).context.fetch(),
{ watch: [isHydrated], immediate: isHydrated.value, lazy: true, default: () => shallowRef() },
)

View File

@ -6,7 +6,7 @@ const handle = $(computedEager(() => params.account as string))
definePageMeta({ name: 'account-followers' })
const account = await fetchAccountByHandle(handle)
const paginator = account ? useMastoClient().v1.accounts.listFollowers(account.id, {}) : null
const paginator = account ? useMastoClient().v1.accounts.$select(account.id).followers.list() : null
const isSelf = useSelfAccount(account)

View File

@ -6,7 +6,7 @@ const handle = $(computedEager(() => params.account as string))
definePageMeta({ name: 'account-following' })
const account = await fetchAccountByHandle(handle)
const paginator = account ? useMastoClient().v1.accounts.listFollowing(account.id, {}) : null
const paginator = account ? useMastoClient().v1.accounts.$select(account.id).following.list() : null
const isSelf = useSelfAccount(account)

View File

@ -14,7 +14,7 @@ function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'account')
}
const paginator = useMastoClient().v1.accounts.listStatuses(account.id, { limit: 30, excludeReplies: true })
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ limit: 30, excludeReplies: true })
if (account) {
useHydratedHead({

View File

@ -7,7 +7,7 @@ const handle = $(computedEager(() => params.account as string))
const account = await fetchAccountByHandle(handle)
const paginator = useMastoClient().v1.accounts.listStatuses(account.id, { onlyMedia: true, excludeReplies: false })
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ onlyMedia: true, excludeReplies: false })
if (account) {
useHydratedHead({

View File

@ -7,7 +7,7 @@ const handle = $(computedEager(() => params.account as string))
const account = await fetchAccountByHandle(handle)
const paginator = useMastoClient().v1.accounts.listStatuses(account.id, { excludeReplies: false })
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ excludeReplies: false })
if (account) {
useHydratedHead({

View File

@ -3,7 +3,7 @@ import { STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS } from '~~/constants'
const { t } = useI18n()
const paginator = useMastoClient().v1.trends.listStatuses()
const paginator = useMastoClient().v1.trends.statuses.list()
const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS, false)

View File

@ -3,7 +3,7 @@ import { STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS } from '~~/constants'
const { t } = useI18n()
const paginator = useMastoClient().v1.trends.listLinks()
const paginator = useMastoClient().v1.trends.links.list()
const hideNewsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS, false)

View File

@ -4,7 +4,7 @@ import { STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS } from '~~/constants'
const { t } = useI18n()
const { client } = $(useMasto())
const paginator = client.v1.trends.listTags({
const paginator = client.v1.trends.tags.list({
limit: 20,
})

View File

@ -32,7 +32,7 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
)
const { client } = $(useMasto())
const { data: listInfo, refresh } = $(await useAsyncData(() => client.v1.lists.fetch(list), { default: () => shallowRef() }))
const { data: listInfo, refresh } = $(await useAsyncData(() => client.v1.lists.$select(list).fetch(), { default: () => shallowRef() }))
if (listInfo) {
useHydratedHead({

View File

@ -6,7 +6,7 @@ definePageMeta({
const params = useRoute().params
const listId = $(computedEager(() => params.list as string))
const paginator = useMastoClient().v1.lists.listAccounts(listId)
const paginator = useMastoClient().v1.lists.$select(listId).accounts.list()
</script>
<template>

View File

@ -8,8 +8,8 @@ const listId = $(computedEager(() => params.list as string))
const client = useMastoClient()
const paginator = client.v1.timelines.listList(listId)
const stream = useStreaming(client => client.v1.stream.streamListTimeline(listId))
const paginator = client.v1.timelines.list.$select(listId).list()
const stream = useStreaming(client => client.list.subscribe({ list: listId }))
</script>
<template>

View File

@ -7,10 +7,10 @@ const params = useRoute().params
const tagName = $(computedEager(() => params.tag as string))
const { client } = $(useMasto())
const { data: tag, refresh } = $(await useAsyncData(() => client.v1.tags.fetch(tagName), { default: () => shallowRef() }))
const { data: tag, refresh } = $(await useAsyncData(() => client.v1.tags.$select(tagName).fetch(), { default: () => shallowRef() }))
const paginator = client.v1.timelines.listHashtag(tagName)
const stream = useStreaming(client => client.v1.stream.streamTagTimeline(tagName))
const paginator = client.v1.timelines.tag.$select(tagName).list()
const stream = useStreaming(client => client.hashtag.subscribe({ tag: tagName }))
if (tag) {
useHydratedHead({

View File

@ -60,7 +60,7 @@ const { submit, submitting } = submitter(async ({ dirtyFields }) => {
if (!isCanSubmit.value)
return
const res = await client.v1.accounts.updateCredentials(dirtyFields.value as mastodon.v1.UpdateCredentialsParams)
const res = await client.v1.accounts.updateCredentials(dirtyFields.value as mastodon.rest.v1.UpdateCredentialsParams)
.then(account => ({ account }))
.catch((error: Error) => ({ error }))

View File

@ -168,8 +168,8 @@ importers:
specifier: ^10.0.0
version: 10.0.1
masto:
specifier: ^5.11.3
version: 5.11.3
specifier: ^6.5.2
version: 6.5.2
node-emoji:
specifier: ^2.1.3
version: 2.1.3
@ -211,10 +211,10 @@ importers:
version: 5.0.1
tauri-plugin-log-api:
specifier: github:tauri-apps/tauri-plugin-log
version: github.com/tauri-apps/tauri-plugin-log/91dd3fe9dcaa69bae62706e75702a0ce18ce9885
version: github.com/tauri-apps/tauri-plugin-log/19f5dcc0425e9127d2c591780e5047b83e77a7c2
tauri-plugin-store-api:
specifier: github:tauri-apps/tauri-plugin-store
version: github.com/tauri-apps/tauri-plugin-store/3367248b2661abcb73d2a89f30df780c387fb57d
version: github.com/tauri-apps/tauri-plugin-store/7d2632996f290b0f18cc5f8a2b2791046400690e
theme-vitesse:
specifier: ^0.7.2
version: 0.7.2
@ -248,6 +248,9 @@ importers:
workbox-window:
specifier: ^7.0.0
version: 7.0.0
ws:
specifier: ^8.15.1
version: 8.15.1
devDependencies:
'@antfu/eslint-config':
specifier: ^0.41.3
@ -279,6 +282,9 @@ importers:
'@types/wicg-file-system-access':
specifier: ^2020.9.6
version: 2020.9.6
'@types/ws':
specifier: ^8.5.10
version: 8.5.10
'@unlazy/nuxt':
specifier: ^0.9.3
version: 0.9.3(rollup@2.79.1)
@ -2699,18 +2705,6 @@ packages:
- encoding
- supports-color
/@mastojs/ponyfills@1.0.4:
resolution: {integrity: sha512-1NaIGmcU7OmyNzx0fk+cYeGTkdXlOJOSdetaC4pStVWsrhht2cdlYSAfe5NDW3FcUmcEm2vVceB9lcClN1RCxw==}
dependencies:
'@types/node': 18.16.19
'@types/node-fetch': 2.6.4
abort-controller: 3.0.0
form-data: 4.0.0
node-fetch: 2.6.12
transitivePeerDependencies:
- encoding
dev: false
/@netlify/functions@2.4.0:
resolution: {integrity: sha512-dIqhdj5u4Lu/8qbYwtYpn8NfvIyPHbSTV2lAP4ocL+iwC9As06AXT0wa/xOpO2vRWJa0IMxdZaqCPnkyHlHiyg==}
engines: {node: '>=14.0.0'}
@ -4172,8 +4166,8 @@ packages:
string.prototype.matchall: 4.0.8
dev: false
/@tauri-apps/api@1.5.1:
resolution: {integrity: sha512-6unsZDOdlXTmauU3NhWhn+Cx0rODV+rvNvTdvolE5Kls5ybA6cqndQENDt1+FS0tF7ozCP66jwWoH6a5h90BrA==}
/@tauri-apps/api@1.5.3:
resolution: {integrity: sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
@ -4574,17 +4568,6 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
/@types/node-fetch@2.6.4:
resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
dependencies:
'@types/node': 20.8.6
form-data: 3.0.1
dev: false
/@types/node@18.16.19:
resolution: {integrity: sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==}
dev: false
/@types/node@20.8.6:
resolution: {integrity: sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==}
dependencies:
@ -4650,6 +4633,12 @@ packages:
resolution: {integrity: sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==}
dev: true
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
'@types/node': 20.8.6
dev: true
/@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -6220,13 +6209,6 @@ packages:
/abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
dev: false
/acorn-import-assertions@1.9.0(acorn@8.11.2):
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
peerDependencies:
@ -8276,13 +8258,13 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
dev: false
/eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
dev: true
/events-to-async@2.0.1:
resolution: {integrity: sha512-RtnLYrMbXp4JkZIoZu+3VTqV21bNVBlJBZ4NmtwvMNqSE3qouhxv2gvLE4JJDaQc54ioPkrX74V6x+hp/hqjkQ==}
dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
@ -8476,15 +8458,6 @@ packages:
cross-spawn: 7.0.3
signal-exit: 4.1.0
/form-data@3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
@ -9511,12 +9484,12 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/isomorphic-ws@5.0.0(ws@8.14.2):
/isomorphic-ws@5.0.0(ws@8.15.1):
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
peerDependencies:
ws: '*'
dependencies:
ws: 8.14.2
ws: 8.15.1
dev: false
/jackspeak@2.2.1:
@ -10015,19 +9988,16 @@ packages:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: true
/masto@5.11.3:
resolution: {integrity: sha512-GtSnrqm5fHPaaU0iwag4LCmvpp82rDng6yOZinmOJHHlUfo6Gnq5QY6x3lJCxCnsPIXpTu1yaX42bWrSQyoQPA==}
/masto@6.5.2:
resolution: {integrity: sha512-JfnG7MSQmhszWnLsvdBuxXc2tcVUyCvlTxnSH/5S+In4dU1tvc1wGhFR87kO+YW8gfDsDw9CHh+AD/z+DbTTfQ==}
dependencies:
'@mastojs/ponyfills': 1.0.4
change-case: 4.1.2
eventemitter3: 5.0.1
isomorphic-ws: 5.0.0(ws@8.14.2)
qs: 6.11.2
semver: 7.5.4
ws: 8.14.2
events-to-async: 2.0.1
isomorphic-ws: 5.0.0(ws@8.15.1)
ts-custom-error: 3.3.1
ws: 8.15.1
transitivePeerDependencies:
- bufferutil
- encoding
- utf-8-validate
dev: false
@ -12310,13 +12280,6 @@ packages:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
/qs@6.11.2:
resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.4
dev: false
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -13231,7 +13194,7 @@ packages:
hasBin: true
peerDependencies:
'@nuxt/kit': ^3.0.0
'@nuxt/schema': 3.8.2
'@nuxt/schema': ^3.0.0
peerDependenciesMeta:
'@nuxt/kit':
optional: true
@ -13681,6 +13644,11 @@ packages:
typescript: 5.1.6
dev: true
/ts-custom-error@3.3.1:
resolution: {integrity: sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==}
engines: {node: '>=14.0.0'}
dev: false
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
@ -15354,6 +15322,19 @@ packages:
utf-8-validate:
optional: true
/ws@8.15.1:
resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
@ -15453,18 +15434,18 @@ packages:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
github.com/tauri-apps/tauri-plugin-log/91dd3fe9dcaa69bae62706e75702a0ce18ce9885:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/91dd3fe9dcaa69bae62706e75702a0ce18ce9885}
github.com/tauri-apps/tauri-plugin-log/19f5dcc0425e9127d2c591780e5047b83e77a7c2:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/19f5dcc0425e9127d2c591780e5047b83e77a7c2}
name: tauri-plugin-log-api
version: 0.0.0
dependencies:
'@tauri-apps/api': 1.5.1
'@tauri-apps/api': 1.5.3
dev: false
github.com/tauri-apps/tauri-plugin-store/3367248b2661abcb73d2a89f30df780c387fb57d:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/3367248b2661abcb73d2a89f30df780c387fb57d}
github.com/tauri-apps/tauri-plugin-store/7d2632996f290b0f18cc5f8a2b2791046400690e:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/7d2632996f290b0f18cc5f8a2b2791046400690e}
name: tauri-plugin-store-api
version: 0.0.0
dependencies:
'@tauri-apps/api': 1.5.1
'@tauri-apps/api': 1.5.3
dev: false

8
tests/setup.ts Normal file
View File

@ -0,0 +1,8 @@
// We have TypeError: AbortSignal.timeout is not a function when running tests against masto.js v6
if (!AbortSignal.timeout) {
AbortSignal.timeout = (ms) => {
const controller = new AbortController()
setTimeout(() => controller.abort(new DOMException('TimeoutError')), ms)
return controller.signal
}
}

View File

@ -47,7 +47,7 @@ export type TranslateFn = ReturnType<typeof useI18n>['t']
export interface Draft {
editingStatus?: mastodon.v1.Status
initialText?: string
params: MarkNonNullable<Mutable<Omit<mastodon.v1.CreateStatusParams, 'poll'>>, 'status' | 'language' | 'sensitive' | 'spoilerText' | 'visibility'> & { poll: Mutable<mastodon.v1.CreateStatusParams['poll']> }
params: MarkNonNullable<Mutable<Omit<mastodon.rest.v1.CreateStatusParams, 'poll'>>, 'status' | 'language' | 'sensitive' | 'spoilerText' | 'visibility'> & { poll: Mutable<mastodon.rest.v1.CreateStatusParams['poll']> }
attachments: mastodon.v1.MediaAttachment[]
lastUpdated: number
mentions?: string[]

View File

@ -4,4 +4,9 @@ export default defineVitestConfig({
define: {
'process.test': 'true',
},
test: {
setupFiles: [
'/tests/setup.ts',
],
},
})