refactor: no reactivity transform (#2600)

zio/stable
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 square?: boolean
}>() }>()
const loaded = $ref(false) const loaded = ref(false)
const error = $ref(false) const error = ref(false)
</script> </script>
<template> <template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ defineSlots<{
const { t } = useI18n() const { t } = useI18n()
const nuxtApp = useNuxtApp() 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', () => { nuxtApp.hook('elk-logo:click', () => {
update() update()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ const props = defineProps<{
lang?: string 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> = { const langMap: Record<string, string> = {
js: 'javascript', js: 'javascript',
@ -13,7 +13,7 @@ const langMap: Record<string, string> = {
} }
const highlighted = computed(() => { 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> </script>

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@ const { userId } = defineProps<{
userId: string userId: string
}>() }>()
const { client } = $(useMasto()) const { client } = useMasto()
const paginator = client.v1.lists.list() const paginator = client.value.v1.lists.list()
const listsWithUser = ref((await client.v1.accounts.$select(userId).lists.list()).map(list => list.id)) const listsWithUser = ref((await client.value.v1.accounts.$select(userId).lists.list()).map(list => list.id))
function indexOfUserInList(listId: string) { function indexOfUserInList(listId: string) {
return listsWithUser.value.indexOf(listId) return listsWithUser.value.indexOf(listId)
@ -15,11 +15,11 @@ async function edit(listId: string) {
try { try {
const index = indexOfUserInList(listId) const index = indexOfUserInList(listId)
if (index === -1) { 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) listsWithUser.value.push(listId)
} }
else { 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) listsWithUser.value = listsWithUser.value.filter(id => id !== listId)
} }
} }

View File

@ -22,7 +22,7 @@ interface ShortcutItemGroup {
} }
const isMac = useIsMac() const isMac = useIsMac()
const modifierKeyName = $computed(() => isMac.value ? '⌘' : 'Ctrl') const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
const shortcutItemGroups: ShortcutItemGroup[] = [ const shortcutItemGroups: ShortcutItemGroup[] = [
{ {
@ -55,11 +55,11 @@ const shortcutItemGroups: ShortcutItemGroup[] = [
items: [ items: [
{ {
description: t('magic_keys.groups.actions.search'), 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'), 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'), 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 () => { onHydrated(async () => {
// TODO: force NuxtLink to reevaluate, we now we are in this route though, so we should force it to active // 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 // we don't have currentServer defined until later
activeClass = '' activeClass.value = ''
await nextTick() 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 // 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 items: GroupedNotifications
}>() }>()
const count = $computed(() => items.items.length) const count = computed(() => items.items.length)
const isExpanded = ref(false) const isExpanded = ref(false)
const lang = $computed(() => { const lang = computed(() => {
return (count > 1 || count === 0) ? undefined : items.items[0].status?.language return (count.value > 1 || count.value === 0) ? undefined : items.items[0].status?.language
}) })
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
v-if="$pwa?.showInstallPrompt && !$pwa?.needRefresh" v-if="useNuxtApp().$pwa?.showInstallPrompt && !useNuxtApp().$pwa?.needRefresh"
m-2 p5 bg="primary-fade" relative m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden rounded-lg of-hidden
flex="~ col gap-3" flex="~ col gap-3"
@ -10,10 +10,10 @@
{{ $t('pwa.install_title') }} {{ $t('pwa.install_title') }}
</h2> </h2>
<div flex="~ gap-1"> <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') }} {{ $t('pwa.install') }}
</button> </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') }} {{ $t('pwa.dismiss') }}
</button> </button>
</div> </div>

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
v-if="$pwa?.needRefresh" v-if="useNuxtApp().$pwa?.needRefresh"
m-2 p5 bg="primary-fade" relative m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden rounded-lg of-hidden
flex="~ col gap-3" flex="~ col gap-3"
@ -9,10 +9,10 @@
{{ $t('pwa.title') }} {{ $t('pwa.title') }}
</h2> </h2>
<div flex="~ gap-1"> <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') }} {{ $t('pwa.update') }}
</button> </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') }} {{ $t('pwa.dismiss') }}
</button> </button>
</div> </div>

View File

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

View File

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

View File

@ -2,12 +2,12 @@
import type { ThemeColors } from '~/composables/settings' import type { ThemeColors } from '~/composables/settings'
const themes = await import('~/constants/themes.json').then(r => r.default) as [string, ThemeColors][] 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) { function updateTheme(theme: ThemeColors) {
settings.themeColors = theme settings.value.themeColors = theme
} }
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

@ -26,45 +26,45 @@ const props = withDefaults(
const userSettings = useUserSettings() const userSettings = useUserSettings()
const status = $computed(() => { const status = computed(() => {
if (props.status.reblog && (!props.status.content || props.status.content === props.status.reblog.content)) if (props.status.reblog && (!props.status.content || props.status.content === props.status.reblog.content))
return props.status.reblog return props.status.reblog
return props.status return props.status
}) })
// Use original status, avoid connecting a reblog // 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 // 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 // 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() const router = useRouter()
function go(evt: MouseEvent | KeyboardEvent) { function go(evt: MouseEvent | KeyboardEvent) {
if (evt.metaKey || evt.ctrlKey) { if (evt.metaKey || evt.ctrlKey) {
window.open(statusRoute.href) window.open(statusRoute.value.href)
} }
else { else {
cacheStatus(status) cacheStatus(status.value)
router.push(statusRoute) router.push(statusRoute.value)
} }
} }
const createdAt = useFormattedDateTime(status.createdAt) const createdAt = useFormattedDateTime(status.value.createdAt)
const timeAgoOptions = useTimeAgoOptions(true) 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 isSelfReply = computed(() => status.value.inReplyToAccountId === status.value.account.id)
const collapseRebloggedBy = $computed(() => rebloggedBy?.id === status.account.id) const collapseRebloggedBy = computed(() => rebloggedBy.value?.id === status.value.account.id)
const isDM = $computed(() => status.visibility === 'direct') const isDM = computed(() => status.value.visibility === 'direct')
const showUpperBorder = $computed(() => props.newer && !directReply) const showUpperBorder = computed(() => props.newer && !directReply)
const showReplyTo = $computed(() => !replyToMain && !directReply) const showReplyTo = computed(() => !replyToMain && !directReply)
const forceShow = ref(false) const forceShow = ref(false)
</script> </script>

View File

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

View File

@ -14,18 +14,18 @@ defineEmits<{
(event: 'refetchStatus'): void (event: 'refetchStatus'): void
}>() }>()
const status = $computed(() => { const status = computed(() => {
if (props.status.reblog && props.status.reblog) if (props.status.reblog && props.status.reblog)
return props.status.reblog return props.status.reblog
return props.status return props.status
}) })
const createdAt = useFormattedDateTime(status.createdAt) const createdAt = useFormattedDateTime(status.value.createdAt)
const { t } = useI18n() const { t } = useI18n()
useHydratedHead({ 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> </script>

View File

@ -5,7 +5,7 @@ const { status } = defineProps<{
status: mastodon.v1.Status status: mastodon.v1.Status
}>() }>()
const vnode = $computed(() => { const vnode = computed(() => {
if (!status.card?.html) if (!status.card?.html)
return null return null
const node = sanitizeEmbeddedIframe(status.card?.html)?.children[0] 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 type = ref<'favourited-by' | 'boosted-by'>('favourited-by')
const { client } = $(useMasto()) const { client } = useMasto()
function load() { 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() { function showFavouritedBy() {
type.value = 'favourited-by' type.value = 'favourited-by'

View File

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

View File

@ -15,7 +15,7 @@ const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!) const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
const { formatPercentage } = useHumanReadableNumber() const { formatPercentage } = useHumanReadableNumber()
const { client } = $(useMasto()) const { client } = useMasto()
async function vote(e: Event) { async function vote(e: Event) {
const formData = new FormData(e.target as HTMLFormElement) const formData = new FormData(e.target as HTMLFormElement)
@ -36,10 +36,10 @@ async function vote(e: Event) {
cacheStatus({ ...status, poll }, undefined, true) 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> </script>
<template> <template>

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ const {
isSelfReply: boolean 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) const account = isSelf ? computed(() => status.account) : useAccountById(status.inReplyToAccountId)
</script> </script>

View File

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

View File

@ -5,7 +5,7 @@ const { status } = defineProps<{
status: mastodon.v1.Status 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> </script>
<template> <template>

View File

@ -9,7 +9,7 @@ const emit = defineEmits<{
(event: 'change'): void (event: 'change'): void
}>() }>()
const { client } = $(useMasto()) const { client } = useMasto()
async function toggleFollowTag() { 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 // 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 { try {
if (previousFollowingState) if (previousFollowingState)
await client.v1.tags.$select(tag.name).unfollow() await client.value.v1.tags.$select(tag.name).unfollow()
else else
await client.v1.tags.$select(tag.name).follow() await client.value.v1.tags.$select(tag.name).follow()
emit('change') emit('change')
} }

View File

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

View File

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

View File

@ -15,9 +15,9 @@ const { paginator, stream, account, buffer = 10, endMessage = true } = definePro
}>() }>()
const { formatNumber } = useHumanReadableNumber() 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, account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
) )
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,10 +32,10 @@ export function useHumanReadableNumber() {
export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>, export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>,
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) { options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
const { locale } = useI18n() const { locale } = useI18n()
const formatter = $computed(() => Intl.DateTimeFormat(locale.value, options)) const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
return computed(() => { return computed(() => {
const v = resolveUnref(value) 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() { export function useNotifications() {
const id = currentUser.value?.account.id const id = currentUser.value?.account.id
const { client, streamingClient } = $(useMasto()) const { client, streamingClient } = useMasto()
async function clearNotifications() { async function clearNotifications() {
if (!id || !notifications[id]) if (!id || !notifications[id])
@ -15,7 +15,7 @@ export function useNotifications() {
notifications[id]![1] = [] notifications[id]![1] = []
if (lastReadId) { if (lastReadId) {
await client.v1.markers.create({ await client.value.v1.markers.create({
notifications: { lastReadId }, notifications: { lastReadId },
}) })
} }
@ -36,15 +36,15 @@ export function useNotifications() {
const streamPromise = new Promise<mastodon.streaming.Subscription>(resolve => resolveStream = resolve) const streamPromise = new Promise<mastodon.streaming.Subscription>(resolve => resolveStream = resolve)
notifications[id] = [streamPromise, []] notifications[id] = [streamPromise, []]
await until($$(streamingClient)).toBeTruthy() await until(streamingClient).toBeTruthy()
const stream = streamingClient!.user.subscribe() const stream = streamingClient.value!.user.subscribe()
resolveStream!(stream) resolveStream!(stream)
processNotifications(stream, id) processNotifications(stream, id)
const position = await client.v1.markers.fetch({ timeline: ['notifications'] }) const position = await client.value.v1.markers.fetch({ timeline: ['notifications'] })
const paginator = client.v1.notifications.list({ limit: 30 }) const paginator = client.value.v1.notifications.list({ limit: 30 })
do { do {
const result = await paginator.next() const result = await paginator.next()

View File

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

View File

@ -33,7 +33,7 @@ async function fetchRelationships() {
} }
export async function toggleFollowAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) { export async function toggleFollowAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto()) const { client } = useMasto()
const i18n = useNuxtApp().$i18n const i18n = useNuxtApp().$i18n
const unfollow = relationship!.following || relationship!.requested const unfollow = relationship!.following || relationship!.requested
@ -59,11 +59,11 @@ export async function toggleFollowAccount(relationship: mastodon.v1.Relationship
relationship!.following = true 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) { export async function toggleMuteAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto()) const { client } = useMasto()
const i18n = useNuxtApp().$i18n const i18n = useNuxtApp().$i18n
if (!relationship!.muting && await openConfirmDialog({ if (!relationship!.muting && await openConfirmDialog({
@ -76,14 +76,14 @@ export async function toggleMuteAccount(relationship: mastodon.v1.Relationship,
relationship!.muting = !relationship!.muting relationship!.muting = !relationship!.muting
relationship = 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 // 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) { export async function toggleBlockAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto()) const { client } = useMasto()
const i18n = useNuxtApp().$i18n const i18n = useNuxtApp().$i18n
if (!relationship!.blocking && await openConfirmDialog({ if (!relationship!.blocking && await openConfirmDialog({
@ -95,11 +95,11 @@ export async function toggleBlockAccount(relationship: mastodon.v1.Relationship,
return return
relationship!.blocking = !relationship!.blocking 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) { export async function toggleBlockDomain(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
const { client } = $(useMasto()) const { client } = useMasto()
const i18n = useNuxtApp().$i18n const i18n = useNuxtApp().$i18n
if (!relationship!.domainBlocking && await openConfirmDialog({ if (!relationship!.domainBlocking && await openConfirmDialog({
@ -111,5 +111,5 @@ export async function toggleBlockDomain(relationship: mastodon.v1.Relationship,
return return
relationship!.domainBlocking = !relationship!.domainBlocking 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 = {}) { export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOptions = {}) {
const done = ref(false) const done = ref(false)
const { client } = $(useMasto()) const { client } = useMasto()
const loading = ref(false) const loading = ref(false)
const accounts = ref<AccountSearchResult[]>([]) const accounts = ref<AccountSearchResult[]>([])
const hashtags = ref<HashTagSearchResult[]>([]) const hashtags = ref<HashTagSearchResult[]>([])
const statuses = ref<StatusSearchResult[]>([]) 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 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, * 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. * 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({ paginator = client.value.v2.search.list({
q, q: q.value,
...resolveUnref(options), ...resolveUnref(options),
resolve: !!currentUser.value, resolve: !!currentUser.value,
}) })

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ const supportsPushNotifications = typeof window !== 'undefined'
&& 'getKey' in PushSubscription.prototype && 'getKey' in PushSubscription.prototype
export function usePushManager() { export function usePushManager() {
const { client } = $(useMasto()) const { client } = useMasto()
const isSubscribed = ref(false) const isSubscribed = ref(false)
const notificationPermission = ref<PermissionState | undefined>( const notificationPermission = ref<PermissionState | undefined>(
Notification.permission === 'denied' Notification.permission === 'denied'
@ -25,7 +25,7 @@ export function usePushManager() {
? 'prompt' ? 'prompt'
: undefined, : undefined,
) )
const isSupported = $computed(() => supportsPushNotifications) const isSupported = computed(() => supportsPushNotifications)
const hiddenNotification = useLocalStorage<PushNotificationRequest>(STORAGE_KEY_NOTIFICATION, {}) const hiddenNotification = useLocalStorage<PushNotificationRequest>(STORAGE_KEY_NOTIFICATION, {})
const configuredPolicy = useLocalStorage<PushNotificationPolicy>(STORAGE_KEY_NOTIFICATION_POLICY, {}) const configuredPolicy = useLocalStorage<PushNotificationPolicy>(STORAGE_KEY_NOTIFICATION_POLICY, {})
const pushNotificationData = ref(createRawSettings( const pushNotificationData = ref(createRawSettings(
@ -173,7 +173,7 @@ export function usePushManager() {
if (policyChanged) if (policyChanged)
await subscribe(data, policy, true) await subscribe(data, policy, true)
else 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() 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 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 }>) { export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { account?: mastodon.v1.AccountCredentials }>) {
const { client } = $(masto) const { client } = masto
const instance = mastoLogin(masto, user) const instance = mastoLogin(masto, user)
// GoToSocial only API // GoToSocial only API
@ -145,11 +145,11 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
currentUserHandle.value = account.acct currentUserHandle.value = account.acct
const [me, pushSubscription] = await Promise.all([ 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 // if PWA is not enabled, don't get push subscription
useAppConfig().pwaEnabled useAppConfig().pwaEnabled
// we get 404 response instead empty data // 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), : Promise.resolve(undefined),
]) ])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@ definePageMeta({ name: 'account-media' })
const { t } = useI18n() const { t } = useI18n()
const params = useRoute().params 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 }) 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 { t } = useI18n()
const params = useRoute().params 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 }) 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 { t } = useI18n()
const search = $ref<{ input?: HTMLInputElement }>() const search = ref<{ input?: HTMLInputElement }>()
const route = useRoute() const route = useRoute()
watchEffect(() => { watchEffect(() => {
if (isMediumOrLargeScreen && route.name === 'explore' && search?.input) if (isMediumOrLargeScreen && route.name === 'explore' && search.value?.input)
search?.input?.focus() search.value?.input?.focus()
}) })
onActivated(() => onActivated(() =>
search?.input?.focus(), search.value?.input?.focus(),
) )
onDeactivated(() => search?.input?.blur()) onDeactivated(() => search.value?.input?.blur())
const userSettings = useUserSettings() const userSettings = useUserSettings()
const tabs = $computed<CommonRouteTabOption[]>(() => [ const tabs = computed<CommonRouteTabOption[]>(() => [
{ {
to: isHydrated.value ? `/${currentServer.value}/explore` : '/explore', to: isHydrated.value ? `/${currentServer.value}/explore` : '/explore',
display: isHydrated.value ? t('tab.posts') : '', 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 { t } = useI18n()
const { client } = $(useMasto()) const { client } = useMasto()
const paginator = client.v1.trends.tags.list({ const paginator = client.value.v1.trends.tags.list({
limit: 20, limit: 20,
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,12 +6,12 @@ useHydratedHead({
title: () => `${t('settings.about.label')} | ${t('nav.settings')}`, 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) const builtTime = useFormattedDateTime(buildInfo.time)
function handleShowCommit() { function handleShowCommit() {
setTimeout(() => { setTimeout(() => {
showCommit = true showCommit.value = true
}, 50) }, 50)
} }
</script> </script>

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