feat: cache for publish widget
This commit is contained in:
parent
5d5cdebb56
commit
193d1cf5c5
|
@ -17,7 +17,7 @@ defineProps<{
|
||||||
<h4 font-bold>
|
<h4 font-bold>
|
||||||
{{ account.displayName }}
|
{{ account.displayName }}
|
||||||
</h4>
|
</h4>
|
||||||
<p op50>
|
<p op35 text-sm>
|
||||||
@{{ account.acct }}
|
@{{ account.acct }}
|
||||||
</p>
|
</p>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
|
@ -9,7 +9,7 @@ const account = $computed(() => currentUser?.account)
|
||||||
<!-- TODO: multiple account switcher -->
|
<!-- TODO: multiple account switcher -->
|
||||||
<template v-if="account">
|
<template v-if="account">
|
||||||
<AccountInfo :account="account" />
|
<AccountInfo :account="account" />
|
||||||
<PublishWidget />
|
<PublishWidget draft-key="home" />
|
||||||
</template>
|
</template>
|
||||||
<!-- TODO: dialog for select server -->
|
<!-- TODO: dialog for select server -->
|
||||||
<a v-else href="/api/mas.to/login" px2 py1 bg-teal6 text-white m2 rounded>Login</a>
|
<a v-else href="/api/mas.to/login" px2 py1 bg-teal6 text-white m2 rounded>Login</a>
|
||||||
|
|
68
components/publish/PublishWidget.client.vue
Normal file
68
components/publish/PublishWidget.client.vue
Normal 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>
|
|
@ -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>
|
|
|
@ -59,7 +59,7 @@ export function treeToVNode(
|
||||||
return h(
|
return h(
|
||||||
RouterLink as any,
|
RouterLink as any,
|
||||||
attrs,
|
attrs,
|
||||||
node.childNodes.map(n => treeToVNode(n, handle)),
|
() => node.childNodes.map(n => treeToVNode(n, handle)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return h(
|
return h(
|
||||||
|
|
28
composables/useCacheStorage.ts
Normal file
28
composables/useCacheStorage.ts
Normal 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
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,8 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
|
||||||
modelValue?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const id = computed(() => params.post as string)
|
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 />
|
<StatusCard :status="comment" border="t border" pt-4 />
|
||||||
</template>
|
</template>
|
||||||
<StatusDetails :status="status" border="t border" pt-4 />
|
<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">
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { login as loginMasto } from 'masto'
|
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() {
|
function createClientState() {
|
||||||
const { server, token } = useAppCookies()
|
const { server, token } = useAppCookies()
|
||||||
|
@ -43,10 +45,39 @@ function createClientState() {
|
||||||
return true
|
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 {
|
return {
|
||||||
currentUser,
|
currentUser,
|
||||||
accounts,
|
accounts,
|
||||||
login,
|
login,
|
||||||
|
serverInfos,
|
||||||
|
fetchServerInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AccountCredentials } from 'masto'
|
import type { AccountCredentials, Emoji, Instance } from 'masto'
|
||||||
|
|
||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
id: string
|
id: string
|
||||||
|
@ -17,3 +17,9 @@ export interface UserLogin {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaginatorState = 'idle' | 'loading' | 'done' | 'error'
|
export type PaginatorState = 'idle' | 'loading' | 'done' | 'error'
|
||||||
|
|
||||||
|
export interface ServerInfo extends Instance {
|
||||||
|
server: string
|
||||||
|
timeUpdated: number
|
||||||
|
customEmojis?: Record<string, Emoji>
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue