Browse Source

fix pulse flakiness

imwald
Silberengel 1 month ago
parent
commit
9319da677a
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 2
      src/components/CacheRelaysSetting/index.tsx
  4. 73
      src/components/PostEditor/PostContent.tsx
  5. 6
      src/components/PostEditor/PostTextarea/index.tsx
  6. 2
      src/constants.ts
  7. 2
      src/lib/pubkey.ts
  8. 4
      src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
  9. 82
      src/providers/FavoriteRelaysActivityProvider.tsx
  10. 12
      src/providers/NostrProvider/index.tsx
  11. 171
      src/services/post-editor-cache.service.ts

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "jumble-imwald",
"version": "19.3.4",
"version": "19.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jumble-imwald",
"version": "19.3.4",
"version": "19.4.0",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "jumble-imwald",
"version": "19.3.4",
"version": "19.4.0",
"description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble",
"private": true,
"type": "module",

2
src/components/CacheRelaysSetting/index.tsx

@ -272,7 +272,7 @@ export default function CacheRelaysSetting() { @@ -272,7 +272,7 @@ export default function CacheRelaysSetting() {
}
// Clear post editor cache
postEditorCache.clearPostCache({})
postEditorCache.clearAllPostCaches()
// Clear in-memory caches so profile pics and reactions work after clear
client.clearInMemoryCaches()

73
src/components/PostEditor/PostContent.tsx

@ -308,39 +308,6 @@ export default function PostContent({ @@ -308,39 +308,6 @@ export default function PostContent({
}
}, [initialHighlightData])
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
const cachedSettings = postEditorCache.getPostSettingsCache({
defaultContent,
parentEvent
})
if (cachedSettings) {
setIsNsfw(cachedSettings.isNsfw ?? false)
setIsPoll(cachedSettings.isPoll ?? false)
setPollCreateData(
cachedSettings.pollCreateData ?? {
isMultipleChoice: false,
options: ['', ''],
endsAt: undefined,
relays: []
}
)
setAddClientTag(cachedSettings.addClientTag ?? storage.getAddClientTag())
}
return
}
postEditorCache.setPostSettingsCache(
{ defaultContent, parentEvent },
{
isNsfw,
isPoll,
pollCreateData,
addClientTag
}
)
}, [defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag])
// Extract mentions from content for public messages
const extractMentionsFromContent = useCallback(async (content: string) => {
try {
@ -440,6 +407,40 @@ export default function PostContent({ @@ -440,6 +407,40 @@ export default function PostContent({
parentEvent
])
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
const cachedSettings = postEditorCache.getPostSettingsCache({
kind: getDeterminedKind,
defaultContent,
parentEvent
})
if (cachedSettings) {
setIsNsfw(cachedSettings.isNsfw ?? false)
setIsPoll(cachedSettings.isPoll ?? false)
setPollCreateData(
cachedSettings.pollCreateData ?? {
isMultipleChoice: false,
options: ['', ''],
endsAt: undefined,
relays: []
}
)
setAddClientTag(cachedSettings.addClientTag ?? storage.getAddClientTag())
}
return
}
postEditorCache.setPostSettingsCache(
{ kind: getDeterminedKind, defaultContent, parentEvent },
{
isNsfw,
isPoll,
pollCreateData,
addClientTag
}
)
}, [getDeterminedKind, defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag])
const rssReplyExtraPreviewTags = useMemo((): string[][] | undefined => {
if (!parentEvent || parentEvent.kind !== ExtendedKind.RSS_THREAD_ROOT) return undefined
const raw =
@ -920,7 +921,7 @@ export default function PostContent({ @@ -920,7 +921,7 @@ export default function PostContent({
}
// Full success - clean up and close
postEditorCache.clearPostCache({ defaultContent, parentEvent })
postEditorCache.clearPostCache({ kind: getDeterminedKind, defaultContent, parentEvent })
deleteDraftEventCache(draftEvent)
const relayStatuses = (newEvent as any).relayStatuses as TRelayPublishStatus[] | undefined
const cleanEvent = { ...newEvent }
@ -970,7 +971,7 @@ export default function PostContent({ @@ -970,7 +971,7 @@ export default function PostContent({
delete (clean as any).relayStatuses
mergePublishedReplyIntoThread(clean, (error as any).relayStatuses)
}
postEditorCache.clearPostCache({ defaultContent, parentEvent })
postEditorCache.clearPostCache({ kind: getDeterminedKind, defaultContent, parentEvent })
if (draftEvent) deleteDraftEventCache(draftEvent)
onPublishSuccess?.()
close()
@ -1482,7 +1483,7 @@ export default function PostContent({ @@ -1482,7 +1483,7 @@ export default function PostContent({
const handleClear = () => {
// Clear the post editor cache
postEditorCache.clearPostCache({ defaultContent, parentEvent })
postEditorCache.clearPostCache({ kind: getDeterminedKind, defaultContent, parentEvent })
// Clear the editor content
textareaRef.current?.clear()

6
src/components/PostEditor/PostTextarea/index.tsx

@ -153,10 +153,10 @@ const PostTextarea = forwardRef< @@ -153,10 +153,10 @@ const PostTextarea = forwardRef<
return parseEditorJsonToText(content.toJSON())
}
},
content: postEditorCache.getPostContentCache({ defaultContent, parentEvent }),
content: postEditorCache.getPostContentCache({ kind, defaultContent, parentEvent }),
onUpdate(props) {
setText(parseEditorJsonToText(props.editor.getJSON()))
postEditorCache.setPostContentCache({ defaultContent, parentEvent }, props.editor.getJSON())
postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, props.editor.getJSON())
},
onCreate(props) {
setText(parseEditorJsonToText(props.editor.getJSON()))
@ -207,7 +207,7 @@ const PostTextarea = forwardRef< @@ -207,7 +207,7 @@ const PostTextarea = forwardRef<
// Clear the editor content and reset to empty document
editor.chain().clearContent().run()
// Also clear the cache
postEditorCache.setPostContentCache({ defaultContent, parentEvent }, editor.getJSON())
postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, editor.getJSON())
setText('')
}
},

2
src/constants.ts

@ -122,6 +122,8 @@ export const StorageKey = { @@ -122,6 +122,8 @@ export const StorageKey = {
SHOW_RSS_FEED: 'showRssFeed',
PANE_MODE: 'paneMode',
ADD_RANDOM_RELAYS_TO_PUBLISH: 'addRandomRelaysToPublish',
/** Temporary draft cache: new notes and replies. Persisted after 30s idle; restored on refresh; cleared on logout/switch. */
POST_EDITOR_DRAFT: 'postEditorDraft',
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated

2
src/lib/pubkey.ts

@ -78,7 +78,7 @@ export function hexPubkeysEqual(a: string, b: string): boolean { @@ -78,7 +78,7 @@ export function hexPubkeysEqual(a: string, b: string): boolean {
}
export function isValidPubkey(pubkey: string) {
return /^[0-9a-f]{64}$/.test(pubkey)
return /^[0-9a-f]{64}$/i.test(pubkey)
}
const pubkeyImageCache = new LRUCache<string, string>({ max: 1000 })

4
src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx

@ -290,7 +290,7 @@ export default function CreateThreadDialog({ @@ -290,7 +290,7 @@ export default function CreateThreadDialog({
setTopicInput(displayTopicLabel('general', DISCUSSION_TOPICS))
setErrors({})
postEditorCache.clearThreadDraft()
postEditorCache.clearPostCache({ parentEvent: THREAD_POST_EDITOR_PARENT })
postEditorCache.clearPostCache({ kind: ExtendedKind.DISCUSSION, parentEvent: THREAD_POST_EDITOR_PARENT })
postTextareaRef.current?.clear()
}, [])
@ -542,7 +542,7 @@ export default function CreateThreadDialog({ @@ -542,7 +542,7 @@ export default function CreateThreadDialog({
}
postEditorCache.clearThreadDraft()
postEditorCache.clearPostCache({ parentEvent: THREAD_POST_EDITOR_PARENT })
postEditorCache.clearPostCache({ kind: ExtendedKind.DISCUSSION, parentEvent: THREAD_POST_EDITOR_PARENT })
onThreadCreated(publishedEvent)
onClose()
} else {

82
src/providers/FavoriteRelaysActivityProvider.tsx

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import logger from '@/lib/logger'
import { getFavoritesFeedRelayUrls } from '@/lib/favorites-feed-relays'
import { hexPubkeysEqual, normalizeHexPubkey } from '@/lib/pubkey'
import { hexPubkeysEqual, normalizeHexPubkey, userIdToPubkey } from '@/lib/pubkey'
import { getPubkeysFromPTags } from '@/lib/tag'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFollowListOptional } from '@/providers/FollowListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { queryService, replaceableEventService } from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import type { Event } from 'nostr-tools'
import { kinds } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -14,6 +15,7 @@ import { @@ -14,6 +15,7 @@ import {
} from './favorite-relays-activity-context'
const ACTIVE_WINDOW_SEC = 3600
const FETCH_RETRY_DELAY_MS = 2500
/** Wall-clock cadence while the tab is visible */
const POLL_INTERVAL_MS = 60 * 60 * 1000
/** Enough events to surface many distinct authors without overloading relays */
@ -40,13 +42,16 @@ function partitionByFollows(orderedPubkeys: string[], followings: string[]) { @@ -40,13 +42,16 @@ function partitionByFollows(orderedPubkeys: string[], followings: string[]) {
}
}
const followSet = new Set(
followings.map((p) => normalizeHexPubkey(p)).filter((p) => p.length === 64)
followings
.map((p) => userIdToPubkey(p))
.filter((hex): hex is string => !!hex && /^[0-9a-f]{64}$/i.test(hex))
.map((hex) => hex.toLowerCase())
)
const followPubkeys: string[] = []
const otherPubkeys: string[] = []
for (const pk of orderedPubkeys) {
const normalized = normalizeHexPubkey(pk)
if (normalized.length === 64 && followSet.has(normalized)) followPubkeys.push(pk)
const hex = normalizeHexPubkey(pk)
if (hex.length === 64 && followSet.has(hex)) followPubkeys.push(pk)
else otherPubkeys.push(pk)
}
return {
@ -59,9 +64,11 @@ function partitionByFollows(orderedPubkeys: string[], followings: string[]) { @@ -59,9 +64,11 @@ function partitionByFollows(orderedPubkeys: string[], followings: string[]) {
export function FavoriteRelaysActivityProvider({ children }: { children: React.ReactNode }) {
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const followList = useFollowListOptional()
const followings = followList?.followings ?? []
const { pubkey: viewerPubkey } = useNostr()
const { pubkey: viewerPubkey, followListEvent } = useNostr()
const followings = useMemo(
() => (followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []),
[followListEvent]
)
const [orderedPubkeys, setOrderedPubkeys] = useState<string[]>([])
const [loading, setLoading] = useState(false)
const [relayActivityReady, setRelayActivityReady] = useState(false)
@ -69,17 +76,19 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -69,17 +76,19 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
const [profileKind0ByPubkey, setProfileKind0ByPubkey] = useState<Record<string, Event>>({})
const [profilesLoading, setProfilesLoading] = useState(false)
const [activeNpubsDrawerOpen, setActiveNpubsDrawerOpen] = useState(false)
const [fallbackFollowings, setFallbackFollowings] = useState<string[]>([])
const lastCompletedFetchAtRef = useRef(Date.now())
const relayKey = useMemo(
() => getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays).join('\n'),
[favoriteRelays, blockedRelays]
)
const fetchActive = useCallback(async () => {
const urls = getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
const fetchActive = useCallback(
async (useDefaultRelays = false) => {
const urls = useDefaultRelays
? getFavoritesFeedRelayUrls([], blockedRelays)
: getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
if (urls.length === 0) {
setOrderedPubkeys([])
setProfileKind0ByPubkey({})
setLoading(false)
setRelayActivityReady(true)
const now = Date.now()
@ -99,19 +108,22 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -99,19 +108,22 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
globalTimeout: 14_000
}
)
const now = Date.now()
setOrderedPubkeys(aggregatePubkeysByRecency(events))
lastCompletedFetchAtRef.current = now
setLastFetchedAtMs(now)
} catch (error) {
logger.debug('[FavoriteRelaysActivity] fetch failed', { error })
setOrderedPubkeys([])
setProfileKind0ByPubkey({})
logger.debug('[FavoriteRelaysActivity] fetch failed', { error, useDefaultRelays })
if (!useDefaultRelays && favoriteRelays.length > 0) {
setTimeout(() => void fetchRef.current(true), FETCH_RETRY_DELAY_MS)
}
} finally {
setLoading(false)
setRelayActivityReady(true)
const now = Date.now()
lastCompletedFetchAtRef.current = now
setLastFetchedAtMs(now)
}
}, [favoriteRelays, blockedRelays])
},
[favoriteRelays, blockedRelays]
)
const fetchRef = useRef(fetchActive)
fetchRef.current = fetchActive
@ -123,7 +135,8 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -123,7 +135,8 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
setProfileKind0ByPubkey({})
}, [])
/** Initial fetch on mount and when relay set changes (refresh snapshot, not hourly cadence). */
/** Initial fetch on mount and when relay set changes. Use stale-while-revalidate: keep previous
* data visible until new fetch completes instead of clearing and showing skeleton. */
const prevRelayKeyRef = useRef<string | undefined>(undefined)
useEffect(() => {
if (prevRelayKeyRef.current === undefined) {
@ -133,20 +146,40 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -133,20 +146,40 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
}
if (prevRelayKeyRef.current === relayKey) return
prevRelayKeyRef.current = relayKey
resetForRefetch()
void fetchRef.current()
}, [relayKey, resetForRefetch])
}, [relayKey])
/** Logged-in user changed — refetch for the new account. Follow list changes update partition via useMemo. */
const prevViewerRef = useRef<string | undefined>(undefined)
useEffect(() => {
if (prevViewerRef.current !== undefined && prevViewerRef.current !== viewerPubkey) {
resetForRefetch()
setFallbackFollowings([])
void fetchRef.current()
}
prevViewerRef.current = viewerPubkey ?? undefined
}, [viewerPubkey, resetForRefetch])
/** When follow list from context is empty but we have a logged-in viewer, try IndexedDB cache.
* Fixes race where pulse data arrives before NostrProvider has hydrated follow list from cache. */
useEffect(() => {
if (!viewerPubkey || followings.length > 0) {
setFallbackFollowings([])
return
}
let cancelled = false
indexedDb
.getReplaceableEvent(viewerPubkey, kinds.Contacts)
.then((evt) => {
if (cancelled || !evt) return
setFallbackFollowings(getPubkeysFromPTags(evt.tags))
})
.catch(() => {})
return () => {
cancelled = true
}
}, [viewerPubkey, followings.length])
/** While the document is visible: poll once per hour; when returning after a long background, catch up if due. */
useEffect(() => {
let intervalId: ReturnType<typeof setInterval> | undefined
@ -222,9 +255,10 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R @@ -222,9 +255,10 @@ export function FavoriteRelaysActivityProvider({ children }: { children: React.R
return orderedPubkeys.filter((pk) => !hexPubkeysEqual(pk, viewerPubkey))
}, [orderedPubkeys, viewerPubkey])
const effectiveFollowings = followings.length > 0 ? followings : fallbackFollowings
const { followPubkeys, otherPubkeys, followCount, otherCount } = useMemo(
() => partitionByFollows(displayPubkeys, followings),
[displayPubkeys, followings]
() => partitionByFollows(displayPubkeys, effectiveFollowings),
[displayPubkeys, effectiveFollowings]
)
const pubkeys = useMemo(

12
src/providers/NostrProvider/index.tsx

@ -27,6 +27,7 @@ import client from '@/services/client.service' @@ -27,6 +27,7 @@ import client from '@/services/client.service'
import { queryService, replaceableEventService } from '@/services/client.service'
import customEmojiService from '@/services/custom-emoji.service'
import indexedDb from '@/services/indexed-db.service'
import postEditorCache from '@/services/post-editor-cache.service'
import storage from '@/services/local-storage.service'
import noteStatsService from '@/services/note-stats.service'
import {
@ -688,6 +689,17 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -688,6 +689,17 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
}
}, [account, accountNetworkHydrateBump])
/** Clear persisted post draft when user logs out or switches accounts (not on initial load). */
const prevAccountPubkeyRef = useRef<string | null | undefined>(undefined)
useEffect(() => {
const prev = prevAccountPubkeyRef.current
const curr = account?.pubkey ?? null
prevAccountPubkeyRef.current = curr
if (prev !== undefined && prev !== curr) {
postEditorCache.clearOnAccountChange()
}
}, [account?.pubkey])
/** Recovery: if hydrate finished but follow list is still null, fetch using user write + search relays. */
useEffect(() => {
if (!account || followListEvent !== null || isAccountSessionHydrating) return

171
src/services/post-editor-cache.service.ts

@ -1,7 +1,11 @@ @@ -1,7 +1,11 @@
import { StorageKey } from '@/constants'
import storage from '@/services/local-storage.service'
import { TPollCreateData } from '@/types'
import { Content } from '@tiptap/react'
import { Event } from 'nostr-tools'
const PERSIST_DEBOUNCE_MS = 30_000
type TPostSettings = {
isNsfw?: boolean
isPoll?: boolean
@ -9,6 +13,12 @@ type TPostSettings = { @@ -9,6 +13,12 @@ type TPostSettings = {
addClientTag?: boolean
}
type TCacheKeyParams = {
kind: number
defaultContent?: string
parentEvent?: Event
}
/** Cached draft for the Discussions "Create Thread" dialog (kind 11). */
export type TThreadDraft = {
title: string
@ -16,12 +26,21 @@ export type TThreadDraft = { @@ -16,12 +26,21 @@ export type TThreadDraft = {
topic: string
}
type TPersistedDraft = {
accountPubkey: string
postContentCache: Record<string, Content>
postSettingsCache: Record<string, TPostSettings>
threadDraft: TThreadDraft | null
}
class PostEditorCacheService {
static instance: PostEditorCacheService
private postContentCache: Map<string, Content> = new Map()
private postSettingsCache: Map<string, TPostSettings> = new Map()
private threadDraftCache: TThreadDraft | null = null
private persistTimeoutId: ReturnType<typeof setTimeout> | null = null
private restoredFromStorage = false
constructor() {
if (!PostEditorCacheService.instance) {
@ -38,11 +57,89 @@ class PostEditorCacheService { @@ -38,11 +57,89 @@ class PostEditorCacheService {
return text.replace(/&/g, '&amp;')
}
getPostContentCache({
defaultContent,
parentEvent
}: { defaultContent?: string; parentEvent?: Event } = {}) {
const cached = this.postContentCache.get(this.generateCacheKey(defaultContent, parentEvent))
private restoreFromStorageIfNeeded() {
if (this.restoredFromStorage) return
this.restoredFromStorage = true
const account = storage.getCurrentAccount()
if (!account?.pubkey) return
try {
const raw = window.localStorage.getItem(StorageKey.POST_EDITOR_DRAFT)
if (!raw) return
const data = JSON.parse(raw) as TPersistedDraft
if (data.accountPubkey !== account.pubkey) return
if (data.postContentCache && typeof data.postContentCache === 'object') {
Object.entries(data.postContentCache).forEach(([k, v]) => {
if (v) this.postContentCache.set(k, v)
})
}
if (data.postSettingsCache && typeof data.postSettingsCache === 'object') {
Object.entries(data.postSettingsCache).forEach(([k, v]) => {
if (v) this.postSettingsCache.set(k, v)
})
}
if (data.threadDraft) {
this.threadDraftCache = data.threadDraft
}
} catch {
// Ignore corrupt or stale data
}
}
private schedulePersist() {
if (this.persistTimeoutId) {
clearTimeout(this.persistTimeoutId)
}
this.persistTimeoutId = setTimeout(() => {
this.persistTimeoutId = null
this.persistNow()
}, PERSIST_DEBOUNCE_MS)
}
private persistNow() {
const account = storage.getCurrentAccount()
if (!account?.pubkey) return
try {
const postContentCache: Record<string, Content> = {}
this.postContentCache.forEach((v, k) => {
postContentCache[k] = v
})
const postSettingsCache: Record<string, TPostSettings> = {}
this.postSettingsCache.forEach((v, k) => {
postSettingsCache[k] = v
})
const data: TPersistedDraft = {
accountPubkey: account.pubkey,
postContentCache,
postSettingsCache,
threadDraft: this.threadDraftCache
}
window.localStorage.setItem(StorageKey.POST_EDITOR_DRAFT, JSON.stringify(data))
} catch {
// Ignore quota / serialization errors
}
}
/** Call when user logs out or switches accounts. Clears in-memory cache and persisted draft. */
clearOnAccountChange() {
if (this.persistTimeoutId) {
clearTimeout(this.persistTimeoutId)
this.persistTimeoutId = null
}
this.postContentCache.clear()
this.postSettingsCache.clear()
this.threadDraftCache = null
this.restoredFromStorage = false
try {
window.localStorage.removeItem(StorageKey.POST_EDITOR_DRAFT)
} catch {
// Ignore
}
}
getPostContentCache({ kind, defaultContent, parentEvent }: TCacheKeyParams) {
this.restoreFromStorageIfNeeded()
const cacheKey = this.generateCacheKey({ kind, defaultContent, parentEvent })
const cached = this.postContentCache.get(cacheKey)
if (cached !== undefined) return cached
if (defaultContent !== undefined && defaultContent !== '') {
return this.escapeAmpersandsForHtml(defaultContent)
@ -50,53 +147,67 @@ class PostEditorCacheService { @@ -50,53 +147,67 @@ class PostEditorCacheService {
return defaultContent
}
setPostContentCache(
{ defaultContent, parentEvent }: { defaultContent?: string; parentEvent?: Event },
content: Content
) {
this.postContentCache.set(this.generateCacheKey(defaultContent, parentEvent), content)
setPostContentCache({ kind, defaultContent, parentEvent }: TCacheKeyParams, content: Content) {
const cacheKey = this.generateCacheKey({ kind, defaultContent, parentEvent })
this.postContentCache.set(cacheKey, content)
this.schedulePersist()
}
getPostSettingsCache({
defaultContent,
parentEvent
}: { defaultContent?: string; parentEvent?: Event } = {}): TPostSettings | undefined {
return this.postSettingsCache.get(this.generateCacheKey(defaultContent, parentEvent))
getPostSettingsCache({ kind, defaultContent, parentEvent }: TCacheKeyParams): TPostSettings | undefined {
this.restoreFromStorageIfNeeded()
return this.postSettingsCache.get(this.generateCacheKey({ kind, defaultContent, parentEvent }))
}
setPostSettingsCache(
{ defaultContent, parentEvent }: { defaultContent?: string; parentEvent?: Event },
settings: TPostSettings
) {
this.postSettingsCache.set(this.generateCacheKey(defaultContent, parentEvent), settings)
setPostSettingsCache({ kind, defaultContent, parentEvent }: TCacheKeyParams, settings: TPostSettings) {
const cacheKey = this.generateCacheKey({ kind, defaultContent, parentEvent })
this.postSettingsCache.set(cacheKey, settings)
this.schedulePersist()
}
clearPostCache({
defaultContent,
parentEvent
}: {
defaultContent?: string
parentEvent?: Event
}) {
const cacheKey = this.generateCacheKey(defaultContent, parentEvent)
clearPostCache({ kind, defaultContent, parentEvent }: TCacheKeyParams) {
const cacheKey = this.generateCacheKey({ kind, defaultContent, parentEvent })
this.postContentCache.delete(cacheKey)
this.postSettingsCache.delete(cacheKey)
if (this.persistTimeoutId) {
clearTimeout(this.persistTimeoutId)
this.persistTimeoutId = null
}
this.persistNow()
}
generateCacheKey(defaultContent: string = '', parentEvent?: Event): string {
return parentEvent ? parentEvent.id : defaultContent
/** Clear all post and settings drafts. Use when user explicitly clears caches. */
clearAllPostCaches() {
this.postContentCache.clear()
this.postSettingsCache.clear()
if (this.persistTimeoutId) {
clearTimeout(this.persistTimeoutId)
this.persistTimeoutId = null
}
this.persistNow()
}
generateCacheKey({ kind, defaultContent = '', parentEvent }: TCacheKeyParams): string {
const parentPart = parentEvent ? parentEvent.id : ''
return `${kind}:${parentPart}`
}
getThreadDraft(): TThreadDraft | null {
this.restoreFromStorageIfNeeded()
return this.threadDraftCache
}
setThreadDraft(draft: TThreadDraft): void {
this.threadDraftCache = draft
this.schedulePersist()
}
clearThreadDraft(): void {
this.threadDraftCache = null
if (this.persistTimeoutId) {
clearTimeout(this.persistTimeoutId)
this.persistTimeoutId = null
}
this.persistNow()
}
}

Loading…
Cancel
Save