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,
|
"editor.formatOnSave": false,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"masto",
|
||||||
"Nuxtodon"
|
"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">
|
<script setup lang="ts">
|
||||||
import type { Account } from '~/api-client/types'
|
import type { Account } from 'masto'
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
account: Account
|
account: Account
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,15 +9,17 @@ const props = defineProps<{
|
||||||
<template>
|
<template>
|
||||||
<div flex gap-2>
|
<div flex gap-2>
|
||||||
<div p1>
|
<div p1>
|
||||||
|
<NuxtLink :to="`/@${account.acct}`">
|
||||||
<img :src="account.avatar" rounded w-10 h-10>
|
<img :src="account.avatar" rounded w-10 h-10>
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div flex flex-col>
|
<NuxtLink flex flex-col :to="`/@${account.acct}`">
|
||||||
<h4 font-bold>
|
<h4 font-bold>
|
||||||
{{ account.display_name }}
|
{{ account.displayName }}
|
||||||
</h4>
|
</h4>
|
||||||
<p op50>
|
<p op50>
|
||||||
@{{ account.acct }}
|
@{{ account.acct }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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">
|
<script setup lang="ts">
|
||||||
import type { Post } from '~/api-client/types'
|
import type { Status } from 'masto'
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
post: Post
|
status: Status
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -10,15 +10,15 @@ const props = defineProps<{
|
||||||
<div flex justify-between gap-4>
|
<div flex justify-between gap-4>
|
||||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||||
<div i-ri:chat-3-line />
|
<div i-ri:chat-3-line />
|
||||||
<span>{{ post.replies_count }}</span>
|
<span>{{ status.repliesCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||||
<div i-ri:repeat-fill />
|
<div i-ri:repeat-fill />
|
||||||
<span>{{ post.reblogs_count }}</span>
|
<span>{{ status.reblogsCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||||
<div i-ri:heart-3-line />
|
<div i-ri:heart-3-line />
|
||||||
<span>{{ post.favourites_count }}</span>
|
<span>{{ status.favouritesCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
<button flex gap-1 justify-center items-center p1 w-full rounded hover="bg-gray/10">
|
||||||
<div i-ri:more-2-fill />
|
<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({
|
export function useMasto() {
|
||||||
host: 'https://mas.to',
|
return inject('masto') as Promise<MastoClient>
|
||||||
})
|
|
||||||
|
|
||||||
export function useClient() {
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
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">
|
<script setup lang="ts">
|
||||||
const client = useClient()
|
const masto = await useMasto()
|
||||||
const { data: posts } = await useAsyncData(() => client.getPublicTimeline())
|
const { data: timelines } = await useAsyncData('public-timelines', () => masto.timelines.fetchPublic().then(r => r.value))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div w-150>
|
<div w-150>
|
||||||
<template v-for="post in posts" :key="post.id">
|
<template v-for="status of timelines" :key="status.id">
|
||||||
<PostCard :post="post" border="t gray/10" pt-4 />
|
<StatusCard :status="status" border="t gray/10" pt-4 />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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