blog post and other fixes/additions #124
@ -9,37 +9,41 @@ import { marked } from 'marked'
|
|||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import { ProfileSection } from 'components/ProfileSection'
|
import { ProfileSection } from 'components/ProfileSection'
|
||||||
import { Comments } from 'components/comment'
|
import { Comments } from 'components/comment'
|
||||||
import { Addressable, BlogDetails } from 'types'
|
import { Addressable, BlogPageLoaderResult } from 'types'
|
||||||
import placeholder from '../../assets/img/DEGMods Placeholder Img.png'
|
import placeholder from '../../assets/img/DEGMods Placeholder Img.png'
|
||||||
import { PublishDetails } from 'components/Internal/PublishDetails'
|
import { PublishDetails } from 'components/Internal/PublishDetails'
|
||||||
import { Interactions } from 'components/Internal/Interactions'
|
import { Interactions } from 'components/Internal/Interactions'
|
||||||
|
import { BlogCard } from 'components/BlogCard'
|
||||||
|
|
||||||
export const BlogPage = () => {
|
export const BlogPage = () => {
|
||||||
const data = useLoaderData() as Partial<BlogDetails>
|
const { blog, latest } = useLoaderData() as BlogPageLoaderResult
|
||||||
const [commentCount, setCommentCount] = useState(0)
|
const [commentCount, setCommentCount] = useState(0)
|
||||||
const html = marked.parse(data?.content || '', { async: false })
|
const html = marked.parse(blog?.content || '', { async: false })
|
||||||
const sanitized = DOMPurify.sanitize(html)
|
const sanitized = DOMPurify.sanitize(html)
|
||||||
const editor = useEditor({
|
const editor = useEditor(
|
||||||
content: sanitized,
|
{
|
||||||
extensions: [
|
content: sanitized,
|
||||||
StarterKit,
|
extensions: [
|
||||||
Link,
|
StarterKit,
|
||||||
Image.configure({
|
Link,
|
||||||
inline: true,
|
Image.configure({
|
||||||
HTMLAttributes: {
|
inline: true,
|
||||||
class: 'IBMSMSMBSSPostImg'
|
HTMLAttributes: {
|
||||||
}
|
class: 'IBMSMSMBSSPostImg'
|
||||||
})
|
}
|
||||||
],
|
})
|
||||||
editable: false
|
],
|
||||||
})
|
editable: false
|
||||||
|
},
|
||||||
|
[sanitized]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='InnerBodyMain'>
|
<div className='InnerBodyMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
||||||
<div className='IBMSMSplitMain'>
|
<div className='IBMSMSplitMain'>
|
||||||
{!data ? (
|
{!blog ? (
|
||||||
<LoadingSpinner desc={'Loading...'} />
|
<LoadingSpinner desc={'Loading...'} />
|
||||||
) : (
|
) : (
|
||||||
<div className='IBMSMSplitMainBigSide'>
|
<div className='IBMSMSplitMainBigSide'>
|
||||||
@ -67,27 +71,27 @@ export const BlogPage = () => {
|
|||||||
className='IBMSMSMBSSPostPicture'
|
className='IBMSMSMBSSPostPicture'
|
||||||
style={{
|
style={{
|
||||||
background: `url("${
|
background: `url("${
|
||||||
data.image !== '' ? data.image : placeholder
|
blog.image !== '' ? blog.image : placeholder
|
||||||
}") center / cover no-repeat`
|
}") center / cover no-repeat`
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
<div className='IBMSMSMBSSPostInside'>
|
<div className='IBMSMSMBSSPostInside'>
|
||||||
<div className='IBMSMSMBSSPostTitle'>
|
<div className='IBMSMSMBSSPostTitle'>
|
||||||
<h1 className='IBMSMSMBSSPostTitleHeading'>
|
<h1 className='IBMSMSMBSSPostTitleHeading'>
|
||||||
{data.title}
|
{blog.title}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className='IBMSMSMBSSPostBody'>
|
<div className='IBMSMSMBSSPostBody'>
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
</div>
|
</div>
|
||||||
<div className='IBMSMSMBSSTags'>
|
<div className='IBMSMSMBSSTags'>
|
||||||
{data.nsfw && (
|
{blog.nsfw && (
|
||||||
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagNSFW'>
|
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagNSFW'>
|
||||||
<p>NSFW</p>
|
<p>NSFW</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.tTags &&
|
{blog.tTags &&
|
||||||
data.tTags.map((t) => (
|
blog.tTags.map((t) => (
|
||||||
<a key={t} className='IBMSMSMBSSTagsTag'>
|
<a key={t} className='IBMSMSMBSSTagsTag'>
|
||||||
{t}
|
{t}
|
||||||
</a>
|
</a>
|
||||||
@ -96,48 +100,38 @@ export const BlogPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Interactions
|
<Interactions
|
||||||
addressable={data as Addressable}
|
addressable={blog as Addressable}
|
||||||
commentCount={commentCount}
|
commentCount={commentCount}
|
||||||
/>
|
/>
|
||||||
<PublishDetails
|
<PublishDetails
|
||||||
published_at={data.published_at || 0}
|
published_at={blog.published_at || 0}
|
||||||
edited_at={data.edited_at || 0}
|
edited_at={blog.edited_at || 0}
|
||||||
site={data.rTag || 'N/A'}
|
site={blog.rTag || 'N/A'}
|
||||||
/>
|
/>
|
||||||
{/* <div className="IBMSMSplitMainBigSideSec">
|
{!!latest.length && (
|
||||||
<div className="IBMSMSMBSSPostsWrapper">
|
<div className='IBMSMSplitMainBigSideSec'>
|
||||||
<h4 className="IBMSMSMBSSPostsTitle">Latest POSTER-NAME Posts</h4>
|
<div className='IBMSMSMBSSPostsWrapper'>
|
||||||
<div className="IBMSMList IBMSMListAlt"><a className="cardBlogMainWrapperLink" href="blog-inner.html">
|
<h4 className='IBMSMSMBSSPostsTitle'>
|
||||||
<div className="cardBlogMain" style="background: url("https://nichegamer.com/wp-content/uploads/2023/01/onimai-01-07-2023.jpg") center / cover no-repeat;">
|
Latest blog posts
|
||||||
<div className="cardBlogMainInside" style="background: linear-gradient(rgba(255,255,255,0) 0%, #232323 100%);">
|
</h4>
|
||||||
<h3 style="display: -webkit-box;-webkit-box-orient: vertical;overflow: hidden;-webkit-line-clamp: 2;font-size: 20px;line-height: 1.5;color: rgba(255,255,255,0.75);text-shadow: 0 0 8px rgba(0,0,0,0.25);">This is a blog title, the best blog title in the world!</h3>
|
<div className='IBMSMList IBMSMListAlt'>
|
||||||
</div>
|
{latest.map((b) => (
|
||||||
</div>
|
<BlogCard key={b.id} {...b} />
|
||||||
</a><a className="cardBlogMainWrapperLink" href="blog-inner.html">
|
))}
|
||||||
<div className="cardBlogMain" style="background: url("https://pbs.twimg.com/media/GDrRJOOXYAAeysT.jpg:large") center / cover no-repeat;">
|
</div>
|
||||||
<div className="cardBlogMainInside" style="background: linear-gradient(rgba(255,255,255,0) 0%, #232323 100%);">
|
|
||||||
<h3 style="display: -webkit-box;-webkit-box-orient: vertical;overflow: hidden;-webkit-line-clamp: 2;font-size: 20px;line-height: 1.5;color: rgba(255,255,255,0.75);text-shadow: 0 0 8px rgba(0,0,0,0.25);">This is a blog title, the best blog title in the world!</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a><a className="cardBlogMainWrapperLink" href="blog-inner.html">
|
|
||||||
<div className="cardBlogMain" style="background: url("assets/img/DEGMods%20Placeholder%20Img.png") center / cover no-repeat;">
|
|
||||||
<div className="cardBlogMainInside" style="background: linear-gradient(rgba(255,255,255,0) 0%, #232323 100%);">
|
|
||||||
<h3 style="display: -webkit-box;-webkit-box-orient: vertical;overflow: hidden;-webkit-line-clamp: 2;font-size: 20px;line-height: 1.5;color: rgba(255,255,255,0.75);text-shadow: 0 0 8px rgba(0,0,0,0.25);">This is a blog title, the best blog title in the world!</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div>
|
||||||
|
)}
|
||||||
<div className='IBMSMSplitMainBigSideSec'>
|
<div className='IBMSMSplitMainBigSideSec'>
|
||||||
<Comments
|
<Comments
|
||||||
addressable={data as Addressable}
|
addressable={blog as Addressable}
|
||||||
setCommentCount={setCommentCount}
|
setCommentCount={setCommentCount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!data?.author && <ProfileSection pubkey={data.author} />}
|
{!!blog?.author && <ProfileSection pubkey={blog.author} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { filterForEventsTaggingId } from '@nostr-dev-kit/ndk'
|
import { filterForEventsTaggingId, NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
import { NDKContextType } from 'contexts/NDKContext'
|
import { NDKContextType } from 'contexts/NDKContext'
|
||||||
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { appRoutes } from 'routes'
|
import { appRoutes } from 'routes'
|
||||||
import { log, LogType } from 'utils'
|
import { BlogPageLoaderResult, FilterOptions, NSFWFilter } from 'types'
|
||||||
import { extractBlogDetails } from 'utils/blog'
|
import {
|
||||||
|
DEFAULT_FILTER_OPTIONS,
|
||||||
|
getLocalStorageItem,
|
||||||
|
log,
|
||||||
|
LogType
|
||||||
|
} from 'utils'
|
||||||
|
import { extractBlogCardDetails, extractBlogDetails } from 'utils/blog'
|
||||||
|
|
||||||
export const blogRouteLoader =
|
export const blogRouteLoader =
|
||||||
(ndkContext: NDKContextType) =>
|
(ndkContext: NDKContextType) =>
|
||||||
@ -15,21 +22,86 @@ export const blogRouteLoader =
|
|||||||
return redirect(appRoutes.blogs)
|
return redirect(appRoutes.blogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode author from naddr
|
||||||
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
|
const { pubkey } = decoded.data
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Get the filter with #a from naddr for the main blog content
|
||||||
const filter = filterForEventsTaggingId(naddr)
|
const filter = filterForEventsTaggingId(naddr)
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
log(true, LogType.Error, 'Unable to create filter from blog naddr.')
|
log(true, LogType.Error, 'Unable to create filter from blog naddr.')
|
||||||
return redirect(appRoutes.blogs)
|
return redirect(appRoutes.blogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = await ndkContext.fetchEvent(filter)
|
// Get the blog filter options for latest blogs
|
||||||
if (!event) {
|
const filterOptions = JSON.parse(
|
||||||
log(true, LogType.Error, 'Unable to fetch the blog event.')
|
getLocalStorageItem('filter-blog', DEFAULT_FILTER_OPTIONS)
|
||||||
return null
|
) as FilterOptions
|
||||||
|
|
||||||
|
// Fetch 4 in case the current blog is included in the latest
|
||||||
|
const latestModsFilter: NDKFilter = {
|
||||||
|
authors: [pubkey],
|
||||||
|
kinds: [kinds.LongFormArticle],
|
||||||
|
limit: 4
|
||||||
|
}
|
||||||
|
// Add source filter
|
||||||
|
if (filterOptions.source === window.location.host) {
|
||||||
|
latestModsFilter['#r'] = [filterOptions.source]
|
||||||
|
}
|
||||||
|
// Filter by NSFW tag
|
||||||
|
// NSFWFilter.Show_NSFW -> filter not needed
|
||||||
|
// NSFWFilter.Only_NSFW -> true
|
||||||
|
// NSFWFilter.Hide_NSFW -> false
|
||||||
|
if (filterOptions.nsfw !== NSFWFilter.Show_NSFW) {
|
||||||
|
latestModsFilter['#nsfw'] = [
|
||||||
|
(filterOptions.nsfw === NSFWFilter.Only_NSFW).toString()
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const blogDetails = extractBlogDetails(event)
|
// Parallel fetch blog event and latest events
|
||||||
return blogDetails
|
const settled = await Promise.allSettled([
|
||||||
|
ndkContext.fetchEvent(filter),
|
||||||
|
ndkContext.fetchEvents(latestModsFilter)
|
||||||
|
])
|
||||||
|
|
||||||
|
const result: BlogPageLoaderResult = {
|
||||||
|
blog: undefined,
|
||||||
|
latest: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the blog event result
|
||||||
|
const fetchEventResult = settled[0]
|
||||||
|
if (fetchEventResult.status === 'fulfilled' && fetchEventResult.value) {
|
||||||
|
// Extract the blog details from the event
|
||||||
|
result.blog = extractBlogDetails(fetchEventResult.value)
|
||||||
|
} else if (fetchEventResult.status === 'rejected') {
|
||||||
|
log(
|
||||||
|
true,
|
||||||
|
LogType.Error,
|
||||||
|
'Unable to fetch the blog event.',
|
||||||
|
fetchEventResult.reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the lateast blog events
|
||||||
|
const fetchEventsResult = settled[1]
|
||||||
|
if (fetchEventsResult.status === 'fulfilled' && fetchEventsResult.value) {
|
||||||
|
// Extract the blog card details from the events
|
||||||
|
result.latest = fetchEventsResult.value
|
||||||
|
.map(extractBlogCardDetails)
|
||||||
|
.filter((b) => b.id !== result.blog?.id) // Filter out current blog if present
|
||||||
|
.slice(0, 3) // Take only three
|
||||||
|
} else if (fetchEventsResult.status === 'rejected') {
|
||||||
|
log(
|
||||||
|
true,
|
||||||
|
LogType.Error,
|
||||||
|
'Unable to fetch the latest blog events.',
|
||||||
|
fetchEventsResult.reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
true,
|
true,
|
||||||
|
@ -27,3 +27,8 @@ export interface BlogFormErrors extends Partial<BlogEventSubmitForm> {}
|
|||||||
export interface BlogCardDetails extends BlogDetails {
|
export interface BlogCardDetails extends BlogDetails {
|
||||||
naddr: string
|
naddr: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlogPageLoaderResult {
|
||||||
|
blog: Partial<BlogDetails> | undefined
|
||||||
|
latest: Partial<BlogDetails>[]
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user