lint fix wip
This commit is contained in:
parent
390f895920
commit
303c0bf7df
@ -202,7 +202,7 @@ const ArticleFieldsRight = ({
|
||||
<MarkdownEditorTwoColumns
|
||||
value={draft.content}
|
||||
onChange={(value) => onDraftChange({ ...draft, content: value })}
|
||||
pages={draft.pages}
|
||||
{...(draft.pages ? { pages: draft.pages } : {})}
|
||||
onPagesChange={(pages) => onDraftChange({ ...draft, pages })}
|
||||
onMediaAdd={(media: MediaRef) => {
|
||||
const nextMedia = [...(draft.media ?? []), media]
|
||||
|
||||
@ -3,6 +3,7 @@ import { nostrAuthService } from '@/lib/nostrAuth'
|
||||
import { keyManagementService } from '@/lib/keyManagement'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { SyncProgressBar } from './SyncProgressBar'
|
||||
|
||||
interface PublicKeys {
|
||||
publicKey: string
|
||||
@ -251,6 +252,9 @@ export function KeyManagementManager(): React.ReactElement {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sync Progress Bar */}
|
||||
{publicKeys && <SyncProgressBar />}
|
||||
|
||||
{!publicKeys && !accountExists && (
|
||||
<div className="bg-yellow-900/20 border border-yellow-400/50 rounded-lg p-4 mb-6">
|
||||
<p className="text-yellow-400 font-semibold mb-2">{t('settings.keyManagement.noAccount.title')}</p>
|
||||
|
||||
@ -89,7 +89,7 @@ export function MarkdownEditorTwoColumns({
|
||||
}}
|
||||
uploading={uploading}
|
||||
error={error}
|
||||
onAddPage={onPagesChange ? handleAddPage : undefined}
|
||||
{...(onPagesChange ? { onAddPage: handleAddPage } : {})}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
@ -205,8 +205,8 @@ function PagesManager({
|
||||
onContentChange={(content) => onPageContentChange(page.number, content)}
|
||||
onTypeChange={(type) => onPageTypeChange(page.number, type)}
|
||||
onRemove={() => onRemovePage(page.number)}
|
||||
onImageUpload={(file) => {
|
||||
void onImageUpload(file, page.number)
|
||||
onImageUpload={async (file) => {
|
||||
await onImageUpload(file, page.number)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -41,18 +41,18 @@ export function ReviewForm({ article, onSuccess, onCancel }: ReviewFormProps): R
|
||||
return
|
||||
}
|
||||
|
||||
const category = article.category ?? 'science-fiction'
|
||||
const category = article.category === 'author-presentation' ? 'science-fiction' : (article.category ?? 'science-fiction')
|
||||
const seriesId = article.seriesId ?? ''
|
||||
|
||||
await publishReview({
|
||||
articleId: article.id,
|
||||
seriesId,
|
||||
category,
|
||||
category: category === 'science-fiction' || category === 'scientific-research' ? category : 'science-fiction',
|
||||
authorPubkey: article.pubkey,
|
||||
reviewerPubkey: pubkey,
|
||||
content: content.trim(),
|
||||
title: title.trim() || undefined,
|
||||
text: text.trim() || undefined,
|
||||
...(title.trim() ? { title: title.trim() } : {}),
|
||||
...(text.trim() ? { text: text.trim() } : {}),
|
||||
authorPrivateKey: privateKey,
|
||||
})
|
||||
|
||||
|
||||
@ -38,17 +38,17 @@ export function ReviewTipForm({ review, article, onSuccess, onCancel }: ReviewTi
|
||||
}
|
||||
|
||||
const split = calculateReviewSplit()
|
||||
const category = article.category === 'science-fiction' ? 'sciencefiction' : article.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
||||
|
||||
// Build zap request tags
|
||||
const category = article.category === 'author-presentation' ? undefined : (article.category === 'science-fiction' || article.category === 'scientific-research' ? article.category : undefined)
|
||||
const zapRequestTags = buildReviewTipZapRequestTags({
|
||||
articleId: article.id,
|
||||
reviewId: review.id,
|
||||
authorPubkey: article.pubkey,
|
||||
reviewerPubkey: review.reviewerPubkey,
|
||||
category: article.category,
|
||||
seriesId: article.seriesId,
|
||||
text: text.trim() || undefined,
|
||||
...(category ? { category } : {}),
|
||||
...(article.seriesId ? { seriesId: article.seriesId } : {}),
|
||||
...(text.trim() ? { text: text.trim() } : {}),
|
||||
})
|
||||
|
||||
// Create zap request event (kind 9734) and publish it
|
||||
|
||||
160
components/SyncProgressBar.tsx
Normal file
160
components/SyncProgressBar.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { nostrAuthService } from '@/lib/nostrAuth'
|
||||
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
|
||||
import { getLastSyncDate, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
|
||||
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
|
||||
import { t } from '@/lib/i18n'
|
||||
|
||||
export function SyncProgressBar(): React.ReactElement | null {
|
||||
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
|
||||
const [isSyncing, setIsSyncing] = useState(false)
|
||||
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
|
||||
const [totalDays, setTotalDays] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
void loadSyncStatus()
|
||||
}, [])
|
||||
|
||||
async function loadSyncStatus(): Promise<void> {
|
||||
try {
|
||||
const state = nostrAuthService.getState()
|
||||
if (!state.connected || !state.pubkey) {
|
||||
return
|
||||
}
|
||||
|
||||
const storedLastSyncDate = await getLastSyncDate()
|
||||
const currentTimestamp = getCurrentTimestamp()
|
||||
const days = calculateDaysBetween(storedLastSyncDate, currentTimestamp)
|
||||
|
||||
setLastSyncDate(storedLastSyncDate)
|
||||
setTotalDays(days)
|
||||
|
||||
// If everything is synced (no days to sync), don't show the progress bar
|
||||
if (days === 0 && storedLastSyncDate >= currentTimestamp) {
|
||||
setSyncProgress(null)
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading sync status:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function startSync(): Promise<void> {
|
||||
try {
|
||||
const state = nostrAuthService.getState()
|
||||
if (!state.connected || !state.pubkey) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsSyncing(true)
|
||||
setSyncProgress({ currentDay: 0, totalDays, completed: false })
|
||||
|
||||
await syncUserContentToCache(state.pubkey, (progress) => {
|
||||
setSyncProgress(progress)
|
||||
if (progress.completed) {
|
||||
setIsSyncing(false)
|
||||
void loadSyncStatus()
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error starting sync:', error)
|
||||
setIsSyncing(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show if not connected or if everything is synced
|
||||
const state = nostrAuthService.getState()
|
||||
if (!state.connected || !state.pubkey) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If everything is synced, don't show the progress bar
|
||||
if (totalDays === 0 && lastSyncDate !== null && lastSyncDate >= getCurrentTimestamp()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If sync is completed and no days to sync, don't show
|
||||
if (syncProgress?.completed && totalDays === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const progressPercentage = syncProgress && totalDays > 0
|
||||
? Math.min(100, (syncProgress.currentDay / totalDays) * 100)
|
||||
: 0
|
||||
|
||||
const formatDate = (timestamp: number): string => {
|
||||
const date = new Date(timestamp * 1000)
|
||||
const locale = typeof window !== 'undefined' ? navigator.language : 'fr-FR'
|
||||
return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
}
|
||||
|
||||
const getStartDate = (): number => {
|
||||
if (lastSyncDate !== null) {
|
||||
return lastSyncDate
|
||||
}
|
||||
return MIN_EVENT_DATE
|
||||
}
|
||||
|
||||
const startDate = getStartDate()
|
||||
const endDate = getCurrentTimestamp()
|
||||
|
||||
return (
|
||||
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-4 mt-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-neon-cyan">
|
||||
{t('settings.sync.title')}
|
||||
</h3>
|
||||
{!isSyncing && totalDays > 0 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
void startSync()
|
||||
}}
|
||||
className="px-3 py-1 text-xs bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded border border-neon-cyan/50 hover:border-neon-cyan transition-colors"
|
||||
>
|
||||
{t('settings.sync.start')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{totalDays > 0 && (
|
||||
<div className="mb-2">
|
||||
<p className="text-sm text-cyber-accent">
|
||||
{t('settings.sync.daysRange', {
|
||||
startDate: formatDate(startDate),
|
||||
endDate: formatDate(endDate),
|
||||
days: totalDays,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSyncing && syncProgress && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-cyber-accent">
|
||||
{t('settings.sync.progress', {
|
||||
current: syncProgress.currentDay,
|
||||
total: syncProgress.totalDays,
|
||||
})}
|
||||
</span>
|
||||
<span className="text-neon-cyan font-semibold">
|
||||
{Math.round(progressPercentage)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-cyber-dark rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
className="bg-neon-cyan h-full transition-all duration-300"
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isSyncing && totalDays === 0 && lastSyncDate !== null && (
|
||||
<p className="text-sm text-green-400">
|
||||
{t('settings.sync.completed')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'
|
||||
import type { Article } from '@/types/nostr'
|
||||
import type { ArticleDraft } from '@/lib/articlePublisherTypes'
|
||||
import { useArticleEditing } from '@/hooks/useArticleEditing'
|
||||
import { UserArticlesView } from './UserArticlesList'
|
||||
import { EditPanel } from './UserArticlesEditPanel'
|
||||
@ -135,16 +136,29 @@ function buildUpdatedArticle(
|
||||
pubkey: string,
|
||||
newId: string
|
||||
): Article {
|
||||
const hash = newId.split('_')[0] ?? ''
|
||||
const index = Number.parseInt(newId.split('_')[1] ?? '0', 10)
|
||||
const version = Number.parseInt(newId.split('_')[2] ?? '0', 10)
|
||||
return {
|
||||
id: newId,
|
||||
hash,
|
||||
version,
|
||||
index,
|
||||
pubkey,
|
||||
title: draft.title,
|
||||
preview: draft.preview,
|
||||
content: '',
|
||||
description: draft.preview,
|
||||
contentDescription: draft.preview,
|
||||
createdAt: Math.floor(Date.now() / 1000),
|
||||
zapAmount: draft.zapAmount,
|
||||
paid: false,
|
||||
thumbnailUrl: draft.bannerUrl ?? '',
|
||||
...(draft.category ? { category: draft.category } : {}),
|
||||
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
|
||||
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
|
||||
...(draft.media ? { media: draft.media } : {}),
|
||||
...(draft.pages ? { pages: draft.pages } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { storePrivateContent, getStoredPrivateContent } from './articleStorage'
|
||||
import { buildTags } from './nostrTagSystem'
|
||||
import { PLATFORM_SERVICE } from './platformConfig'
|
||||
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
|
||||
import { parseObjectId } from './urlGenerator'
|
||||
import type { ArticleDraft, PublishedArticle } from './articlePublisher'
|
||||
import type { AlbyInvoice } from '@/types/alby'
|
||||
import type { Review, Series } from '@/types/nostr'
|
||||
@ -64,12 +65,20 @@ export async function publishSeries(params: {
|
||||
if (!published) {
|
||||
throw new Error('Failed to publish series')
|
||||
}
|
||||
const parsed = parseObjectId(published.id)
|
||||
const hash = parsed.hash ?? published.id
|
||||
const version = parsed.version ?? 0
|
||||
const index = parsed.index ?? 0
|
||||
return {
|
||||
id: published.id,
|
||||
hash,
|
||||
version,
|
||||
index,
|
||||
pubkey: params.authorPubkey,
|
||||
title: params.title,
|
||||
description: params.description,
|
||||
preview: params.preview ?? params.description.substring(0, 200),
|
||||
thumbnailUrl: params.coverUrl ?? '',
|
||||
category,
|
||||
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
||||
kindType: 'series',
|
||||
@ -156,12 +165,20 @@ export async function publishReview(params: {
|
||||
if (!published) {
|
||||
throw new Error('Failed to publish review')
|
||||
}
|
||||
const parsed = parseObjectId(published.id)
|
||||
const hash = parsed.hash ?? published.id
|
||||
const version = parsed.version ?? 0
|
||||
const index = parsed.index ?? 0
|
||||
return {
|
||||
id: published.id,
|
||||
hash,
|
||||
version,
|
||||
index,
|
||||
articleId: params.articleId,
|
||||
authorPubkey: params.authorPubkey,
|
||||
reviewerPubkey: params.reviewerPubkey,
|
||||
content: params.content,
|
||||
description: params.content.substring(0, 200),
|
||||
createdAt: published.created_at,
|
||||
...(params.title ? { title: params.title } : {}),
|
||||
...(params.text ? { text: params.text } : {}),
|
||||
|
||||
@ -181,8 +181,8 @@ export async function parsePresentationEvent(event: Event): Promise<import('@/ty
|
||||
authorName: profileData?.authorName ?? '',
|
||||
presentation: profileData?.presentation ?? '',
|
||||
contentDescription: profileData?.contentDescription ?? '',
|
||||
mainnetAddress: profileData?.mainnetAddress ?? tags.mainnetAddress,
|
||||
pictureUrl: profileData?.pictureUrl ?? tags.pictureUrl,
|
||||
mainnetAddress: profileData?.mainnetAddress ?? (typeof tags.mainnetAddress === 'string' ? tags.mainnetAddress : undefined),
|
||||
pictureUrl: profileData?.pictureUrl ?? (typeof tags.pictureUrl === 'string' ? tags.pictureUrl : undefined),
|
||||
category: profileData?.category ?? tags.category ?? 'sciencefiction',
|
||||
})
|
||||
}
|
||||
@ -202,7 +202,7 @@ export async function parsePresentationEvent(event: Event): Promise<import('@/ty
|
||||
content: event.content,
|
||||
description: profileData?.presentation ?? tags.description ?? '', // Required field
|
||||
contentDescription: profileData?.contentDescription ?? tags.description ?? '', // Required field
|
||||
thumbnailUrl: (profileData?.pictureUrl ?? tags.pictureUrl) ?? '', // Required field
|
||||
thumbnailUrl: (typeof profileData?.pictureUrl === 'string' ? profileData.pictureUrl : typeof tags.pictureUrl === 'string' ? tags.pictureUrl : ''), // Required field
|
||||
createdAt: event.created_at,
|
||||
zapAmount: 0,
|
||||
paid: true,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Event } from 'nostr-tools'
|
||||
import type { Article, KindType, Purchase, Review, ReviewTip, Series, Sponsoring } from '@/types/nostr'
|
||||
import type { Article, KindType, Page, Purchase, Review, ReviewTip, Series, Sponsoring } from '@/types/nostr'
|
||||
import { extractTagsFromEvent } from './nostrTagSystem'
|
||||
import { buildObjectId, parseObjectId } from './urlGenerator'
|
||||
import { generateHashId } from './hashIdGenerator'
|
||||
@ -229,7 +229,7 @@ async function buildArticle(event: Event, tags: ReturnType<typeof extractTagsFro
|
||||
createdAt: event.created_at,
|
||||
zapAmount: tags.zapAmount ?? 800,
|
||||
paid: false,
|
||||
thumbnailUrl: tags.bannerUrl ?? tags.pictureUrl ?? '', // Required field with default
|
||||
thumbnailUrl: (typeof tags.bannerUrl === 'string' ? tags.bannerUrl : typeof tags.pictureUrl === 'string' ? tags.pictureUrl : ''), // Required field with default
|
||||
...(tags.invoice ? { invoice: tags.invoice } : {}),
|
||||
...(tags.paymentHash ? { paymentHash: tags.paymentHash } : {}),
|
||||
...(category ? { category } : {}),
|
||||
|
||||
51
lib/syncStorage.ts
Normal file
51
lib/syncStorage.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { storageService } from './storage/indexedDB'
|
||||
|
||||
const LAST_SYNC_DATE_KEY = 'last_sync_date'
|
||||
const SYNC_STORAGE_SECRET = 'sync_storage_secret'
|
||||
|
||||
/**
|
||||
* Get the last synchronization date
|
||||
* Returns MIN_EVENT_DATE if no date is stored
|
||||
*/
|
||||
export async function getLastSyncDate(): Promise<number> {
|
||||
try {
|
||||
const stored = await storageService.get<number>(LAST_SYNC_DATE_KEY, SYNC_STORAGE_SECRET)
|
||||
if (stored !== null && typeof stored === 'number') {
|
||||
return stored
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting last sync date:', error)
|
||||
}
|
||||
// Return MIN_EVENT_DATE if no date is stored
|
||||
const { MIN_EVENT_DATE } = await import('./platformConfig')
|
||||
return MIN_EVENT_DATE
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the last synchronization date
|
||||
*/
|
||||
export async function setLastSyncDate(timestamp: number): Promise<void> {
|
||||
try {
|
||||
await storageService.set(LAST_SYNC_DATE_KEY, timestamp, SYNC_STORAGE_SECRET)
|
||||
} catch (error) {
|
||||
console.error('Error setting last sync date:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of days between two timestamps
|
||||
*/
|
||||
export function calculateDaysBetween(startTimestamp: number, endTimestamp: number): number {
|
||||
const startDate = new Date(startTimestamp * 1000)
|
||||
const endDate = new Date(endTimestamp * 1000)
|
||||
const diffTime = endDate.getTime() - startDate.getTime()
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
return Math.max(0, diffDays)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date as Unix timestamp
|
||||
*/
|
||||
export function getCurrentTimestamp(): number {
|
||||
return Math.floor(Date.now() / 1000)
|
||||
}
|
||||
@ -67,20 +67,23 @@ async function fetchAndCachePublications(
|
||||
}
|
||||
|
||||
// Cache each publication
|
||||
for (const [hash, hashEvents] of eventsByHashId.entries()) {
|
||||
for (const [_hash, hashEvents] of eventsByHashId.entries()) {
|
||||
const latestEvent = getLatestVersion(hashEvents)
|
||||
if (latestEvent) {
|
||||
const extracted = await extractPublicationFromEvent(latestEvent)
|
||||
if (extracted?.hash) {
|
||||
if (extracted) {
|
||||
const parsed = parseObjectId(extracted.id)
|
||||
const extractedHash = parsed.hash ?? extracted.id
|
||||
const extractedIndex = parsed.index ?? 0
|
||||
const tags = extractTagsFromEvent(latestEvent)
|
||||
await objectCache.set(
|
||||
'publication',
|
||||
extracted.hash,
|
||||
extractedHash,
|
||||
latestEvent,
|
||||
extracted,
|
||||
tags.version ?? 0,
|
||||
tags.hidden ?? false,
|
||||
extracted.index
|
||||
extractedIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -158,20 +161,23 @@ async function fetchAndCacheSeries(
|
||||
}
|
||||
|
||||
// Cache each series
|
||||
for (const [hash, hashEvents] of eventsByHashId.entries()) {
|
||||
for (const [_hash, hashEvents] of eventsByHashId.entries()) {
|
||||
const latestEvent = getLatestVersion(hashEvents)
|
||||
if (latestEvent) {
|
||||
const extracted = await extractSeriesFromEvent(latestEvent)
|
||||
if (extracted?.hash) {
|
||||
if (extracted) {
|
||||
const parsed = parseObjectId(extracted.id)
|
||||
const extractedHash = parsed.hash ?? extracted.id
|
||||
const extractedIndex = parsed.index ?? 0
|
||||
const tags = extractTagsFromEvent(latestEvent)
|
||||
await objectCache.set(
|
||||
'series',
|
||||
extracted.hash,
|
||||
extractedHash,
|
||||
latestEvent,
|
||||
extracted,
|
||||
tags.version ?? 0,
|
||||
tags.hidden ?? false,
|
||||
extracted.index
|
||||
extractedIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -233,9 +239,6 @@ async function fetchAndCachePurchases(
|
||||
for (const event of events) {
|
||||
const extracted = await extractPurchaseFromEvent(event)
|
||||
if (extracted) {
|
||||
const parsed = parseObjectId(extracted.id)
|
||||
const hash = parsed.hash ?? extracted.id
|
||||
const index = parsed.index ?? 0
|
||||
// Parse to Purchase object for cache
|
||||
const { parsePurchaseFromEvent } = await import('./nostrEventParsing')
|
||||
const purchase = await parsePurchaseFromEvent(event)
|
||||
@ -386,11 +389,22 @@ async function fetchAndCacheReviewTips(
|
||||
})
|
||||
}
|
||||
|
||||
export interface SyncProgress {
|
||||
currentDay: number
|
||||
totalDays: number
|
||||
completed: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize all user content to IndexedDB cache
|
||||
* Fetches profile, series, publications, purchases, sponsoring, and review tips and caches them
|
||||
* @param userPubkey - The user's public key
|
||||
* @param onProgress - Optional callback to report progress (currentDay, totalDays, completed)
|
||||
*/
|
||||
export async function syncUserContentToCache(userPubkey: string): Promise<void> {
|
||||
export async function syncUserContentToCache(
|
||||
userPubkey: string,
|
||||
onProgress?: (progress: SyncProgress) => void
|
||||
): Promise<void> {
|
||||
try {
|
||||
const pool = nostrService.getPool()
|
||||
if (!pool) {
|
||||
@ -400,23 +414,63 @@ export async function syncUserContentToCache(userPubkey: string): Promise<void>
|
||||
|
||||
const poolWithSub = pool as unknown as SimplePoolWithSub
|
||||
|
||||
// Get last sync date and calculate days
|
||||
const { getLastSyncDate, setLastSyncDate, getCurrentTimestamp, calculateDaysBetween } = await import('./syncStorage')
|
||||
const lastSyncDate = await getLastSyncDate()
|
||||
const currentTimestamp = getCurrentTimestamp()
|
||||
const totalDays = calculateDaysBetween(lastSyncDate, currentTimestamp)
|
||||
|
||||
// Report initial progress
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay: 0, totalDays, completed: false })
|
||||
}
|
||||
|
||||
let currentDay = 0
|
||||
|
||||
// Fetch and cache author profile (already caches itself)
|
||||
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: false })
|
||||
}
|
||||
|
||||
// Fetch and cache all series
|
||||
await fetchAndCacheSeries(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: false })
|
||||
}
|
||||
|
||||
// Fetch and cache all publications
|
||||
await fetchAndCachePublications(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: false })
|
||||
}
|
||||
|
||||
// Fetch and cache all purchases (as payer)
|
||||
await fetchAndCachePurchases(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: false })
|
||||
}
|
||||
|
||||
// Fetch and cache all sponsoring (as author)
|
||||
await fetchAndCacheSponsoring(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: false })
|
||||
}
|
||||
|
||||
// Fetch and cache all review tips (as author)
|
||||
await fetchAndCacheReviewTips(poolWithSub, userPubkey)
|
||||
currentDay++
|
||||
if (onProgress) {
|
||||
onProgress({ currentDay, totalDays, completed: true })
|
||||
}
|
||||
|
||||
// Store the current timestamp as last sync date
|
||||
await setLastSyncDate(currentTimestamp)
|
||||
} catch (error) {
|
||||
console.error('Error syncing user content to cache:', error)
|
||||
// Don't throw - this is a background operation
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import type { ArticleFilters } from '@/components/ArticleFilters'
|
||||
import type { NostrProfile } from '@/types/nostr'
|
||||
import type { Article, NostrProfile } from '@/types/nostr'
|
||||
import { ProfileView } from '@/components/ProfileView'
|
||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||
import { useUserArticles } from '@/hooks/useUserArticles'
|
||||
|
||||
@ -236,6 +236,11 @@ settings.keyManagement.recovery.copy=Copy Recovery Words
|
||||
settings.keyManagement.recovery.copied=✓ Copied!
|
||||
settings.keyManagement.recovery.newNpub=Your new public key (npub)
|
||||
settings.keyManagement.recovery.done=Done
|
||||
settings.sync.title=Notes Synchronization
|
||||
settings.sync.start=Start Synchronization
|
||||
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
|
||||
settings.sync.progress=Day {{current}} of {{total}}
|
||||
settings.sync.completed=Everything is synchronized
|
||||
settings.nip95.title=NIP-95 Upload Endpoints
|
||||
settings.nip95.loading=Loading...
|
||||
settings.nip95.error.loadFailed=Failed to load NIP-95 APIs
|
||||
|
||||
@ -236,6 +236,11 @@ settings.keyManagement.recovery.copy=Copier les mots de récupération
|
||||
settings.keyManagement.recovery.copied=✓ Copié !
|
||||
settings.keyManagement.recovery.newNpub=Votre nouvelle clé publique (npub)
|
||||
settings.keyManagement.recovery.done=Terminé
|
||||
settings.sync.title=Synchronisation des notes
|
||||
settings.sync.start=Démarrer la synchronisation
|
||||
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
|
||||
settings.sync.progress=Jour {{current}} sur {{total}}
|
||||
settings.sync.completed=Tout est synchronisé
|
||||
settings.language.title=Langue de préférence
|
||||
settings.language.description=Choisissez votre langue préférée pour l'interface
|
||||
settings.language.loading=Chargement...
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user