refactor: no reactivity transform (#2600)

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

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>