forked from Mirrors/elk
Merge branch 'main' into fix/avatar-outline
This commit is contained in:
commit
30a4bed80f
162 changed files with 1140 additions and 896 deletions
|
@ -1,4 +1,5 @@
|
|||
NUXT_PUBLIC_TRANSLATE_API=
|
||||
NUXT_PUBLIC_DEFAULT_SERVER=
|
||||
|
||||
# Production only
|
||||
NUXT_CLOUDFLARE_ACCOUNT_ID=
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
*.png
|
||||
*.ico
|
||||
*.toml
|
||||
*.patch
|
||||
https-dev-config/localhost.crt
|
||||
https-dev-config/localhost.key
|
||||
|
|
|
@ -141,6 +141,7 @@ This is the full list of entries that will be available for number formatting in
|
|||
- `account.followers_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `account.following_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `account.posts_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `compose.drafts`: `{v}` for formatted number and `{n}` for raw number - **{v} should be use**
|
||||
- `notification.followed_you_count`: `{followers}` for formatted number and `{n}` for raw number - **{followers} should be use**
|
||||
- `status.poll.count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `time_ago_options.*`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**: since numbers will be always small, we can also use `{n}`
|
||||
|
|
11
README.md
11
README.md
|
@ -17,7 +17,12 @@
|
|||
|
||||
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you to use if if you would like to help us building it. We appreciate your feedback and contributions. Check out the [Open Issues](https://github.com/elk-zone/elk/issues) and jump in the action. Join the [Elk discord server](https://chat.elk.zone) to chat with us and learn more about the project.
|
||||
|
||||
The client is deployed to [elk.zone](https://elk.zone), you can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friedns and invite others you think could be interested in helping to improve Elk.
|
||||
The client is deployed on:
|
||||
|
||||
- 🦌 Production: [elk.zone](https://elk.zone)
|
||||
- 🐙 Canary: [main.elk.zone](https://main.elk.zone) (deploys on every commit to `main` branch)
|
||||
|
||||
You can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friends and invite others you think could be interested in helping to improve Elk.
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
@ -41,6 +46,10 @@ And all the companies and individuals sponsoring Elk Team members. If you're enj
|
|||
|
||||
We would also appreciate sponsoring other contributors to the Elk project. If someone helps you solve an issue or implement a feature you wanted, supporting them would help make this project and OS more sustainable.
|
||||
|
||||
## Roadmap
|
||||
|
||||
[Open board on Volta](https://volta.net/elk-zone/elk)
|
||||
|
||||
## Contributing
|
||||
|
||||
We're really excited that you're interested in contributing to Elk! Before submitting your contribution, please read through the following guide.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
square?: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
// Avatar with a background base achieving a 3px border to be used in status cards
|
||||
// The border is used for Avatar on Avatar for reblogs and connecting replies
|
||||
|
||||
defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
square?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :key="account.avatar" v-bind="$attrs" :class="{ 'rounded-full bg-base': !square }" w-54px h-54px flex items-center justify-center>
|
||||
<div :key="account.avatar" v-bind="$attrs" :style="{ 'clip-path': square ? `url(#avatar-mask)` : 'none' }" :class="{ 'rounded-full bg-base': !square }" w-54px h-54px flex items-center justify-center>
|
||||
<AccountAvatar :account="account" w-48px h-48px :square="square" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account, as = 'div' } = $defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
as?: string
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
showLabel?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~" items-center border="~ base" text-secondary-light rounded-md px-1 text-xs my-auto>
|
||||
<div
|
||||
flex="~ gap1" items-center
|
||||
:class="{ 'border border-base rounded-md px-1': showLabel }"
|
||||
text-secondary-light
|
||||
>
|
||||
<slot name="prepend" />
|
||||
<CommonTooltip :content="$t('account.bot')" :disabled="showLabel">
|
||||
<div i-ri:robot-line />
|
||||
</CommonTooltip>
|
||||
<div v-if="showLabel">
|
||||
{{ $t('account.bot') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
hoverCard?: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account, Relationship } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account, command, ...props } = defineProps<{
|
||||
account: Account
|
||||
relationship?: Relationship
|
||||
account: mastodon.v1.Account
|
||||
relationship?: mastodon.v1.Relationship
|
||||
command?: boolean
|
||||
}>()
|
||||
|
||||
|
@ -15,7 +15,7 @@ const masto = useMasto()
|
|||
async function toggleFollow() {
|
||||
relationship!.following = !relationship!.following
|
||||
try {
|
||||
const newRel = await masto.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
|
||||
const newRel = await masto.v1.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
|
||||
Object.assign(relationship!, newRel)
|
||||
}
|
||||
catch {
|
||||
|
@ -27,7 +27,7 @@ async function toggleFollow() {
|
|||
async function unblock() {
|
||||
relationship!.blocking = false
|
||||
try {
|
||||
const newRel = await masto.accounts.unblock(account.id)
|
||||
const newRel = await masto.v1.accounts.unblock(account.id)
|
||||
Object.assign(relationship!, newRel)
|
||||
}
|
||||
catch {
|
||||
|
@ -39,7 +39,7 @@ async function unblock() {
|
|||
async function unmute() {
|
||||
relationship!.muting = false
|
||||
try {
|
||||
const newRel = await masto.accounts.unmute(account.id)
|
||||
const newRel = await masto.v1.accounts.unmute(account.id)
|
||||
Object.assign(relationship!, newRel)
|
||||
}
|
||||
catch {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
|
||||
const serverName = $computed(() => getServerName(account))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account, Field } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
command?: boolean
|
||||
}>()
|
||||
|
||||
|
@ -14,8 +14,8 @@ const createdAt = $(useFormattedDateTime(() => account.createdAt, {
|
|||
year: 'numeric',
|
||||
}))
|
||||
|
||||
const namedFields = ref<Field[]>([])
|
||||
const iconFields = ref<Field[]>([])
|
||||
const namedFields = ref<mastodon.v1.AccountField[]>([])
|
||||
const iconFields = ref<mastodon.v1.AccountField[]>([])
|
||||
|
||||
function getFieldIconTitle(fieldName: string) {
|
||||
return fieldName === 'Joined' ? t('account.joined') : fieldName
|
||||
|
@ -40,8 +40,8 @@ function previewAvatar() {
|
|||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const named: Field[] = []
|
||||
const icons: Field[] = []
|
||||
const named: mastodon.v1.AccountField[] = []
|
||||
const icons: mastodon.v1.AccountField[] = []
|
||||
|
||||
account.fields?.forEach((field) => {
|
||||
const icon = getAccountFieldIcon(field.name)
|
||||
|
@ -76,7 +76,7 @@ const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
|||
<div flex="~ col gap1">
|
||||
<div flex justify-between>
|
||||
<AccountDisplayName :account="account" font-bold sm:text-2xl text-xl />
|
||||
<AccountBotIndicator v-if="account.bot" />
|
||||
<AccountBotIndicator v-if="account.bot" show-label />
|
||||
</div>
|
||||
<AccountHandle :account="account" />
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
|
||||
const relationship = $(useRelationship(account))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
account?: Account
|
||||
account?: mastodon.v1.Account
|
||||
handle?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account, as = 'div' } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
as?: string
|
||||
hoverCard?: boolean
|
||||
square?: boolean
|
||||
|
@ -23,7 +23,7 @@ defineOptions({
|
|||
<div flex="~ col" shrink pt-1 h-full overflow-hidden justify-center leading-none>
|
||||
<div flex="~" gap-2>
|
||||
<AccountDisplayName :account="account" font-bold line-clamp-1 ws-pre-wrap break-all text-lg />
|
||||
<AccountBotIndicator v-if="account.bot" />
|
||||
<AccountBotIndicator v-if="account.bot" text-xs />
|
||||
</div>
|
||||
<AccountHandle :account="account" text-secondary-light />
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { link = true, avatar = true } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
link?: boolean
|
||||
avatar?: boolean
|
||||
}>()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
command?: boolean
|
||||
}>()
|
||||
let relationship = $(useRelationship(account))
|
||||
|
@ -15,24 +15,31 @@ const toggleMute = async () => {
|
|||
|
||||
relationship!.muting = !relationship!.muting
|
||||
relationship = relationship!.muting
|
||||
? await masto.accounts.mute(account.id, {
|
||||
? await masto.v1.accounts.mute(account.id, {
|
||||
// TODO support more options
|
||||
})
|
||||
: await masto.accounts.unmute(account.id)
|
||||
: await masto.v1.accounts.unmute(account.id)
|
||||
}
|
||||
|
||||
const toggleBlockUser = async () => {
|
||||
// TODO: Add confirmation
|
||||
|
||||
relationship!.blocking = !relationship!.blocking
|
||||
relationship = await masto.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
|
||||
relationship = await masto.v1.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
|
||||
}
|
||||
|
||||
const toggleBlockDomain = async () => {
|
||||
// TODO: Add confirmation
|
||||
|
||||
relationship!.domainBlocking = !relationship!.domainBlocking
|
||||
await masto.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
||||
await masto.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
||||
}
|
||||
|
||||
const toggleReblogs = async () => {
|
||||
// TODO: Add confirmation
|
||||
|
||||
const showingReblogs = !relationship?.showingReblogs
|
||||
relationship = await masto.v1.accounts.follow(account.id, { reblogs: showingReblogs })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -68,6 +75,21 @@ const toggleBlockDomain = async () => {
|
|||
@click="directMessageUser(account)"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!relationship?.showingReblogs"
|
||||
icon="i-ri:repeat-line"
|
||||
:text="$t('menu.show_reblogs', [`@${account.acct}`])"
|
||||
:command="command"
|
||||
@click="toggleReblogs"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.hide_reblogs', [`@${account.acct}`])"
|
||||
icon="i-ri:repeat-line"
|
||||
:command="command"
|
||||
@click="toggleReblogs"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!relationship?.muting"
|
||||
:text="$t('menu.mute_account', [`@${account.acct}`])"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
// type used in <template>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
@ -16,9 +14,8 @@ defineProps<{
|
|||
</div>
|
||||
|
||||
<div flex>
|
||||
<!-- type error of masto.js -->
|
||||
<NuxtLink :to="getAccountRoute(account.moved as unknown as Account)">
|
||||
<AccountInfo :account="account.moved as unknown as Account" />
|
||||
<NuxtLink :to="getAccountRoute(account.moved!)">
|
||||
<AccountInfo :account="account.moved!" />
|
||||
</NuxtLink>
|
||||
<div flex-auto />
|
||||
<div flex items-center>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account, Paginator } from 'masto'
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
|
||||
const { paginator } = defineProps<{
|
||||
paginator: Paginator<any, Account[]>
|
||||
paginator: Paginator<mastodon.v1.Account[], mastodon.DefaultPaginationParams>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
const { formatHumanReadableNumber, formatNumber, forSR } = useHumanReadableNumber()
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ const tabs = $computed(() => [
|
|||
params: { server, account },
|
||||
},
|
||||
display: t('tab.posts_with_replies'),
|
||||
icon: 'i-ri:chat-3-line',
|
||||
icon: 'i-ri:chat-1-line',
|
||||
},
|
||||
{
|
||||
name: 'account-media',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ResolvedCommand } from '@/composables/command'
|
||||
import type { ResolvedCommand } from '~/composables/command'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'activate'): void
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { AccountResult, HashTagResult, SearchResult as SearchResultType } from '@/components/search/types'
|
||||
import type { CommandScope, QueryResult, QueryResultItem } from '@/composables/command'
|
||||
import type { SearchResult as SearchResultType } from '~/composables/masto/search'
|
||||
import type { CommandScope, QueryResult, QueryResultItem } from '~/composables/command'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'close'): void
|
||||
|
@ -39,22 +39,8 @@ const searchResult = $computed<QueryResult>(() => {
|
|||
|
||||
// TODO extract this scope
|
||||
// duplicate in SearchWidget.vue
|
||||
const hashtagList = hashtags.value.slice(0, 3)
|
||||
.map<HashTagResult>(hashtag => ({
|
||||
type: 'hashtag',
|
||||
id: hashtag.id,
|
||||
hashtag,
|
||||
to: getTagRoute(hashtag.name),
|
||||
}))
|
||||
.map(toSearchQueryResultItem)
|
||||
const accountList = accounts.value
|
||||
.map<AccountResult>(account => ({
|
||||
type: 'account',
|
||||
id: account.id,
|
||||
account,
|
||||
to: getAccountRoute(account),
|
||||
}))
|
||||
.map(toSearchQueryResultItem)
|
||||
const hashtagList = hashtags.value.slice(0, 3).map(toSearchQueryResultItem)
|
||||
const accountList = accounts.value.map(toSearchQueryResultItem)
|
||||
|
||||
const grouped: QueryResult['grouped'] = new Map()
|
||||
grouped.set('Hashtags', hashtagList)
|
||||
|
|
|
@ -20,7 +20,7 @@ function close() {
|
|||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<button text-xl hover:text-primary bg-hover-overflow w-1.4em h-1.4em @click="close()">
|
||||
<button text-xl hover:text-primary bg-hover-overflow w="1.4em" h="1.4em" @click="close()">
|
||||
<div i-ri:close-line />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="T, O">
|
||||
// @ts-expect-error missing types
|
||||
import { DynamicScroller } from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
@ -12,20 +12,25 @@ const {
|
|||
eventType = 'update',
|
||||
preprocess,
|
||||
} = defineProps<{
|
||||
paginator: Paginator<any, any[]>
|
||||
keyProp?: string
|
||||
paginator: Paginator<T[], O>
|
||||
keyProp?: keyof T
|
||||
virtualScroller?: boolean
|
||||
stream?: Promise<WsEvents>
|
||||
eventType?: 'notification' | 'update'
|
||||
preprocess?: (items: any[]) => any[]
|
||||
preprocess?: (items: T[]) => any[]
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
default: {
|
||||
item: any
|
||||
items: T[]
|
||||
item: T
|
||||
index: number
|
||||
active?: boolean
|
||||
older?: any
|
||||
newer?: any // newer is undefined when index === 0
|
||||
older?: T
|
||||
newer?: T // newer is undefined when index === 0
|
||||
}
|
||||
items: {
|
||||
items: T[]
|
||||
}
|
||||
updater: {
|
||||
number: number
|
||||
|
@ -35,6 +40,8 @@ defineSlots<{
|
|||
done: {}
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, stream, eventType, preprocess)
|
||||
</script>
|
||||
|
||||
|
@ -56,16 +63,20 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
|||
:active="active"
|
||||
:older="items[index + 1]"
|
||||
:newer="items[index - 1]"
|
||||
:index="index"
|
||||
:items="items"
|
||||
/>
|
||||
</DynamicScroller>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot
|
||||
v-for="item, index of items"
|
||||
:key="item[keyProp]"
|
||||
:key="(item as any)[keyProp]"
|
||||
:item="item"
|
||||
:older="items[index + 1]"
|
||||
:newer="items[index - 1]"
|
||||
:index="index"
|
||||
:items="items"
|
||||
/>
|
||||
</template>
|
||||
</slot>
|
||||
|
@ -75,11 +86,11 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
|||
</slot>
|
||||
<slot v-else-if="state === 'done'" name="done">
|
||||
<div p5 text-secondary italic text-center>
|
||||
{{ $t('common.end_of_list') }}
|
||||
{{ t('common.end_of_list') }}
|
||||
</div>
|
||||
</slot>
|
||||
<div v-else-if="state === 'error'" p5 text-secondary>
|
||||
{{ $t('common.error') }}: {{ error }}
|
||||
{{ t('common.error') }}: {{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import type { History } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const {
|
||||
history,
|
||||
maxDay = 2,
|
||||
} = $defineProps<{
|
||||
history: History[]
|
||||
history: mastodon.v1.TagHistory[]
|
||||
maxDay?: number
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { History } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import sparkline from '@fnando/sparkline'
|
||||
|
||||
const {
|
||||
|
@ -7,7 +7,7 @@ const {
|
|||
width = 60,
|
||||
height = 40,
|
||||
} = $defineProps<{
|
||||
history?: History[]
|
||||
history?: mastodon.v1.TagHistory[]
|
||||
width?: number
|
||||
height?: number
|
||||
}>()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Emoji } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineOptions({
|
||||
name: 'ContentRich',
|
||||
|
@ -10,7 +10,7 @@ const {
|
|||
markdown = true,
|
||||
} = defineProps<{
|
||||
content: string
|
||||
emojis?: Emoji[]
|
||||
emojis?: mastodon.v1.CustomEmoji[]
|
||||
markdown?: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Conversation } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { conversation } = defineProps<{
|
||||
conversation: Conversation
|
||||
conversation: mastodon.v1.Conversation
|
||||
}>()
|
||||
|
||||
const withAccounts = $computed(() =>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Conversation, Paginator } from 'masto'
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
|
||||
const { paginator } = defineProps<{
|
||||
paginator: Paginator<any, Conversation[]>
|
||||
paginator: Paginator<mastodon.v1.Conversation[], mastodon.DefaultPaginationParams>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import type { ConfirmDialogChoice } from '~/types'
|
||||
import {
|
||||
isCommandPanelOpen,
|
||||
|
@ -30,7 +30,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
|
|||
}
|
||||
})
|
||||
|
||||
const handlePublished = (status: Status) => {
|
||||
const handlePublished = (status: mastodon.v1.Status) => {
|
||||
lastPublishDialogStatus.value = status
|
||||
isPublishDialogOpen.value = false
|
||||
}
|
||||
|
|
|
@ -40,14 +40,14 @@ onUnmounted(() => locked.value = false)
|
|||
<div relative h-full w-full flex pt-12 w-100vh @click="onClick">
|
||||
<button
|
||||
v-if="hasNext" pointer-events-auto btn-action-icon bg="black/20" :aria-label="$t('action.previous')"
|
||||
hover:bg="black/40" dark:bg="white/30" dark:hover:bg="white/20" absolute top="1/2" right-1 z5
|
||||
hover:bg="black/40" dark:bg="white/30" dark-hover:bg="white/20" absolute top="1/2" right-1 z5
|
||||
:title="$t('action.next')" @click="next"
|
||||
>
|
||||
<div i-ri:arrow-right-s-line text-white />
|
||||
</button>
|
||||
<button
|
||||
v-if="hasPrev" pointer-events-auto btn-action-icon bg="black/20" aria-label="action.next"
|
||||
hover:bg="black/40" dark:bg="white/30" dark:hover:bg="white/20" absolute top="1/2" left-1 z5
|
||||
hover:bg="black/40" dark:bg="white/30" dark:hover-bg="white/20" absolute top="1/2" left-1 z5
|
||||
:title="$t('action.prev')" @click="prev"
|
||||
>
|
||||
<div i-ri:arrow-left-s-line text-white />
|
||||
|
@ -60,7 +60,7 @@ onUnmounted(() => locked.value = false)
|
|||
<div absolute top-0 w-full flex justify-between>
|
||||
<button
|
||||
btn-action-icon bg="black/30" aria-label="action.close" hover:bg="black/40" dark:bg="white/30"
|
||||
dark:hover:bg="white/20" pointer-events-auto shrink-0 @click="emit('close')"
|
||||
dark:hover-bg="white/20" pointer-events-auto shrink-0 @click="emit('close')"
|
||||
>
|
||||
<div i-ri:close-line text-white />
|
||||
</button>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { SwipeDirection } from '@vueuse/core'
|
||||
import { useReducedMotion } from '@vueuse/motion'
|
||||
import type { Attachment } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { media = [], threshold = 20 } = defineProps<{
|
||||
media?: Attachment[]
|
||||
media?: mastodon.v1.MediaAttachment[]
|
||||
threshold?: number
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
|
||||
const buildInfo = useRuntimeConfig().public.buildInfo
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
const buildTimeDate = new Date(buildInfo.time)
|
||||
|
@ -16,7 +15,7 @@ function toggleDark() {
|
|||
<footer p4 text-sm text-secondary-light flex="~ col">
|
||||
<div flex="~ gap2" items-center mb4>
|
||||
<CommonTooltip :content="$t('nav.toggle_theme')">
|
||||
<button flex i-ri:sun-line dark:i-ri:moon-line text-lg :aria-label="$t('nav.toggle_theme')" @click="toggleDark()" />
|
||||
<button flex i-ri:sun-line dark-i-ri:moon-line text-lg :aria-label="$t('nav.toggle_theme')" @click="toggleDark()" />
|
||||
</CommonTooltip>
|
||||
<CommonTooltip :content="$t('nav.zen_mode')">
|
||||
<button
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
|
||||
const { env } = buildInfo
|
||||
const { env } = useBuildInfo()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Notification } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { notification } = defineProps<{
|
||||
notification: Notification
|
||||
notification: mastodon.v1.Notification
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
// type used in <template>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
import type { Notification, Paginator, WsEvents } from 'masto'
|
||||
import { mastodon } from 'masto'
|
||||
import type { Paginator, WsEvents } from 'masto'
|
||||
// type used in <template>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
import type { GroupedAccountLike, GroupedLikeNotifications, NotificationSlot } from '~/types'
|
||||
|
||||
const { paginator, stream } = defineProps<{
|
||||
paginator: Paginator<any, Notification[]>
|
||||
paginator: Paginator<mastodon.v1.Notification[], mastodon.v1.ListNotificationsParams>
|
||||
stream?: Promise<WsEvents>
|
||||
}>()
|
||||
|
||||
const groupCapacity = Number.MAX_VALUE // No limit
|
||||
|
||||
// Group by type (and status when applicable)
|
||||
const groupId = (item: Notification): string => {
|
||||
const groupId = (item: mastodon.v1.Notification): string => {
|
||||
// If the update is related to an status, group notifications from the same account (boost + favorite the same status)
|
||||
const id = item.status
|
||||
? {
|
||||
|
@ -27,12 +26,12 @@ const groupId = (item: Notification): string => {
|
|||
return JSON.stringify(id)
|
||||
}
|
||||
|
||||
function groupItems(items: Notification[]): NotificationSlot[] {
|
||||
function groupItems(items: mastodon.v1.Notification[]): NotificationSlot[] {
|
||||
const results: NotificationSlot[] = []
|
||||
|
||||
let id = 0
|
||||
let currentGroupId = ''
|
||||
let currentGroup: Notification[] = []
|
||||
let currentGroup: mastodon.v1.Notification[] = []
|
||||
const processGroup = () => {
|
||||
if (currentGroup.length === 0)
|
||||
return
|
||||
|
@ -127,7 +126,7 @@ const { formatNumber } = useHumanReadableNumber()
|
|||
/>
|
||||
<NotificationCard
|
||||
v-else
|
||||
:notification="item as Notification"
|
||||
:notification="item as mastodon.v1.Notification"
|
||||
hover:bg-active
|
||||
border="b base"
|
||||
/>
|
||||
|
|
|
@ -31,7 +31,7 @@ const { modelValue } = defineModel<{
|
|||
:aria-label="$t('settings.notifications.push_notifications.subscription_error.clear_error')"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
<span aria-hidden="true" w-1.75em h-1.75em i-ri:close-line />
|
||||
<span aria-hidden="true" w="1.75em" h="1.75em" i-ri:close-line />
|
||||
</button>
|
||||
</CommonTooltip>
|
||||
</head>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Attachment } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
attachment: Attachment
|
||||
attachment: mastodon.v1.MediaAttachment
|
||||
alt?: string
|
||||
removable?: boolean
|
||||
dialogLabelledBy?: string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Attachment, CreateStatusParams, Status, StatusVisibility } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import { fileOpen } from 'browser-fs-access'
|
||||
import { useDropZone } from '@vueuse/core'
|
||||
import { EditorContent } from '@tiptap/vue-3'
|
||||
|
@ -18,13 +18,13 @@ const {
|
|||
initial?: () => Draft
|
||||
placeholder?: string
|
||||
inReplyToId?: string
|
||||
inReplyToVisibility?: StatusVisibility
|
||||
inReplyToVisibility?: mastodon.v1.StatusVisibility
|
||||
expanded?: boolean
|
||||
dialogLabelledBy?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(evt: 'published', status: Status): void
|
||||
(evt: 'published', status: mastodon.v1.Status): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
@ -103,7 +103,7 @@ async function uploadAttachments(files: File[]) {
|
|||
if (draft.attachments.length < limit) {
|
||||
isExceedingAttachmentLimit = false
|
||||
try {
|
||||
const attachment = await masto.mediaAttachments.create({
|
||||
const attachment = await masto.v1.mediaAttachments.create({
|
||||
file,
|
||||
})
|
||||
draft.attachments.push(attachment)
|
||||
|
@ -122,9 +122,9 @@ async function uploadAttachments(files: File[]) {
|
|||
isUploading = false
|
||||
}
|
||||
|
||||
async function setDescription(att: Attachment, description: string) {
|
||||
async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
|
||||
att.description = description
|
||||
await masto.mediaAttachments.update(att.id, { description: att.description })
|
||||
await masto.v1.mediaAttachments.update(att.id, { description: att.description })
|
||||
}
|
||||
|
||||
function removeAttachment(index: number) {
|
||||
|
@ -136,8 +136,8 @@ async function publish() {
|
|||
...draft.params,
|
||||
status: htmlToText(draft.params.status || ''),
|
||||
mediaIds: draft.attachments.map(a => a.id),
|
||||
...(masto.version.includes('+glitch') ? { 'content-type': 'text/markdown' } : {}),
|
||||
} as CreateStatusParams
|
||||
...((masto.config as any).props.version.raw.includes('+glitch') ? { 'content-type': 'text/markdown' } : {}),
|
||||
} as mastodon.v1.CreateStatusParams
|
||||
|
||||
if (process.dev) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -154,11 +154,13 @@ async function publish() {
|
|||
try {
|
||||
isSending = true
|
||||
|
||||
let status: Status
|
||||
let status: mastodon.v1.Status
|
||||
if (!draft.editingStatus)
|
||||
status = await masto.statuses.create(payload)
|
||||
status = await masto.v1.statuses.create(payload)
|
||||
else
|
||||
status = await masto.statuses.update(draft.editingStatus.id, payload)
|
||||
status = await masto.v1.statuses.update(draft.editingStatus.id, payload)
|
||||
if (draft.params.inReplyToId)
|
||||
navigateToStatus({ status })
|
||||
|
||||
draft = initial()
|
||||
emit('published', status)
|
||||
|
@ -251,7 +253,7 @@ defineExpose({
|
|||
:aria-label="$t('action.clear_upload_failed')"
|
||||
@click="failed = []"
|
||||
>
|
||||
<span aria-hidden="true" w-1.75em h-1.75em i-ri:close-line />
|
||||
<span aria-hidden="true" w="1.75em" h="1.75em" i-ri:close-line />
|
||||
</button>
|
||||
</CommonTooltip>
|
||||
</head>
|
||||
|
@ -313,7 +315,7 @@ defineExpose({
|
|||
|
||||
<div flex-auto />
|
||||
|
||||
<div dir="ltr" pointer-events-none pe-1 pt-2 text-sm tabular-nums text-secondary flex gap-0.5 :class="{ 'text-rose-500': characterCount > characterLimit }">
|
||||
<div dir="ltr" pointer-events-none pe-1 pt-2 text-sm tabular-nums text-secondary flex gap="0.5" :class="{ 'text-rose-500': characterCount > characterLimit }">
|
||||
{{ characterCount ?? 0 }}<span text-secondary-light>/</span><span text-secondary-light>{{ characterLimit }}</span>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import { formatTimeAgo } from '@vueuse/core'
|
||||
|
||||
const route = useRoute()
|
||||
const { formatNumber } = useHumanReadableNumber()
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
let draftKey = $ref('home')
|
||||
|
||||
|
@ -25,7 +27,7 @@ onMounted(() => {
|
|||
<div text-right h-8>
|
||||
<VDropdown v-if="nonEmptyDrafts.length" placement="bottom-end">
|
||||
<button btn-text flex="inline center">
|
||||
Drafts ({{ nonEmptyDrafts.length }}) <div i-ri:arrow-down-s-line />
|
||||
{{ $t('compose.drafts', nonEmptyDrafts.length, { named: { v: formatNumber(nonEmptyDrafts.length) } }) }} <div aria-hidden="true" i-ri:arrow-down-s-line />
|
||||
</button>
|
||||
<template #popper="{ hide }">
|
||||
<div flex="~ col">
|
||||
|
@ -38,9 +40,11 @@ onMounted(() => {
|
|||
>
|
||||
<div>
|
||||
<div flex="~ gap-1" items-center>
|
||||
Draft <code>{{ key }}</code>
|
||||
<i18n-t keypath="compose.draft_title">
|
||||
<code>{{ key }}</code>
|
||||
</i18n-t>
|
||||
<span v-if="draft.lastUpdated" text-secondary text-sm>
|
||||
· {{ formatTimeAgo(new Date(draft.lastUpdated)) }}
|
||||
· {{ formatTimeAgo(new Date(draft.lastUpdated), timeAgoOptions) }}
|
||||
</span>
|
||||
</div>
|
||||
<div text-secondary>
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button flex gap-2 items-center>
|
||||
<div flex gap-2 items-center>
|
||||
<AccountAvatar w-10 h-10 :account="account" shrink-0 />
|
||||
<div flex="~ col gap1" shrink h-full overflow-hidden leading-none>
|
||||
<div flex="~" gap-2>
|
||||
<AccountDisplayName :account="account" line-clamp-1 ws-pre-wrap break-all text-base />
|
||||
<AccountBotIndicator v-if="account.bot" />
|
||||
<AccountBotIndicator v-if="account.bot" text-xs />
|
||||
</div>
|
||||
<AccountHandle text-sm :account="account" text-secondary-light />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { Tag } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { hashtag } = defineProps<{ hashtag: Tag }>()
|
||||
const { hashtag } = defineProps<{
|
||||
hashtag: mastodon.v1.Tag
|
||||
}>()
|
||||
|
||||
const totalTrend = $computed(() =>
|
||||
hashtag.history?.reduce((total: number, item) => total + (Number(item.accounts) || 0), 0),
|
||||
|
@ -20,7 +22,10 @@ const totalTrend = $computed(() =>
|
|||
<CommonTrending :history="hashtag.history" text-xs text-secondary truncate />
|
||||
</div>
|
||||
<div v-if="totalTrend" absolute left-15 right-0 top-0 bottom-4 op35 flex place-items-center place-content-center ml-auto>
|
||||
<CommonTrendingCharts :history="hashtag.history" text-xs text-secondary width="150" height="20" h-full w-full />
|
||||
<CommonTrendingCharts
|
||||
:history="hashtag.history" :width="150" :height="20"
|
||||
text-xs text-secondary h-full w-full
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { SearchResult } from './types'
|
||||
import type { SearchResult } from '~/composables/masto/search'
|
||||
|
||||
defineProps<{
|
||||
result: SearchResult
|
||||
|
@ -21,9 +21,9 @@ const onActivate = () => {
|
|||
:class="{ 'bg-active': active }"
|
||||
@click="() => onActivate()"
|
||||
>
|
||||
<SearchHashtagInfo v-if="result.type === 'hashtag'" :hashtag="result.hashtag" />
|
||||
<SearchAccountInfo v-else-if="result.type === 'account' && result.account" :account="result.account" />
|
||||
<StatusCard v-else-if="result.type === 'status' && result.status" :status="result.status" :actions="false" :show-reply-to="false" />
|
||||
<SearchHashtagInfo v-if="result.type === 'hashtag'" :hashtag="result.data" />
|
||||
<SearchAccountInfo v-else-if="result.type === 'account'" :account="result.data" />
|
||||
<StatusCard v-else-if="result.type === 'status'" :status="result.data" :actions="false" :show-reply-to="false" />
|
||||
<!-- <div v-else-if="result.type === 'action'" text-center>
|
||||
{{ result.action!.label }}
|
||||
</div> -->
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import type { AccountResult, HashTagResult, StatusResult } from './types'
|
||||
|
||||
const query = ref('')
|
||||
const { accounts, hashtags, loading, statuses } = useSearch(query)
|
||||
const index = ref(0)
|
||||
|
@ -15,24 +13,9 @@ const results = computed(() => {
|
|||
return []
|
||||
|
||||
const results = [
|
||||
...hashtags.value.slice(0, 3).map<HashTagResult>(hashtag => ({
|
||||
type: 'hashtag',
|
||||
id: hashtag.id,
|
||||
hashtag,
|
||||
to: getTagRoute(hashtag.name),
|
||||
})),
|
||||
...accounts.value.map<AccountResult>(account => ({
|
||||
type: 'account',
|
||||
id: account.id,
|
||||
account,
|
||||
to: getAccountRoute(account),
|
||||
})),
|
||||
...statuses.value.map<StatusResult>(status => ({
|
||||
type: 'status',
|
||||
id: status.id,
|
||||
status,
|
||||
to: getStatusRoute(status),
|
||||
})),
|
||||
...hashtags.value.slice(0, 3),
|
||||
...accounts.value,
|
||||
...statuses.value,
|
||||
|
||||
// Disable until search page is implemented
|
||||
// {
|
||||
|
@ -53,16 +36,18 @@ watch([results, focused], () => index.value = -1)
|
|||
const shift = (delta: number) => index.value = (index.value + delta % results.value.length + results.value.length) % results.value.length
|
||||
|
||||
const activate = () => {
|
||||
(document.activeElement as HTMLElement).blur()
|
||||
const currentIndex = index.value
|
||||
index.value = -1
|
||||
|
||||
if (query.value.length === 0)
|
||||
return
|
||||
|
||||
(document.activeElement as HTMLElement).blur()
|
||||
|
||||
// Disable until search page is implemented
|
||||
// if (currentIndex === -1)
|
||||
if (currentIndex === -1)
|
||||
// router.push(`/search?q=${query.value}`)
|
||||
return
|
||||
|
||||
router.push(results.value[currentIndex].to)
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import type { Account, Status } from 'masto'
|
||||
import type { RouteLocation } from 'vue-router'
|
||||
|
||||
export type BuildResult<K extends keyof any, T> = {
|
||||
[P in K]: T
|
||||
} & {
|
||||
id: string
|
||||
type: K
|
||||
to: RouteLocation & {
|
||||
href: string
|
||||
}
|
||||
}
|
||||
export type HashTagResult = BuildResult<'hashtag', any>
|
||||
export type AccountResult = BuildResult<'account', Account>
|
||||
export type StatusResult = BuildResult<'status', Status>
|
||||
|
||||
export type SearchResult = HashTagResult | AccountResult | StatusResult
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { UpdateCredentialsParams } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { form } = defineModel<{
|
||||
form: {
|
||||
fieldsAttributes: NonNullable<UpdateCredentialsParams['fieldsAttributes']>
|
||||
fieldsAttributes: NonNullable<mastodon.v1.UpdateCredentialsParams['fieldsAttributes']>
|
||||
}
|
||||
}>()
|
||||
const dropdown = $ref<any>()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account, link = true } = defineProps<{
|
||||
account: Account
|
||||
account: mastodon.v1.Account
|
||||
link?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
const { as = 'button', command, disabled, content, icon } = defineProps<{
|
||||
text?: string | number
|
||||
content: string
|
||||
color: string
|
||||
|
@ -27,10 +27,10 @@ useCommand({
|
|||
scope: 'Actions',
|
||||
|
||||
order: -2,
|
||||
visible: () => props.command && !props.disabled,
|
||||
visible: () => command && !disabled,
|
||||
|
||||
name: () => props.content,
|
||||
icon: () => props.icon,
|
||||
name: () => content,
|
||||
icon: () => icon,
|
||||
|
||||
onActivate() {
|
||||
if (!checkLogin())
|
||||
|
@ -47,18 +47,27 @@ useCommand({
|
|||
|
||||
<template>
|
||||
<component
|
||||
:is="as || 'button'"
|
||||
:is="as"
|
||||
v-bind="$attrs" ref="el"
|
||||
w-fit flex gap-1 items-center
|
||||
rounded group :hover="hover"
|
||||
focus:outline-none cursor-pointer
|
||||
rounded group
|
||||
:hover=" !disabled ? hover : undefined"
|
||||
focus:outline-none
|
||||
:focus-visible="hover"
|
||||
:class="active ? [color] : 'text-secondary'"
|
||||
:class="active ? color : 'text-secondary'"
|
||||
:aria-label="content"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<CommonTooltip placement="bottom" :content="content">
|
||||
<div rounded-full p2 :group-hover="groupHover" :group-focus-visible="groupHover" group-focus-visible:ring="2 current">
|
||||
<div :class="[active && activeIcon ? activeIcon : icon, { 'pointer-events-none': disabled }]" />
|
||||
<div
|
||||
rounded-full p2
|
||||
v-bind="disabled ? {} : {
|
||||
'group-hover': groupHover,
|
||||
'group-focus-visible': groupHover,
|
||||
'group-focus-visible:ring': '2 current',
|
||||
}"
|
||||
>
|
||||
<div :class="active && activeIcon ? activeIcon : icon" />
|
||||
</div>
|
||||
</CommonTooltip>
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
details?: boolean
|
||||
command?: boolean
|
||||
}>()
|
||||
|
@ -14,6 +14,7 @@ const { details, command } = $(props)
|
|||
const {
|
||||
status,
|
||||
isLoading,
|
||||
canReblog,
|
||||
toggleBookmark,
|
||||
toggleFavourite,
|
||||
toggleReblog,
|
||||
|
@ -26,9 +27,8 @@ const reply = () => {
|
|||
return
|
||||
if (details)
|
||||
focusEditor()
|
||||
|
||||
else
|
||||
navigateTo({ path: getStatusRoute(status).href, state: { focusReply: true } })
|
||||
navigateToStatus({ status, focusReply: true })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -39,7 +39,7 @@ const reply = () => {
|
|||
:content="$t('action.reply')"
|
||||
:text="status.repliesCount || ''"
|
||||
color="text-blue" hover="text-blue" group-hover="bg-blue/10"
|
||||
icon="i-ri:chat-3-line"
|
||||
icon="i-ri:chat-1-line"
|
||||
:command="command"
|
||||
@click="reply"
|
||||
>
|
||||
|
@ -63,7 +63,7 @@ const reply = () => {
|
|||
icon="i-ri:repeat-line"
|
||||
active-icon="i-ri:repeat-fill"
|
||||
:active="!!status.reblogged"
|
||||
:disabled="isLoading.reblogged"
|
||||
:disabled="isLoading.reblogged || !canReblog"
|
||||
:command="command"
|
||||
@click="toggleReblog()"
|
||||
>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
details?: boolean
|
||||
command?: boolean
|
||||
}>()
|
||||
|
@ -40,21 +40,21 @@ const toggleTranslation = async () => {
|
|||
|
||||
const masto = useMasto()
|
||||
|
||||
const getPermalinkUrl = (status: Status) => {
|
||||
const getPermalinkUrl = (status: mastodon.v1.Status) => {
|
||||
const url = getStatusPermalinkRoute(status)
|
||||
if (url)
|
||||
return `${location.origin}/${url}`
|
||||
return null
|
||||
}
|
||||
|
||||
const copyLink = async (status: Status) => {
|
||||
const copyLink = async (status: mastodon.v1.Status) => {
|
||||
const url = getPermalinkUrl(status)
|
||||
if (url)
|
||||
await clipboard.copy(url)
|
||||
}
|
||||
|
||||
const { share, isSupported: isShareSupported } = useShare()
|
||||
const shareLink = async (status: Status) => {
|
||||
const shareLink = async (status: mastodon.v1.Status) => {
|
||||
const url = getPermalinkUrl(status)
|
||||
if (url)
|
||||
await share({ url })
|
||||
|
@ -69,7 +69,7 @@ const deleteStatus = async () => {
|
|||
return
|
||||
|
||||
removeCachedStatus(status.id)
|
||||
await masto.statuses.remove(status.id)
|
||||
await masto.v1.statuses.remove(status.id)
|
||||
|
||||
if (route.name === 'status')
|
||||
router.back()
|
||||
|
@ -87,7 +87,7 @@ const deleteAndRedraft = async () => {
|
|||
}
|
||||
|
||||
removeCachedStatus(status.id)
|
||||
await masto.statuses.remove(status.id)
|
||||
await masto.v1.statuses.remove(status.id)
|
||||
await openPublishDialog('dialog', await getDraftFromStatus(status), true)
|
||||
|
||||
// Go to the new status, if the page is the old status
|
||||
|
@ -129,7 +129,7 @@ async function editStatus() {
|
|||
<template v-if="userSettings.zenMode">
|
||||
<CommonDropdownItem
|
||||
:text="$t('action.reply')"
|
||||
icon="i-ri:chat-3-line"
|
||||
icon="i-ri:chat-1-line"
|
||||
:command="command"
|
||||
@click="reply()"
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { clamp } from '@vueuse/core'
|
||||
import type { Attachment } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const {
|
||||
attachment,
|
||||
fullSize = false,
|
||||
} = defineProps<{
|
||||
attachment: Attachment
|
||||
attachments?: Attachment[]
|
||||
attachment: mastodon.v1.MediaAttachment
|
||||
attachments?: mastodon.v1.MediaAttachment[]
|
||||
fullSize?: boolean
|
||||
}>()
|
||||
|
||||
|
@ -65,14 +65,23 @@ const video = ref<HTMLVideoElement | undefined>()
|
|||
const prefersReducedMotion = usePreferredReducedMotion()
|
||||
|
||||
useIntersectionObserver(video, (entries) => {
|
||||
if (prefersReducedMotion.value === 'reduce')
|
||||
const ready = video.value?.dataset.ready === 'true'
|
||||
if (prefersReducedMotion.value === 'reduce') {
|
||||
if (ready && !video.value?.paused)
|
||||
video.value?.pause()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
if (entry.intersectionRatio <= 0.75)
|
||||
!video.value!.paused && video.value!.pause()
|
||||
else
|
||||
video.value!.play()
|
||||
if (entry.intersectionRatio <= 0.75) {
|
||||
ready && !video.value?.paused && video.value?.pause()
|
||||
}
|
||||
else {
|
||||
video.value?.play().then(() => {
|
||||
video.value!.dataset.ready = 'true'
|
||||
}).catch(noop)
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.75 })
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status, StatusEdit } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const {
|
||||
status,
|
||||
withAction = true,
|
||||
} = defineProps<{
|
||||
status: Status | StatusEdit
|
||||
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
||||
withAction?: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import type { FilterContext, Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
actions?: boolean
|
||||
context?: FilterContext
|
||||
context?: mastodon.v2.FilterContext
|
||||
hover?: boolean
|
||||
faded?: boolean
|
||||
|
||||
// If we know the prev and next status in the timeline, we can simplify the card
|
||||
older?: Status
|
||||
newer?: Status
|
||||
older?: mastodon.v1.Status
|
||||
newer?: mastodon.v1.Status
|
||||
// Manual overrides
|
||||
hasOlder?: boolean
|
||||
hasNewer?: boolean
|
||||
|
||||
// When looking into a detailed view of a post, we can simplify the replying badges
|
||||
// to the main expanded post
|
||||
main?: Status
|
||||
main?: mastodon.v1.Status
|
||||
}>(),
|
||||
{ actions: true, showReplyTo: true },
|
||||
{ actions: true },
|
||||
)
|
||||
|
||||
const status = $computed(() => {
|
||||
|
@ -32,9 +33,13 @@ const status = $computed(() => {
|
|||
const directReply = $computed(() => props.hasNewer || (!!status.inReplyToId && (status.inReplyToId === props.newer?.id || status.inReplyToId === props.newer?.reblog?.id)))
|
||||
// Use reblogged status, connect it to further replies
|
||||
const connectReply = $computed(() => props.hasOlder || status.id === props.older?.inReplyToId || status.id === props.older?.reblog?.inReplyToId)
|
||||
// Open a detailed status, the replies directly to it
|
||||
const replyToMain = $computed(() => props.main && props.main.id === status.inReplyToId)
|
||||
|
||||
const rebloggedBy = $computed(() => props.status.reblog ? props.status.account : null)
|
||||
|
||||
const statusRoute = $computed(() => getStatusRoute(status))
|
||||
|
||||
const el = ref<HTMLElement>()
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -47,13 +52,12 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
|
|||
}
|
||||
|
||||
function go(evt: MouseEvent | KeyboardEvent) {
|
||||
const route = getStatusRoute(status)
|
||||
if (evt.metaKey || evt.ctrlKey) {
|
||||
window.open(route.href)
|
||||
window.open(statusRoute.href)
|
||||
}
|
||||
else {
|
||||
cacheStatus(status)
|
||||
router.push(route)
|
||||
router.push(statusRoute)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,24 +67,19 @@ const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
|
|||
|
||||
// Content Filter logic
|
||||
const filterResult = $computed(() => status.filtered?.length ? status.filtered[0] : null)
|
||||
const filter = $computed(() => filterResult?.filter)
|
||||
const filter = $computed(() => filterResult?.filter as mastodon.v2.Filter)
|
||||
|
||||
// a bit of a hack due to Filter being different in v1 and v2
|
||||
// clean up when masto.js supports explicit versions: https://github.com/neet/masto.js/issues/722
|
||||
const filterPhrase = $computed(() => filter?.phrase || (filter as any)?.title)
|
||||
const isFiltered = $computed(() => filterPhrase && (props.context ? filter?.context.includes(props.context) : false))
|
||||
|
||||
const isSelfReply = $computed(() => status.inReplyToAccountId === status.account.id)
|
||||
const collapseRebloggedBy = $computed(() => rebloggedBy?.id === status.account.id)
|
||||
|
||||
// Collapse ReplyingTo badge if it is a self-reply (thread)
|
||||
const collapseReplyingTo = $computed(() => (!rebloggedBy || collapseRebloggedBy) && status.inReplyToAccountId === status.account.id)
|
||||
|
||||
// Only show avatar in ReplyingTo badge if it was reblogged by the same account or if it is against the main post
|
||||
const simplifyReplyingTo = $computed(() =>
|
||||
(props.main && props.main.account.id === status.inReplyToAccountId) || (rebloggedBy && rebloggedBy.id === status.inReplyToAccountId),
|
||||
)
|
||||
|
||||
const isDM = $computed(() => status.visibility === 'direct')
|
||||
|
||||
const showUpperBorder = $computed(() => props.newer && !directReply)
|
||||
const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -88,8 +87,7 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
v-if="filter?.filterAction !== 'hide'"
|
||||
:id="`status-${status.id}`"
|
||||
ref="el"
|
||||
relative flex flex-col gap-1 pl-3 pr-4 pt-1
|
||||
class="pb-1.5"
|
||||
relative flex="~ col gap1" p="l-3 r-4 b-2"
|
||||
:class="{ 'hover:bg-active': hover }"
|
||||
tabindex="0"
|
||||
focus:outline-none focus-visible:ring="2 primary"
|
||||
|
@ -97,11 +95,38 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
@click="onclick"
|
||||
@keydown.enter="onclick"
|
||||
>
|
||||
<div v-if="newer && !directReply" w-auto h-1px bg-border />
|
||||
<div flex justify-between>
|
||||
<!-- Upper border -->
|
||||
<div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 />
|
||||
|
||||
<slot name="meta">
|
||||
<div v-if="rebloggedBy && !collapseRebloggedBy" relative text-secondary ws-nowrap flex="~" items-center pt1 pb0.5 px-1px bg-base>
|
||||
<div i-ri:repeat-fill me-46px text-primary w-16px h-16px />
|
||||
<!-- Line connecting to previous status -->
|
||||
<template v-if="status.inReplyToAccountId">
|
||||
<StatusReplyingTo
|
||||
v-if="showReplyTo"
|
||||
ml-6 pt-1 pl-5
|
||||
:status="status"
|
||||
:is-self-reply="isSelfReply"
|
||||
:class="faded ? 'text-secondary-light' : ''"
|
||||
/>
|
||||
<div flex="~ col gap-1" items-center pos="absolute top-0 left-0" w="20.5" z--1>
|
||||
<template v-if="showReplyTo">
|
||||
<div w="1px" h="0.5" border="x base" mt-3 />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
</template>
|
||||
<div w="1px" h-10 border="x base" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Reblog status -->
|
||||
<div flex="~ col" justify-between>
|
||||
<div
|
||||
v-if="rebloggedBy && !collapseRebloggedBy"
|
||||
flex="~" items-center
|
||||
p="t-1 b-0.5 x-1px"
|
||||
relative text-secondary ws-nowrap
|
||||
>
|
||||
<div i-ri:repeat-fill me-46px text-green w-16px h-16px />
|
||||
<div absolute top-1 ms-24px w-32px h-32px rounded-full>
|
||||
<AccountHoverWrapper :account="rebloggedBy">
|
||||
<NuxtLink :to="getAccountRoute(rebloggedBy)">
|
||||
|
@ -111,38 +136,39 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
</div>
|
||||
<AccountInlineInfo font-bold :account="rebloggedBy" :avatar="false" text-sm />
|
||||
</div>
|
||||
<div v-else />
|
||||
</slot>
|
||||
<StatusReplyingTo v-if="!directReply && !collapseReplyingTo" :status="status" :simplified="!!simplifyReplyingTo" :class="faded ? 'text-secondary-light' : ''" pt1 />
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<div flex gap-3 :class="{ 'text-secondary': faded }">
|
||||
<!-- Avatar -->
|
||||
<div relative>
|
||||
<div v-if="collapseRebloggedBy" absolute flex items-center justify-center top--6px px-2px py-3px rounded-full bg-base>
|
||||
<div i-ri:repeat-fill text-primary w-16px h-16px />
|
||||
<div i-ri:repeat-fill text-green w-16px h-16px />
|
||||
</div>
|
||||
<AccountHoverWrapper :account="status.account">
|
||||
<NuxtLink :to="getAccountRoute(status.account)" rounded-full>
|
||||
<AccountBigAvatar :account="status.account" />
|
||||
</NuxtLink>
|
||||
</AccountHoverWrapper>
|
||||
<div v-if="connectReply" w-full h-full flex justify-center>
|
||||
<div class="w-2.5px" bg-primary-light />
|
||||
|
||||
<div v-if="connectReply" w-full h-full flex mt--3px justify-center>
|
||||
<div w-1px border="x base" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main -->
|
||||
<div flex="~ col 1" min-w-0>
|
||||
<!-- Account Info -->
|
||||
<div flex items-center space-x-1>
|
||||
<AccountHoverWrapper :account="status.account">
|
||||
<StatusAccountDetails :account="status.account" />
|
||||
</AccountHoverWrapper>
|
||||
<div v-if="!directReply && collapseReplyingTo" flex="~" ps-1 items-center justify-center>
|
||||
<StatusReplyingTo :collapsed="true" :status="status" :class="faded ? 'text-secondary-light' : ''" />
|
||||
</div>
|
||||
<div flex-auto />
|
||||
<div v-if="!userSettings.zenMode" text-sm text-secondary flex="~ row nowrap" hover:underline>
|
||||
<div v-show="!userSettings.zenMode" text-sm text-secondary flex="~ row nowrap" hover:underline>
|
||||
<AccountBotIndicator v-if="status.account.bot" me-2 />
|
||||
<div flex>
|
||||
<CommonTooltip :content="createdAt">
|
||||
<a :title="status.createdAt" :href="getStatusRoute(status).href" @click.prevent="go($event)">
|
||||
<a :title="status.createdAt" :href="statusRoute.href" @click.prevent="go($event)">
|
||||
<time text-sm ws-nowrap hover:underline :datetime="status.createdAt">
|
||||
{{ timeago }}
|
||||
</time>
|
||||
|
@ -153,10 +179,10 @@ const isDM = $computed(() => status.visibility === 'direct')
|
|||
</div>
|
||||
<StatusActionsMore v-if="actions !== false" :status="status" me--2 />
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<StatusContent :status="status" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
||||
<div>
|
||||
<StatusActions v-if="(actions !== false && !userSettings.zenMode)" :status="status" />
|
||||
</div>
|
||||
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { FilterContext, Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { status, context } = defineProps<{
|
||||
status: Status
|
||||
context?: FilterContext | 'details'
|
||||
status: mastodon.v1.Status
|
||||
context?: mastodon.v2.FilterContext | 'details'
|
||||
}>()
|
||||
|
||||
const isDM = $computed(() => status.visibility === 'direct')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
command?: boolean
|
||||
actions?: boolean
|
||||
}>(), {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status, StatusEdit } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status | StatusEdit
|
||||
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
||||
fullSize?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
}>()
|
||||
const poll = reactive({ ...status.poll! })
|
||||
|
||||
|
@ -30,7 +30,7 @@ async function vote(e: Event) {
|
|||
poll.votersCount = (poll.votersCount || 0) + 1
|
||||
cacheStatus({ ...status, poll }, undefined, true)
|
||||
|
||||
await masto.poll.vote(poll.id, { choices })
|
||||
await masto.v1.polls.vote(poll.id, { choices })
|
||||
}
|
||||
|
||||
const votersCount = $computed(() => poll.votersCount ?? 0)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Card, CardType } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
card: Card
|
||||
card: mastodon.v1.PreviewCard
|
||||
/** For the preview image, only the small image mode is displayed */
|
||||
smallPictureOnly?: boolean
|
||||
/** When it is root card in the list, not appear as a child card */
|
||||
|
@ -24,7 +24,7 @@ const providerName = $computed(() => props.card.providerName ? props.card.provid
|
|||
const gitHubCards = $(useFeatureFlag('experimentalGitHubCards'))
|
||||
|
||||
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
||||
const cardTypeIconMap: Record<CardType, string> = {
|
||||
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||
link: 'i-ri:profile-line',
|
||||
photo: 'i-ri:image-line',
|
||||
video: 'i-ri:play-line',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Card } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
card: Card
|
||||
card: mastodon.v1.PreviewCard
|
||||
/** When it is root card in the list, not appear as a child card */
|
||||
root?: boolean
|
||||
/** For the preview image, only the small image mode is displayed */
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Card } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
card: Card
|
||||
card: mastodon.v1.PreviewCard
|
||||
}>()
|
||||
|
||||
type UrlType = 'user' | 'repo' | 'issue' | 'pull'
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { status, collapsed = false, simplified = false } = defineProps<{
|
||||
status: Status
|
||||
collapsed?: boolean
|
||||
simplified?: boolean
|
||||
const {
|
||||
status,
|
||||
isSelfReply = false,
|
||||
} = defineProps<{
|
||||
status: mastodon.v1.Status
|
||||
isSelfReply: boolean
|
||||
}>()
|
||||
|
||||
const isSelf = $computed(() => status.inReplyToAccountId === status.account.id)
|
||||
|
@ -12,21 +14,27 @@ const account = isSelf ? computed(() => status.account) : useAccountById(status.
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="status.inReplyToAccountId" flex="~ wrap" gap-1 items-end>
|
||||
<NuxtLink
|
||||
v-if="status.inReplyToId"
|
||||
flex="~" items-center h-auto font-bold text-sm text-secondary gap-1
|
||||
flex="~ gap2" items-center h-auto text-sm text-secondary
|
||||
:to="getStatusInReplyToRoute(status)"
|
||||
:title="account ? `Replying to ${getDisplayName(account)}` : 'Replying to someone'"
|
||||
:title="$t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])"
|
||||
text-blue saturate-50 hover:saturate-100
|
||||
>
|
||||
<template v-if="isSelfReply">
|
||||
<div i-ri-discuss-line text-blue />
|
||||
<span>{{ $t('status.show_full_thread') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div i-ri-chat-1-line text-blue />
|
||||
<i18n-t keypath="status.replying_to">
|
||||
<template v-if="account">
|
||||
<div i-ri:reply-fill :class="collapsed ? '' : 'scale-x-[-1]'" text-secondary-light />
|
||||
<template v-if="!collapsed">
|
||||
<AccountAvatar v-if="isSelf || simplified || status.inReplyToAccountId === currentUser?.account.id" :account="account" :link="false" w-5 h-5 mx-0.5 />
|
||||
<AccountInlineInfo v-else :account="account" :link="false" mx-0.5 />
|
||||
<AccountInlineInfo :account="account" :link="false" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('status.someone') }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
<div i-ri:question-answer-line text-secondary-light text-lg />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -11,7 +11,7 @@ watchEffect(() => {
|
|||
|
||||
<template>
|
||||
<div v-if="enabled" flex flex-col items-start>
|
||||
<div class="content-rich" px-4 pb-2.5 text-center text-secondary w-full border="~ base" border-0 border-b-dotted border-b-3 mt-2>
|
||||
<div class="content-rich" p="x-4 b-2.5" text-center text-secondary w-full border="~ base" border-0 border-b-dotted border-b-3 mt-2>
|
||||
<slot name="spoiler" />
|
||||
</div>
|
||||
<div flex="~ gap-1 center" w-full mt="-4.5">
|
||||
|
|
|
@ -1,43 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status, StatusEdit } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import { formatTimeAgo } from '@vueuse/core'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
}>()
|
||||
|
||||
const masto = useMasto()
|
||||
const { data: statusEdits } = useAsyncData(`status:history:${status.id}`, () => masto.statuses.fetchHistory(status.id).then(res => res.reverse()))
|
||||
const paginator = useMasto().v1.statuses.listHistory(status.id)
|
||||
|
||||
const showHistory = (edit: StatusEdit) => {
|
||||
const showHistory = (edit: mastodon.v1.StatusEdit) => {
|
||||
openEditHistoryDialog(edit)
|
||||
}
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
const reverseHistory = (items: mastodon.v1.StatusEdit[]) =>
|
||||
[...items].reverse()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="statusEdits">
|
||||
<CommonPaginator :paginator="paginator" key-prop="createdAt" :preprocess="reverseHistory">
|
||||
<template #default="{ items, item, index }">
|
||||
<CommonDropdownItem
|
||||
v-for="(edit, idx) in statusEdits"
|
||||
:key="idx"
|
||||
px="0.5"
|
||||
@click="showHistory(edit)"
|
||||
@click="showHistory(item)"
|
||||
>
|
||||
{{ getDisplayName(edit.account) }}
|
||||
{{ getDisplayName(item.account) }}
|
||||
|
||||
<template v-if="idx === statusEdits.length - 1">
|
||||
<template v-if="index === items.length - 1">
|
||||
<i18n-t keypath="status_history.created">
|
||||
{{ formatTimeAgo(new Date(edit.createdAt), timeAgoOptions) }}
|
||||
{{ formatTimeAgo(new Date(item.createdAt), timeAgoOptions) }}
|
||||
</i18n-t>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i18n-t keypath="status_history.edited">
|
||||
{{ formatTimeAgo(new Date(edit.createdAt), timeAgoOptions) }}
|
||||
<i18n-t v-else keypath="status_history.edited">
|
||||
{{ formatTimeAgo(new Date(item.createdAt), timeAgoOptions) }}
|
||||
</i18n-t>
|
||||
</template>
|
||||
</CommonDropdownItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div i-ri:loader-2-fill animate-spin text-2xl ma />
|
||||
<template #loading>
|
||||
<StatusEditHistorySkeleton />
|
||||
<StatusEditHistorySkeleton op50 />
|
||||
<StatusEditHistorySkeleton op25 />
|
||||
</template>
|
||||
<template #done>
|
||||
<span />
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
||||
|
|
3
components/status/edit/StatusEditHistorySkeleton.vue
Normal file
3
components/status/edit/StatusEditHistorySkeleton.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<div class="skeleton-loading-bg" h-5 w-full rounded my2 />
|
||||
</template>
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
inline: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { StatusEdit } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { edit } = defineProps<{
|
||||
edit: StatusEdit
|
||||
edit: mastodon.v1.StatusEdit
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import type { Tag } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { tag } = defineProps<{
|
||||
tag: Tag
|
||||
tag: mastodon.v1.Tag
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'change'): void
|
||||
}>()
|
||||
|
||||
const { tags } = useMasto()
|
||||
const masto = useMasto()
|
||||
|
||||
const toggleFollowTag = async () => {
|
||||
if (tag.following)
|
||||
await tags.unfollow(tag.name)
|
||||
await masto.v1.tags.unfollow(tag.name)
|
||||
else
|
||||
await tags.follow(tag.name)
|
||||
await masto.v1.tags.follow(tag.name)
|
||||
|
||||
emit('change')
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Tag } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const {
|
||||
tag,
|
||||
} = $defineProps<{
|
||||
tag: Tag
|
||||
tag: mastodon.v1.Tag
|
||||
}>()
|
||||
|
||||
const to = $computed(() => new URL(tag.url).pathname)
|
||||
|
|
22
components/tag/TagCardPaginator.vue
Normal file
22
components/tag/TagCardPaginator.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
|
||||
const { paginator } = defineProps<{
|
||||
paginator: Paginator<mastodon.v1.Tag[], mastodon.DefaultPaginationParams>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonPaginator :paginator="paginator" key-prop="name">
|
||||
<template #default="{ item }">
|
||||
<TagCard :tag="item" border="b base" />
|
||||
</template>
|
||||
<template #loading>
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op25 />
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().blocks.iterate()
|
||||
const paginator = useMasto().v1.blocks.list()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().bookmarks.iterate()
|
||||
const paginator = useMasto().v1.bookmarks.list()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().conversations.iterate()
|
||||
const paginator = useMasto().v1.conversations.list()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
const masto = useMasto()
|
||||
const paginator = masto.domainBlocks.iterate()
|
||||
const paginator = masto.v1.domainBlocks.list()
|
||||
|
||||
const unblock = async (domain: string) => {
|
||||
await masto.domainBlocks.unblock(domain)
|
||||
await masto.v1.domainBlocks.unblock(domain)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().favourites.iterate()
|
||||
const paginator = useMasto().v1.favourites.list()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().timelines.iterateHome()
|
||||
const stream = useMasto().stream.streamUser()
|
||||
const paginator = useMasto().v1.timelines.listHome()
|
||||
const stream = useMasto().v1.stream.streamUser()
|
||||
onBeforeUnmount(() => stream?.then(s => s.disconnect()))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
timelines: Status[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="status of timelines" :key="status.id">
|
||||
<StatusCard :status="status" border="t base" />
|
||||
</template>
|
||||
</template>
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
// Default limit is 20 notifications, and servers are normally caped to 30
|
||||
const paginator = useMasto().notifications.iterate({ limit: 30, types: ['mention'] })
|
||||
const paginator = useMasto().v1.notifications.list({ limit: 30, types: ['mention'] })
|
||||
|
||||
const { clearNotifications } = useNotifications()
|
||||
onActivated(clearNotifications)
|
||||
|
||||
const stream = useMasto().stream.streamUser()
|
||||
const stream = useMasto().v1.stream.streamUser()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().mutes.iterate()
|
||||
const paginator = useMasto().v1.mutes.list()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
// Default limit is 20 notifications, and servers are normally caped to 30
|
||||
const paginator = useMasto().notifications.iterate({ limit: 30 })
|
||||
const paginator = useMasto().v1.notifications.list({ limit: 30 })
|
||||
|
||||
const { clearNotifications } = useNotifications()
|
||||
onActivated(clearNotifications)
|
||||
|
||||
const stream = useMasto().stream.streamUser()
|
||||
const stream = useMasto().v1.stream.streamUser()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
// @ts-expect-error missing types
|
||||
import { DynamicScrollerItem } from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
import type { Account, FilterContext, Paginator, Status, WsEvents } from 'masto'
|
||||
import type { Paginator, WsEvents, mastodon } from 'masto'
|
||||
|
||||
const { paginator, stream, account } = defineProps<{
|
||||
paginator: Paginator<any, Status[]>
|
||||
paginator: Paginator<mastodon.v1.Status[], mastodon.v1.ListAccountStatusesParams>
|
||||
stream?: Promise<WsEvents>
|
||||
context?: FilterContext
|
||||
account?: Account
|
||||
preprocess?: (items: any[]) => any[]
|
||||
context?: mastodon.v2.FilterContext
|
||||
account?: mastodon.v1.Account
|
||||
preprocess?: (items: mastodon.v1.Status[]) => mastodon.v1.Status[]
|
||||
}>()
|
||||
|
||||
const { formatNumber } = useHumanReadableNumber()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().accounts.iterateStatuses(currentUser.value!.account.id, { pinned: true })
|
||||
const paginator = useMasto().v1.accounts.listStatuses(currentUser.value!.account.id, { pinned: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().timelines.iteratePublic()
|
||||
const stream = useMasto().stream.streamPublicTimeline()
|
||||
const paginator = useMasto().v1.timelines.listPublic()
|
||||
const stream = useMasto().v1.stream.streamPublicTimeline()
|
||||
onBeforeUnmount(() => stream.then(s => s.disconnect()))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMasto().timelines.iteratePublic({ local: true })
|
||||
const stream = useMasto().stream.streamCommunityTimeline()
|
||||
const paginator = useMasto().v1.timelines.listPublic({ local: true })
|
||||
const stream = useMasto().v1.stream.streamCommunityTimeline()
|
||||
onBeforeUnmount(() => stream.then(s => s.disconnect()))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { Tag } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import CommonScrollIntoView from '../common/CommonScrollIntoView.vue'
|
||||
|
||||
const { items, command } = defineProps<{
|
||||
items: Tag[]
|
||||
items: mastodon.v1.Tag[]
|
||||
command: Function
|
||||
isPending?: boolean
|
||||
}>()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import CommonScrollIntoView from '../common/CommonScrollIntoView.vue'
|
||||
|
||||
const { items, command } = defineProps<{
|
||||
items: Account[]
|
||||
items: mastodon.v1.Account[]
|
||||
command: Function
|
||||
isPending?: boolean
|
||||
}>()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import Fuse from 'fuse.js'
|
||||
import { $fetch } from 'ofetch'
|
||||
import { DEFAULT_SERVER } from '~/constants'
|
||||
|
||||
const input = $ref<HTMLInputElement>()
|
||||
let server = $ref<string>('')
|
||||
|
@ -26,7 +25,7 @@ async function oauth() {
|
|||
server = server.split('/')[0]
|
||||
|
||||
try {
|
||||
location.href = await $fetch<string>(`/api/${server || DEFAULT_SERVER}/login`, {
|
||||
location.href = await $fetch<string>(`/api/${server || publicServer.value}/login`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
origin: location.origin,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { BuildInfo } from '~~/types'
|
||||
|
||||
export interface Team {
|
||||
github: string
|
||||
display: string
|
||||
|
@ -31,3 +33,7 @@ export const teams: Team[] = [
|
|||
mastodon: 'sxzz@webtoo.ls',
|
||||
},
|
||||
].sort(() => Math.random() - 0.5)
|
||||
|
||||
export function useBuildInfo() {
|
||||
return useRuntimeConfig().public.buildInfo as BuildInfo
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import LRU from 'lru-cache'
|
||||
import type { Account, Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const cache = new LRU<string, any>({
|
||||
max: 1000,
|
||||
|
@ -17,13 +17,13 @@ function removeCached(key: string) {
|
|||
cache.delete(key)
|
||||
}
|
||||
|
||||
export function fetchStatus(id: string, force = false): Promise<Status> {
|
||||
export function fetchStatus(id: string, force = false): Promise<mastodon.v1.Status> {
|
||||
const server = currentServer.value
|
||||
const key = `${server}:status:${id}`
|
||||
const cached = cache.get(key)
|
||||
if (cached && !force)
|
||||
return cached
|
||||
const promise = useMasto().statuses.fetch(id)
|
||||
const promise = useMasto().v1.statuses.fetch(id)
|
||||
.then((status) => {
|
||||
cacheStatus(status)
|
||||
return status
|
||||
|
@ -32,7 +32,7 @@ export function fetchStatus(id: string, force = false): Promise<Status> {
|
|||
return promise
|
||||
}
|
||||
|
||||
export function fetchAccountById(id?: string | null): Promise<Account | null> {
|
||||
export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Account | null> {
|
||||
if (!id)
|
||||
return Promise.resolve(null)
|
||||
|
||||
|
@ -41,11 +41,11 @@ export function fetchAccountById(id?: string | null): Promise<Account | null> {
|
|||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
const uri = currentInstance.value?.uri
|
||||
const promise = useMasto().accounts.fetch(id)
|
||||
const domain = currentInstance.value?.uri
|
||||
const promise = useMasto().v1.accounts.fetch(id)
|
||||
.then((r) => {
|
||||
if (r.acct && !r.acct.includes('@') && uri)
|
||||
r.acct = `${r.acct}@${uri}`
|
||||
if (r.acct && !r.acct.includes('@') && domain)
|
||||
r.acct = `${r.acct}@${domain}`
|
||||
|
||||
cacheAccount(r, server, true)
|
||||
return r
|
||||
|
@ -54,17 +54,17 @@ export function fetchAccountById(id?: string | null): Promise<Account | null> {
|
|||
return promise
|
||||
}
|
||||
|
||||
export async function fetchAccountByHandle(acct: string): Promise<Account> {
|
||||
export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Account> {
|
||||
const server = currentServer.value
|
||||
const key = `${server}:account:${acct}`
|
||||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
const uri = currentInstance.value?.uri
|
||||
const account = useMasto().accounts.lookup({ acct })
|
||||
const domain = currentInstance.value?.uri
|
||||
const account = useMasto().v1.accounts.lookup({ acct })
|
||||
.then((r) => {
|
||||
if (r.acct && !r.acct.includes('@') && uri)
|
||||
r.acct = `${r.acct}@${uri}`
|
||||
if (r.acct && !r.acct.includes('@') && domain)
|
||||
r.acct = `${r.acct}@${domain}`
|
||||
|
||||
cacheAccount(r, server, true)
|
||||
return r
|
||||
|
@ -81,7 +81,7 @@ export function useAccountById(id?: string | null) {
|
|||
return useAsyncState(() => fetchAccountById(id), null).state
|
||||
}
|
||||
|
||||
export function cacheStatus(status: Status, server = currentServer.value, override?: boolean) {
|
||||
export function cacheStatus(status: mastodon.v1.Status, server = currentServer.value, override?: boolean) {
|
||||
setCached(`${server}:status:${status.id}`, status, override)
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ export function removeCachedStatus(id: string, server = currentServer.value) {
|
|||
removeCached(`${server}:status:${id}`)
|
||||
}
|
||||
|
||||
export function cacheAccount(account: Account, server = currentServer.value, override?: boolean) {
|
||||
export function cacheAccount(account: mastodon.v1.Account, server = currentServer.value, override?: boolean) {
|
||||
setCached(`${server}:account:${account.id}`, account, override)
|
||||
setCached(`${server}:account:${account.acct}`, account, override)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { ComputedRef } from 'vue'
|
|||
import { defineStore } from 'pinia'
|
||||
import Fuse from 'fuse.js'
|
||||
import type { LocaleObject } from '#i18n'
|
||||
import type { SearchResult } from '@/components/search/types'
|
||||
import type { SearchResult } from '~/composables/masto/search'
|
||||
|
||||
// @unocss-include
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
// @unimport-disable
|
||||
import type { Emoji } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import type { Node } from 'ultrahtml'
|
||||
import { ELEMENT_NODE, TEXT_NODE, h, parse, render } from 'ultrahtml'
|
||||
import { DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE, h, parse, render } from 'ultrahtml'
|
||||
import { findAndReplaceEmojisInText } from '@iconify/utils'
|
||||
import { emojiRegEx, getEmojiAttributes } from '../config/emojis'
|
||||
|
||||
export interface ContentParseOptions {
|
||||
emojis?: Record<string, Emoji>
|
||||
emojis?: Record<string, mastodon.v1.CustomEmoji>
|
||||
markdown?: boolean
|
||||
replaceUnicodeEmoji?: boolean
|
||||
astTransforms?: Transform[]
|
||||
convertMentionLink?: boolean
|
||||
}
|
||||
|
||||
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
|
||||
|
@ -53,6 +54,7 @@ export function parseMastodonHTML(
|
|||
const {
|
||||
markdown = true,
|
||||
replaceUnicodeEmoji = true,
|
||||
convertMentionLink = false,
|
||||
} = options
|
||||
|
||||
if (markdown) {
|
||||
|
@ -77,19 +79,25 @@ export function parseMastodonHTML(
|
|||
if (markdown)
|
||||
transforms.push(transformMarkdown)
|
||||
|
||||
if (convertMentionLink)
|
||||
transforms.push(transformMentionLink)
|
||||
|
||||
transforms.push(replaceCustomEmoji(options.emojis || {}))
|
||||
|
||||
transforms.push(transformParagraphs)
|
||||
|
||||
return transformSync(parse(html), transforms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw HTML form Mastodon server to HTML for Tiptap editor
|
||||
*/
|
||||
export function convertMastodonHTML(html: string, customEmojis: Record<string, Emoji> = {}) {
|
||||
export function convertMastodonHTML(html: string, customEmojis: Record<string, mastodon.v1.CustomEmoji> = {}) {
|
||||
const tree = parseMastodonHTML(html, {
|
||||
emojis: customEmojis,
|
||||
markdown: true,
|
||||
replaceUnicodeEmoji: false,
|
||||
convertMentionLink: true,
|
||||
})
|
||||
return render(tree)
|
||||
}
|
||||
|
@ -285,7 +293,7 @@ function transformUnicodeEmoji(node: Node) {
|
|||
return matches.filter(Boolean)
|
||||
}
|
||||
|
||||
function replaceCustomEmoji(customEmojis: Record<string, Emoji>): Transform {
|
||||
function replaceCustomEmoji(customEmojis: Record<string, mastodon.v1.CustomEmoji>): Transform {
|
||||
return (node) => {
|
||||
if (node.type !== TEXT_NODE)
|
||||
return node
|
||||
|
@ -313,6 +321,8 @@ const _markdownReplacements: [RegExp, (c: (string | Node)[]) => Node][] = [
|
|||
[/\*(.*?)\*/g, c => h('em', null, c)],
|
||||
[/~~(.*?)~~/g, c => h('del', null, c)],
|
||||
[/`([^`]+?)`/g, c => h('code', null, c)],
|
||||
// transform @username@twitter.com as links
|
||||
[/\B@([a-zA-Z0-9_]+)@twitter\.com\b/gi, c => h('a', { href: `https://twitter.com/${c}`, target: '_blank', class: 'mention external' }, `@${c}@twitter.com`)],
|
||||
]
|
||||
|
||||
function _markdownProcess(value: string) {
|
||||
|
@ -349,3 +359,26 @@ function transformMarkdown(node: Node) {
|
|||
return node
|
||||
return _markdownProcess(node.value)
|
||||
}
|
||||
|
||||
function transformParagraphs(node: Node): Node | Node[] {
|
||||
// For top level paragraphs, inject an empty <p> to preserve status paragraphs in our editor (except for the last one)
|
||||
if (node.parent?.type === DOCUMENT_NODE && node.name === 'p' && node.parent.children.at(-1) !== node)
|
||||
return [node, h('p')]
|
||||
return node
|
||||
}
|
||||
|
||||
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
|
||||
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
|
||||
const href = node.attributes.href
|
||||
if (href) {
|
||||
const matchUser = href.match(UserLinkRE)
|
||||
if (matchUser) {
|
||||
const [, server, username] = matchUser
|
||||
const handle = `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
||||
// convert to TipTap mention node
|
||||
return h('span', { 'data-type': 'mention', 'data-id': handle }, handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import type { Attachment, Status, StatusEdit } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import type { ConfirmDialogChoice, ConfirmDialogLabel, Draft } from '~/types'
|
||||
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
|
||||
|
||||
export const confirmDialogChoice = ref<ConfirmDialogChoice>()
|
||||
export const confirmDialogLabel = ref<ConfirmDialogLabel>()
|
||||
|
||||
export const mediaPreviewList = ref<Attachment[]>([])
|
||||
export const mediaPreviewList = ref<mastodon.v1.MediaAttachment[]>([])
|
||||
export const mediaPreviewIndex = ref(0)
|
||||
|
||||
export const statusEdit = ref<StatusEdit>()
|
||||
export const statusEdit = ref<mastodon.v1.StatusEdit>()
|
||||
export const dialogDraftKey = ref<string>()
|
||||
|
||||
export const commandPanelInput = ref('')
|
||||
|
@ -23,7 +23,7 @@ export const isPreviewHelpOpen = ref(isFirstVisit.value)
|
|||
export const isCommandPanelOpen = ref(false)
|
||||
export const isConfirmDialogOpen = ref(false)
|
||||
|
||||
export const lastPublishDialogStatus = ref<Status | null>(null)
|
||||
export const lastPublishDialogStatus = ref<mastodon.v1.Status | null>(null)
|
||||
|
||||
export function openSigninDialog() {
|
||||
isSigninDialogOpen.value = true
|
||||
|
@ -80,7 +80,7 @@ if (process.client) {
|
|||
restoreMediaPreviewFromState()
|
||||
}
|
||||
|
||||
export function openMediaPreview(attachments: Attachment[], index = 0) {
|
||||
export function openMediaPreview(attachments: mastodon.v1.MediaAttachment[], index = 0) {
|
||||
mediaPreviewList.value = attachments
|
||||
mediaPreviewIndex.value = index
|
||||
isMediaPreviewOpen.value = true
|
||||
|
@ -97,7 +97,7 @@ export function closeMediaPreview() {
|
|||
history.back()
|
||||
}
|
||||
|
||||
export function openEditHistoryDialog(edit: StatusEdit) {
|
||||
export function openEditHistoryDialog(edit: mastodon.v1.StatusEdit) {
|
||||
statusEdit.value = edit
|
||||
isEditHistoryDialogOpen.value = true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Emoji } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import type { CustomEmojisInfo } from './push-notifications/types'
|
||||
import { STORAGE_KEY_CUSTOM_EMOJIS } from '~/constants'
|
||||
|
||||
|
@ -20,14 +20,14 @@ export async function updateCustomEmojis() {
|
|||
return
|
||||
|
||||
const masto = useMasto()
|
||||
const emojis = await masto.customEmojis.fetchAll()
|
||||
const emojis = await masto.v1.customEmojis.list()
|
||||
Object.assign(currentCustomEmojis.value, {
|
||||
lastUpdate: Date.now(),
|
||||
emojis,
|
||||
})
|
||||
}
|
||||
|
||||
function transformEmojiData(emojis: Emoji[]) {
|
||||
function transformEmojiData(emojis: mastodon.v1.CustomEmoji[]) {
|
||||
const result = []
|
||||
|
||||
for (const emoji of emojis) {
|
||||
|
@ -52,9 +52,9 @@ export const customEmojisData = computed(() => currentCustomEmojis.value.emojis.
|
|||
}]
|
||||
: undefined)
|
||||
|
||||
export function useEmojisFallback(emojisGetter: () => Emoji[] | undefined) {
|
||||
export function useEmojisFallback(emojisGetter: () => mastodon.v1.CustomEmoji[] | undefined) {
|
||||
return computed(() => {
|
||||
const result: Emoji[] = []
|
||||
const result: mastodon.v1.CustomEmoji[] = []
|
||||
const emojis = emojisGetter()
|
||||
if (emojis)
|
||||
result.push(...emojis)
|
||||
|
|
|
@ -2,11 +2,11 @@ import type { MaybeComputedRef, MaybeRef, UseTimeAgoOptions } from '@vueuse/core
|
|||
|
||||
const formatter = Intl.NumberFormat()
|
||||
|
||||
export const formattedNumber = (num: number, useFormatter: Intl.NumberFormat = formatter) => {
|
||||
export function formattedNumber(num: number, useFormatter: Intl.NumberFormat = formatter) {
|
||||
return useFormatter.format(num)
|
||||
}
|
||||
|
||||
export const useHumanReadableNumber = () => {
|
||||
export function useHumanReadableNumber() {
|
||||
const { n, locale } = useI18n()
|
||||
|
||||
const fn = (num: number) => {
|
||||
|
@ -29,10 +29,8 @@ export const useHumanReadableNumber = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const useFormattedDateTime = (
|
||||
value: MaybeComputedRef<string | number | Date | undefined | null>,
|
||||
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' },
|
||||
) => {
|
||||
export function useFormattedDateTime(value: MaybeComputedRef<string | number | Date | undefined | null>,
|
||||
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
|
||||
const { locale } = useI18n()
|
||||
const formatter = $computed(() => Intl.DateTimeFormat(locale.value, options))
|
||||
return computed(() => {
|
||||
|
@ -41,7 +39,7 @@ export const useFormattedDateTime = (
|
|||
})
|
||||
}
|
||||
|
||||
export const useTimeAgoOptions = (short = false): UseTimeAgoOptions<false> => {
|
||||
export function useTimeAgoOptions(short = false): UseTimeAgoOptions<false> {
|
||||
const { d, t, n: fnf, locale } = useI18n()
|
||||
const prefix = short ? 'short_' : ''
|
||||
|
||||
|
@ -56,7 +54,7 @@ export const useTimeAgoOptions = (short = false): UseTimeAgoOptions<false> => {
|
|||
return {
|
||||
rounding: 'floor',
|
||||
showSecond: !short,
|
||||
updateInterval: short ? 60_000 : 1_000,
|
||||
updateInterval: short ? 60000 : 1000,
|
||||
messages: {
|
||||
justNow: t('time_ago_options.just_now'),
|
||||
// just return the value
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import type { Account } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
export function getDisplayName(account?: Account, options?: { rich?: boolean }) {
|
||||
export function getDisplayName(account?: mastodon.v1.Account, options?: { rich?: boolean }) {
|
||||
const displayName = account?.displayName || account?.username || ''
|
||||
if (options?.rich)
|
||||
return displayName
|
||||
return displayName.replace(/:([\w-]+?):/g, '')
|
||||
}
|
||||
|
||||
export function getShortHandle({ acct }: Account) {
|
||||
export function getShortHandle({ acct }: mastodon.v1.Account) {
|
||||
if (!acct)
|
||||
return ''
|
||||
return `@${acct.includes('@') ? acct.split('@')[0] : acct}`
|
||||
}
|
||||
|
||||
export function getServerName(account: Account) {
|
||||
export function getServerName(account: mastodon.v1.Account) {
|
||||
if (account.acct?.includes('@'))
|
||||
return account.acct.split('@')[1]
|
||||
// We should only lack the server name if we're on the same server as the account
|
||||
return currentInstance.value?.uri || ''
|
||||
}
|
||||
|
||||
export function getFullHandle(account: Account) {
|
||||
export function getFullHandle(account: mastodon.v1.Account) {
|
||||
const handle = `@${account.acct}`
|
||||
if (!currentUser.value || account.acct.includes('@'))
|
||||
return handle
|
||||
|
@ -36,7 +36,7 @@ export function toShortHandle(fullHandle: string) {
|
|||
return fullHandle
|
||||
}
|
||||
|
||||
export function extractAccountHandle(account: Account) {
|
||||
export function extractAccountHandle(account: mastodon.v1.Account) {
|
||||
let handle = getFullHandle(account).slice(1)
|
||||
const uri = currentInstance.value?.uri ?? currentServer.value
|
||||
if (currentInstance.value && handle.endsWith(`@${uri}`))
|
||||
|
@ -45,7 +45,7 @@ export function extractAccountHandle(account: Account) {
|
|||
return handle
|
||||
}
|
||||
|
||||
export function useAccountHandle(account: Account, fullServer = true) {
|
||||
export function useAccountHandle(account: mastodon.v1.Account, fullServer = true) {
|
||||
return computed(() => fullServer
|
||||
? getFullHandle(account)
|
||||
: getShortHandle(account),
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import type { Account, Relationship } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
// Batch requests for relationships when used in the UI
|
||||
// We don't want to hold to old values, so every time a Relationship is needed it
|
||||
// is requested again from the server to show the latest state
|
||||
|
||||
const requestedRelationships = new Map<string, Ref<Relationship | undefined>>()
|
||||
const requestedRelationships = new Map<string, Ref<mastodon.v1.Relationship | undefined>>()
|
||||
let timeoutHandle: NodeJS.Timeout | undefined
|
||||
|
||||
export function useRelationship(account: Account): Ref<Relationship | undefined> {
|
||||
export function useRelationship(account: mastodon.v1.Account): Ref<mastodon.v1.Relationship | undefined> {
|
||||
if (!currentUser.value)
|
||||
return ref()
|
||||
let relationship = requestedRelationships.get(account.id)
|
||||
if (relationship)
|
||||
return relationship
|
||||
relationship = ref<Relationship | undefined>()
|
||||
relationship = ref<mastodon.v1.Relationship | undefined>()
|
||||
requestedRelationships.set(account.id, relationship)
|
||||
if (timeoutHandle)
|
||||
clearTimeout(timeoutHandle)
|
||||
|
@ -27,7 +27,7 @@ export function useRelationship(account: Account): Ref<Relationship | undefined>
|
|||
|
||||
async function fetchRelationships() {
|
||||
const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value)
|
||||
const relationships = await useMasto().accounts.fetchRelationships(requested.map(([id]) => id))
|
||||
const relationships = await useMasto().v1.accounts.fetchRelationships(requested.map(([id]) => id))
|
||||
for (let i = 0; i < requested.length; i++)
|
||||
requested[i][1].value = relationships[i]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { withoutProtocol } from 'ufo'
|
||||
import type { Account, Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
export function getAccountRoute(account: Account) {
|
||||
export function getAccountRoute(account: mastodon.v1.Account) {
|
||||
return useRouter().resolve({
|
||||
name: 'account-index',
|
||||
params: {
|
||||
|
@ -10,7 +10,7 @@ export function getAccountRoute(account: Account) {
|
|||
},
|
||||
})
|
||||
}
|
||||
export function getAccountFollowingRoute(account: Account) {
|
||||
export function getAccountFollowingRoute(account: mastodon.v1.Account) {
|
||||
return useRouter().resolve({
|
||||
name: 'account-following',
|
||||
params: {
|
||||
|
@ -19,7 +19,7 @@ export function getAccountFollowingRoute(account: Account) {
|
|||
},
|
||||
})
|
||||
}
|
||||
export function getAccountFollowersRoute(account: Account) {
|
||||
export function getAccountFollowersRoute(account: mastodon.v1.Account) {
|
||||
return useRouter().resolve({
|
||||
name: 'account-followers',
|
||||
params: {
|
||||
|
@ -29,7 +29,7 @@ export function getAccountFollowersRoute(account: Account) {
|
|||
})
|
||||
}
|
||||
|
||||
export function getStatusRoute(status: Status) {
|
||||
export function getStatusRoute(status: mastodon.v1.Status) {
|
||||
return useRouter().resolve({
|
||||
name: 'status',
|
||||
params: {
|
||||
|
@ -50,11 +50,11 @@ export function getTagRoute(tag: string) {
|
|||
})
|
||||
}
|
||||
|
||||
export function getStatusPermalinkRoute(status: Status) {
|
||||
export function getStatusPermalinkRoute(status: mastodon.v1.Status) {
|
||||
return status.url ? withoutProtocol(status.url) : null
|
||||
}
|
||||
|
||||
export function getStatusInReplyToRoute(status: Status) {
|
||||
export function getStatusInReplyToRoute(status: mastodon.v1.Status) {
|
||||
return useRouter().resolve({
|
||||
name: 'status-by-id',
|
||||
params: {
|
||||
|
|
|
@ -1,19 +1,64 @@
|
|||
import type { MaybeRef } from '@vueuse/core'
|
||||
import type { Account, Paginator, Results, SearchParams, Status } from 'masto'
|
||||
import type { MaybeComputedRef } from '@vueuse/core'
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
import type { RouteLocation } from 'vue-router'
|
||||
|
||||
export interface UseSearchOptions {
|
||||
type?: MaybeRef<'accounts' | 'hashtags' | 'statuses'>
|
||||
export type UseSearchOptions = MaybeComputedRef<
|
||||
Partial<Omit<mastodon.v1.SearchParams, keyof mastodon.DefaultPaginationParams | 'q'>>
|
||||
>
|
||||
|
||||
export interface BuildSearchResult<K extends keyof any, T> {
|
||||
id: string
|
||||
type: K
|
||||
data: T
|
||||
to: RouteLocation & {
|
||||
href: string
|
||||
}
|
||||
}
|
||||
export type AccountSearchResult = BuildSearchResult<'account', mastodon.v1.Account>
|
||||
export type HashTagSearchResult = BuildSearchResult<'hashtag', mastodon.v1.Tag>
|
||||
export type StatusSearchResult = BuildSearchResult<'status', mastodon.v1.Status>
|
||||
|
||||
export function useSearch(query: MaybeRef<string>, options?: UseSearchOptions) {
|
||||
export type SearchResult = HashTagSearchResult | AccountSearchResult | StatusSearchResult
|
||||
|
||||
export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOptions = {}) {
|
||||
const done = ref(false)
|
||||
const masto = useMasto()
|
||||
const loading = ref(false)
|
||||
const statuses = ref<Status[]>([])
|
||||
const accounts = ref<Account[]>([])
|
||||
const hashtags = ref<any[]>([])
|
||||
const accounts = ref<AccountSearchResult[]>([])
|
||||
const hashtags = ref<HashTagSearchResult[]>([])
|
||||
const statuses = ref<StatusSearchResult[]>([])
|
||||
|
||||
let paginator: Paginator<SearchParams, Results> | undefined
|
||||
let paginator: Paginator<mastodon.v2.Search, mastodon.v2.SearchParams> | undefined
|
||||
|
||||
const appendResults = (results: mastodon.v2.Search, empty = false) => {
|
||||
if (empty) {
|
||||
accounts.value = []
|
||||
hashtags.value = []
|
||||
statuses.value = []
|
||||
}
|
||||
accounts.value = [...accounts.value, ...results.accounts.map<AccountSearchResult>(account => ({
|
||||
type: 'account',
|
||||
id: account.id,
|
||||
data: account,
|
||||
to: getAccountRoute(account),
|
||||
}))]
|
||||
hashtags.value = [...hashtags.value, ...results.hashtags.map<HashTagSearchResult>(hashtag => ({
|
||||
type: 'hashtag',
|
||||
id: `hashtag-${hashtag.name}`,
|
||||
data: hashtag,
|
||||
to: getTagRoute(hashtag.name),
|
||||
}))]
|
||||
statuses.value = [...statuses.value, ...results.statuses.map<StatusSearchResult>(status => ({
|
||||
type: 'status',
|
||||
id: status.id,
|
||||
data: status,
|
||||
to: getStatusRoute(status),
|
||||
}))]
|
||||
}
|
||||
|
||||
watch(() => unref(query), () => {
|
||||
loading.value = !!(unref(query) && isMastoInitialised.value)
|
||||
})
|
||||
|
||||
debouncedWatch(() => unref(query), async () => {
|
||||
if (!unref(query) || !isMastoInitialised.value)
|
||||
|
@ -25,17 +70,19 @@ export function useSearch(query: MaybeRef<string>, options?: UseSearchOptions) {
|
|||
* Based on the source it seems like modifying the params when calling next would result in a new search,
|
||||
* but that doesn't seem to be the case. So instead we just create a new paginator with the new params.
|
||||
*/
|
||||
paginator = masto.search({ q: unref(query), resolve: !!currentUser.value, type: unref(options?.type) })
|
||||
paginator = masto.v2.search({
|
||||
q: resolveUnref(query),
|
||||
...resolveUnref(options),
|
||||
resolve: !!currentUser.value,
|
||||
})
|
||||
const nextResults = await paginator.next()
|
||||
|
||||
done.value = nextResults.done || false
|
||||
|
||||
statuses.value = nextResults.value?.statuses || []
|
||||
accounts.value = nextResults.value?.accounts || []
|
||||
hashtags.value = nextResults.value?.hashtags || []
|
||||
done.value = !!nextResults.done
|
||||
if (!nextResults.done)
|
||||
appendResults(nextResults.value, true)
|
||||
|
||||
loading.value = false
|
||||
}, { debounce: 500 })
|
||||
}, { debounce: 300 })
|
||||
|
||||
const next = async () => {
|
||||
if (!unref(query) || !isMastoInitialised.value || !paginator)
|
||||
|
@ -45,19 +92,9 @@ export function useSearch(query: MaybeRef<string>, options?: UseSearchOptions) {
|
|||
const nextResults = await paginator.next()
|
||||
loading.value = false
|
||||
|
||||
done.value = nextResults.done || false
|
||||
statuses.value = [
|
||||
...statuses.value,
|
||||
...(nextResults.value.statuses || []),
|
||||
]
|
||||
accounts.value = [
|
||||
...statuses.value,
|
||||
...(nextResults.value.accounts || []),
|
||||
]
|
||||
hashtags.value = [
|
||||
...statuses.value,
|
||||
...(nextResults.value.statuses || []),
|
||||
]
|
||||
done.value = !!nextResults.done
|
||||
if (!nextResults.done)
|
||||
appendResults(nextResults.value)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import type { Status } from 'masto'
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
type Action = 'reblogged' | 'favourited' | 'bookmarked' | 'pinned' | 'muted'
|
||||
type CountField = 'reblogsCount' | 'favouritesCount'
|
||||
|
||||
export interface StatusActionsProps {
|
||||
status: Status
|
||||
status: mastodon.v1.Status
|
||||
}
|
||||
|
||||
export function useStatusActions(props: StatusActionsProps) {
|
||||
let status = $ref<Status>({ ...props.status })
|
||||
let status = $ref<mastodon.v1.Status>({ ...props.status })
|
||||
const masto = useMasto()
|
||||
|
||||
watch(
|
||||
|
@ -27,10 +27,11 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
muted: false,
|
||||
})
|
||||
|
||||
async function toggleStatusAction(action: Action, fetchNewStatus: () => Promise<Status>, countField?: CountField) {
|
||||
async function toggleStatusAction(action: Action, fetchNewStatus: () => Promise<mastodon.v1.Status>, countField?: CountField) {
|
||||
// check login
|
||||
if (!checkLogin())
|
||||
return
|
||||
|
||||
isLoading[action] = true
|
||||
fetchNewStatus().then((newStatus) => {
|
||||
Object.assign(status, newStatus)
|
||||
|
@ -44,9 +45,15 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
if (countField)
|
||||
status[countField] += status[action] ? 1 : -1
|
||||
}
|
||||
|
||||
const canReblog = $computed(() =>
|
||||
status.visibility !== 'direct'
|
||||
&& (status.visibility !== 'private' || status.account.id === currentUser.value?.account.id),
|
||||
)
|
||||
|
||||
const toggleReblog = () => toggleStatusAction(
|
||||
'reblogged',
|
||||
() => masto.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
|
||||
() => masto.v1.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
|
||||
if (status.reblogged)
|
||||
// returns the original status
|
||||
return res.reblog!
|
||||
|
@ -57,28 +64,29 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
|
||||
const toggleFavourite = () => toggleStatusAction(
|
||||
'favourited',
|
||||
() => masto.statuses[status.favourited ? 'unfavourite' : 'favourite'](status.id),
|
||||
() => masto.v1.statuses[status.favourited ? 'unfavourite' : 'favourite'](status.id),
|
||||
'favouritesCount',
|
||||
)
|
||||
|
||||
const toggleBookmark = () => toggleStatusAction(
|
||||
'bookmarked',
|
||||
() => masto.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
|
||||
() => masto.v1.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
|
||||
)
|
||||
|
||||
const togglePin = async () => toggleStatusAction(
|
||||
'pinned',
|
||||
() => masto.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
|
||||
() => masto.v1.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
|
||||
)
|
||||
|
||||
const toggleMute = async () => toggleStatusAction(
|
||||
'muted',
|
||||
() => masto.statuses[status.muted ? 'unmute' : 'mute'](status.id),
|
||||
() => masto.v1.statuses[status.muted ? 'unmute' : 'mute'](status.id),
|
||||
)
|
||||
|
||||
return {
|
||||
status: $$(status),
|
||||
isLoading: $$(isLoading),
|
||||
canReblog: $$(canReblog),
|
||||
toggleMute,
|
||||
toggleReblog,
|
||||
toggleFavourite,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue