Skip to content
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
001fb30
Create developer-spotlight-codytseng-jumblesocial.mdx
Kokkomaki May 25, 2026
7d9dae7
date typo
Kokkomaki May 25, 2026
3c3e18b
fix(blog): update Cody Tseng frontmatter
dergigi May 25, 2026
9a8e151
fix(blog): update Cody Tseng publish date
dergigi May 25, 2026
a8d2517
style(blog): wrap Cody Tseng spotlight prose lines
dergigi May 25, 2026
e5ad48e
fix(blog): restore blockquote markers in Cody Tseng spotlight
dergigi May 25, 2026
4b92764
feat(blog): add Cody Tseng spotlight images
dergigi May 25, 2026
446f565
fix(blog): re-export Cody Tseng spotlight images at higher quality
dergigi May 25, 2026
87abafc
fix: remove "on Nostr"
dergigi May 25, 2026
1c5fdd7
Merge branch 'developer-spotlight-codytseng-jumblesocial' of https://…
dergigi May 25, 2026
29126bb
fix(blog): add footer links to Cody Tseng spotlight
dergigi May 25, 2026
94c5e99
fix(blog): point spotlight series links to /spotlight
dergigi May 25, 2026
a742e92
fix(blog): reword Cody Tseng spotlight footer CTA
dergigi May 25, 2026
2a9f7b4
fix(blog): link OpenSats grant in Cody Tseng spotlight footer
dergigi May 25, 2026
e010e70
fix(blog): use relative path for Damus link in Cody Tseng spotlight
dergigi May 25, 2026
f436c3b
feat(blog): add Jumble screenshot to Cody Tseng spotlight
dergigi May 25, 2026
de22698
fix(blog): update Jumble screenshot in Cody Tseng spotlight
dergigi May 25, 2026
c1e8b50
feat(blog): add theme-aware Jumble screenshot to Cody Tseng spotlight
dergigi May 25, 2026
7fbe4e5
refactor(blog): rename Jumble screenshots to match site convention
dergigi May 25, 2026
b2ec681
Fixing few loose ends
Kokkomaki May 25, 2026
1a466fa
feat(blog): replace Cody Tseng spotlight footer rule with separator i…
dergigi May 25, 2026
911dfb2
Merge branch 'developer-spotlight-codytseng-jumblesocial' of https://…
dergigi May 25, 2026
b2d9575
Revert "feat(blog): replace Cody Tseng spotlight footer rule with sep…
dergigi May 25, 2026
ba664b1
fix(blog): correct typo in Cody Tseng fiatjaf quote
dergigi May 25, 2026
a8e089a
content(blog): add Jumble desktop screenshots to Cody spotlight
dergigi May 25, 2026
860205f
content(blog): move Jumble desktop screenshot to article closing
dergigi May 25, 2026
050e9c2
content(blog): add nostr-relay-tray screenshots to Cody spotlight
dergigi May 25, 2026
77e2172
content(blog): move Jumble desktop screenshot after walled gardens pa…
dergigi May 25, 2026
7aa0725
feat(blog): add sidebar pull quotes to spotlight posts (#794)
dergigi May 25, 2026
03e03d1
fix(blog): restore spotlight footer below article on mobile
dergigi May 25, 2026
6dfdbed
style(blog): tighten spotlight pull quote range to 20–80%
dergigi May 28, 2026
81ab9cf
style(blog): tighten spotlight pull quote range to 20–70%
dergigi May 28, 2026
cc0a3f8
style(blog): tighten spotlight pull quote range to 20–75%
dergigi May 28, 2026
610f036
style(blog): restore spotlight pull quote range to 20–80%
dergigi May 28, 2026
3b97dbd
style(blog): widen spotlight pull quote range to 20–85%
dergigi May 28, 2026
2e9bcca
style(blog): restore spotlight pull quote range to 20–90%
dergigi May 28, 2026
a2cfe32
content(blog): add opening pull quote to Kurt Unger spotlight
dergigi May 28, 2026
18323de
style(blog): widen spotlight pull quote range to 15–90%
dergigi May 28, 2026
4b32536
style(blog): adjust spotlight pull quote range to 15–87%
dergigi May 28, 2026
766b823
style(blog): adjust spotlight pull quote range to 15–89%
dergigi May 28, 2026
a40460a
content(blog): replace abrupt pivot with counterweight line in Cody s…
dergigi May 28, 2026
98e1ae8
minor tweaks based on comments
Kokkomaki May 30, 2026
705e489
put date to june first
Kokkomaki May 31, 2026
d3372b8
style(blog): remove italic formatting from closing paragraphs
dergigi Jun 1, 2026
360985a
style(blog): rewrap closing paragraphs
dergigi Jun 1, 2026
0099e12
edit(blog): move grant link to date, drop "one year"
dergigi Jun 1, 2026
7a42217
fix(blog): link grant to thirteenth wave announcement
dergigi Jun 1, 2026
f0793d5
edit(blog): drop superfluous "supporting Jumble's development"
dergigi Jun 1, 2026
5474996
fix(blog): note both grants, link first to twelfth wave
dergigi Jun 1, 2026
c43ac84
style(blog): use straight quotes and apostrophes
dergigi Jun 1, 2026
c73b3a7
style(blog): rename grant link ref to second-grant
dergigi Jun 1, 2026
397cfaa
fix(blog): attribute funding to The Nostr Fund
dergigi Jun 1, 2026
902c1d4
edit(blog): reword grant intro to "grantee since June 2025"
dergigi Jun 1, 2026
f7f8784
style(blog): rewrap grant paragraph
dergigi Jun 1, 2026
d8307a4
chore(blog): bump publication date to 2026-06-02
dergigi Jun 1, 2026
07ead96
Add browser frame mockup for screenshots (#812)
dergigi Jun 1, 2026
464c3a4
fix: apply suggestion from Tuma
dergigi Jun 2, 2026
9b83bd6
fix(spotlight): rephrase firewall line
dergigi Jun 2, 2026
2ae9d37
fix(spotlight): use present tense title
dergigi Jun 2, 2026
82ad41d
Update data/blog/developer-spotlight-codytseng-jumblesocial.mdx
dergigi Jun 2, 2026
aecd3d3
Update data/blog/developer-spotlight-codytseng-jumblesocial.mdx
dergigi Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions components/BrowserFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ReactNode } from 'react'

const BrowserFrame = ({
url,
children,
}: {
url?: string
children: ReactNode
}) => {
return (
<div className="my-16 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2 border-b border-gray-200 bg-gray-100 px-3 py-2 dark:border-gray-700 dark:bg-gray-800">
<span className="h-3 w-3 rounded-full bg-[#ff5f57]" />
<span className="h-3 w-3 rounded-full bg-[#febc2e]" />
<span className="h-3 w-3 rounded-full bg-[#28c840]" />
{url && (
<div className="ml-3 flex-1 truncate rounded-md bg-white px-3 py-1 text-xs text-gray-500 dark:bg-gray-900 dark:text-gray-400">
{url}
</div>
)}
</div>
<div className="[&_img]:my-0">{children}</div>
</div>
)
}

export default BrowserFrame
2 changes: 2 additions & 0 deletions components/MDXComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Pre } from 'pliny/ui/Pre'
import { BlogNewsletterForm } from 'pliny/ui/NewsletterForm'

import Image from './Image'
import BrowserFrame from './BrowserFrame'
import VideoPlayer from './VideoPlayer'
import CustomLink from './Link'
import DonateToGeneralFundForm from './DonateToGeneralFundForm'
Expand Down Expand Up @@ -33,6 +34,7 @@ export const Wrapper = ({ layout, content, ...rest }: MDXLayout) => {

export const MDXComponents: ComponentMap = {
Image,
BrowserFrame,
VideoPlayer,
TOCInline,
a: CustomLink,
Expand Down
256 changes: 143 additions & 113 deletions components/post/PostArticleBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Image from '@/components/Image'
import Tag from '@/components/Tag'
import siteMetadata from '@/data/siteMetadata'
import { discussUrl, editUrl } from '@/components/post/postShared'
import SpotlightPullQuotes from '@/components/post/SpotlightPullQuotes'

interface Props {
content: CoreContent<Blog>
Expand Down Expand Up @@ -37,14 +38,15 @@ const postArticleBodyClasses = {
spotlight: {
wrapper:
'grid-rows-[auto_1fr] divide-y divide-gray-200 pb-8 dark:divide-gray-700 min-[1000px]:grid min-[1000px]:grid-cols-4 min-[1000px]:gap-x-6 min-[1000px]:divide-y-0',
authorBlock:
'pb-10 pt-6 min-[1000px]:border-b min-[1000px]:border-gray-200 min-[1000px]:pt-11 min-[1000px]:dark:border-gray-700',
sidebar:
'min-[1000px]:col-start-1 min-[1000px]:row-span-2 min-[1000px]:flex min-[1000px]:min-h-0 min-[1000px]:flex-col',
authorBlock: 'pb-10 pt-6 min-[1000px]:pt-11',
authorList:
'justify-start min-[1000px]:block min-[1000px]:space-x-0 min-[1000px]:space-y-8',
contentBlock:
'divide-y divide-gray-200 dark:divide-gray-700 min-[1000px]:col-span-3 min-[1000px]:row-span-2 min-[1000px]:pb-0',
footerBlock:
'divide-gray-200 text-sm font-medium leading-5 dark:divide-gray-700 min-[1000px]:col-start-1 min-[1000px]:row-start-2 min-[1000px]:divide-y',
'divide-gray-200 text-sm font-medium leading-5 dark:divide-gray-700 min-[1000px]:divide-y',
tagsBlock: 'py-4 min-[1000px]:py-8',
paginationBlock:
'flex justify-between py-4 min-[1000px]:block min-[1000px]:space-y-8 min-[1000px]:py-8',
Expand All @@ -60,130 +62,158 @@ export default function PostArticleBody({
children,
spotlight = false,
}: Props) {
const { filePath, path, slug, tags } = content
const { filePath, path, slug, tags, pullQuotes } = content
const basePath = path.split('/')[0]
const [loadComments, setLoadComments] = useState(false)
const classes = spotlight
? postArticleBodyClasses.spotlight
: postArticleBodyClasses.default

return (
<div className={classes.wrapper}>
<dl className={classes.authorBlock}>
<dt className="sr-only">Authors</dt>
<dd>
<ul className={`flex flex-wrap gap-4 ${classes.authorList}`}>
{authorDetails.map((author) => (
<li className="flex items-center space-x-2" key={author.name}>
{author.avatar && (
<Link href={`/about/${author.slug}`}>
<Image
src={author.avatar}
width={38}
height={38}
alt="avatar"
className="h-10 w-10 rounded-full"
/>
</Link>
)}
<dl className="whitespace-nowrap text-sm font-medium leading-5">
<dt className="sr-only">Name</dt>
<dd className="text-gray-900 dark:text-gray-100">
{author.name}
</dd>
<dt className="sr-only">Twitter</dt>
<dd>
{author.nym && (
<Link
href={`/about/${author.slug}`}
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
>
@{author.nym}
</Link>
)}
</dd>
</dl>
</li>
))}
</ul>
</dd>
</dl>
<div className={classes.contentBlock}>
<div className="prose max-w-none pb-8 pt-10 dark:prose-dark">
{children}
</div>
<div className="pb-6 pt-6 text-sm text-gray-700 dark:text-gray-300">
<Link href={discussUrl()} rel="nofollow">
Discuss on nostr
</Link>
{` • `}
<Link href={editUrl(filePath)}>View on GitHub</Link>
</div>
{siteMetadata.comments && (
<div
className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300"
id="comment"
>
{!loadComments && (
<button onClick={() => setLoadComments(true)}>
Load Comments
</button>
const authorBlock = (
<dl className={classes.authorBlock}>
<dt className="sr-only">Authors</dt>
<dd>
<ul className={`flex flex-wrap gap-4 ${classes.authorList}`}>
{authorDetails.map((author) => (
<li className="flex items-center space-x-2" key={author.name}>
{author.avatar && (
<Link href={`/about/${author.slug}`}>
<Image
src={author.avatar}
width={38}
height={38}
alt="avatar"
className="h-10 w-10 rounded-full"
/>
</Link>
)}
<dl className="whitespace-nowrap text-sm font-medium leading-5">
<dt className="sr-only">Name</dt>
<dd className="text-gray-900 dark:text-gray-100">
{author.name}
</dd>
<dt className="sr-only">Twitter</dt>
<dd>
{author.nym && (
<Link
href={`/about/${author.slug}`}
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
>
@{author.nym}
</Link>
)}
</dd>
</dl>
</li>
))}
</ul>
</dd>
</dl>
)

const sidebarFooter = (
<footer>
<div className={classes.footerBlock}>
{tags && (
<div className={classes.tagsBlock}>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Tags
</h2>
<div className="flex flex-wrap">
{tags.map((tag) => (
<Tag key={tag} text={tag} />
))}
</div>
</div>
)}
{(next || prev) && (
<div className={classes.paginationBlock}>
{prev && (
<div>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Previous Post
</h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
<Link href={`/${prev.path}`}>{prev.title}</Link>
</div>
</div>
)}
{loadComments && (
<Comments commentsConfig={siteMetadata.comments} slug={slug} />
{next && (
<div>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Next Post
</h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
<Link href={`/${next.path}`}>{next.title}</Link>
</div>
</div>
)}
</div>
)}
</div>
<footer>
<div className={classes.footerBlock}>
{tags && (
<div className={classes.tagsBlock}>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Tags
</h2>
<div className="flex flex-wrap">
{tags.map((tag) => (
<Tag key={tag} text={tag} />
))}
</div>
</div>
<div className={classes.backLinkBlock}>
<Link
href={`/${basePath}`}
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
aria-label="Back to the blog"
>
&larr; Back to the blog
</Link>
</div>
</footer>
)

const contentBlock = (
<div className={classes.contentBlock}>
<div className="prose max-w-none pb-8 pt-10 dark:prose-dark">
{children}
</div>
<div className="pb-6 pt-6 text-sm text-gray-700 dark:text-gray-300">
<Link href={discussUrl()} rel="nofollow">
Discuss on nostr
</Link>
{` • `}
<Link href={editUrl(filePath)}>View on GitHub</Link>
</div>
{siteMetadata.comments && (
<div
className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300"
id="comment"
>
{!loadComments && (
<button onClick={() => setLoadComments(true)}>Load Comments</button>
)}
{(next || prev) && (
<div className={classes.paginationBlock}>
{prev && (
<div>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Previous Post
</h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
<Link href={`/${prev.path}`}>{prev.title}</Link>
</div>
</div>
)}
{next && (
<div>
<h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Next Post
</h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
<Link href={`/${next.path}`}>{next.title}</Link>
</div>
</div>
)}
</div>
{loadComments && (
<Comments commentsConfig={siteMetadata.comments} slug={slug} />
)}
</div>
<div className={classes.backLinkBlock}>
<Link
href={`/${basePath}`}
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
aria-label="Back to the blog"
>
&larr; Back to the blog
</Link>
</div>
</footer>
)}
</div>
)

return (
<div className={classes.wrapper}>
{spotlight ? (
<>
<aside className={postArticleBodyClasses.spotlight.sidebar}>
{authorBlock}
<div className="hidden min-[1000px]:contents">
{sidebarFooter}
{pullQuotes && pullQuotes.length > 0 && (
<SpotlightPullQuotes quotes={pullQuotes} />
)}
</div>
</aside>
{contentBlock}
<div className="min-[1000px]:hidden">{sidebarFooter}</div>
</>
) : (
<>
{authorBlock}
{contentBlock}
{sidebarFooter}
</>
)}
</div>
)
}
Loading
Loading