feat: cache post content
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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)
|
||||
|
||||
32
src/services/post-content-cache.service.ts
Normal file
32
src/services/post-content-cache.service.ts
Normal 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
|
||||
Reference in New Issue
Block a user