2026-01-13 14:49:19 +01:00

102 lines
4.0 KiB
TypeScript

import { useMemo, useState } from 'react'
import type { Article } from '@/types/nostr'
import { useArticleEditing } from '@/hooks/useArticleEditing'
import type { ArticleDraft } from '@/lib/articlePublisherTypes'
import type { UserArticlesController, UserArticlesControllerParams } from './types'
export function useUserArticlesController(params: UserArticlesControllerParams): UserArticlesController {
const [deletedArticleIds, setDeletedArticleIds] = useState<Set<string>>(new Set())
const [articleOverridesById, setArticleOverridesById] = useState<Map<string, Article>>(new Map())
const [unlockedArticles, setUnlockedArticles] = useState<Set<string>>(new Set())
const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null)
const editingCtx = useArticleEditing(params.currentPubkey)
const localArticles = useMemo((): Article[] => {
return params.articles.filter((a) => !deletedArticleIds.has(a.id)).map((a) => articleOverridesById.get(a.id) ?? a)
}, [articleOverridesById, deletedArticleIds, params.articles])
return {
localArticles,
unlockedArticles,
pendingDeleteId,
requestDelete: (id: string) => setPendingDeleteId(id),
handleUnlock: createHandleUnlock(params.onLoadContent, setUnlockedArticles),
handleDelete: createHandleDelete(editingCtx.deleteArticle, setDeletedArticleIds, setPendingDeleteId),
handleEditSubmit: createHandleEditSubmit(editingCtx.submitEdit, editingCtx.editingDraft, params.currentPubkey, setArticleOverridesById),
...editingCtx,
}
}
function createHandleUnlock(
onLoadContent: (id: string, pubkey: string) => Promise<Article | null>,
setUnlocked: React.Dispatch<React.SetStateAction<Set<string>>>
): (article: Article) => Promise<void> {
return async (article: Article): Promise<void> => {
const full = await onLoadContent(article.id, article.pubkey)
if (full?.paid) {
setUnlocked((prev) => new Set([...prev, article.id]))
}
}
}
function createHandleDelete(
deleteArticle: (id: string) => Promise<boolean>,
setDeletedArticleIds: React.Dispatch<React.SetStateAction<Set<string>>>,
setPendingDeleteId: React.Dispatch<React.SetStateAction<string | null>>
): (article: Article) => Promise<void> {
return async (article: Article): Promise<void> => {
const ok = await deleteArticle(article.id)
if (ok) {
setDeletedArticleIds((prev) => new Set([...prev, article.id]))
}
setPendingDeleteId(null)
}
}
function createHandleEditSubmit(
submitEdit: () => Promise<import('@/lib/articleMutations').ArticleUpdateResult | null>,
draft: ReturnType<typeof useArticleEditing>['editingDraft'],
currentPubkey: string | null,
setArticleOverridesById: React.Dispatch<React.SetStateAction<Map<string, Article>>>
): () => Promise<void> {
return async (): Promise<void> => {
const result = await submitEdit()
if (result && draft) {
const updated = buildUpdatedArticle(draft, currentPubkey ?? '', result.articleId)
setArticleOverridesById((prev) => {
const next = new Map(prev)
next.set(result.originalArticleId, { ...updated, id: result.originalArticleId })
return next
})
}
}
}
function buildUpdatedArticle(draft: ArticleDraft, pubkey: string, newId: string): Article {
const parts = newId.split('_')
const hash = parts[0] ?? ''
const index = Number.parseInt(parts[1] ?? '0', 10)
const version = Number.parseInt(parts[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 } : {}),
}
}