mirror of
https://github.com/elk-zone/elk
synced 2024-11-23 22:56:09 +09:00
feat: cache for publish widget
This commit is contained in:
parent
5d5cdebb56
commit
193d1cf5c5
@ -17,7 +17,7 @@ defineProps<{
|
||||
<h4 font-bold>
|
||||
{{ account.displayName }}
|
||||
</h4>
|
||||
<p op50>
|
||||
<p op35 text-sm>
|
||||
@{{ account.acct }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
|
@ -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>
|
||||
|
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(
|
||||
RouterLink as any,
|
||||
attrs,
|
||||
node.childNodes.map(n => treeToVNode(n, handle)),
|
||||
() => node.childNodes.map(n => treeToVNode(n, handle)),
|
||||
)
|
||||
}
|
||||
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">
|
||||
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>
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user