feat: cache post content

This commit is contained in:
codytseng
2025-03-09 15:28:12 +08:00
parent 6c2d3e1a64
commit b04e628e00
4 changed files with 67 additions and 4 deletions

View File

@@ -4,9 +4,10 @@ import { createCommentDraftEvent, createShortTextNoteDraftEvent } from '@/lib/dr
import { getRootEventTag } from '@/lib/event.ts'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
import postContentCache from '@/services/post-content-cache.service'
import { ChevronDown, ImageUp, LoaderCircle } from 'lucide-react'
import { Event, kinds, nip19 } from 'nostr-tools'
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import TextareaWithMentions from '../TextareaWithMentions.tsx'
import Mentions from './Mentions'
@@ -27,7 +28,7 @@ export default function NormalPostContent({
const { t } = useTranslation()
const { toast } = useToast()
const { publish, checkLogin } = useNostr()
const [content, setContent] = useState(defaultContent)
const [content, setContent] = useState('')
const [pictureInfos, setPictureInfos] = useState<{ url: string; tags: string[][] }[]>([])
const [posting, setPosting] = useState(false)
const [showMoreOptions, setShowMoreOptions] = useState(false)
@@ -35,8 +36,26 @@ export default function NormalPostContent({
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
const [uploadingPicture, setUploadingPicture] = useState(false)
const [mentions, setMentions] = useState<string[]>([])
const [cursorOffset, setCursorOffset] = useState(0)
const initializedRef = useRef(false)
const canPost = !!content && !posting
useEffect(() => {
const cachedContent = postContentCache.get({ defaultContent, parentEvent })
if (cachedContent) {
setContent(cachedContent)
}
if (defaultContent) {
setCursorOffset(defaultContent.length)
}
initializedRef.current = true
}, [defaultContent, parentEvent])
useEffect(() => {
if (!initializedRef.current) return
postContentCache.set({ defaultContent, parentEvent }, content)
}, [content])
const post = async (e: React.MouseEvent) => {
e.stopPropagation()
checkLogin(async () => {
@@ -130,6 +149,7 @@ export default function NormalPostContent({
setTextValue={setContent}
textValue={content}
placeholder={t('Write something...')}
cursorOffset={cursorOffset}
/>
{content && <Preview content={content} />}
<SendOnlyToSwitch

View File

@@ -10,11 +10,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { Event } from 'nostr-tools'
import { Dispatch, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../ui/sheet'
import NormalPostContent from './NormalPostContent'
import PicturePostContent from './PicturePostContent'
import Title from './Title'
import { useTranslation } from 'react-i18next'
export default function PostEditor({
defaultContent = '',
@@ -55,7 +55,7 @@ export default function PostEditor({
</TabsContent>
</Tabs>
)
}, [parentEvent])
}, [parentEvent, defaultContent])
if (isSmallScreen) {
return (

View File

@@ -21,10 +21,12 @@ import { getCurrentWord, replaceWord } from './utils'
export default function TextareaWithMentions({
textValue,
setTextValue,
cursorOffset = 0,
...props
}: ComponentProps<'textarea'> & {
textValue: string
setTextValue: Dispatch<SetStateAction<string>>
cursorOffset?: number
}) {
const textareaRef = useRef<HTMLTextAreaElement>(null)
const dropdownRef = useRef<HTMLDivElement>(null)
@@ -33,6 +35,15 @@ export default function TextareaWithMentions({
const [debouncedCommandValue, setDebouncedCommandValue] = useState(commandValue)
const [profiles, setProfiles] = useState<TProfile[]>([])
useEffect(() => {
if (textareaRef.current && cursorOffset !== 0) {
const textarea = textareaRef.current
const newPos = Math.max(0, textarea.selectionStart - cursorOffset)
textarea.setSelectionRange(newPos, newPos)
textarea.focus()
}
}, [cursorOffset])
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedCommandValue(commandValue)

View File

@@ -0,0 +1,32 @@
import { Event } from 'nostr-tools'
class PostContentCacheService {
static instance: PostContentCacheService
private cache: Map<string, string> = new Map()
constructor() {
if (!PostContentCacheService.instance) {
PostContentCacheService.instance = this
}
return PostContentCacheService.instance
}
get({ defaultContent, parentEvent }: { defaultContent?: string; parentEvent?: Event } = {}) {
return this.cache.get(this.generateCacheKey(defaultContent, parentEvent)) ?? defaultContent
}
set(
{ defaultContent, parentEvent }: { defaultContent?: string; parentEvent?: Event },
content: string
) {
this.cache.set(this.generateCacheKey(defaultContent, parentEvent), content)
}
generateCacheKey(defaultContent: string = '', parentEvent?: Event): string {
return parentEvent ? parentEvent.id : defaultContent
}
}
const instance = new PostContentCacheService()
export default instance