feat: cache for publish widget

zio/stable
Anthony Fu 2022-11-21 14:55:31 +08:00
parent 5d5cdebb56
commit 193d1cf5c5
9 changed files with 149 additions and 43 deletions

View File

@ -17,7 +17,7 @@ defineProps<{
<h4 font-bold>
{{ account.displayName }}
</h4>
<p op50>
<p op35 text-sm>
@{{ account.acct }}
</p>
</NuxtLink>

View File

@ -9,7 +9,7 @@ const account = $computed(() => currentUser?.account)
<!-- TODO: multiple account switcher -->
<template v-if="account">
<AccountInfo :account="account" />
<PublishWidget />
<PublishWidget draft-key="home" />
</template>
<!-- TODO: dialog for select server -->
<a v-else href="/api/mas.to/login" px2 py1 bg-teal6 text-white m2 rounded>Login</a>

View File

@ -0,0 +1,68 @@
<script setup lang="ts">
import type { CreateStatusParamsWithStatus } from 'masto'
const {
draftKey,
placeholder = 'What is on your mind?',
inReplyToId,
} = defineProps<{
draftKey: string
placeholder?: string
inReplyToId?: string
}>()
const masto = await useMasto()
let isSending = $ref(false)
const storageKey = `nuxtodon-draft-${draftKey}`
function getDefaultStatus(): CreateStatusParamsWithStatus {
return {
status: '',
inReplyToId,
}
}
const draft = useLocalStorage<CreateStatusParamsWithStatus>(storageKey, getDefaultStatus())
async function publish() {
try {
isSending = true
await masto.statuses.create(draft.value)
draft.value = getDefaultStatus()
}
finally {
isSending = false
}
}
onUnmounted(() => {
if (!draft.value.status) {
draft.value = undefined
nextTick(() => {
localStorage.removeItem(storageKey)
})
}
})
</script>
<template>
<div
flex flex-col gap-4
:class="isSending ? 'pointer-events-none' : ''"
>
<textarea
v-model="draft.status"
:placeholder="placeholder"
p2 border-rounded w-full h-40
bg-gray:10 outline-none border="~ border"
/>
<div flex justify-end>
<button
h-9 w-22 bg-primary border-rounded
:disabled="draft.status === ''"
@click="publish"
>
Publish!
</button>
</div>
</div>
</template>

View File

@ -1,33 +0,0 @@
<script setup lang="ts">
const masto = await useMasto()
let draftPost = $ref('')
let isSending = $ref(false)
async function publish() {
try {
isSending = true
await masto.statuses.create({ status: draftPost })
draftPost = ''
}
finally {
isSending = false
}
}
</script>
<template>
<div xl:w-70 flex flex-col gap-4 :class="isSending ? ' pointer-events-none' : ''">
<textarea
v-model="draftPost"
placeholder="What's on your mind?"
p2 border-rounded w-full h-40
bg-gray:10 outline-none border="~ border"
/>
<div flex justify-end>
<button h-9 w-22 bg-primary border-rounded :disabled="draftPost === ''" @click="publish">
Publish!
</button>
</div>
</div>
</template>

View File

@ -59,7 +59,7 @@ export function treeToVNode(
return h(
RouterLink as any,
attrs,
node.childNodes.map(n => treeToVNode(n, handle)),
() => node.childNodes.map(n => treeToVNode(n, handle)),
)
}
return h(

View File

@ -0,0 +1,28 @@
export function useCacheStorage<T>(
key: string,
getter: () => T | Promise<T>,
TTL = 1000 * 60 * 60 * 12, // 12 hours
) {
const storage = useLocalStorage(key, {
time: 0,
value: null as T | null,
})
if (storage.value.time + TTL < Date.now()) {
Promise.resolve(getter()).then((v) => {
storage.value = {
time: Date.now(),
value: v,
}
})
}
return computed({
get() {
return storage.value.value
},
set(v) {
storage.value.value = v
},
})
}

View File

@ -1,8 +1,4 @@
<script setup lang="ts">
const props = defineProps<{
modelValue?: boolean
}>()
const params = useRoute().params
const id = computed(() => params.post as string)
@ -16,7 +12,17 @@ const { data: context } = await useAsyncData(`${id}-context`, () => masto.status
<StatusCard :status="comment" border="t border" pt-4 />
</template>
<StatusDetails :status="status" border="t border" pt-4 />
<div border="t border" p6 flex gap-4>
<img :src="status?.account.avatar" rounded w-10 h-10 bg-gray:10>
<PublishWidget
w-full
:draft-key="`reply-${id}`"
:placeholder="`Reply to ${status?.account?.displayName || status?.account?.acct || 'this thread'}`"
:in-reply-to-id="id"
/>
</div>
<template v-for="comment of context?.descendants" :key="comment.id">
<StatusCard :status="comment" pt-4 />
<StatusCard :status="comment" border="t border" pt-4 />
</template>
</template>

View File

@ -1,5 +1,7 @@
import { login as loginMasto } from 'masto'
import type { UserLogin } from '~/types'
import type { ServerInfo, UserLogin } from '~/types'
const ServerInfoTTL = 60 * 60 * 1000 * 12 // 12 hour
function createClientState() {
const { server, token } = useAppCookies()
@ -43,10 +45,39 @@ function createClientState() {
return true
}
const serverInfos = useLocalStorage<Record<string, ServerInfo>>('nuxtodon-server-info', {})
async function fetchServerInfo(server: string) {
if (!serverInfos.value[server]) {
// @ts-expect-error init
serverInfos.value[server] = {
timeUpdated: 0,
server,
}
}
if (serverInfos.value[server].timeUpdated + ServerInfoTTL < Date.now()) {
const masto = await useMasto()
await Promise.allSettled([
masto.instances.fetch().then((r) => {
Object.assign(serverInfos.value[server], r)
}),
masto.customEmojis.fetchAll().then((r) => {
serverInfos.value[server].customEmojis = Object.fromEntries(r.map(i => [i.shortcode, i]))
}),
])
}
return serverInfos.value[server]
}
if (server.value)
fetchServerInfo(server.value)
return {
currentUser,
accounts,
login,
serverInfos,
fetchServerInfo,
}
}

View File

@ -1,4 +1,4 @@
import type { AccountCredentials } from 'masto'
import type { AccountCredentials, Emoji, Instance } from 'masto'
export interface AppInfo {
id: string
@ -17,3 +17,9 @@ export interface UserLogin {
}
export type PaginatorState = 'idle' | 'loading' | 'done' | 'error'
export interface ServerInfo extends Instance {
server: string
timeUpdated: number
customEmojis?: Record<string, Emoji>
}