refactor: no reactivity transform ()

This commit is contained in:
patak 2024-02-21 16:20:08 +01:00 committed by GitHub
parent b9394c2fa5
commit ccfa7a8d10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
102 changed files with 649 additions and 652 deletions
components
account
aria
command
common
content
conversation
emoji
list
magickeys
nav
notification
publish
pwa
search
settings
status
tag
timeline
tiptap
user
composables
nuxt.config.ts
pages

View file

@ -6,8 +6,8 @@ defineProps<{
square?: boolean
}>()
const loaded = $ref(false)
const error = $ref(false)
const loaded = ref(false)
const error = ref(false)
</script>
<template>

View file

@ -5,7 +5,7 @@ defineOptions({
inheritAttrs: false,
})
const { account, as = 'div' } = $defineProps<{
const { account, as = 'div' } = defineProps<{
account: mastodon.v1.Account
as?: string
}>()

View file

@ -10,35 +10,35 @@ const { account, command, context, ...props } = defineProps<{
}>()
const { t } = useI18n()
const isSelf = $(useSelfAccount(() => account))
const enable = $computed(() => !isSelf && currentUser.value)
const relationship = $computed(() => props.relationship || useRelationship(account).value)
const isSelf = useSelfAccount(() => account)
const enable = computed(() => !isSelf.value && currentUser.value)
const relationship = computed(() => props.relationship || useRelationship(account).value)
const { client } = $(useMasto())
const { client } = useMasto()
async function unblock() {
relationship!.blocking = false
relationship.value!.blocking = false
try {
const newRel = await client.v1.accounts.$select(account.id).unblock()
const newRel = await client.value.v1.accounts.$select(account.id).unblock()
Object.assign(relationship!, newRel)
}
catch (err) {
console.error(err)
// TODO error handling
relationship!.blocking = true
relationship.value!.blocking = true
}
}
async function unmute() {
relationship!.muting = false
relationship.value!.muting = false
try {
const newRel = await client.v1.accounts.$select(account.id).unmute()
const newRel = await client.value.v1.accounts.$select(account.id).unmute()
Object.assign(relationship!, newRel)
}
catch (err) {
console.error(err)
// TODO error handling
relationship!.muting = true
relationship.value!.muting = true
}
}
@ -46,21 +46,21 @@ useCommand({
scope: 'Actions',
order: -2,
visible: () => command && enable,
name: () => `${relationship?.following ? t('account.unfollow') : t('account.follow')} ${getShortHandle(account)}`,
name: () => `${relationship.value?.following ? t('account.unfollow') : t('account.follow')} ${getShortHandle(account)}`,
icon: 'i-ri:star-line',
onActivate: () => toggleFollowAccount(relationship!, account),
onActivate: () => toggleFollowAccount(relationship.value!, account),
})
const buttonStyle = $computed(() => {
if (relationship?.blocking)
const buttonStyle = computed(() => {
if (relationship.value?.blocking)
return 'text-inverted bg-red border-red'
if (relationship?.muting)
if (relationship.value?.muting)
return 'text-base bg-card border-base'
// If following, use a label style with a strong border for Mutuals
if (relationship ? relationship.following : context === 'following')
return `text-base ${relationship?.followedBy ? 'border-strong' : 'border-base'}`
if (relationship.value ? relationship.value.following : context === 'following')
return `text-base ${relationship.value?.followedBy ? 'border-strong' : 'border-base'}`
// If not following, use a button style
return 'text-inverted bg-primary border-primary'

View file

@ -5,32 +5,32 @@ const { account, ...props } = defineProps<{
account: mastodon.v1.Account
relationship?: mastodon.v1.Relationship
}>()
const relationship = $computed(() => props.relationship || useRelationship(account).value)
const { client } = $(useMasto())
const relationship = computed(() => props.relationship || useRelationship(account).value)
const { client } = useMasto()
async function authorizeFollowRequest() {
relationship!.requestedBy = false
relationship!.followedBy = true
relationship.value!.requestedBy = false
relationship.value!.followedBy = true
try {
const newRel = await client.v1.followRequests.$select(account.id).authorize()
const newRel = await client.value.v1.followRequests.$select(account.id).authorize()
Object.assign(relationship!, newRel)
}
catch (err) {
console.error(err)
relationship!.requestedBy = true
relationship!.followedBy = false
relationship.value!.requestedBy = true
relationship.value!.followedBy = false
}
}
async function rejectFollowRequest() {
relationship!.requestedBy = false
relationship.value!.requestedBy = false
try {
const newRel = await client.v1.followRequests.$select(account.id).reject()
const newRel = await client.value.v1.followRequests.$select(account.id).reject()
Object.assign(relationship!, newRel)
}
catch (err) {
console.error(err)
relationship!.requestedBy = true
relationship.value!.requestedBy = true
}
}
</script>

View file

@ -5,7 +5,7 @@ const { account } = defineProps<{
account: mastodon.v1.Account
}>()
const serverName = $computed(() => getServerName(account))
const serverName = computed(() => getServerName(account))
</script>
<template>

View file

@ -6,22 +6,22 @@ const { account } = defineProps<{
command?: boolean
}>()
const { client } = $(useMasto())
const { client } = useMasto()
const { t } = useI18n()
const createdAt = $(useFormattedDateTime(() => account.createdAt, {
const createdAt = useFormattedDateTime(() => account.createdAt, {
month: 'long',
day: 'numeric',
year: 'numeric',
}))
})
const relationship = $(useRelationship(account))
const relationship = useRelationship(account)
const namedFields = ref<mastodon.v1.AccountField[]>([])
const iconFields = ref<mastodon.v1.AccountField[]>([])
const isEditingPersonalNote = ref<boolean>(false)
const hasHeader = $computed(() => !account.header.endsWith('/original/missing.png'))
const hasHeader = computed(() => !account.header.endsWith('/original/missing.png'))
const isCopied = ref<boolean>(false)
function getFieldIconTitle(fieldName: string) {
@ -29,7 +29,7 @@ function getFieldIconTitle(fieldName: string) {
}
function getNotificationIconTitle() {
return relationship?.notifying ? t('account.notifications_on_post_disable', { username: `@${account.username}` }) : t('account.notifications_on_post_enable', { username: `@${account.username}` })
return relationship.value?.notifying ? t('account.notifications_on_post_disable', { username: `@${account.username}` }) : t('account.notifications_on_post_enable', { username: `@${account.username}` })
}
function previewHeader() {
@ -51,14 +51,14 @@ function previewAvatar() {
}
async function toggleNotifications() {
relationship!.notifying = !relationship?.notifying
relationship.value!.notifying = !relationship.value?.notifying
try {
const newRel = await client.v1.accounts.$select(account.id).follow({ notify: relationship?.notifying })
const newRel = await client.value.v1.accounts.$select(account.id).follow({ notify: relationship.value?.notifying })
Object.assign(relationship!, newRel)
}
catch {
// TODO error handling
relationship!.notifying = !relationship?.notifying
relationship.value!.notifying = !relationship.value?.notifying
}
}
@ -75,35 +75,35 @@ watchEffect(() => {
})
icons.push({
name: 'Joined',
value: createdAt,
value: createdAt.value,
})
namedFields.value = named
iconFields.value = icons
})
const personalNoteDraft = ref(relationship?.note ?? '')
watch($$(relationship), (relationship, oldValue) => {
const personalNoteDraft = ref(relationship.value?.note ?? '')
watch(relationship, (relationship, oldValue) => {
if (!oldValue && relationship)
personalNoteDraft.value = relationship.note ?? ''
})
async function editNote(event: Event) {
if (!event.target || !('value' in event.target) || !relationship)
if (!event.target || !('value' in event.target) || !relationship.value)
return
const newNote = event.target?.value as string
if (relationship.note?.trim() === newNote.trim())
if (relationship.value.note?.trim() === newNote.trim())
return
const newNoteApiResult = await client.v1.accounts.$select(account.id).note.create({ comment: newNote })
relationship.note = newNoteApiResult.note
personalNoteDraft.value = relationship.note ?? ''
const newNoteApiResult = await client.value.v1.accounts.$select(account.id).note.create({ comment: newNote })
relationship.value.note = newNoteApiResult.note
personalNoteDraft.value = relationship.value.note ?? ''
}
const isSelf = $(useSelfAccount(() => account))
const isNotifiedOnPost = $computed(() => !!relationship?.notifying)
const isSelf = useSelfAccount(() => account)
const isNotifiedOnPost = computed(() => !!relationship.value?.notifying)
const personalNoteMaxLength = 2000

View file

@ -5,7 +5,7 @@ const { account } = defineProps<{
account: mastodon.v1.Account
}>()
const relationship = $(useRelationship(account))
const relationship = useRelationship(account)
</script>
<template>

View file

@ -11,12 +11,12 @@ const emit = defineEmits<{
(evt: 'removeNote'): void
}>()
let relationship = $(useRelationship(account))
const relationship = useRelationship(account)
const isSelf = $(useSelfAccount(() => account))
const isSelf = useSelfAccount(() => account)
const { t } = useI18n()
const { client } = $(useMasto())
const { client } = useMasto()
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
const { share, isSupported: isShareSupported } = useShare()
@ -25,7 +25,7 @@ function shareAccount() {
}
async function toggleReblogs() {
if (!relationship!.showingReblogs && await openConfirmDialog({
if (!relationship.value!.showingReblogs && await openConfirmDialog({
title: t('confirm.show_reblogs.title'),
description: t('confirm.show_reblogs.description', [account.acct]),
confirm: t('confirm.show_reblogs.confirm'),
@ -33,8 +33,8 @@ async function toggleReblogs() {
}) !== 'confirm')
return
const showingReblogs = !relationship?.showingReblogs
relationship = await client.v1.accounts.$select(account.id).follow({ reblogs: showingReblogs })
const showingReblogs = !relationship.value?.showingReblogs
relationship.value = await client.value.v1.accounts.$select(account.id).follow({ reblogs: showingReblogs })
}
async function addUserNote() {
@ -42,11 +42,11 @@ async function addUserNote() {
}
async function removeUserNote() {
if (!relationship!.note || relationship!.note.length === 0)
if (!relationship.value!.note || relationship.value!.note.length === 0)
return
const newNote = await client.v1.accounts.$select(account.id).note.create({ comment: '' })
relationship!.note = newNote.note
const newNote = await client.value.v1.accounts.$select(account.id).note.create({ comment: '' })
relationship.value!.note = newNote.note
emit('removeNote')
}
</script>

View file

@ -8,10 +8,10 @@ const { paginator, account, context } = defineProps<{
relationshipContext?: 'followedBy' | 'following'
}>()
const fallbackContext = $computed(() => {
const fallbackContext = computed(() => {
return ['following', 'followers'].includes(context!)
})
const showOriginSite = $computed(() =>
const showOriginSite = computed(() =>
account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
)
</script>

View file

@ -4,15 +4,15 @@ import type { CommonRouteTabOption } from '../common/CommonRouteTabs.vue'
const { t } = useI18n()
const route = useRoute()
const server = $(computedEager(() => route.params.server as string))
const account = $(computedEager(() => route.params.account as string))
const server = computedEager(() => route.params.server as string)
const account = computedEager(() => route.params.account as string)
const tabs = $computed<CommonRouteTabOption[]>(() => [
const tabs = computed<CommonRouteTabOption[]>(() => [
{
name: 'account-index',
to: {
name: 'account-index',
params: { server, account },
params: { server: server.value, account: account.value },
},
display: t('tab.posts'),
icon: 'i-ri:file-list-2-line',
@ -21,7 +21,7 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
name: 'account-replies',
to: {
name: 'account-replies',
params: { server, account },
params: { server: server.value, account: account.value },
},
display: t('tab.posts_with_replies'),
icon: 'i-ri:chat-1-line',
@ -30,7 +30,7 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
name: 'account-media',
to: {
name: 'account-media',
params: { server, account },
params: { server: server.value, account: account.value },
},
display: t('tab.media'),
icon: 'i-ri:camera-2-line',

View file

@ -11,16 +11,16 @@ const localeMap = (locales.value as LocaleObject[]).reduce((acc, l) => {
return acc
}, {} as Record<string, string>)
let ariaLive = $ref<AriaLive>('polite')
let ariaMessage = $ref<string>('')
const ariaLive = ref<AriaLive>('polite')
const ariaMessage = ref<string>('')
function onMessage(event: AriaAnnounceType, message?: string) {
if (event === 'announce')
ariaMessage = message!
ariaMessage.value = message!
else if (event === 'mute')
ariaLive = 'off'
ariaLive.value = 'off'
else
ariaLive = 'polite'
ariaLive.value = 'polite'
}
watch(locale, (l, ol) => {

View file

@ -1,19 +1,19 @@
<script lang="ts" setup>
import type { ResolvedCommand } from '~/composables/command'
const emit = defineEmits<{
(event: 'activate'): void
}>()
const {
cmd,
index,
active = false,
} = $defineProps<{
} = defineProps<{
cmd: ResolvedCommand
index: number
active?: boolean
}>()
const emit = defineEmits<{
(event: 'activate'): void
}>()
</script>
<template>

View file

@ -5,7 +5,7 @@ const props = defineProps<{
const isMac = useIsMac()
const keys = $computed(() => props.name.toLowerCase().split('+'))
const keys = computed(() => props.name.toLowerCase().split('+'))
</script>
<template>

View file

@ -10,21 +10,21 @@ const registry = useCommandRegistry()
const router = useRouter()
const inputEl = $ref<HTMLInputElement>()
const resultEl = $ref<HTMLDivElement>()
const inputEl = ref<HTMLInputElement>()
const resultEl = ref<HTMLDivElement>()
const scopes = $ref<CommandScope[]>([])
let input = $(commandPanelInput)
const scopes = ref<CommandScope[]>([])
const input = commandPanelInput
onMounted(() => {
inputEl?.focus()
inputEl.value?.focus()
})
const commandMode = $computed(() => input.startsWith('>'))
const commandMode = computed(() => input.value.startsWith('>'))
const query = $computed(() => commandMode ? '' : input.trim())
const query = computed(() => commandMode ? '' : input.value.trim())
const { accounts, hashtags, loading } = useSearch($$(query))
const { accounts, hashtags, loading } = useSearch(query)
function toSearchQueryResultItem(search: SearchResultType): QueryResultItem {
return {
@ -35,8 +35,8 @@ function toSearchQueryResultItem(search: SearchResultType): QueryResultItem {
}
}
const searchResult = $computed<QueryResult>(() => {
if (query.length === 0 || loading.value)
const searchResult = computed<QueryResult>(() => {
if (query.value.length === 0 || loading.value)
return { length: 0, items: [], grouped: {} as any }
// TODO extract this scope
@ -61,22 +61,22 @@ const searchResult = $computed<QueryResult>(() => {
}
})
const result = $computed<QueryResult>(() => commandMode
? registry.query(scopes.map(s => s.id).join('.'), input.slice(1).trim())
: searchResult,
const result = computed<QueryResult>(() => commandMode
? registry.query(scopes.value.map(s => s.id).join('.'), input.value.slice(1).trim())
: searchResult.value,
)
const isMac = useIsMac()
const modifierKeyName = $computed(() => isMac.value ? '⌘' : 'Ctrl')
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
let active = $ref(0)
watch($$(result), (n, o) => {
const active = ref(0)
watch(result, (n, o) => {
if (n.length !== o.length || !n.items.every((i, idx) => i === o.items[idx]))
active = 0
active.value = 0
})
function findItemEl(index: number) {
return resultEl?.querySelector(`[data-index="${index}"]`) as HTMLDivElement | null
return resultEl.value?.querySelector(`[data-index="${index}"]`) as HTMLDivElement | null
}
function onCommandActivate(item: QueryResultItem) {
if (item.onActivate) {
@ -84,14 +84,14 @@ function onCommandActivate(item: QueryResultItem) {
emit('close')
}
else if (item.onComplete) {
scopes.push(item.onComplete())
input = '> '
scopes.value.push(item.onComplete())
input.value = '> '
}
}
function onCommandComplete(item: QueryResultItem) {
if (item.onComplete) {
scopes.push(item.onComplete())
input = '> '
scopes.value.push(item.onComplete())
input.value = '> '
}
else if (item.onActivate) {
item.onActivate()
@ -105,9 +105,9 @@ function intoView(index: number) {
}
function setActive(index: number) {
const len = result.length
active = (index + len) % len
intoView(active)
const len = result.value.length
active.value = (index + len) % len
intoView(active.value)
}
function onKeyDown(e: KeyboardEvent) {
@ -118,7 +118,7 @@ function onKeyDown(e: KeyboardEvent) {
break
e.preventDefault()
setActive(active - 1)
setActive(active.value - 1)
break
}
@ -128,7 +128,7 @@ function onKeyDown(e: KeyboardEvent) {
break
e.preventDefault()
setActive(active + 1)
setActive(active.value + 1)
break
}
@ -136,9 +136,9 @@ function onKeyDown(e: KeyboardEvent) {
case 'Home': {
e.preventDefault()
active = 0
active.value = 0
intoView(active)
intoView(active.value)
break
}
@ -146,7 +146,7 @@ function onKeyDown(e: KeyboardEvent) {
case 'End': {
e.preventDefault()
setActive(result.length - 1)
setActive(result.value.length - 1)
break
}
@ -154,7 +154,7 @@ function onKeyDown(e: KeyboardEvent) {
case 'Enter': {
e.preventDefault()
const cmd = result.items[active]
const cmd = result.value.items[active.value]
if (cmd)
onCommandActivate(cmd)
@ -164,7 +164,7 @@ function onKeyDown(e: KeyboardEvent) {
case 'Tab': {
e.preventDefault()
const cmd = result.items[active]
const cmd = result.value.items[active.value]
if (cmd)
onCommandComplete(cmd)
@ -172,9 +172,9 @@ function onKeyDown(e: KeyboardEvent) {
}
case 'Backspace': {
if (input === '>' && scopes.length) {
if (input.value === '>' && scopes.value.length) {
e.preventDefault()
scopes.pop()
scopes.value.pop()
}
break
}

View file

@ -44,7 +44,7 @@ defineSlots<{
const { t } = useI18n()
const nuxtApp = useNuxtApp()
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, $$(stream), preprocess)
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, toRef(() => stream), preprocess)
nuxtApp.hook('elk-logo:click', () => {
update()

View file

@ -1,6 +1,14 @@
<script setup lang="ts">
import type { RouteLocationRaw } from 'vue-router'
const { options, command, replace, preventScrollTop = false, moreOptions } = defineProps<{
options: CommonRouteTabOption[]
moreOptions?: CommonRouteTabMoreOption
command?: boolean
replace?: boolean
preventScrollTop?: boolean
}>()
const { t } = useI18n()
export interface CommonRouteTabOption {
@ -18,14 +26,6 @@ export interface CommonRouteTabMoreOption {
tooltip?: string
match?: boolean
}
const { options, command, replace, preventScrollTop = false, moreOptions } = $defineProps<{
options: CommonRouteTabOption[]
moreOptions?: CommonRouteTabMoreOption
command?: boolean
replace?: boolean
preventScrollTop?: boolean
}>()
const router = useRouter()
useCommands(() => command

View file

@ -10,7 +10,7 @@ const { options, command } = defineProps<{
const modelValue = defineModel<string>({ required: true })
const tabs = $computed(() => {
const tabs = computed(() => {
return options.map((option) => {
if (typeof option === 'string')
return { name: option, display: option }
@ -24,7 +24,7 @@ function toValidName(otpion: string) {
}
useCommands(() => command
? tabs.map(tab => ({
? tabs.value.map(tab => ({
scope: 'Tabs',
name: tab.display,

View file

@ -4,15 +4,15 @@ import type { mastodon } from 'masto'
const {
history,
maxDay = 2,
} = $defineProps<{
} = defineProps<{
history: mastodon.v1.TagHistory[]
maxDay?: number
}>()
const ongoingHot = $computed(() => history.slice(0, maxDay))
const ongoingHot = computed(() => history.slice(0, maxDay))
const people = $computed(() =>
ongoingHot.reduce((total: number, item) => total + (Number(item.accounts) || 0), 0),
const people = computed(() =>
ongoingHot.value.reduce((total: number, item) => total + (Number(item.accounts) || 0), 0),
)
</script>

View file

@ -6,22 +6,22 @@ const {
history,
width = 60,
height = 40,
} = $defineProps<{
} = defineProps<{
history?: mastodon.v1.TagHistory[]
width?: number
height?: number
}>()
const historyNum = $computed(() => {
const historyNum = computed(() => {
if (!history)
return [1, 1, 1, 1, 1, 1, 1]
return [...history].reverse().map(item => Number(item.accounts) || 0)
})
const sparklineEl = $ref<SVGSVGElement>()
const sparklineEl = ref<SVGSVGElement>()
const sparklineFn = typeof sparkline !== 'function' ? (sparkline as any).default : sparkline
watch([$$(historyNum), $$(sparklineEl)], ([historyNum, sparklineEl]) => {
watch([historyNum, sparklineEl], ([historyNum, sparklineEl]) => {
if (!sparklineEl)
return
sparklineFn(sparklineEl, historyNum)

View file

@ -10,9 +10,9 @@ const props = defineProps<{
const { formatHumanReadableNumber, formatNumber, forSR } = useHumanReadableNumber()
const useSR = $computed(() => forSR(props.count))
const rawNumber = $computed(() => formatNumber(props.count))
const humanReadableNumber = $computed(() => formatHumanReadableNumber(props.count))
const useSR = computed(() => forSR(props.count))
const rawNumber = computed(() => formatNumber(props.count))
const humanReadableNumber = computed(() => formatHumanReadableNumber(props.count))
</script>
<template>

View file

@ -6,11 +6,11 @@ defineProps<{
autoBoundaryMaxSize?: boolean
}>()
const dropdown = $ref<any>()
const dropdown = ref<any>()
const colorMode = useColorMode()
function hide() {
return dropdown.hide()
return dropdown.value.hide()
}
provide(InjectionKeyDropdownContext, {
hide,

View file

@ -4,7 +4,7 @@ const props = defineProps<{
lang?: string
}>()
const raw = $computed(() => decodeURIComponent(props.code).replace(/&#39;/g, '\''))
const raw = computed(() => decodeURIComponent(props.code).replace(/&#39;/g, '\''))
const langMap: Record<string, string> = {
js: 'javascript',
@ -13,7 +13,7 @@ const langMap: Record<string, string> = {
}
const highlighted = computed(() => {
return props.lang ? highlightCode(raw, (langMap[props.lang] || props.lang) as any) : raw
return props.lang ? highlightCode(raw.value, (langMap[props.lang] || props.lang) as any) : raw
})
</script>

View file

@ -5,7 +5,7 @@ const { conversation } = defineProps<{
conversation: mastodon.v1.Conversation
}>()
const withAccounts = $computed(() =>
const withAccounts = computed(() =>
conversation.accounts.filter(account => account.id !== conversation.lastStatus?.account.id),
)
</script>

View file

@ -1,26 +1,26 @@
<script setup lang="ts">
const { as, alt, dataEmojiId } = $defineProps<{
const { as, alt, dataEmojiId } = defineProps<{
as: string
alt?: string
dataEmojiId?: string
}>()
let title = $ref<string | undefined>()
const title = ref<string | undefined>()
if (alt) {
if (alt.startsWith(':')) {
title = alt.replace(/:/g, '')
title.value = alt.replace(/:/g, '')
}
else {
import('node-emoji').then(({ find }) => {
title = find(alt)?.key.replace(/_/g, ' ')
title.value = find(alt)?.key.replace(/_/g, ' ')
})
}
}
// if it has a data-emoji-id, use that as the title instead
if (dataEmojiId)
title = dataEmojiId
title.value = dataEmojiId
</script>
<template>

View file

@ -15,23 +15,23 @@ const { form, isDirty, submitter, reset } = useForm({
form: () => ({ ...list.value }),
})
let isEditing = $ref<boolean>(false)
let deleting = $ref<boolean>(false)
let actionError = $ref<string | undefined>(undefined)
const isEditing = ref<boolean>(false)
const deleting = ref<boolean>(false)
const actionError = ref<string | undefined>(undefined)
const input = ref<HTMLInputElement>()
const editBtn = ref<HTMLButtonElement>()
const deleteBtn = ref<HTMLButtonElement>()
async function prepareEdit() {
isEditing = true
actionError = undefined
isEditing.value = true
actionError.value = undefined
await nextTick()
input.value?.focus()
}
async function cancelEdit() {
isEditing = false
actionError = undefined
isEditing.value = false
actionError.value = undefined
reset()
await nextTick()
@ -47,14 +47,14 @@ const { submit, submitting } = submitter(async () => {
}
catch (err) {
console.error(err)
actionError = (err as Error).message
actionError.value = (err as Error).message
await nextTick()
input.value?.focus()
}
})
async function removeList() {
if (deleting)
if (deleting.value)
return
const confirmDelete = await openConfirmDialog({
@ -64,8 +64,8 @@ async function removeList() {
cancel: t('confirm.delete_list.cancel'),
})
deleting = true
actionError = undefined
deleting.value = true
actionError.value = undefined
await nextTick()
if (confirmDelete === 'confirm') {
@ -76,21 +76,21 @@ async function removeList() {
}
catch (err) {
console.error(err)
actionError = (err as Error).message
actionError.value = (err as Error).message
await nextTick()
deleteBtn.value?.focus()
}
finally {
deleting = false
deleting.value = false
}
}
else {
deleting = false
deleting.value = false
}
}
async function clearError() {
actionError = undefined
actionError.value = undefined
await nextTick()
if (isEditing)
input.value?.focus()

View file

@ -3,9 +3,9 @@ const { userId } = defineProps<{
userId: string
}>()
const { client } = $(useMasto())
const paginator = client.v1.lists.list()
const listsWithUser = ref((await client.v1.accounts.$select(userId).lists.list()).map(list => list.id))
const { client } = useMasto()
const paginator = client.value.v1.lists.list()
const listsWithUser = ref((await client.value.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.$select(listId).accounts.create({ accountIds: [userId] })
await client.value.v1.lists.$select(listId).accounts.create({ accountIds: [userId] })
listsWithUser.value.push(listId)
}
else {
await client.v1.lists.$select(listId).accounts.remove({ accountIds: [userId] })
await client.value.v1.lists.$select(listId).accounts.remove({ accountIds: [userId] })
listsWithUser.value = listsWithUser.value.filter(id => id !== listId)
}
}

View file

@ -22,7 +22,7 @@ interface ShortcutItemGroup {
}
const isMac = useIsMac()
const modifierKeyName = $computed(() => isMac.value ? '⌘' : 'Ctrl')
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
const shortcutItemGroups: ShortcutItemGroup[] = [
{
@ -55,11 +55,11 @@ const shortcutItemGroups: ShortcutItemGroup[] = [
items: [
{
description: t('magic_keys.groups.actions.search'),
shortcut: { keys: [modifierKeyName, 'k'], isSequence: false },
shortcut: { keys: [modifierKeyName.value, 'k'], isSequence: false },
},
{
description: t('magic_keys.groups.actions.command_mode'),
shortcut: { keys: [modifierKeyName, '/'], isSequence: false },
shortcut: { keys: [modifierKeyName.value, '/'], isSequence: false },
},
{
description: t('magic_keys.groups.actions.compose'),

View file

@ -28,13 +28,13 @@ useCommand({
},
})
let activeClass = $ref('text-primary')
const activeClass = ref('text-primary')
onHydrated(async () => {
// TODO: force NuxtLink to reevaluate, we now we are in this route though, so we should force it to active
// we don't have currentServer defined until later
activeClass = ''
activeClass.value = ''
await nextTick()
activeClass = 'text-primary'
activeClass.value = 'text-primary'
})
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items

View file

@ -5,10 +5,10 @@ const { items } = defineProps<{
items: GroupedNotifications
}>()
const count = $computed(() => items.items.length)
const count = computed(() => items.items.length)
const isExpanded = ref(false)
const lang = $computed(() => {
return (count > 1 || count === 0) ? undefined : items.items[0].status?.language
const lang = computed(() => {
return (count.value > 1 || count.value === 0) ? undefined : items.items[0].status?.language
})
</script>

View file

@ -6,8 +6,8 @@ const { group } = defineProps<{
}>()
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
const reblogs = $computed(() => group.likes.filter(i => i.reblog))
const likes = $computed(() => group.likes.filter(i => i.favourite && !i.reblog))
const reblogs = computed(() => group.likes.filter(i => i.reblog))
const likes = computed(() => group.likes.filter(i => i.favourite && !i.reblog))
</script>
<template>

View file

@ -17,12 +17,12 @@ const { t } = useI18n()
const pwaEnabled = useAppConfig().pwaEnabled
let busy = $ref<boolean>(false)
let animateSave = $ref<boolean>(false)
let animateSubscription = $ref<boolean>(false)
let animateRemoveSubscription = $ref<boolean>(false)
let subscribeError = $ref<string>('')
let showSubscribeError = $ref<boolean>(false)
const busy = ref<boolean>(false)
const animateSave = ref<boolean>(false)
const animateSubscription = ref<boolean>(false)
const animateRemoveSubscription = ref<boolean>(false)
const subscribeError = ref<string>('')
const showSubscribeError = ref<boolean>(false)
function hideNotification() {
const key = currentUser.value?.account?.acct
@ -30,7 +30,7 @@ function hideNotification() {
hiddenNotification.value[key] = true
}
const showWarning = $computed(() => {
const showWarning = computed(() => {
if (!pwaEnabled)
return false
@ -40,12 +40,12 @@ const showWarning = $computed(() => {
})
async function saveSettings() {
if (busy)
if (busy.value)
return
busy = true
busy.value = true
await nextTick()
animateSave = true
animateSave.value = true
try {
await updateSubscription()
@ -55,48 +55,48 @@ async function saveSettings() {
console.error(err)
}
finally {
busy = false
animateSave = false
busy.value = false
animateSave.value = false
}
}
async function doSubscribe() {
if (busy)
if (busy.value)
return
busy = true
busy.value = true
await nextTick()
animateSubscription = true
animateSubscription.value = true
try {
const result = await subscribe()
if (result !== 'subscribed') {
subscribeError = t(`settings.notifications.push_notifications.subscription_error.${result === 'notification-denied' ? 'permission_denied' : 'request_error'}`)
showSubscribeError = true
subscribeError.value = t(`settings.notifications.push_notifications.subscription_error.${result === 'notification-denied' ? 'permission_denied' : 'request_error'}`)
showSubscribeError.value = true
}
}
catch (err) {
if (err instanceof PushSubscriptionError) {
subscribeError = t(`settings.notifications.push_notifications.subscription_error.${err.code}`)
subscribeError.value = t(`settings.notifications.push_notifications.subscription_error.${err.code}`)
}
else {
console.error(err)
subscribeError = t('settings.notifications.push_notifications.subscription_error.request_error')
subscribeError.value = t('settings.notifications.push_notifications.subscription_error.request_error')
}
showSubscribeError = true
showSubscribeError.value = true
}
finally {
busy = false
animateSubscription = false
busy.value = false
animateSubscription.value = false
}
}
async function removeSubscription() {
if (busy)
if (busy.value)
return
busy = true
busy.value = true
await nextTick()
animateRemoveSubscription = true
animateRemoveSubscription.value = true
try {
await unsubscribe()
}
@ -104,11 +104,11 @@ async function removeSubscription() {
console.error(err)
}
finally {
busy = false
animateRemoveSubscription = false
busy.value = false
animateRemoveSubscription.value = false
}
}
onActivated(() => (busy = false))
onActivated(() => (busy.value = false))
</script>
<template>

View file

@ -19,10 +19,10 @@ const emit = defineEmits<{
const maxDescriptionLength = 1500
const isEditDialogOpen = ref(false)
const description = ref(props.attachment.description ?? '')
const description = computed(() => props.attachment.description ?? '')
function toggleApply() {
isEditDialogOpen.value = false
emit('setDescription', unref(description))
emit('setDescription', description.value)
}
</script>

View file

@ -9,16 +9,16 @@ const emit = defineEmits<{
const { locale } = useI18n()
const el = $ref<HTMLElement>()
let picker = $ref<Picker>()
const el = ref<HTMLElement>()
const picker = ref<Picker>()
const colorMode = useColorMode()
async function openEmojiPicker() {
await updateCustomEmojis()
if (picker) {
picker.update({
theme: colorMode.value,
if (picker.value) {
picker.value.update({
theme: colorMode,
custom: customEmojisData.value,
})
}
@ -29,7 +29,7 @@ async function openEmojiPicker() {
importEmojiLang(locale.value.split('-')[0]),
])
picker = new Picker({
picker.value = new Picker({
data: () => dataPromise,
onEmojiSelect({ native, src, alt, name }: any) {
native
@ -37,19 +37,19 @@ async function openEmojiPicker() {
: emit('selectCustom', { src, alt, 'data-emoji-id': name })
},
set: 'twitter',
theme: colorMode.value,
theme: colorMode,
custom: customEmojisData.value,
i18n,
})
}
await nextTick()
// TODO: custom picker
el?.appendChild(picker as any as HTMLElement)
el.value?.appendChild(picker.value as any as HTMLElement)
}
function hideEmojiPicker() {
if (picker)
el?.removeChild(picker as any as HTMLElement)
if (picker.value)
el.value?.removeChild(picker.value as any as HTMLElement)
}
</script>

View file

@ -6,16 +6,16 @@ const modelValue = defineModel<string>({ required: true })
const { t } = useI18n()
const userSettings = useUserSettings()
const languageKeyword = $ref('')
const languageKeyword = ref('')
const fuse = new Fuse(languagesNameList, {
keys: ['code', 'nativeName', 'name'],
shouldSort: true,
})
const languages = $computed(() =>
languageKeyword.trim()
? fuse.search(languageKeyword).map(r => r.item)
const languages = computed(() =>
languageKeyword.value.trim()
? fuse.search(languageKeyword.value).map(r => r.item)
: [...languagesNameList].filter(entry => !userSettings.value.disabledTranslationLanguages.includes(entry.code))
.sort(({ code: a }, { code: b }) => {
// Put English on the top

View file

@ -7,7 +7,7 @@ const modelValue = defineModel<string>({
required: true,
})
const currentVisibility = $computed(() =>
const currentVisibility = computed(() =>
statusVisibilities.find(v => v.value === modelValue.value) || statusVisibilities[0],
)

View file

@ -27,61 +27,61 @@ const emit = defineEmits<{
const { t } = useI18n()
const draftState = useDraft(draftKey, initial)
const { draft } = $(draftState)
const { draft } = draftState
const {
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
uploadAttachments, pickAttachments, setDescription, removeAttachment,
dropZoneRef,
} = $(useUploadMediaAttachment($$(draft)))
} = useUploadMediaAttachment(draft)
let { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft, failedMessages, preferredLanguage, publishSpoilerText } = $(usePublish(
const { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft, failedMessages, preferredLanguage, publishSpoilerText } = usePublish(
{
draftState,
...$$({ expanded, isUploading, initialDraft: initial }),
...{ expanded: toRef(() => expanded), isUploading, initialDraft: toRef(() => initial) },
},
))
)
const { editor } = useTiptap({
content: computed({
get: () => draft.params.status,
get: () => draft.value.params.status,
set: (newVal) => {
draft.params.status = newVal
draft.lastUpdated = Date.now()
draft.value.params.status = newVal
draft.value.lastUpdated = Date.now()
},
}),
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
autofocus: shouldExpanded,
placeholder: computed(() => placeholder ?? draft.value.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
autofocus: shouldExpanded.value,
onSubmit: publish,
onFocus() {
if (!isExpanded && draft.initialText) {
editor.value?.chain().insertContent(`${draft.initialText} `).focus('end').run()
draft.initialText = ''
if (!isExpanded && draft.value.initialText) {
editor.value?.chain().insertContent(`${draft.value.initialText} `).focus('end').run()
draft.value.initialText = ''
}
isExpanded = true
isExpanded.value = true
},
onPaste: handlePaste,
})
function trimPollOptions() {
const indexLastNonEmpty = draft.params.poll!.options.findLastIndex(option => option.trim().length > 0)
const trimmedOptions = draft.params.poll!.options.slice(0, indexLastNonEmpty + 1)
const indexLastNonEmpty = draft.value.params.poll!.options.findLastIndex(option => option.trim().length > 0)
const trimmedOptions = draft.value.params.poll!.options.slice(0, indexLastNonEmpty + 1)
if (currentInstance.value?.configuration
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
draft.params.poll!.options = trimmedOptions
draft.value.params.poll!.options = trimmedOptions
else
draft.params.poll!.options = [...trimmedOptions, '']
draft.value.params.poll!.options = [...trimmedOptions, '']
}
function editPollOptionDraft(event: Event, index: number) {
draft.params.poll!.options = Object.assign(draft.params.poll!.options.slice(), { [index]: (event.target as HTMLInputElement).value })
draft.value.params.poll!.options = Object.assign(draft.value.params.poll!.options.slice(), { [index]: (event.target as HTMLInputElement).value })
trimPollOptions()
}
function deletePollOption(index: number) {
draft.params.poll!.options = draft.params.poll!.options.slice().splice(index, 1)
draft.value.params.poll!.options = draft.value.params.poll!.options.slice().splice(index, 1)
trimPollOptions()
}
@ -110,7 +110,7 @@ const expiresInOptions = computed(() => [
const expiresInDefaultOptionIndex = 2
const characterCount = $computed(() => {
const characterCount = computed(() => {
const text = htmlToText(editor.value?.getHTML() || '')
let length = stringLength(text)
@ -131,24 +131,24 @@ const characterCount = $computed(() => {
for (const [fullMatch, before, _handle, username] of text.matchAll(countableMentionRegex))
length -= fullMatch.length - (before + username).length - 1 // - 1 for the @
if (draft.mentions) {
if (draft.value.mentions) {
// + 1 is needed as mentions always need a space seperator at the end
length += draft.mentions.map((mention) => {
length += draft.value.mentions.map((mention) => {
const [handle] = mention.split('@')
return `@${handle}`
}).join(' ').length + 1
}
length += stringLength(publishSpoilerText)
length += stringLength(publishSpoilerText.value)
return length
})
const isExceedingCharacterLimit = $computed(() => {
return characterCount > characterLimit.value
const isExceedingCharacterLimit = computed(() => {
return characterCount.value > characterLimit.value
})
const postLanguageDisplay = $computed(() => languagesNameList.find(i => i.code === (draft.params.language || preferredLanguage))?.nativeName)
const postLanguageDisplay = computed(() => languagesNameList.find(i => i.code === (draft.value.params.language || preferredLanguage))?.nativeName)
async function handlePaste(evt: ClipboardEvent) {
const files = evt.clipboardData?.files
@ -167,7 +167,7 @@ function insertCustomEmoji(image: any) {
}
async function toggleSensitive() {
draft.params.sensitive = !draft.params.sensitive
draft.value.params.sensitive = !draft.value.params.sensitive
}
async function publish() {

View file

@ -5,16 +5,16 @@ const route = useRoute()
const { formatNumber } = useHumanReadableNumber()
const timeAgoOptions = useTimeAgoOptions()
let draftKey = $ref('home')
const draftKey = ref('home')
const draftKeys = $computed(() => Object.keys(currentUserDrafts.value))
const nonEmptyDrafts = $computed(() => draftKeys
.filter(i => i !== draftKey && !isEmptyDraft(currentUserDrafts.value[i]))
const draftKeys = computed(() => Object.keys(currentUserDrafts.value))
const nonEmptyDrafts = computed(() => draftKeys.value
.filter(i => i !== draftKey.value && !isEmptyDraft(currentUserDrafts.value[i]))
.map(i => [i, currentUserDrafts.value[i]] as const),
)
watchEffect(() => {
draftKey = route.query.draft?.toString() || 'home'
draftKey.value = route.query.draft?.toString() || 'home'
})
onDeactivated(() => {

View file

@ -1,9 +1,9 @@
<template>
<button
v-if="$pwa?.needRefresh"
v-if="useNuxtApp().$pwa?.needRefresh"
bg="primary-fade" relative rounded
flex="~ gap-1 center" px3 py1 text-primary
@click="$pwa.updateServiceWorker()"
@click="useNuxtApp().$pwa?.updateServiceWorker()"
>
<div i-ri-download-cloud-2-line />
<h2 flex="~ gap-2" items-center>

View file

@ -1,6 +1,6 @@
<template>
<div
v-if="$pwa?.showInstallPrompt && !$pwa?.needRefresh"
v-if="useNuxtApp().$pwa?.showInstallPrompt && !useNuxtApp().$pwa?.needRefresh"
m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden
flex="~ col gap-3"
@ -10,10 +10,10 @@
{{ $t('pwa.install_title') }}
</h2>
<div flex="~ gap-1">
<button type="button" btn-solid px-4 py-1 text-center text-sm @click="$pwa.install()">
<button type="button" btn-solid px-4 py-1 text-center text-sm @click="useNuxtApp().$pwa?.install()">
{{ $t('pwa.install') }}
</button>
<button type="button" btn-text filter-saturate-0 px-4 py-1 text-center text-sm @click="$pwa.cancelInstall()">
<button type="button" btn-text filter-saturate-0 px-4 py-1 text-center text-sm @click="useNuxtApp().$pwa?.cancelInstall()">
{{ $t('pwa.dismiss') }}
</button>
</div>

View file

@ -1,6 +1,6 @@
<template>
<div
v-if="$pwa?.needRefresh"
v-if="useNuxtApp().$pwa?.needRefresh"
m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden
flex="~ col gap-3"
@ -9,10 +9,10 @@
{{ $t('pwa.title') }}
</h2>
<div flex="~ gap-1">
<button type="button" btn-solid px-4 py-1 text-center text-sm @click="$pwa.updateServiceWorker()">
<button type="button" btn-solid px-4 py-1 text-center text-sm @click="useNuxtApp().$pwa?.updateServiceWorker()">
{{ $t('pwa.update') }}
</button>
<button type="button" btn-text filter-saturate-0 px-4 py-1 text-center text-sm @click="$pwa.close()">
<button type="button" btn-text filter-saturate-0 px-4 py-1 text-center text-sm @click="useNuxtApp().$pwa?.close()">
{{ $t('pwa.dismiss') }}
</button>
</div>

View file

@ -5,7 +5,7 @@ const { hashtag } = defineProps<{
hashtag: mastodon.v1.Tag
}>()
const totalTrend = $computed(() =>
const totalTrend = computed(() =>
hashtag.history?.reduce((total: number, item) => total + (Number(item.accounts) || 0), 0),
)
</script>

View file

@ -4,7 +4,7 @@ import type { mastodon } from 'masto'
const form = defineModel<{
fieldsAttributes: NonNullable<mastodon.rest.v1.UpdateCredentialsParams['fieldsAttributes']>
}>({ required: true })
const dropdown = $ref<any>()
const dropdown = ref<any>()
const fieldIcons = computed(() =>
Array.from({ length: maxAccountFieldCount.value }, (_, i) =>
@ -12,7 +12,7 @@ const fieldIcons = computed(() =>
),
)
const fieldCount = $computed(() => {
const fieldCount = computed(() => {
// find last non-empty field
const idx = [...form.value.fieldsAttributes].reverse().findIndex(f => f.name || f.value)
if (idx === -1)
@ -25,7 +25,7 @@ const fieldCount = $computed(() => {
function chooseIcon(i: number, text: string) {
form.value.fieldsAttributes[i].name = text
dropdown[i]?.hide()
dropdown.value[i]?.hide()
}
</script>

View file

@ -2,12 +2,12 @@
import type { ThemeColors } from '~/composables/settings'
const themes = await import('~/constants/themes.json').then(r => r.default) as [string, ThemeColors][]
const settings = $(useUserSettings())
const settings = useUserSettings()
const currentTheme = $computed(() => settings.themeColors?.['--theme-color-name'] || themes[0][1]['--theme-color-name'])
const currentTheme = computed(() => settings.value.themeColors?.['--theme-color-name'] || themes[0][1]['--theme-color-name'])
function updateTheme(theme: ThemeColors) {
settings.themeColors = theme
settings.value.themeColors = theme
}
</script>

View file

@ -9,7 +9,7 @@ const props = defineProps<{
const focusEditor = inject<typeof noop>('focus-editor', noop)
const { details, command } = $(props)
const { details, command } = props // TODO
const userSettings = useUserSettings()
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
@ -21,7 +21,7 @@ const {
toggleBookmark,
toggleFavourite,
toggleReblog,
} = $(useStatusActions(props))
} = useStatusActions(props)
function reply() {
if (!checkLogin())
@ -29,7 +29,7 @@ function reply() {
if (details)
focusEditor()
else
navigateToStatus({ status, focusReply: true })
navigateToStatus({ status: status.value, focusReply: true })
}
</script>

View file

@ -14,8 +14,6 @@ const emit = defineEmits<{
const focusEditor = inject<typeof noop>('focus-editor', noop)
const { details, command } = $(props)
const {
status,
isLoading,
@ -24,7 +22,7 @@ const {
togglePin,
toggleReblog,
toggleMute,
} = $(useStatusActions(props))
} = useStatusActions(props)
const clipboard = useClipboard()
const router = useRouter()
@ -33,9 +31,9 @@ const { t } = useI18n()
const userSettings = useUserSettings()
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
const isAuthor = $computed(() => status.account.id === currentUser.value?.account.id)
const isAuthor = computed(() => status.value.account.id === currentUser.value?.account.id)
const { client } = $(useMasto())
const { client } = useMasto()
function getPermalinkUrl(status: mastodon.v1.Status) {
const url = getStatusPermalinkRoute(status)
@ -72,8 +70,8 @@ async function deleteStatus() {
}) !== 'confirm')
return
removeCachedStatus(status.id)
await client.v1.statuses.$select(status.id).remove()
removeCachedStatus(status.value.id)
await client.value.v1.statuses.$select(status.value.id).remove()
if (route.name === 'status')
router.back()
@ -97,9 +95,9 @@ async function deleteAndRedraft() {
return
}
removeCachedStatus(status.id)
await client.v1.statuses.$select(status.id).remove()
await openPublishDialog('dialog', await getDraftFromStatus(status), true)
removeCachedStatus(status.value.id)
await client.value.v1.statuses.$select(status.value.id).remove()
await openPublishDialog('dialog', await getDraftFromStatus(status.value), true)
// Go to the new status, if the page is the old status
if (lastPublishDialogStatus.value && route.name === 'status')
@ -109,25 +107,25 @@ async function deleteAndRedraft() {
function reply() {
if (!checkLogin())
return
if (details) {
if (props.details) {
focusEditor()
}
else {
const { key, draft } = getReplyDraft(status)
const { key, draft } = getReplyDraft(status.value)
openPublishDialog(key, draft())
}
}
async function editStatus() {
await openPublishDialog(`edit-${status.id}`, {
...await getDraftFromStatus(status),
editingStatus: status,
await openPublishDialog(`edit-${status.value.id}`, {
...await getDraftFromStatus(status.value),
editingStatus: status.value,
}, true)
emit('afterEdit')
}
function showFavoritedAndBoostedBy() {
openFavoridedBoostedByDialog(status.id)
openFavoridedBoostedByDialog(status.value.id)
}
</script>

View file

@ -14,8 +14,8 @@ const {
isPreview?: boolean
}>()
const src = $computed(() => attachment.previewUrl || attachment.url || attachment.remoteUrl!)
const srcset = $computed(() => [
const src = computed(() => attachment.previewUrl || attachment.url || attachment.remoteUrl!)
const srcset = computed(() => [
[attachment.url, attachment.meta?.original?.width],
[attachment.remoteUrl, attachment.meta?.original?.width],
[attachment.previewUrl, attachment.meta?.small?.width],
@ -53,12 +53,12 @@ const typeExtsMap = {
gifv: ['gifv', 'gif'],
}
const type = $computed(() => {
const type = computed(() => {
if (attachment.type && attachment.type !== 'unknown')
return attachment.type
// some server returns unknown type, we need to guess it based on file extension
for (const [type, exts] of Object.entries(typeExtsMap)) {
if (exts.some(ext => src?.toLowerCase().endsWith(`.${ext}`)))
if (exts.some(ext => src.value?.toLowerCase().endsWith(`.${ext}`)))
return type
}
return 'unknown'
@ -66,8 +66,8 @@ const type = $computed(() => {
const video = ref<HTMLVideoElement | undefined>()
const prefersReducedMotion = usePreferredReducedMotion()
const isAudio = $computed(() => attachment.type === 'audio')
const isVideo = $computed(() => attachment.type === 'video')
const isAudio = computed(() => attachment.type === 'audio')
const isVideo = computed(() => attachment.type === 'video')
const enableAutoplay = usePreferences('enableAutoplay')
@ -100,21 +100,21 @@ function loadAttachment() {
shouldLoadAttachment.value = true
}
const blurHashSrc = $computed(() => {
const blurHashSrc = computed(() => {
if (!attachment.blurhash)
return ''
const pixels = decode(attachment.blurhash, 32, 32)
return getDataUrlFromArr(pixels, 32, 32)
})
let videoThumbnail = shouldLoadAttachment.value
const videoThumbnail = ref(shouldLoadAttachment.value
? attachment.previewUrl
: blurHashSrc
: blurHashSrc.value)
watch(shouldLoadAttachment, () => {
videoThumbnail = shouldLoadAttachment
videoThumbnail.value = shouldLoadAttachment.value
? attachment.previewUrl
: blurHashSrc
: blurHashSrc.value
})
</script>

View file

@ -14,7 +14,7 @@ const {
const { translation } = useTranslation(status, getLanguageCode())
const emojisObject = useEmojisFallback(() => status.emojis)
const vnode = $computed(() => {
const vnode = computed(() => {
if (!status.content)
return null
return contentToVNode(status.content, {

View file

@ -26,45 +26,45 @@ const props = withDefaults(
const userSettings = useUserSettings()
const status = $computed(() => {
const status = computed(() => {
if (props.status.reblog && (!props.status.content || props.status.content === props.status.reblog.content))
return props.status.reblog
return props.status
})
// Use original status, avoid connecting a reblog
const directReply = $computed(() => props.hasNewer || (!!status.inReplyToId && (status.inReplyToId === props.newer?.id || status.inReplyToId === props.newer?.reblog?.id)))
const directReply = computed(() => props.hasNewer || (!!status.value.inReplyToId && (status.value.inReplyToId === props.newer?.id || status.value.inReplyToId === props.newer?.reblog?.id)))
// Use reblogged status, connect it to further replies
const connectReply = $computed(() => props.hasOlder || status.id === props.older?.inReplyToId || status.id === props.older?.reblog?.inReplyToId)
const connectReply = computed(() => props.hasOlder || status.value.id === props.older?.inReplyToId || status.value.id === props.older?.reblog?.inReplyToId)
// Open a detailed status, the replies directly to it
const replyToMain = $computed(() => props.main && props.main.id === status.inReplyToId)
const replyToMain = computed(() => props.main && props.main.id === status.value.inReplyToId)
const rebloggedBy = $computed(() => props.status.reblog ? props.status.account : null)
const rebloggedBy = computed(() => props.status.reblog ? props.status.account : null)
const statusRoute = $computed(() => getStatusRoute(status))
const statusRoute = computed(() => getStatusRoute(status.value))
const router = useRouter()
function go(evt: MouseEvent | KeyboardEvent) {
if (evt.metaKey || evt.ctrlKey) {
window.open(statusRoute.href)
window.open(statusRoute.value.href)
}
else {
cacheStatus(status)
router.push(statusRoute)
cacheStatus(status.value)
router.push(statusRoute.value)
}
}
const createdAt = useFormattedDateTime(status.createdAt)
const createdAt = useFormattedDateTime(status.value.createdAt)
const timeAgoOptions = useTimeAgoOptions(true)
const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
const timeago = useTimeAgo(() => status.value.createdAt, timeAgoOptions)
const isSelfReply = $computed(() => status.inReplyToAccountId === status.account.id)
const collapseRebloggedBy = $computed(() => rebloggedBy?.id === status.account.id)
const isDM = $computed(() => status.visibility === 'direct')
const isSelfReply = computed(() => status.value.inReplyToAccountId === status.value.account.id)
const collapseRebloggedBy = computed(() => rebloggedBy.value?.id === status.value.account.id)
const isDM = computed(() => status.value.visibility === 'direct')
const showUpperBorder = $computed(() => props.newer && !directReply)
const showReplyTo = $computed(() => !replyToMain && !directReply)
const showUpperBorder = computed(() => props.newer && !directReply)
const showReplyTo = computed(() => !replyToMain && !directReply)
const forceShow = ref(false)
</script>

View file

@ -9,28 +9,28 @@ const { status, context } = defineProps<{
inNotification?: boolean
}>()
const isDM = $computed(() => status.visibility === 'direct')
const isDetails = $computed(() => context === 'details')
const isDM = computed(() => status.visibility === 'direct')
const isDetails = computed(() => context === 'details')
// Content Filter logic
const filterResult = $computed(() => status.filtered?.length ? status.filtered[0] : null)
const filter = $computed(() => filterResult?.filter)
const filterResult = computed(() => status.filtered?.length ? status.filtered[0] : null)
const filter = computed(() => filterResult.value?.filter)
const filterPhrase = $computed(() => filter?.title)
const isFiltered = $computed(() => status.account.id !== currentUser.value?.account.id && filterPhrase && context && context !== 'details' && !!filter?.context.includes(context))
const filterPhrase = computed(() => filter.value?.title)
const isFiltered = computed(() => status.account.id !== currentUser.value?.account.id && filterPhrase && context && context !== 'details' && !!filter.value?.context.includes(context))
// check spoiler text or media attachment
// needed to handle accounts that mark all their posts as sensitive
const spoilerTextPresent = $computed(() => !!status.spoilerText && status.spoilerText.trim().length > 0)
const hasSpoilerOrSensitiveMedia = $computed(() => spoilerTextPresent || (status.sensitive && !!status.mediaAttachments.length))
const spoilerTextPresent = computed(() => !!status.spoilerText && status.spoilerText.trim().length > 0)
const hasSpoilerOrSensitiveMedia = computed(() => spoilerTextPresent.value || (status.sensitive && !!status.mediaAttachments.length))
const isSensitiveNonSpoiler = computed(() => status.sensitive && !status.spoilerText && !!status.mediaAttachments.length)
const hideAllMedia = computed(
() => {
return currentUser.value ? (getHideMediaByDefault(currentUser.value.account) && (!!status.mediaAttachments.length || !!status.card?.html)) : false
},
)
const embeddedMediaPreference = $(usePreferences('experimentalEmbeddedMedia'))
const allowEmbeddedMedia = $computed(() => status.card?.html && embeddedMediaPreference)
const embeddedMediaPreference = usePreferences('experimentalEmbeddedMedia')
const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPreference)
</script>
<template>

View file

@ -14,18 +14,18 @@ defineEmits<{
(event: 'refetchStatus'): void
}>()
const status = $computed(() => {
const status = computed(() => {
if (props.status.reblog && props.status.reblog)
return props.status.reblog
return props.status
})
const createdAt = useFormattedDateTime(status.createdAt)
const createdAt = useFormattedDateTime(status.value.createdAt)
const { t } = useI18n()
useHydratedHead({
title: () => `${getDisplayName(status.account)} ${t('common.in')} ${t('app_name')}: "${removeHTMLTags(status.content) || ''}"`,
title: () => `${getDisplayName(status.value.account)} ${t('common.in')} ${t('app_name')}: "${removeHTMLTags(status.value.content) || ''}"`,
})
</script>

View file

@ -5,7 +5,7 @@ const { status } = defineProps<{
status: mastodon.v1.Status
}>()
const vnode = $computed(() => {
const vnode = computed(() => {
if (!status.card?.html)
return null
const node = sanitizeEmbeddedIframe(status.card?.html)?.children[0]

View file

@ -3,13 +3,13 @@ import { favouritedBoostedByStatusId } from '~/composables/dialog'
const type = ref<'favourited-by' | 'boosted-by'>('favourited-by')
const { client } = $(useMasto())
const { client } = useMasto()
function load() {
return client.v1.statuses.$select(favouritedBoostedByStatusId.value!)[type.value === 'favourited-by' ? 'favouritedBy' : 'rebloggedBy'].list()
return client.value.v1.statuses.$select(favouritedBoostedByStatusId.value!)[type.value === 'favourited-by' ? 'favouritedBy' : 'rebloggedBy'].list()
}
const paginator = $computed(() => load())
const paginator = computed(() => load())
function showFavouritedBy() {
type.value = 'favourited-by'

View file

@ -8,7 +8,7 @@ const props = defineProps<{
const el = ref<HTMLElement>()
const router = useRouter()
const statusRoute = $computed(() => getStatusRoute(props.status))
const statusRoute = computed(() => getStatusRoute(props.status))
function onclick(evt: MouseEvent | KeyboardEvent) {
const path = evt.composedPath() as HTMLElement[]
@ -20,11 +20,11 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
function go(evt: MouseEvent | KeyboardEvent) {
if (evt.metaKey || evt.ctrlKey) {
window.open(statusRoute.href)
window.open(statusRoute.value.href)
}
else {
cacheStatus(props.status)
router.push(statusRoute)
router.push(statusRoute.value)
}
}
</script>

View file

@ -15,7 +15,7 @@ const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
const { formatPercentage } = useHumanReadableNumber()
const { client } = $(useMasto())
const { client } = useMasto()
async function vote(e: Event) {
const formData = new FormData(e.target as HTMLFormElement)
@ -36,10 +36,10 @@ async function vote(e: Event) {
cacheStatus({ ...status, poll }, undefined, true)
await client.v1.polls.$select(poll.id).votes.create({ choices })
await client.value.v1.polls.$select(poll.id).votes.create({ choices })
}
const votersCount = $computed(() => poll.votersCount ?? poll.votesCount ?? 0)
const votersCount = computed(() => poll.votersCount ?? poll.votesCount ?? 0)
</script>
<template>

View file

@ -11,7 +11,7 @@ const props = defineProps<{
const providerName = props.card.providerName
const gitHubCards = $(usePreferences('experimentalGitHubCards'))
const gitHubCards = usePreferences('experimentalGitHubCards')
</script>
<template>

View file

@ -12,14 +12,14 @@ const props = defineProps<{
// mastodon's default max og image width
const ogImageWidth = 400
const alt = $computed(() => `${props.card.title} - ${props.card.title}`)
const isSquare = $computed(() => (
const alt = computed(() => `${props.card.title} - ${props.card.title}`)
const isSquare = computed(() => (
props.smallPictureOnly
|| props.card.width === props.card.height
|| Number(props.card.width || 0) < ogImageWidth
|| Number(props.card.height || 0) < ogImageWidth / 2
))
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
const providerName = computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
// TODO: handle card.type: 'photo' | 'video' | 'rich';
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {

View file

@ -29,7 +29,7 @@ interface Meta {
// /sponsors/user
const supportedReservedRoutes = ['sponsors']
const meta = $computed(() => {
const meta = computed(() => {
const { url } = props.card
const path = url.split('https://github.com/')[1]
const [firstName, secondName] = path?.split('/') || []
@ -64,7 +64,7 @@ const meta = $computed(() => {
const avatar = `https://github.com/${user}.png?size=256`
const author = props.card.authorName
const info = $ref<Meta>({
const info = {
type,
user,
titleUrl: `https://github.com/${user}${repo ? `/${repo}` : ''}`,
@ -78,7 +78,7 @@ const meta = $computed(() => {
user: author,
}
: undefined,
})
}
return info
})
</script>

View file

@ -19,31 +19,31 @@ interface Meta {
// Protect against long code snippets
const maxLines = 20
const meta = $computed(() => {
const meta = computed(() => {
const { description } = props.card
const meta = description.match(/.*Code Snippet from (.+), lines (\S+)\n\n(.+)/s)
const file = meta?.[1]
const lines = meta?.[2]
const code = meta?.[3].split('\n').slice(0, maxLines).join('\n')
const project = props.card.title?.replace(' - StackBlitz', '')
const info = $ref<Meta>({
const info = {
file,
lines,
code,
project,
})
}
return info
})
const vnodeCode = $computed(() => {
if (!meta.code)
const vnodeCode = computed(() => {
if (!meta.value.code)
return null
const code = meta.code
const code = meta.value.code
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/`/g, '&#96;')
const vnode = contentToVNode(`<p>\`\`\`${meta.file?.split('.')?.[1] ?? ''}\n${code}\n\`\`\`\</p>`, {
const vnode = contentToVNode(`<p>\`\`\`${meta.value.file?.split('.')?.[1] ?? ''}\n${code}\n\`\`\`\</p>`, {
markdown: true,
})
return vnode

View file

@ -9,7 +9,7 @@ const {
isSelfReply: boolean
}>()
const isSelf = $computed(() => status.inReplyToAccountId === status.account.id)
const isSelf = computed(() => status.inReplyToAccountId === status.account.id)
const account = isSelf ? computed(() => status.account) : useAccountById(status.inReplyToAccountId)
</script>

View file

@ -18,14 +18,14 @@ const showButton = computed(() =>
&& status.content.trim().length,
)
let translating = $ref(false)
const translating = ref(false)
async function toggleTranslation() {
translating = true
translating.value = true
try {
await _toggleTranslation()
}
finally {
translating = false
translating.value = false
}
}
</script>

View file

@ -5,7 +5,7 @@ const { status } = defineProps<{
status: mastodon.v1.Status
}>()
const visibility = $computed(() => statusVisibilities.find(v => v.value === status.visibility)!)
const visibility = computed(() => statusVisibilities.find(v => v.value === status.visibility)!)
</script>
<template>

View file

@ -9,7 +9,7 @@ const emit = defineEmits<{
(event: 'change'): void
}>()
const { client } = $(useMasto())
const { client } = useMasto()
async function toggleFollowTag() {
// We save the state so be can do an optimistic UI update, but fallback to the previous state if the API call fails
@ -20,9 +20,9 @@ async function toggleFollowTag() {
try {
if (previousFollowingState)
await client.v1.tags.$select(tag.name).unfollow()
await client.value.v1.tags.$select(tag.name).unfollow()
else
await client.v1.tags.$select(tag.name).follow()
await client.value.v1.tags.$select(tag.name).follow()
emit('change')
}

View file

@ -3,11 +3,11 @@ import type { mastodon } from 'masto'
const {
tag,
} = $defineProps<{
} = defineProps<{
tag: mastodon.v1.Tag
}>()
const to = $computed(() => {
const to = computed(() => {
const { hostname, pathname } = new URL(tag.url)
return `/${hostname}${pathname}`
})
@ -24,9 +24,9 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
function go(evt: MouseEvent | KeyboardEvent) {
if (evt.metaKey || evt.ctrlKey)
window.open(to)
window.open(to.value)
else
router.push(to)
router.push(to.value)
}
</script>

View file

@ -1,9 +1,9 @@
<script setup lang="ts">
const { client } = $(useMasto())
const paginator = client.v1.domainBlocks.list()
const { client } = useMasto()
const paginator = client.value.v1.domainBlocks.list()
async function unblock(domain: string) {
await client.v1.domainBlocks.remove({ domain })
await client.value.v1.domainBlocks.remove({ domain })
}
</script>

View file

@ -15,9 +15,9 @@ const { paginator, stream, account, buffer = 10, endMessage = true } = definePro
}>()
const { formatNumber } = useHumanReadableNumber()
const virtualScroller = $(usePreferences('experimentalVirtualScroller'))
const virtualScroller = usePreferences('experimentalVirtualScroller')
const showOriginSite = $computed(() =>
const showOriginSite = computed(() =>
account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
)
</script>

View file

@ -37,10 +37,10 @@ const emojis = computed(() => {
})
})
let selectedIndex = $ref(0)
const selectedIndex = ref(0)
watch(items, () => {
selectedIndex = 0
watch(() => items, () => {
selectedIndex.value = 0
})
function onKeyDown(event: KeyboardEvent) {
@ -48,15 +48,15 @@ function onKeyDown(event: KeyboardEvent) {
return false
if (event.key === 'ArrowUp') {
selectedIndex = ((selectedIndex + items.length) - 1) % items.length
selectedIndex.value = ((selectedIndex.value + items.length) - 1) % items.length
return true
}
else if (event.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % items.length
selectedIndex.value = (selectedIndex.value + 1) % items.length
return true
}
else if (event.key === 'Enter') {
selectItem(selectedIndex)
selectItem(selectedIndex.value)
return true
}

View file

@ -9,10 +9,10 @@ const { items, command } = defineProps<{
isPending?: boolean
}>()
let selectedIndex = $ref(0)
const selectedIndex = ref(0)
watch(items, () => {
selectedIndex = 0
watch(() => items, () => {
selectedIndex.value = 0
})
function onKeyDown(event: KeyboardEvent) {
@ -20,15 +20,15 @@ function onKeyDown(event: KeyboardEvent) {
return false
if (event.key === 'ArrowUp') {
selectedIndex = ((selectedIndex + items.length) - 1) % items.length
selectedIndex.value = ((selectedIndex.value + items.length) - 1) % items.length
return true
}
else if (event.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % items.length
selectedIndex.value = (selectedIndex.value + 1) % items.length
return true
}
else if (event.key === 'Enter') {
selectItem(selectedIndex)
selectItem(selectedIndex.value)
return true
}

View file

@ -9,10 +9,10 @@ const { items, command } = defineProps<{
isPending?: boolean
}>()
let selectedIndex = $ref(0)
const selectedIndex = ref(0)
watch(items, () => {
selectedIndex = 0
watch(() => items, () => {
selectedIndex.value = 0
})
function onKeyDown(event: KeyboardEvent) {
@ -20,15 +20,15 @@ function onKeyDown(event: KeyboardEvent) {
return false
if (event.key === 'ArrowUp') {
selectedIndex = ((selectedIndex + items.length) - 1) % items.length
selectedIndex.value = ((selectedIndex.value + items.length) - 1) % items.length
return true
}
else if (event.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % items.length
selectedIndex.value = (selectedIndex.value + 1) % items.length
return true
}
else if (event.key === 'Enter') {
selectItem(selectedIndex)
selectItem(selectedIndex.value)
return true
}

View file

@ -2,19 +2,19 @@
import Fuse from 'fuse.js'
const input = ref<HTMLInputElement | undefined>()
let knownServers = $ref<string[]>([])
let autocompleteIndex = $ref(0)
let autocompleteShow = $ref(false)
const knownServers = ref<string[]>([])
const autocompleteIndex = ref(0)
const autocompleteShow = ref(false)
const { busy, error, displayError, server, oauth } = useSignIn(input)
let fuse = $shallowRef(new Fuse([] as string[]))
const fuse = shallowRef(new Fuse([] as string[]))
const filteredServers = $computed(() => {
const filteredServers = computed(() => {
if (!server.value)
return []
const results = fuse.search(server.value, { limit: 6 }).map(result => result.item)
const results = fuse.value.search(server.value, { limit: 6 }).map(result => result.item)
if (results[0] === server.value)
return []
@ -44,52 +44,52 @@ async function handleInput() {
isValidUrl(`https://${input}`)
&& input.match(/^[a-z0-9-]+(\.[a-z0-9-]+)+(:[0-9]+)?$/i)
// Do not hide the autocomplete if a result has an exact substring match on the input
&& !filteredServers.some(s => s.includes(input))
&& !filteredServers.value.some(s => s.includes(input))
)
autocompleteShow = false
autocompleteShow.value = false
else
autocompleteShow = true
autocompleteShow.value = true
}
function toSelector(server: string) {
return server.replace(/[^\w-]/g, '-')
}
function move(delta: number) {
if (filteredServers.length === 0) {
autocompleteIndex = 0
if (filteredServers.value.length === 0) {
autocompleteIndex.value = 0
return
}
autocompleteIndex = ((autocompleteIndex + delta) + filteredServers.length) % filteredServers.length
document.querySelector(`#${toSelector(filteredServers[autocompleteIndex])}`)?.scrollIntoView(false)
autocompleteIndex.value = ((autocompleteIndex.value + delta) + filteredServers.value.length) % filteredServers.value.length
document.querySelector(`#${toSelector(filteredServers.value[autocompleteIndex.value])}`)?.scrollIntoView(false)
}
function onEnter(e: KeyboardEvent) {
if (autocompleteShow === true && filteredServers[autocompleteIndex]) {
server.value = filteredServers[autocompleteIndex]
if (autocompleteShow.value === true && filteredServers.value[autocompleteIndex.value]) {
server.value = filteredServers.value[autocompleteIndex.value]
e.preventDefault()
autocompleteShow = false
autocompleteShow.value = false
}
}
function escapeAutocomplete(evt: KeyboardEvent) {
if (!autocompleteShow)
return
autocompleteShow = false
autocompleteShow.value = false
evt.stopPropagation()
}
function select(index: number) {
server.value = filteredServers[index]
server.value = filteredServers.value[index]
}
onMounted(async () => {
input?.value?.focus()
knownServers = await (globalThis.$fetch as any)('/api/list-servers')
fuse = new Fuse(knownServers, { shouldSort: true })
knownServers.value = await (globalThis.$fetch as any)('/api/list-servers')
fuse.value = new Fuse(knownServers.value, { shouldSort: true })
})
onClickOutside(input, () => {
autocompleteShow = false
autocompleteShow.value = false
})
</script>

View file

@ -20,18 +20,18 @@ export function useAriaAnnouncer() {
}
export function useAriaLog() {
let logs = $ref<any[]>([])
const logs = ref<any[]>([])
const announceLogs = (messages: any[]) => {
logs = messages
logs.value = messages
}
const appendLogs = (messages: any[]) => {
logs = logs.concat(messages)
logs.value = logs.value.concat(messages)
}
const clearLogs = () => {
logs = []
logs.value = []
}
return {
@ -43,14 +43,14 @@ export function useAriaLog() {
}
export function useAriaStatus() {
let status = $ref<any>('')
const status = ref<any>('')
const announceStatus = (message: any) => {
status = message
status.value = message
}
const clearStatus = () => {
status = ''
status.value = ''
}
return {

View file

@ -19,8 +19,8 @@ export async function updateCustomEmojis() {
if (Date.now() - currentCustomEmojis.value.lastUpdate < TTL)
return
const { client } = $(useMasto())
const emojis = await client.v1.customEmojis.list()
const { client } = useMasto()
const emojis = await client.value.v1.customEmojis.list()
Object.assign(currentCustomEmojis.value, {
lastUpdate: Date.now(),
emojis,

View file

@ -32,10 +32,10 @@ export function useHumanReadableNumber() {
export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>,
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
const { locale } = useI18n()
const formatter = $computed(() => Intl.DateTimeFormat(locale.value, options))
const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
return computed(() => {
const v = resolveUnref(value)
return v ? formatter.format(new Date(v)) : ''
return v ? formatter.value.format(new Date(v)) : ''
})
}

View file

@ -5,7 +5,7 @@ const notifications = reactive<Record<string, undefined | [Promise<mastodon.stre
export function useNotifications() {
const id = currentUser.value?.account.id
const { client, streamingClient } = $(useMasto())
const { client, streamingClient } = useMasto()
async function clearNotifications() {
if (!id || !notifications[id])
@ -15,7 +15,7 @@ export function useNotifications() {
notifications[id]![1] = []
if (lastReadId) {
await client.v1.markers.create({
await client.value.v1.markers.create({
notifications: { lastReadId },
})
}
@ -36,15 +36,15 @@ export function useNotifications() {
const streamPromise = new Promise<mastodon.streaming.Subscription>(resolve => resolveStream = resolve)
notifications[id] = [streamPromise, []]
await until($$(streamingClient)).toBeTruthy()
await until(streamingClient).toBeTruthy()
const stream = streamingClient!.user.subscribe()
const stream = streamingClient.value!.user.subscribe()
resolveStream!(stream)
processNotifications(stream, id)
const position = await client.v1.markers.fetch({ timeline: ['notifications'] })
const paginator = client.v1.notifications.list({ limit: 30 })
const position = await client.value.v1.markers.fetch({ timeline: ['notifications'] })
const paginator = client.value.v1.notifications.list({ limit: 30 })
do {
const result = await paginator.next()

View file

@ -10,69 +10,68 @@ export function usePublish(options: {
isUploading: Ref<boolean>
initialDraft: Ref<() => Draft>
}) {
const { expanded, isUploading, initialDraft } = $(options)
let { draft, isEmpty } = $(options.draftState)
const { client } = $(useMasto())
const { draft, isEmpty } = options.draftState
const { client } = useMasto()
const settings = useUserSettings()
const preferredLanguage = $computed(() => (currentUser.value?.account.source.language || settings.value?.language || 'en').split('-')[0])
const preferredLanguage = computed(() => (currentUser.value?.account.source.language || settings.value?.language || 'en').split('-')[0])
let isSending = $ref(false)
const isExpanded = $ref(false)
const failedMessages = $ref<string[]>([])
const isSending = ref(false)
const isExpanded = ref(false)
const failedMessages = ref<string[]>([])
const publishSpoilerText = $computed({
const publishSpoilerText = computed({
get() {
return draft.params.sensitive ? draft.params.spoilerText : ''
return draft.value.params.sensitive ? draft.value.params.spoilerText : ''
},
set(val) {
if (!draft.params.sensitive)
if (!draft.value.params.sensitive)
return
draft.params.spoilerText = val
draft.value.params.spoilerText = val
},
})
const shouldExpanded = $computed(() => expanded || isExpanded || !isEmpty)
const isPublishDisabled = $computed(() => {
const firstEmptyInputIndex = draft.params.poll?.options.findIndex(option => option.trim().length === 0)
return isEmpty
|| isUploading
|| isSending
|| (draft.attachments.length === 0 && !draft.params.status)
|| failedMessages.length > 0
|| (draft.attachments.length > 0 && draft.params.poll !== null && draft.params.poll !== undefined)
|| ((draft.params.poll !== null && draft.params.poll !== undefined)
const shouldExpanded = computed(() => options.expanded.value || isExpanded.value || !isEmpty.value)
const isPublishDisabled = computed(() => {
const { params, attachments } = draft.value
const firstEmptyInputIndex = params.poll?.options.findIndex(option => option.trim().length === 0)
return isEmpty.value
|| options.isUploading.value
|| isSending.value
|| (attachments.length === 0 && !params.status)
|| failedMessages.value.length > 0
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|| ((params.poll !== null && params.poll !== undefined)
&& (
(firstEmptyInputIndex !== -1
&& firstEmptyInputIndex !== draft.params.poll.options.length - 1
&& firstEmptyInputIndex !== params.poll.options.length - 1
)
|| draft.params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|| (new Set(draft.params.poll.options).size !== draft.params.poll.options.length)
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|| (new Set(params.poll.options).size !== params.poll.options.length)
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
&& draft.params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
)
)
)
})
watch(() => draft, () => {
if (failedMessages.length > 0)
failedMessages.length = 0
if (failedMessages.value.length > 0)
failedMessages.value.length = 0
}, { deep: true })
async function publishDraft() {
if (isPublishDisabled)
if (isPublishDisabled.value)
return
let content = htmlToText(draft.params.status || '')
if (draft.mentions?.length)
content = `${draft.mentions.map(i => `@${i}`).join(' ')} ${content}`
let content = htmlToText(draft.value.params.status || '')
if (draft.value.mentions?.length)
content = `${draft.value.mentions.map(i => `@${i}`).join(' ')} ${content}`
let poll
if (draft.params.poll) {
let options = draft.params.poll.options
if (draft.value.params.poll) {
let options = draft.value.params.poll.options
if (currentInstance.value?.configuration !== undefined
&& (
@ -82,15 +81,15 @@ export function usePublish(options: {
)
options = options.slice(0, options.length - 1)
poll = { ...draft.params.poll, options }
poll = { ...draft.value.params.poll, options }
}
const payload = {
...draft.params,
spoilerText: publishSpoilerText,
...draft.value.params,
spoilerText: publishSpoilerText.value,
status: content,
mediaIds: draft.attachments.map(a => a.id),
language: draft.params.language || preferredLanguage,
mediaIds: draft.value.attachments.map(a => a.id),
language: draft.value.params.language || preferredLanguage.value,
poll,
...(isGlitchEdition.value ? { 'content-type': 'text/markdown' } : {}),
} as mastodon.rest.v1.CreateStatusParams
@ -98,7 +97,7 @@ export function usePublish(options: {
if (process.dev) {
// eslint-disable-next-line no-console
console.info({
raw: draft.params.status,
raw: draft.value.params.status,
...payload,
})
// eslint-disable-next-line no-alert
@ -108,39 +107,39 @@ export function usePublish(options: {
}
try {
isSending = true
isSending.value = true
let status: mastodon.v1.Status
if (!draft.editingStatus) {
status = await client.v1.statuses.create(payload)
if (!draft.value.editingStatus) {
status = await client.value.v1.statuses.create(payload)
}
else {
status = await client.v1.statuses.$select(draft.editingStatus.id).update({
status = await client.value.v1.statuses.$select(draft.value.editingStatus.id).update({
...payload,
mediaAttributes: draft.attachments.map(media => ({
mediaAttributes: draft.value.attachments.map(media => ({
id: media.id,
description: media.description,
})),
})
}
if (draft.params.inReplyToId)
if (draft.value.params.inReplyToId)
navigateToStatus({ status })
draft = initialDraft()
draft.value = options.initialDraft.value()
return status
}
catch (err) {
console.error(err)
failedMessages.push((err as Error).message)
failedMessages.value.push((err as Error).message)
}
finally {
isSending = false
isSending.value = false
}
}
return $$({
return {
isSending,
isExpanded,
shouldExpanded,
@ -149,22 +148,21 @@ export function usePublish(options: {
preferredLanguage,
publishSpoilerText,
publishDraft,
})
}
}
export type MediaAttachmentUploadError = [filename: string, message: string]
export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
const draft = $(draftRef)
const { client } = $(useMasto())
export function useUploadMediaAttachment(draft: Ref<Draft>) {
const { client } = useMasto()
const { t } = useI18n()
let isUploading = $ref<boolean>(false)
let isExceedingAttachmentLimit = $ref<boolean>(false)
let failedAttachments = $ref<MediaAttachmentUploadError[]>([])
const isUploading = ref<boolean>(false)
const isExceedingAttachmentLimit = ref<boolean>(false)
const failedAttachments = ref<MediaAttachmentUploadError[]>([])
const dropZoneRef = ref<HTMLDivElement>()
const maxPixels = $computed(() => {
const maxPixels = computed(() => {
return currentInstance.value?.configuration?.mediaAttachments?.imageMatrixLimit
?? 4096 ** 2
})
@ -186,8 +184,8 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
const canvas = document.createElement('canvas')
const resizedWidth = canvas.width = Math.round(Math.sqrt(maxPixels * aspectRatio))
const resizedHeight = canvas.height = Math.round(Math.sqrt(maxPixels / aspectRatio))
const resizedWidth = canvas.width = Math.round(Math.sqrt(maxPixels.value * aspectRatio))
const resizedHeight = canvas.height = Math.round(Math.sqrt(maxPixels.value / aspectRatio))
const context = canvas.getContext('2d')
@ -202,7 +200,7 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
try {
const image = await loadImage(file) as HTMLImageElement
if (image.width * image.height > maxPixels)
if (image.width * image.height > maxPixels.value)
file = await resizeImage(image, file.type) as File
return file
@ -222,32 +220,32 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
}
async function uploadAttachments(files: File[]) {
isUploading = true
failedAttachments = []
isUploading.value = true
failedAttachments.value = []
// TODO: display some kind of message if too many media are selected
// DONE
const limit = currentInstance.value!.configuration?.statuses.maxMediaAttachments || 4
for (const file of files.slice(0, limit)) {
if (draft.attachments.length < limit) {
isExceedingAttachmentLimit = false
if (draft.value.attachments.length < limit) {
isExceedingAttachmentLimit.value = false
try {
const attachment = await client.v1.media.create({
const attachment = await client.value.v1.media.create({
file: await processFile(file),
})
draft.attachments.push(attachment)
draft.value.attachments.push(attachment)
}
catch (e) {
// TODO: add some human-readable error message, problem is that masto api will not return response code
console.error(e)
failedAttachments = [...failedAttachments, [file.name, (e as Error).message]]
failedAttachments.value = [...failedAttachments.value, [file.name, (e as Error).message]]
}
}
else {
isExceedingAttachmentLimit = true
failedAttachments = [...failedAttachments, [file.name, t('state.attachments_limit_error')]]
isExceedingAttachmentLimit.value = true
failedAttachments.value = [...failedAttachments.value, [file.name, t('state.attachments_limit_error')]]
}
}
isUploading = false
isUploading.value = false
}
async function pickAttachments() {
@ -264,12 +262,12 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
att.description = description
if (!draft.editingStatus)
await client.v1.media.$select(att.id).update({ description: att.description })
if (!draft.value.editingStatus)
await client.value.v1.media.$select(att.id).update({ description: att.description })
}
function removeAttachment(index: number) {
draft.attachments.splice(index, 1)
draft.value.attachments.splice(index, 1)
}
async function onDrop(files: File[] | null) {
@ -279,7 +277,7 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
return $$({
return {
isUploading,
isExceedingAttachmentLimit,
isOverDropZone,
@ -291,5 +289,5 @@ export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
pickAttachments,
setDescription,
removeAttachment,
})
}
}

View file

@ -33,7 +33,7 @@ async function fetchRelationships() {
}
export async function toggleFollowAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto())
const { client } = useMasto()
const i18n = useNuxtApp().$i18n
const unfollow = relationship!.following || relationship!.requested
@ -59,11 +59,11 @@ export async function toggleFollowAccount(relationship: mastodon.v1.Relationship
relationship!.following = true
}
relationship = await client.v1.accounts.$select(account.id)[unfollow ? 'unfollow' : 'follow']()
relationship = await client.value.v1.accounts.$select(account.id)[unfollow ? 'unfollow' : 'follow']()
}
export async function toggleMuteAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto())
const { client } = useMasto()
const i18n = useNuxtApp().$i18n
if (!relationship!.muting && await openConfirmDialog({
@ -76,14 +76,14 @@ export async function toggleMuteAccount(relationship: mastodon.v1.Relationship,
relationship!.muting = !relationship!.muting
relationship = relationship!.muting
? await client.v1.accounts.$select(account.id).mute({
? await client.value.v1.accounts.$select(account.id).mute({
// TODO support more options
})
: await client.v1.accounts.$select(account.id).unmute()
: await client.value.v1.accounts.$select(account.id).unmute()
}
export async function toggleBlockAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto())
const { client } = useMasto()
const i18n = useNuxtApp().$i18n
if (!relationship!.blocking && await openConfirmDialog({
@ -95,11 +95,11 @@ export async function toggleBlockAccount(relationship: mastodon.v1.Relationship,
return
relationship!.blocking = !relationship!.blocking
relationship = await client.v1.accounts.$select(account.id)[relationship!.blocking ? 'block' : 'unblock']()
relationship = await client.value.v1.accounts.$select(account.id)[relationship!.blocking ? 'block' : 'unblock']()
}
export async function toggleBlockDomain(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto())
const { client } = useMasto()
const i18n = useNuxtApp().$i18n
if (!relationship!.domainBlocking && await openConfirmDialog({
@ -111,5 +111,5 @@ export async function toggleBlockDomain(relationship: mastodon.v1.Relationship,
return
relationship!.domainBlocking = !relationship!.domainBlocking
await client.v1.domainBlocks[relationship!.domainBlocking ? 'create' : 'remove']({ domain: getServerName(account) })
await client.value.v1.domainBlocks[relationship!.domainBlocking ? 'create' : 'remove']({ domain: getServerName(account) })
}

View file

@ -22,13 +22,13 @@ export type SearchResult = HashTagSearchResult | AccountSearchResult | StatusSea
export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOptions = {}) {
const done = ref(false)
const { client } = $(useMasto())
const { client } = useMasto()
const loading = ref(false)
const accounts = ref<AccountSearchResult[]>([])
const hashtags = ref<HashTagSearchResult[]>([])
const statuses = ref<StatusSearchResult[]>([])
const q = $computed(() => resolveUnref(query).trim())
const q = computed(() => resolveUnref(query).trim())
let paginator: mastodon.Paginator<mastodon.v2.Search, mastodon.rest.v2.SearchParams> | undefined
@ -72,8 +72,8 @@ 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.list({
q,
paginator = client.value.v2.search.list({
q: q.value,
...resolveUnref(options),
resolve: !!currentUser.value,
})

View file

@ -8,17 +8,17 @@ export interface StatusActionsProps {
}
export function useStatusActions(props: StatusActionsProps) {
let status = $ref<mastodon.v1.Status>({ ...props.status })
const { client } = $(useMasto())
const status = ref<mastodon.v1.Status>({ ...props.status })
const { client } = useMasto()
watch(
() => props.status,
val => status = { ...val },
val => status.value = { ...val },
{ deep: true, immediate: true },
)
// Use different states to let the user press different actions right after the other
const isLoading = $ref({
const isLoading = ref({
reblogged: false,
favourited: false,
bookmarked: false,
@ -32,10 +32,10 @@ export function useStatusActions(props: StatusActionsProps) {
if (!checkLogin())
return
const prevCount = countField ? status[countField] : undefined
const prevCount = countField ? status.value[countField] : undefined
isLoading[action] = true
const isCancel = status[action]
isLoading.value[action] = true
const isCancel = status.value[action]
fetchNewStatus().then((newStatus) => {
// when the action is cancelled, the count is not updated highly likely (if they're the same)
// issue of Mastodon API
@ -45,24 +45,24 @@ export function useStatusActions(props: StatusActionsProps) {
Object.assign(status, newStatus)
cacheStatus(newStatus, undefined, true)
}).finally(() => {
isLoading[action] = false
isLoading.value[action] = false
})
// Optimistic update
status[action] = !status[action]
cacheStatus(status, undefined, true)
status.value[action] = !status.value[action]
cacheStatus(status.value, undefined, true)
if (countField)
status[countField] += status[action] ? 1 : -1
status.value[countField] += status.value[action] ? 1 : -1
}
const canReblog = $computed(() =>
status.visibility !== 'direct'
&& (status.visibility !== 'private' || status.account.id === currentUser.value?.account.id),
const canReblog = computed(() =>
status.value.visibility !== 'direct'
&& (status.value.visibility !== 'private' || status.value.account.id === currentUser.value?.account.id),
)
const toggleReblog = () => toggleStatusAction(
'reblogged',
() => client.v1.statuses.$select(status.id)[status.reblogged ? 'unreblog' : 'reblog']().then((res) => {
if (status.reblogged)
() => client.value.v1.statuses.$select(status.value.id)[status.value.reblogged ? 'unreblog' : 'reblog']().then((res) => {
if (status.value.reblogged)
// returns the original status
return res.reblog!
return res
@ -72,29 +72,29 @@ export function useStatusActions(props: StatusActionsProps) {
const toggleFavourite = () => toggleStatusAction(
'favourited',
() => client.v1.statuses.$select(status.id)[status.favourited ? 'unfavourite' : 'favourite'](),
() => client.value.v1.statuses.$select(status.value.id)[status.value.favourited ? 'unfavourite' : 'favourite'](),
'favouritesCount',
)
const toggleBookmark = () => toggleStatusAction(
'bookmarked',
() => client.v1.statuses.$select(status.id)[status.bookmarked ? 'unbookmark' : 'bookmark'](),
() => client.value.v1.statuses.$select(status.value.id)[status.value.bookmarked ? 'unbookmark' : 'bookmark'](),
)
const togglePin = async () => toggleStatusAction(
'pinned',
() => client.v1.statuses.$select(status.id)[status.pinned ? 'unpin' : 'pin'](),
() => client.value.v1.statuses.$select(status.value.id)[status.value.pinned ? 'unpin' : 'pin'](),
)
const toggleMute = async () => toggleStatusAction(
'muted',
() => client.v1.statuses.$select(status.id)[status.muted ? 'unmute' : 'mute'](),
() => client.value.v1.statuses.$select(status.value.id)[status.value.muted ? 'unmute' : 'mute'](),
)
return {
status: $$(status),
isLoading: $$(isLoading),
canReblog: $$(canReblog),
status,
isLoading,
canReblog,
toggleMute,
toggleReblog,
toggleFavourite,

View file

@ -60,7 +60,7 @@ interface TranslationErr {
export async function translateText(text: string, from: string | null | undefined, to: string) {
const config = useRuntimeConfig()
const status = $ref({
const status = ref({
success: false,
error: '',
text: '',
@ -77,9 +77,9 @@ export async function translateText(text: string, from: string | null | undefine
api_key: '',
},
}) as TranslationResponse
status.success = true
status.value.success = true
// replace the translated links with the original
status.text = response.translatedText.replace(regex, (match) => {
status.value.text = response.translatedText.replace(regex, (match) => {
const tagLink = regex.exec(text)
return tagLink ? tagLink[0] : match
})
@ -87,9 +87,9 @@ export async function translateText(text: string, from: string | null | undefine
catch (err) {
// TODO: improve type
if ((err as TranslationErr).data?.error)
status.error = (err as TranslationErr).data!.error!
status.value.error = (err as TranslationErr).data!.error!
else
status.error = 'Unknown Error, Please check your console in browser devtool.'
status.value.error = 'Unknown Error, Please check your console in browser devtool.'
console.error('Translate Post Error: ', err)
}
return status
@ -115,10 +115,10 @@ export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEd
return
if (!translation.text) {
const { success, text, error } = await translateText(status.content, status.language, to)
translation.error = error
translation.text = text
translation.success = success
const translated = await translateText(status.content, status.language, to)
translation.error = translated.value.error
translation.text = translated.value.text
translation.success = translated.value.success
}
translation.visible = !translation.visible

View file

@ -19,8 +19,8 @@ export function usePaginator<T, P, U = T>(
const prevItems = ref<T[]>([])
const endAnchor = ref<HTMLDivElement>()
const bound = reactive(useElementBounding(endAnchor))
const isInScreen = $computed(() => bound.top < window.innerHeight * 2)
const bound = useElementBounding(endAnchor)
const isInScreen = computed(() => bound.top.value < window.innerHeight * 2)
const error = ref<unknown | undefined>()
const deactivated = useDeactivated()
@ -29,11 +29,11 @@ export function usePaginator<T, P, U = T>(
prevItems.value = []
}
watch(stream, async (stream) => {
if (!stream)
watch(() => stream, async (stream) => {
if (!stream.value)
return
for await (const entry of stream) {
for await (const entry of stream.value) {
if (entry.event === 'update') {
const status = entry.payload
@ -115,11 +115,10 @@ export function usePaginator<T, P, U = T>(
})
}
watch(
() => [isInScreen, state],
watchEffect(
() => {
if (
isInScreen
isInScreen.value
&& state.value === 'idle'
// No new content is loaded when the keepAlive page enters the background
&& deactivated.value === false

View file

@ -14,7 +14,7 @@ const supportsPushNotifications = typeof window !== 'undefined'
&& 'getKey' in PushSubscription.prototype
export function usePushManager() {
const { client } = $(useMasto())
const { client } = useMasto()
const isSubscribed = ref(false)
const notificationPermission = ref<PermissionState | undefined>(
Notification.permission === 'denied'
@ -25,7 +25,7 @@ export function usePushManager() {
? 'prompt'
: undefined,
)
const isSupported = $computed(() => supportsPushNotifications)
const isSupported = computed(() => supportsPushNotifications)
const hiddenNotification = useLocalStorage<PushNotificationRequest>(STORAGE_KEY_NOTIFICATION, {})
const configuredPolicy = useLocalStorage<PushNotificationPolicy>(STORAGE_KEY_NOTIFICATION_POLICY, {})
const pushNotificationData = ref(createRawSettings(
@ -173,7 +173,7 @@ export function usePushManager() {
if (policyChanged)
await subscribe(data, policy, true)
else
currentUser.value.pushSubscription = await client.v1.push.subscription.update({ data })
currentUser.value.pushSubscription = await client.value.v1.push.subscription.update({ data })
policyChanged && await nextTick()

View file

@ -121,7 +121,7 @@ export function useSelfAccount(user: MaybeRefOrGetter<mastodon.v1.Account | unde
export const characterLimit = computed(() => currentInstance.value?.configuration?.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)
export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { account?: mastodon.v1.AccountCredentials }>) {
const { client } = $(masto)
const { client } = masto
const instance = mastoLogin(masto, user)
// GoToSocial only API
@ -145,11 +145,11 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
currentUserHandle.value = account.acct
const [me, pushSubscription] = await Promise.all([
fetchAccountInfo(client, user.server),
fetchAccountInfo(client.value, user.server),
// if PWA is not enabled, don't get push subscription
useAppConfig().pwaEnabled
// we get 404 response instead empty data
? client.v1.push.subscription.fetch().catch(() => Promise.resolve(undefined))
? client.value.v1.push.subscription.fetch().catch(() => Promise.resolve(undefined))
: Promise.resolve(undefined),
])

View file

@ -35,11 +35,13 @@ export default defineNuxtConfig({
],
vue: {
defineModel: true,
propsDestructure: true,
},
macros: {
setupSFC: true,
betterDefine: false,
defineModels: false,
reactivityTransform: false,
},
devtools: {
enabled: true,

View file

@ -11,18 +11,18 @@ definePageMeta({
})
const route = useRoute()
const id = $(computedEager(() => route.params.status as string))
const id = computedEager(() => route.params.status as string)
const main = ref<ComponentPublicInstance | null>(null)
const { data: status, pending, refresh: refreshStatus } = useAsyncData(
`status:${id}`,
() => fetchStatus(id, true),
`status:${id.value}`,
() => fetchStatus(id.value, true),
{ watch: [isHydrated], immediate: isHydrated.value, default: () => shallowRef() },
)
const { client } = $(useMasto())
const { client } = useMasto()
const { data: context, pending: pendingContext, refresh: refreshContext } = useAsyncData(
`context:${id}`,
async () => client.v1.statuses.$select(id).context.fetch(),
async () => client.value.v1.statuses.$select(id.value).context.fetch(),
{ watch: [isHydrated], immediate: isHydrated.value, lazy: true, default: () => shallowRef() },
)
@ -54,7 +54,7 @@ watch(publishWidget, () => {
focusEditor()
})
const replyDraft = $computed(() => status.value ? getReplyDraft(status.value) : null)
const replyDraft = computed(() => status.value ? getReplyDraft(status.value) : null)
onReactivated(() => {
// Silently update data when reentering the page

View file

@ -4,12 +4,12 @@ definePageMeta({
})
const params = useRoute().params
const accountName = $(computedEager(() => toShortHandle(params.account as string)))
const accountName = computedEager(() => toShortHandle(params.account as string))
const { t } = useI18n()
const { data: account, pending, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null), { immediate: process.client, default: () => shallowRef() }))
const relationship = $computed(() => account ? useRelationship(account).value : undefined)
const { data: account, pending, refresh } = await useAsyncData(() => fetchAccountByHandle(accountName.value).catch(() => null), { immediate: process.client, default: () => shallowRef() })
const relationship = computed(() => account ? useRelationship(account.value).value : undefined)
const userSettings = useUserSettings()

View file

@ -1,11 +1,11 @@
<script setup lang="ts">
const { t } = useI18n()
const params = useRoute().params
const handle = $(computedEager(() => params.account as string))
const handle = computedEager(() => params.account as string)
definePageMeta({ name: 'account-followers' })
const account = await fetchAccountByHandle(handle)
const account = await fetchAccountByHandle(handle.value)
const paginator = account ? useMastoClient().v1.accounts.$select(account.id).followers.list() : null
const isSelf = useSelfAccount(account)

View file

@ -1,11 +1,11 @@
<script setup lang="ts">
const { t } = useI18n()
const params = useRoute().params
const handle = $(computedEager(() => params.account as string))
const handle = computedEager(() => params.account as string)
definePageMeta({ name: 'account-following' })
const account = await fetchAccountByHandle(handle)
const account = await fetchAccountByHandle(handle.value)
const paginator = account ? useMastoClient().v1.accounts.$select(account.id).following.list() : null
const isSelf = useSelfAccount(account)

View file

@ -2,13 +2,13 @@
import type { mastodon } from 'masto'
const params = useRoute().params
const handle = $(computedEager(() => params.account as string))
const handle = computedEager(() => params.account as string)
definePageMeta({ name: 'account-index' })
const { t } = useI18n()
const account = await fetchAccountByHandle(handle)
const account = await fetchAccountByHandle(handle.value)
function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'account')

View file

@ -3,9 +3,9 @@ definePageMeta({ name: 'account-media' })
const { t } = useI18n()
const params = useRoute().params
const handle = $(computedEager(() => params.account as string))
const handle = computedEager(() => params.account as string)
const account = await fetchAccountByHandle(handle)
const account = await fetchAccountByHandle(handle.value)
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ onlyMedia: true, excludeReplies: false })

View file

@ -3,9 +3,9 @@ definePageMeta({ name: 'account-replies' })
const { t } = useI18n()
const params = useRoute().params
const handle = $(computedEager(() => params.account as string))
const handle = computedEager(() => params.account as string)
const account = await fetchAccountByHandle(handle)
const account = await fetchAccountByHandle(handle.value)
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ excludeReplies: false })

View file

@ -3,20 +3,20 @@ import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.v
const { t } = useI18n()
const search = $ref<{ input?: HTMLInputElement }>()
const search = ref<{ input?: HTMLInputElement }>()
const route = useRoute()
watchEffect(() => {
if (isMediumOrLargeScreen && route.name === 'explore' && search?.input)
search?.input?.focus()
if (isMediumOrLargeScreen && route.name === 'explore' && search.value?.input)
search.value?.input?.focus()
})
onActivated(() =>
search?.input?.focus(),
search.value?.input?.focus(),
)
onDeactivated(() => search?.input?.blur())
onDeactivated(() => search.value?.input?.blur())
const userSettings = useUserSettings()
const tabs = $computed<CommonRouteTabOption[]>(() => [
const tabs = computed<CommonRouteTabOption[]>(() => [
{
to: isHydrated.value ? `/${currentServer.value}/explore` : '/explore',
display: isHydrated.value ? t('tab.posts') : '',

View file

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

View file

@ -8,14 +8,14 @@ definePageMeta({
const route = useRoute()
const { t } = useI18n()
const list = $computed(() => route.params.list as string)
const server = $computed(() => (route.params.server ?? currentServer.value) as string)
const list = computed(() => route.params.list as string)
const server = computed(() => (route.params.server ?? currentServer.value) as string)
const tabs = $computed<CommonRouteTabOption[]>(() => [
const tabs = computed<CommonRouteTabOption[]>(() => [
{
to: {
name: 'list',
params: { server, list },
params: { server: server.value, list: list.value },
},
display: t('tab.posts'),
icon: 'i-ri:list-unordered',
@ -23,7 +23,7 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
{
to: {
name: 'list-accounts',
params: { server, list },
params: { server: server.value, list: list.value },
},
display: t('tab.accounts'),
icon: 'i-ri:user-line',
@ -31,12 +31,12 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
],
)
const { client } = $(useMasto())
const { data: listInfo, refresh } = $(await useAsyncData(() => client.v1.lists.$select(list).fetch(), { default: () => shallowRef() }))
const { client } = useMasto()
const { data: listInfo, refresh } = await useAsyncData(() => client.value.v1.lists.$select(list.value).fetch(), { default: () => shallowRef() })
if (listInfo) {
useHydratedHead({
title: () => `${listInfo.title} | ${route.fullPath.endsWith('/accounts') ? t('tab.accounts') : t('tab.posts')} | ${t('nav.lists')}`,
title: () => `${listInfo.value.title} | ${route.fullPath.endsWith('/accounts') ? t('tab.accounts') : t('tab.posts')} | ${t('nav.lists')}`,
})
}

View file

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

View file

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

View file

@ -17,17 +17,17 @@ useHydratedHead({
const paginatorRef = ref()
const inputRef = ref<HTMLInputElement>()
let actionError = $ref<string | undefined>(undefined)
let busy = $ref<boolean>(false)
const actionError = ref<string | undefined>(undefined)
const busy = ref<boolean>(false)
const createText = ref('')
const enableSubmit = computed(() => createText.value.length > 0)
async function createList() {
if (busy || !enableSubmit.value)
if (busy.value || !enableSubmit.value)
return
busy = true
actionError = undefined
busy.value = true
actionError.value = undefined
await nextTick()
try {
const newEntry = await client.v1.lists.create({
@ -38,18 +38,18 @@ async function createList() {
}
catch (err) {
console.error(err)
actionError = (err as Error).message
actionError.value = (err as Error).message
nextTick(() => {
inputRef.value?.focus()
})
}
finally {
busy = false
busy.value = false
}
}
function clearError(focusBtn: boolean) {
actionError = undefined
actionError.value = undefined
focusBtn && nextTick(() => {
inputRef.value?.focus()
})

View file

@ -6,15 +6,15 @@ useHydratedHead({
title: () => t('nav.search'),
})
const search = $ref<{ input?: HTMLInputElement }>()
const search = ref<{ input?: HTMLInputElement }>()
watchEffect(() => {
if (search?.input)
search?.input?.focus()
if (search.value?.input)
search.value?.input?.focus()
})
onActivated(() =>
search?.input?.focus(),
search.value?.input?.focus(),
)
onDeactivated(() => search?.input?.blur())
onDeactivated(() => search.value?.input?.blur())
</script>
<template>

View file

@ -4,17 +4,17 @@ definePageMeta({
})
const params = useRoute().params
const tagName = $(computedEager(() => params.tag as string))
const tagName = computedEager(() => params.tag as string)
const { client } = $(useMasto())
const { data: tag, refresh } = $(await useAsyncData(() => client.v1.tags.$select(tagName).fetch(), { default: () => shallowRef() }))
const { client } = useMasto()
const { data: tag, refresh } = await useAsyncData(() => client.value.v1.tags.$select(tagName.value).fetch(), { default: () => shallowRef() })
const paginator = client.v1.timelines.tag.$select(tagName).list()
const stream = useStreaming(client => client.hashtag.subscribe({ tag: tagName }))
const paginator = client.value.v1.timelines.tag.$select(tagName.value).list()
const stream = useStreaming(client => client.hashtag.subscribe({ tag: tagName.value }))
if (tag) {
if (tag.value) {
useHydratedHead({
title: () => `#${tag.name}`,
title: () => `#${tag.value.name}`,
})
}

View file

@ -14,7 +14,7 @@ const route = useRoute()
const { t } = useI18n()
const pwaEnabled = useAppConfig().pwaEnabled
const tabs = $computed<CommonRouteTabOption[]>(() => [
const tabs = computed<CommonRouteTabOption[]>(() => [
{
name: 'all',
to: '/notifications',
@ -27,7 +27,7 @@ const tabs = $computed<CommonRouteTabOption[]>(() => [
},
])
const filter = $computed<mastodon.v1.NotificationType | undefined>(() => {
const filter = computed<mastodon.v1.NotificationType | undefined>(() => {
if (!isHydrated.value)
return undefined
@ -50,22 +50,22 @@ const filterIconMap: Record<mastodon.v1.NotificationType, string> = {
'admin.report': 'i-ri:flag-line',
}
const filterText = $computed(() => (`${t('tab.notifications_more_tooltip')}${filter ? `: ${t(`tab.notifications_${filter}`)}` : ''}`))
const filterText = computed(() => (`${t('tab.notifications_more_tooltip')}${filter ? `: ${t(`tab.notifications_${filter}`)}` : ''}`))
const notificationFilterRoutes = $computed<CommonRouteTabOption[]>(() => NOTIFICATION_FILTER_TYPES.map(
const notificationFilterRoutes = computed<CommonRouteTabOption[]>(() => NOTIFICATION_FILTER_TYPES.map(
name => ({
name,
to: `/notifications/${name}`,
display: isHydrated.value ? t(`tab.notifications_${name}`) : '',
icon: filterIconMap[name],
match: name === filter,
match: name === filter.value,
}),
))
const moreOptions = $computed<CommonRouteTabMoreOption>(() => ({
options: notificationFilterRoutes,
const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
options: notificationFilterRoutes.value,
icon: 'i-ri:filter-2-line',
tooltip: filterText,
match: !!filter,
tooltip: filterText.value,
match: !!filter.value,
}))
</script>

View file

@ -4,7 +4,7 @@ import type { mastodon } from 'masto'
const route = useRoute()
const { t } = useI18n()
const filter = $computed<mastodon.v1.NotificationType | undefined>(() => {
const filter = computed<mastodon.v1.NotificationType | undefined>(() => {
if (!isHydrated.value)
return undefined

View file

@ -6,12 +6,12 @@ useHydratedHead({
title: () => `${t('settings.about.label')} | ${t('nav.settings')}`,
})
let showCommit = $ref(buildInfo.env !== 'release' && buildInfo.env !== 'dev')
const showCommit = ref(buildInfo.env !== 'release' && buildInfo.env !== 'dev')
const builtTime = useFormattedDateTime(buildInfo.time)
function handleShowCommit() {
setTimeout(() => {
showCommit = true
showCommit.value = true
}, 50)
}
</script>

Some files were not shown because too many files have changed in this diff Show more