feat: use masto client
This commit is contained in:
parent
4adab40932
commit
90c45b435f
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -8,6 +8,7 @@
|
|||
},
|
||||
"editor.formatOnSave": false,
|
||||
"cSpell.words": [
|
||||
"masto",
|
||||
"Nuxtodon"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { Headers, createFetch, fetch } from 'ohmyfetch'
|
||||
import type { Post } from './types'
|
||||
|
||||
export interface MastodonClientOptions {
|
||||
host: string
|
||||
}
|
||||
|
||||
export class Client {
|
||||
$fetch: ReturnType<typeof createFetch> = undefined!
|
||||
|
||||
constructor(public options: MastodonClientOptions) {
|
||||
this.$fetch = createFetch({
|
||||
defaults: {
|
||||
baseURL: options.host,
|
||||
},
|
||||
fetch,
|
||||
Headers,
|
||||
})
|
||||
}
|
||||
|
||||
getPublicTimeline(): Promise<Post[]> {
|
||||
return this.$fetch('/api/v1/timelines/public')
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
export interface Post {
|
||||
id: string
|
||||
created_at: Date
|
||||
in_reply_to_id: null | string
|
||||
in_reply_to_account_id: null | string
|
||||
sensitive: boolean
|
||||
spoiler_text: string
|
||||
visibility: Visibility
|
||||
language: string
|
||||
uri: string
|
||||
url: string
|
||||
replies_count: number
|
||||
reblogs_count: number
|
||||
favourites_count: number
|
||||
edited_at: null
|
||||
favourited: boolean
|
||||
reblogged: boolean
|
||||
muted: boolean
|
||||
bookmarked: boolean
|
||||
content: string
|
||||
filtered: any[]
|
||||
reblog: null
|
||||
account: Account
|
||||
media_attachments: MediaAttachment[]
|
||||
mentions: any[]
|
||||
tags: Tag[]
|
||||
emojis: Emoji[]
|
||||
card: null
|
||||
poll: null
|
||||
application?: Application
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
id: string
|
||||
username: string
|
||||
acct: string
|
||||
display_name: string
|
||||
locked: boolean
|
||||
bot: boolean
|
||||
discoverable: boolean
|
||||
group: boolean
|
||||
created_at: Date
|
||||
note: string
|
||||
url: string
|
||||
avatar: string
|
||||
avatar_static: string
|
||||
header: string
|
||||
header_static: string
|
||||
followers_count: number
|
||||
following_count: number
|
||||
statuses_count: number
|
||||
last_status_at: Date
|
||||
emojis: Emoji[]
|
||||
fields: Field[]
|
||||
noindex?: boolean
|
||||
}
|
||||
|
||||
export interface Emoji {
|
||||
shortcode: string
|
||||
url: string
|
||||
static_url: string
|
||||
visible_in_picker: boolean
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
name: string
|
||||
value: string
|
||||
verified_at: Date | null
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
name: string
|
||||
website: null | string
|
||||
}
|
||||
|
||||
export interface MediaAttachment {
|
||||
id: string
|
||||
type: string
|
||||
url: string
|
||||
preview_url: string
|
||||
remote_url: string
|
||||
preview_remote_url: null
|
||||
text_url: null
|
||||
meta: Meta
|
||||
description: null | string
|
||||
blurhash: string
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
focus?: Focus
|
||||
original: Original
|
||||
small: Original
|
||||
}
|
||||
|
||||
export interface Focus {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface Original {
|
||||
width: number
|
||||
height: number
|
||||
size: string
|
||||
aspect: number
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export enum Visibility {
|
||||
Public = 'public',
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { Account } from '~/api-client/types'
|
||||
import type { Account } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
account: Account
|
||||
}>()
|
||||
</script>
|
||||
|
@ -9,15 +9,17 @@ const props = defineProps<{
|
|||
<template>
|
||||
<div flex gap-2>
|
||||
<div p1>
|
||||
<NuxtLink :to="`/@${account.acct}`">
|
||||
<img :src="account.avatar" rounded w-10 h-10>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div flex flex-col>
|
||||
<NuxtLink flex flex-col :to="`/@${account.acct}`">
|
||||
<h4 font-bold>
|
||||
{{ account.display_name }}
|
||||
{{ account.displayName }}
|
||||
</h4>
|
||||
<p op50>
|
||||
@{{ account.acct }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { Post } from '~/api-client/types'
|
||||
|
||||
const props = defineProps<{
|
||||
post: Post
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="post-body" v-html="sanitize(post.content)" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.post-body a {
|
||||
--at-apply: text-primary hover:underline;
|
||||
}
|
||||
.post-body b {
|
||||
--at-apply: font-bold;
|
||||
}
|
||||
.post-body p {
|
||||
--at-apply: my-1;
|
||||
}
|
||||
.post-body a .invisible {
|
||||
--at-apply: hidden;
|
||||
}
|
||||
.post-body a .ellipsis {
|
||||
--at-apply: truncate overflow-hidden ws-nowrap;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { Post } from '~/api-client/types'
|
||||
|
||||
const props = defineProps<{
|
||||
post: Post
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink flex flex-col gap-2 my-4 :to="`/${post.account.acct}/${post.id}`">
|
||||
<AccountInfo :account="post.account" />
|
||||
<PostBody :post="post" />
|
||||
<PostActions :post="post" />
|
||||
</NuxtLink>
|
||||
</template>
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { Post } from '~/api-client/types'
|
||||
import type { Status } from 'masto'
|
||||
|
||||
const props = defineProps<{
|
||||
post: Post
|
||||
defineProps<{
|
||||
status: Status
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
@ -10,15 +10,15 @@ const props = defineProps<{
|
|||
<div flex justify-between gap-4>
|
||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||
<div i-ri:chat-3-line />
|
||||
<span>{{ post.replies_count }}</span>
|
||||
<span>{{ status.repliesCount }}</span>
|
||||
</button>
|
||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||
<div i-ri:repeat-fill />
|
||||
<span>{{ post.reblogs_count }}</span>
|
||||
<span>{{ status.reblogsCount }}</span>
|
||||
</button>
|
||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||
<div i-ri:heart-3-line />
|
||||
<span>{{ post.favourites_count }}</span>
|
||||
<span>{{ status.favouritesCount }}</span>
|
||||
</button>
|
||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||
<div i-ri:more-2-fill />
|
31
components/status/StatusBody.vue
Normal file
31
components/status/StatusBody.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
status: Status
|
||||
}>()
|
||||
|
||||
// TODO: parse and interop content (link, emojis)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="status-body" v-html="sanitize(status.content)" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.status-body a {
|
||||
--at-apply: text-primary hover:underline;
|
||||
}
|
||||
.status-body b {
|
||||
--at-apply: font-bold;
|
||||
}
|
||||
.status-body p {
|
||||
--at-apply: my-1;
|
||||
}
|
||||
.status-body a .invisible {
|
||||
--at-apply: hidden;
|
||||
}
|
||||
.status-body a .ellipsis {
|
||||
--at-apply: truncate overflow-hidden ws-nowrap;
|
||||
}
|
||||
</style>
|
22
components/status/StatusCard.vue
Normal file
22
components/status/StatusCard.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import type { Status } from 'masto'
|
||||
|
||||
const { status } = defineProps<{
|
||||
status: Status
|
||||
}>()
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
const router = useRouter()
|
||||
function go(e: MouseEvent) {
|
||||
if (e.target === el.value)
|
||||
router.push(`/@${status.account.acct}/${status.id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" flex flex-col gap-2 my-4 @click="go">
|
||||
<AccountInfo :account="status.account" />
|
||||
<StatusBody :status="status" />
|
||||
<StatusActions :status="status" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,9 +1,5 @@
|
|||
import { Client } from '~/api-client'
|
||||
import type { MastoClient } from 'masto'
|
||||
|
||||
const client = new Client({
|
||||
host: 'https://mas.to',
|
||||
})
|
||||
|
||||
export function useClient() {
|
||||
return client
|
||||
export function useMasto() {
|
||||
return inject('masto') as Promise<MastoClient>
|
||||
}
|
||||
|
|
13
pages/@[user]/index.vue
Normal file
13
pages/@[user]/index.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
modelValue?: boolean
|
||||
}>()
|
||||
|
||||
const params = useRoute().params
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ params }}
|
||||
</div>
|
||||
</template>
|
|
@ -1,12 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
const client = useClient()
|
||||
const { data: posts } = await useAsyncData(() => client.getPublicTimeline())
|
||||
const masto = await useMasto()
|
||||
const { data: timelines } = await useAsyncData('public-timelines', () => masto.timelines.fetchPublic().then(r => r.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div w-150>
|
||||
<template v-for="post in posts" :key="post.id">
|
||||
<PostCard :post="post" border="t gray/10" pt-4 />
|
||||
<template v-for="status of timelines" :key="status.id">
|
||||
<StatusCard :status="status" border="t gray/10" pt-4 />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
8
plugins/masto.ts
Normal file
8
plugins/masto.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { login } from 'masto'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const masto = login({
|
||||
url: 'https://mas.to',
|
||||
})
|
||||
nuxt.vueApp.provide('masto', masto)
|
||||
})
|
Loading…
Reference in a new issue