mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore(UI): moar raw
This commit is contained in:
parent
82cf351920
commit
78ff8b537b
@ -85,140 +85,112 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-border rounded-none p-2 sm:p-4 bg-transparent">
|
||||
<div className="flex flex-col sm:flex-row gap-2 sm:gap-4">
|
||||
<div className="flex flex-row sm:flex-col items-center justify-between sm:justify-start gap-2 sm:gap-2 w-full sm:w-auto border-b sm:border-b-0 sm:border-r border-border/60 pb-2 sm:pb-0 sm:pr-4">
|
||||
<div className="flex flex-row sm:flex-col items-center gap-2">
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
userUpvoted
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground hover:text-primary'
|
||||
}`}
|
||||
onClick={() => handleVoteComment(true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote ? 'Upvote comment' : permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
<ArrowUp className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
<span className="text-xs sm:text-sm font-semibold text-foreground min-w-[20px] sm:min-w-[24px] text-center">{score}</span>
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
userDownvoted
|
||||
? 'text-blue-400'
|
||||
: 'text-muted-foreground hover:text-blue-400'
|
||||
}`}
|
||||
onClick={() => handleVoteComment(false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote
|
||||
? 'Downvote comment'
|
||||
: permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
<ArrowDown className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{commentVotePending && (
|
||||
<span className="text-[9px] sm:text-[10px] text-yellow-400 sm:mt-1 whitespace-nowrap">
|
||||
syncing…
|
||||
</span>
|
||||
)}
|
||||
<div className="border-b border-border/20 py-3 pl-3 pr-2">
|
||||
<div className="flex gap-3">
|
||||
{/* Vote column */}
|
||||
<div className="flex flex-col items-center gap-0.5 text-xs min-w-[40px]">
|
||||
<button
|
||||
className={`hover:text-primary ${
|
||||
userUpvoted ? 'text-primary' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={() => handleVoteComment(true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote ? 'Upvote comment' : permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
<span className={`font-mono text-xs ${score > 0 ? 'text-primary' : score < 0 ? 'text-red-400' : 'text-muted-foreground'}`}>
|
||||
{score}
|
||||
</span>
|
||||
<button
|
||||
className={`hover:text-blue-400 ${
|
||||
userDownvoted ? 'text-blue-400' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={() => handleVoteComment(false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote
|
||||
? 'Downvote comment'
|
||||
: permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
▼
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 mb-2">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs text-muted-foreground">
|
||||
<AuthorDisplay
|
||||
address={comment.author}
|
||||
className="text-[10px] sm:text-xs truncate"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span className="hidden sm:inline">•</span>
|
||||
<Clock className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="normal-case tracking-normal text-foreground text-[10px] sm:text-xs">
|
||||
{formatDistanceToNow(new Date(comment.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
<PendingBadge id={comment.id} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
||||
<ShareButton
|
||||
size="sm"
|
||||
url={`${window.location.origin}/post/${postId}#comment-${comment.id}`}
|
||||
title={
|
||||
comment.content.substring(0, 50) +
|
||||
(comment.content.length > 50 ? '...' : '')
|
||||
}
|
||||
/>
|
||||
<BookmarkButton
|
||||
isBookmarked={isBookmarked}
|
||||
loading={bookmarkLoading}
|
||||
onClick={handleBookmark}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0 text-xs">
|
||||
{/* Metadata */}
|
||||
<div className="flex flex-wrap items-center gap-1 text-[11px] text-muted-foreground mb-2">
|
||||
<AuthorDisplay
|
||||
address={comment.author}
|
||||
className="text-[11px]"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span>·</span>
|
||||
<span className="text-muted-foreground/80">
|
||||
{formatDistanceToNow(new Date(comment.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
{commentVotePending && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="text-yellow-400 text-[10px]">syncing</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-xs sm:text-sm break-words mb-2 sm:mb-3 prose prose-invert max-w-none">
|
||||
{/* Content */}
|
||||
<div className="text-xs text-foreground leading-relaxed mb-2 prose prose-invert max-w-none">
|
||||
<MarkdownRenderer content={comment.content} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2">
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-3 text-[11px] text-muted-foreground">
|
||||
<button
|
||||
onClick={handleBookmark}
|
||||
disabled={bookmarkLoading}
|
||||
className="hover:underline"
|
||||
>
|
||||
{isBookmarked ? 'unsave' : 'save'}
|
||||
</button>
|
||||
<ShareButton
|
||||
size="sm"
|
||||
url={`${window.location.origin}/post/${postId}#comment-${comment.id}`}
|
||||
title={
|
||||
comment.content.substring(0, 50) +
|
||||
(comment.content.length > 50 ? '...' : '')
|
||||
}
|
||||
/>
|
||||
{canModerate && !isModerated && !isOwnComment && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 sm:h-7 sm:w-7 text-muted-foreground hover:text-orange-500 touch-manipulation"
|
||||
onClick={() => onModerateComment(comment.id)}
|
||||
>
|
||||
<MessageSquareX className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Moderate comment</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<button
|
||||
onClick={() => onModerateComment(comment.id)}
|
||||
className="hover:underline text-orange-400"
|
||||
title="Moderate comment"
|
||||
>
|
||||
moderate
|
||||
</button>
|
||||
)}
|
||||
{canModerate && isModerated && !isOwnComment && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 sm:h-7 px-2 text-[10px] sm:text-[11px] text-muted-foreground hover:text-green-500 touch-manipulation"
|
||||
onClick={() => onUnmoderateComment?.(comment.id)}
|
||||
>
|
||||
Unmoderate
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Unmoderate comment</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<button
|
||||
onClick={() => onUnmoderateComment?.(comment.id)}
|
||||
className="hover:underline text-green-400"
|
||||
title="Unmoderate comment"
|
||||
>
|
||||
unmoderate
|
||||
</button>
|
||||
)}
|
||||
{cellId && canModerate && !isOwnComment && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 sm:h-7 sm:w-7 text-muted-foreground hover:text-red-500 touch-manipulation"
|
||||
onClick={() => onModerateUser(comment.author)}
|
||||
>
|
||||
<UserX className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Moderate user</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<button
|
||||
onClick={() => onModerateUser(comment.author)}
|
||||
className="hover:underline text-red-400"
|
||||
title="Moderate user"
|
||||
>
|
||||
ban user
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -48,7 +48,7 @@ const Footer: React.FC = () => {
|
||||
</nav>
|
||||
|
||||
<div className="text-[9px] sm:text-[10px] uppercase tracking-[0.2em] sm:tracking-[0.3em] text-muted-foreground text-center">
|
||||
Licensed under CC-BY-SA
|
||||
Reference client · build your own with @opchan/core
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -345,6 +345,21 @@ const Header = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Builder Banner */}
|
||||
<div className="mt-1 mb-1 flex items-center justify-between gap-2 text-[10px] sm:text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
|
||||
<span className="truncate">
|
||||
Reference client on top of @opchan/core. UI is intentionally bare.
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/waku-org/opchan"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0 text-primary hover:underline"
|
||||
>
|
||||
Build your own →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Navigation Bar (Desktop) */}
|
||||
<div className="hidden md:flex items-center justify-center border-t border-border py-2">
|
||||
<nav className="flex items-center space-x-0.5 text-[11px] uppercase tracking-[0.2em]">
|
||||
|
||||
@ -72,135 +72,99 @@ const PostCard: React.FC<PostCardProps> = ({ post }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="thread-card mb-2">
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
{/* Voting column */}
|
||||
<div className="flex sm:flex-col flex-row items-center justify-between sm:justify-start gap-2 sm:gap-2 p-2 sm:p-2 border-b sm:border-b-0 sm:border-r border-border/60 bg-transparent sm:w-auto w-full">
|
||||
<div className="flex sm:flex-col items-center gap-2">
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
userUpvoted
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground hover:text-primary'
|
||||
}`}
|
||||
onClick={e => handleVote(e, true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={permissions.canVote ? 'Upvote' : permissions.reasons.vote}
|
||||
>
|
||||
<ArrowUp className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
|
||||
<span className="text-sm font-semibold text-foreground min-w-[24px] text-center">
|
||||
{score}
|
||||
</span>
|
||||
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
userDownvoted
|
||||
? 'text-blue-400'
|
||||
: 'text-muted-foreground hover:text-blue-400'
|
||||
}`}
|
||||
onClick={e => handleVote(e, false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={permissions.canVote ? 'Downvote' : permissions.reasons.vote}
|
||||
>
|
||||
<ArrowDown className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
</div>
|
||||
{isPending && (
|
||||
<span className="text-[9px] sm:text-[10px] text-yellow-400 sm:mt-1">syncing…</span>
|
||||
)}
|
||||
<div className="border-b border-border/30 py-3 px-2 hover:bg-border/5">
|
||||
<div className="flex gap-3">
|
||||
{/* Vote column - compact */}
|
||||
<div className="flex flex-col items-center gap-0.5 text-xs min-w-[40px]">
|
||||
<button
|
||||
className={`hover:text-primary ${
|
||||
userUpvoted ? 'text-primary' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={e => handleVote(e, true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={permissions.canVote ? 'Upvote' : permissions.reasons.vote}
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
<span className={`font-mono text-xs ${score > 0 ? 'text-primary' : score < 0 ? 'text-red-400' : 'text-muted-foreground'}`}>
|
||||
{score}
|
||||
</span>
|
||||
<button
|
||||
className={`hover:text-blue-400 ${
|
||||
userDownvoted ? 'text-blue-400' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={e => handleVote(e, false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={permissions.canVote ? 'Downvote' : permissions.reasons.vote}
|
||||
>
|
||||
▼
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content column */}
|
||||
<div className="flex-1 p-2 sm:p-3 min-w-0">
|
||||
<div className="space-y-3">
|
||||
{/* Post metadata */}
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2 text-[10px] sm:text-[11px] uppercase tracking-[0.1em] sm:tracking-[0.12em] text-muted-foreground">
|
||||
<Link
|
||||
to={cellName ? `/cell/${post.cellId}` : '#'}
|
||||
className="text-primary hover:underline truncate"
|
||||
tabIndex={0}
|
||||
onClick={e => {
|
||||
if (!cellName) e.preventDefault();
|
||||
}}
|
||||
title={cellName ? `Go to /${cellName}` : undefined}
|
||||
>
|
||||
r/{cellName || 'unknown'}
|
||||
</Link>
|
||||
<span className="hidden sm:inline">•</span>
|
||||
<span className="hidden sm:inline">Posted by u/</span>
|
||||
<span className="sm:hidden">u/</span>
|
||||
<AuthorDisplay
|
||||
address={post.author}
|
||||
className="text-[10px] sm:text-xs truncate"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span className="opacity-50 hidden sm:inline">/</span>
|
||||
<span className="normal-case tracking-normal text-foreground text-[10px] sm:text-[11px]">
|
||||
{formatDistanceToNow(new Date(post.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
{'relevanceScore' in post &&
|
||||
typeof (post as Post).relevanceScore === 'number' && (
|
||||
<>
|
||||
<span className="opacity-50 hidden sm:inline">/</span>
|
||||
<RelevanceIndicator
|
||||
score={(post as Post).relevanceScore as number}
|
||||
details={
|
||||
'relevanceDetails' in post
|
||||
? (post as Post).relevanceDetails
|
||||
: undefined
|
||||
}
|
||||
type="post"
|
||||
className="text-[10px] sm:text-[11px]"
|
||||
showTooltip={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0 text-xs">
|
||||
{/* Title */}
|
||||
<Link to={`/post/${post.id}`} className="block mb-1">
|
||||
<h2 className="text-sm font-semibold text-foreground hover:underline break-words">
|
||||
{post.title}
|
||||
</h2>
|
||||
</Link>
|
||||
|
||||
{/* Post title and content - clickable to navigate to post */}
|
||||
<div className="block">
|
||||
<Link to={`/post/${post.id}`} className="block">
|
||||
<h2 className="text-sm sm:text-base font-semibold text-foreground break-words">
|
||||
{post.title}
|
||||
</h2>
|
||||
</Link>
|
||||
{/* Metadata line */}
|
||||
<div className="flex flex-wrap items-center gap-1 text-[11px] text-muted-foreground mb-2">
|
||||
<Link
|
||||
to={cellName ? `/cell/${post.cellId}` : '#'}
|
||||
className="text-primary hover:underline"
|
||||
onClick={e => {
|
||||
if (!cellName) e.preventDefault();
|
||||
}}
|
||||
>
|
||||
r/{cellName}
|
||||
</Link>
|
||||
<span>·</span>
|
||||
<AuthorDisplay
|
||||
address={post.author}
|
||||
className="text-[11px]"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span>·</span>
|
||||
<span className="text-muted-foreground/80">
|
||||
{formatDistanceToNow(new Date(post.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
{isPending && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="text-yellow-400 text-[10px]">syncing</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Post content preview */}
|
||||
<p className="text-muted-foreground text-xs sm:text-sm leading-relaxed mt-1 sm:mt-2 break-words">
|
||||
<LinkRenderer text={contentPreview} />
|
||||
</p>
|
||||
</div>
|
||||
{/* Content preview */}
|
||||
{contentPreview && (
|
||||
<p className="text-muted-foreground text-xs leading-relaxed mb-2">
|
||||
<LinkRenderer text={contentPreview} />
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Post actions */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 sm:gap-4 text-[10px] sm:text-[11px] uppercase tracking-[0.12em] sm:tracking-[0.15em] text-muted-foreground mt-2 sm:mt-3">
|
||||
<div className="flex items-center flex-wrap gap-2 sm:gap-4">
|
||||
<div className="flex items-center space-x-1 hover:text-foreground">
|
||||
<MessageSquare className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
<span>{commentCount} comments</span>
|
||||
</div>
|
||||
{isPending && (
|
||||
<span className="px-1.5 sm:px-2 py-0.5 border border-yellow-500 text-yellow-400 text-[9px] sm:text-[10px]">
|
||||
syncing…
|
||||
</span>
|
||||
)}
|
||||
<ShareButton
|
||||
size="sm"
|
||||
url={`${window.location.origin}/post/${post.id}`}
|
||||
title={post.title}
|
||||
/>
|
||||
</div>
|
||||
<BookmarkButton
|
||||
isBookmarked={isBookmarked}
|
||||
loading={bookmarkLoading}
|
||||
onClick={handleBookmark}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-3 text-[11px] text-muted-foreground">
|
||||
<Link to={`/post/${post.id}`} className="hover:underline">
|
||||
{commentCount} {commentCount === 1 ? 'reply' : 'replies'}
|
||||
</Link>
|
||||
<button
|
||||
onClick={handleBookmark}
|
||||
disabled={bookmarkLoading}
|
||||
className="hover:underline"
|
||||
>
|
||||
{isBookmarked ? 'unsave' : 'save'}
|
||||
</button>
|
||||
<ShareButton
|
||||
size="sm"
|
||||
url={`${window.location.origin}/post/${post.id}`}
|
||||
title={post.title}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -151,67 +151,98 @@ const PostDetail = () => {
|
||||
return (
|
||||
<div className="container mx-auto px-3 sm:px-4 py-4 sm:py-6 max-w-5xl">
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<Button
|
||||
<button
|
||||
onClick={() => navigate(`/cell/${post.cellId}`)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="mb-3 sm:mb-4 text-[10px] sm:text-[11px] px-2 sm:px-3"
|
||||
className="mb-3 text-xs text-primary hover:underline"
|
||||
>
|
||||
<ArrowLeft className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
||||
<span className="hidden sm:inline">Back to /{cell?.name || 'cell'}/</span>
|
||||
<span className="sm:hidden">BACK</span>
|
||||
</Button>
|
||||
← r/{cell?.name || 'cell'}
|
||||
</button>
|
||||
|
||||
<div className="border border-border rounded-none p-2 sm:p-3 mb-4 sm:mb-6">
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-3">
|
||||
<div className="flex flex-row sm:flex-col items-center justify-between sm:justify-start gap-2 sm:gap-1 w-full sm:w-auto border-b sm:border-b-0 sm:border-r border-border/60 pb-3 sm:pb-0 sm:pr-3">
|
||||
<div className="flex flex-row sm:flex-col items-center gap-2 sm:gap-1">
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
isPostUpvoted
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground hover:text-primary'
|
||||
}`}
|
||||
onClick={() => handleVotePost(true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote ? 'Upvote post' : permissions.reasons.vote
|
||||
}
|
||||
<div className="border-b border-border/50 pb-4 mb-4">
|
||||
<div className="flex gap-3">
|
||||
{/* Vote column */}
|
||||
<div className="flex flex-col items-center gap-0.5 text-xs min-w-[40px]">
|
||||
<button
|
||||
className={`hover:text-primary ${
|
||||
isPostUpvoted ? 'text-primary' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={() => handleVotePost(true)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote ? 'Upvote post' : permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
<span className={`font-mono text-xs ${score > 0 ? 'text-primary' : score < 0 ? 'text-red-400' : 'text-muted-foreground'}`}>
|
||||
{score}
|
||||
</span>
|
||||
<button
|
||||
className={`hover:text-blue-400 ${
|
||||
isPostDownvoted ? 'text-blue-400' : 'text-muted-foreground'
|
||||
}`}
|
||||
onClick={() => handleVotePost(false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote
|
||||
? 'Downvote post'
|
||||
: permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
▼
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0 text-xs">
|
||||
{/* Title */}
|
||||
<h1 className="text-base sm:text-lg font-bold text-foreground mb-2">{post.title}</h1>
|
||||
|
||||
{/* Metadata */}
|
||||
<div className="flex flex-wrap items-center gap-1 text-[11px] text-muted-foreground mb-3">
|
||||
<Link
|
||||
to={cell?.id ? `/cell/${cell.id}` : "#"}
|
||||
className="text-primary hover:underline"
|
||||
onClick={e => {
|
||||
if (!cell?.id) e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<ArrowUp className="w-4 h-4 sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
<span className="text-sm font-semibold text-foreground min-w-[24px] text-center">{score}</span>
|
||||
<button
|
||||
className={`p-1.5 sm:p-1 border border-transparent hover:border-border touch-manipulation ${
|
||||
isPostDownvoted
|
||||
? 'text-blue-400'
|
||||
: 'text-muted-foreground hover:text-blue-400'
|
||||
}`}
|
||||
onClick={() => handleVotePost(false)}
|
||||
disabled={!permissions.canVote}
|
||||
title={
|
||||
permissions.canVote
|
||||
? 'Downvote post'
|
||||
: permissions.reasons.vote
|
||||
}
|
||||
>
|
||||
<ArrowDown className="w-4 h-4 sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{postVotePending && (
|
||||
<span className="text-[9px] sm:text-[10px] text-yellow-400 sm:mt-0.5 whitespace-nowrap">
|
||||
syncing…
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-row sm:flex-col items-center gap-1 sm:gap-1 mt-0 sm:mt-1">
|
||||
<BookmarkButton
|
||||
isBookmarked={isBookmarked}
|
||||
loading={bookmarkLoading}
|
||||
onClick={handleBookmark}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
showText={false}
|
||||
r/{cell?.name || 'unknown'}
|
||||
</Link>
|
||||
<span>·</span>
|
||||
<AuthorDisplay
|
||||
address={post.author}
|
||||
className="text-[11px]"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span>·</span>
|
||||
<span className="text-muted-foreground/80">
|
||||
{formatDistanceToNow(new Date(post.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
{postPending && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="text-yellow-400 text-[10px]">syncing</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="text-xs sm:text-sm text-foreground leading-relaxed mb-3 prose prose-invert max-w-none">
|
||||
<MarkdownRenderer content={post.content} />
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-3 text-[11px] text-muted-foreground">
|
||||
<button
|
||||
onClick={handleBookmark}
|
||||
disabled={bookmarkLoading}
|
||||
className="hover:underline"
|
||||
>
|
||||
{isBookmarked ? 'unsave' : 'save'}
|
||||
</button>
|
||||
<ShareButton
|
||||
size="sm"
|
||||
url={`${window.location.origin}/post/${post.id}`}
|
||||
@ -219,82 +250,32 @@ const PostDetail = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs text-muted-foreground mb-1.5 sm:mb-2">
|
||||
<Link
|
||||
to={cell?.id ? `/cell/${cell.id}` : "#"}
|
||||
className="font-medium text-primary hover:underline focus:underline truncate"
|
||||
tabIndex={0}
|
||||
onClick={e => {
|
||||
if (!cell?.id) e.preventDefault();
|
||||
}}
|
||||
title={cell?.name ? `Go to /${cell.name}` : undefined}
|
||||
>
|
||||
r/{cell?.name || 'unknown'}
|
||||
</Link>
|
||||
<span className="hidden sm:inline">•</span>
|
||||
<span className="hidden sm:inline">Posted by u/</span>
|
||||
<span className="sm:hidden">u/</span>
|
||||
<AuthorDisplay
|
||||
address={post.author}
|
||||
className="text-[10px] sm:text-xs truncate"
|
||||
showBadge={false}
|
||||
/>
|
||||
<span className="hidden sm:inline">•</span>
|
||||
<Clock className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="normal-case tracking-normal text-foreground text-[10px] sm:text-xs">
|
||||
{formatDistanceToNow(new Date(post.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
{/* Relevance details unavailable in raw PostMessage; skip indicator */}
|
||||
{postPending && (
|
||||
<>
|
||||
<span className="hidden sm:inline">•</span>
|
||||
<span className="px-1.5 sm:px-2 py-0.5 border border-yellow-500 text-yellow-400 text-[9px] sm:text-[10px]">
|
||||
syncing…
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h1 className="text-lg sm:text-xl md:text-2xl font-bold break-words text-foreground mb-2 sm:mb-3">{post.title}</h1>
|
||||
<div className="text-xs sm:text-sm break-words prose prose-invert max-w-none">
|
||||
<MarkdownRenderer content={post.content} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comment Form */}
|
||||
{permissions.canComment && (
|
||||
<div className="mb-6 sm:mb-8">
|
||||
<div className="mb-6">
|
||||
<form onSubmit={handleCreateComment} onKeyDown={handleKeyDown}>
|
||||
<h2 className="text-xs sm:text-sm font-bold mb-2 sm:mb-3 flex items-center gap-1 uppercase tracking-[0.15em] sm:tracking-[0.2em]">
|
||||
<MessageCircle className="w-3 h-3 sm:w-4 sm:h-4 flex-shrink-0" />
|
||||
<span>Add a comment</span>
|
||||
</h2>
|
||||
<div className="text-xs font-semibold mb-2 text-foreground">Reply:</div>
|
||||
<MarkdownInput
|
||||
placeholder="What are your thoughts?"
|
||||
value={newComment}
|
||||
onChange={setNewComment}
|
||||
disabled={false}
|
||||
minHeight={100}
|
||||
initialHeight={120}
|
||||
minHeight={80}
|
||||
initialHeight={100}
|
||||
maxHeight={600}
|
||||
/>
|
||||
<div className="mt-2 sm:mt-3 flex justify-end">
|
||||
<Button
|
||||
<div className="mt-2 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!permissions.canComment}
|
||||
className="text-primary border-primary hover:bg-primary/10 text-[10px] sm:text-[11px] px-3 sm:px-4"
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
<Send className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
||||
<span className="hidden sm:inline">Post Comment</span>
|
||||
<span className="sm:hidden">POST</span>
|
||||
</Button>
|
||||
post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -308,30 +289,22 @@ const PostDetail = () => {
|
||||
)}
|
||||
|
||||
{!permissions.canComment && (
|
||||
<div className="mb-4 sm:mb-6 p-3 sm:p-4 border border-border rounded-none bg-transparent text-center">
|
||||
<p className="text-xs sm:text-sm mb-2 sm:mb-3 text-muted-foreground">Connect your wallet to comment</p>
|
||||
<Button asChild size="sm" className="text-[10px] sm:text-[11px] px-3 sm:px-4">
|
||||
<Link to="/">Connect Wallet</Link>
|
||||
</Button>
|
||||
<div className="mb-4 p-3 border-t border-b border-border/30 text-xs text-muted-foreground">
|
||||
<Link to="/" className="text-primary hover:underline">Connect wallet</Link> to comment
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Comments */}
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
<h2 className="text-base sm:text-lg font-bold flex items-center gap-2 uppercase tracking-[0.2em] sm:tracking-[0.25em]">
|
||||
<MessageCircle className="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" />
|
||||
<span>Comments ({visibleComments.length})</span>
|
||||
</h2>
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-semibold mb-3 text-foreground">
|
||||
{visibleComments.length} {visibleComments.length === 1 ? 'reply' : 'replies'}
|
||||
</div>
|
||||
|
||||
{visibleComments.length === 0 ? (
|
||||
<div className="text-center py-6 sm:py-8">
|
||||
<MessageCircle className="w-10 h-10 sm:w-12 sm:h-12 mx-auto mb-3 sm:mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-base sm:text-lg font-bold mb-2 uppercase tracking-[0.2em] sm:tracking-[0.25em]">No comments yet</h3>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground px-4">
|
||||
{permissions.canComment
|
||||
? 'Be the first to share your thoughts!'
|
||||
: 'Connect your wallet to join the conversation.'}
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground py-4">
|
||||
{permissions.canComment
|
||||
? 'No replies yet'
|
||||
: 'Connect your wallet to reply'}
|
||||
</div>
|
||||
) : (
|
||||
visibleComments.map(comment => (
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import { cn } from '../../utils';
|
||||
import { useToast } from '../ui/use-toast';
|
||||
|
||||
@ -15,23 +13,10 @@ interface ShareButtonProps {
|
||||
|
||||
export function ShareButton({
|
||||
url,
|
||||
size = 'sm',
|
||||
variant = 'ghost',
|
||||
className,
|
||||
showText = false,
|
||||
}: ShareButtonProps) {
|
||||
const { toast } = useToast();
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'h-8 w-10 flex-shrink-0',
|
||||
lg: 'h-10 whitespace-nowrap px-4',
|
||||
};
|
||||
|
||||
const iconSize = {
|
||||
sm: 14,
|
||||
lg: 18,
|
||||
};
|
||||
|
||||
const handleShare = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -39,8 +24,8 @@ export function ShareButton({
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast({
|
||||
title: 'Link copied!',
|
||||
description: 'Link has been copied to your clipboard.',
|
||||
title: 'Link copied',
|
||||
description: 'Link copied to clipboard',
|
||||
});
|
||||
} catch {
|
||||
// Fallback for older browsers
|
||||
@ -52,26 +37,19 @@ export function ShareButton({
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
toast({
|
||||
title: 'Link copied!',
|
||||
description: 'Link has been copied to your clipboard.',
|
||||
title: 'Link copied',
|
||||
description: 'Link copied to clipboard',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
size={size}
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className={cn(
|
||||
sizeClasses[size],
|
||||
'transition-colors duration-200 text-cyber-neutral hover:text-cyber-light',
|
||||
className
|
||||
)}
|
||||
className={cn('hover:underline', className)}
|
||||
title="Copy link"
|
||||
>
|
||||
<Share2 size={iconSize[size]} />
|
||||
{showText && <span className="ml-2 text-xs">Share</span>}
|
||||
</Button>
|
||||
share
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
35
app/src/components/ui/greentext-renderer.tsx
Normal file
35
app/src/components/ui/greentext-renderer.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
interface GreentextRendererProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders text with greentext support (lines starting with > are colored green)
|
||||
* 4chan-style formatting
|
||||
*/
|
||||
export const GreentextRenderer: React.FC<GreentextRendererProps> = ({
|
||||
text,
|
||||
className = ''
|
||||
}) => {
|
||||
const lines = text.split('\n');
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{lines.map((line, index) => {
|
||||
const isGreentext = line.trim().startsWith('>');
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={isGreentext ? 'text-green-400' : ''}
|
||||
>
|
||||
{line || '\u00A0'} {/* Non-breaking space for empty lines */}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,7 +9,8 @@ interface MarkdownRendererProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders sanitized Markdown with GFM support.
|
||||
* Renders sanitized Markdown with GFM support and 4chan-style greentext.
|
||||
* Lines starting with > are rendered in green.
|
||||
*/
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
||||
content,
|
||||
@ -45,6 +46,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
||||
'td',
|
||||
'a',
|
||||
'img',
|
||||
'span',
|
||||
],
|
||||
attributes: {
|
||||
...defaultSchema.attributes,
|
||||
@ -61,16 +63,53 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
||||
['title'],
|
||||
],
|
||||
code: [...(defaultSchema.attributes?.code || []), ['className']],
|
||||
span: [['className']],
|
||||
},
|
||||
};
|
||||
|
||||
// Preprocess content to wrap greentext lines (lines starting with >) in special markers
|
||||
// We'll handle this by checking for lines that start with > but aren't markdown quotes (>>)
|
||||
const processedContent = content
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
// Check if line starts with > but not >> (markdown quote)
|
||||
// and not a quote block (which would be > followed by space typically)
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('>') && !trimmedLine.startsWith('>>')) {
|
||||
// Wrap in a span with greentext class
|
||||
return `<span class="greentext">${line}</span>`;
|
||||
}
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[[rehypeSanitize, schema]]}
|
||||
components={{
|
||||
p: ({ children, ...props }) => {
|
||||
// Check if this paragraph contains greentext
|
||||
const childText = String(children);
|
||||
if (childText.trim().startsWith('>')) {
|
||||
return (
|
||||
<p {...props} className="text-green-400 my-1">
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return <p {...props}>{children}</p>;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content || ''}
|
||||
</ReactMarkdown>
|
||||
<style>{`
|
||||
.greentext {
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -100,14 +100,14 @@
|
||||
}
|
||||
|
||||
.text-glow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.25em;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.text-glow-subtle {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.01em;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-between border border-border px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground transition-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-60;
|
||||
@apply inline-flex items-center justify-between border border-border px-3 py-2 text-xs font-normal text-foreground transition-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-60;
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,11 +143,11 @@
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@apply text-sm sm:text-base font-semibold uppercase tracking-[0.25em] sm:tracking-[0.3em] text-foreground mb-1;
|
||||
@apply text-sm sm:text-base font-semibold text-foreground mb-1;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
@apply text-[10px] sm:text-xs uppercase tracking-[0.15em] sm:tracking-[0.2em] text-muted-foreground;
|
||||
@apply text-[10px] sm:text-xs text-muted-foreground;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
@ -159,7 +159,7 @@
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
@apply border-t border-border py-2 sm:py-3 text-center text-[10px] uppercase tracking-[0.2em] text-muted-foreground px-3;
|
||||
@apply border-t border-border py-2 sm:py-3 text-center text-[10px] text-muted-foreground px-3;
|
||||
}
|
||||
|
||||
/* Card Components */
|
||||
@ -219,7 +219,7 @@
|
||||
|
||||
/* Button Variants */
|
||||
.btn-cyber {
|
||||
@apply border border-border bg-transparent text-foreground uppercase tracking-[0.2em] text-[11px] hover:bg-white/10;
|
||||
@apply border border-border bg-transparent text-foreground text-xs hover:bg-white/10;
|
||||
}
|
||||
|
||||
.btn-cyber-outline {
|
||||
@ -263,7 +263,7 @@
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
@apply text-base sm:text-lg md:text-xl font-mono font-bold text-white mb-2 tracking-[0.2em] sm:tracking-[0.25em];
|
||||
@apply text-base sm:text-lg md:text-xl font-mono font-bold text-white mb-2;
|
||||
}
|
||||
|
||||
.empty-state-description {
|
||||
@ -272,7 +272,7 @@
|
||||
}
|
||||
|
||||
.cyberpunk-glow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
@ -150,12 +150,13 @@ const FeedPage: React.FC = () => {
|
||||
No posts yet
|
||||
</h3>
|
||||
<p className="empty-state-description">
|
||||
Be the first to create a post in a cell!
|
||||
This is a reference UI built on top of @opchan/core.
|
||||
Swap this screen with your own feed layout.
|
||||
</p>
|
||||
{verificationStatus !==
|
||||
EVerificationStatus.ENS_VERIFIED && (
|
||||
<p className="text-xs sm:text-sm text-cyber-neutral/80">
|
||||
Connect your wallet to start posting
|
||||
Connect your wallet to try the reference client – or fork it and build your own.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
88
package-lock.json
generated
88
package-lock.json
generated
@ -8014,17 +8014,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/core": {
|
||||
"version": "0.0.40-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.40-ff9c430.0.tgz",
|
||||
"integrity": "sha512-+ZY3OgAcIvazZrwX31lh3RDDtye/m4+WPyqNqrMEG+Ybd88ZJH8svdF9BxldPOltDBkM/5h2bsWoon6jLuGefw==",
|
||||
"version": "0.0.41-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.41-339e26e.0.tgz",
|
||||
"integrity": "sha512-SgD/ne6F9ib8P71xUsohSdHAIAcU6HyQGvyzPKJXu60szLbh6dBXb/nouln9druEzxjULOYosBYMSP9cv5feFA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@libp2p/ping": "2.0.35",
|
||||
"@noble/hashes": "^1.3.2",
|
||||
"@waku/enr": "0.0.34-ff9c430.0",
|
||||
"@waku/interfaces": "0.0.35-ff9c430.0",
|
||||
"@waku/proto": "0.0.15-ff9c430.0",
|
||||
"@waku/utils": "0.0.28-ff9c430.0",
|
||||
"@waku/enr": "0.0.34-339e26e.0",
|
||||
"@waku/interfaces": "0.0.35-339e26e.0",
|
||||
"@waku/proto": "0.0.16-339e26e.0",
|
||||
"@waku/utils": "0.0.28-339e26e.0",
|
||||
"debug": "^4.3.4",
|
||||
"it-all": "^3.0.4",
|
||||
"it-length-prefixed": "^9.0.4",
|
||||
@ -8079,16 +8079,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/discovery": {
|
||||
"version": "0.0.13-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.13-ff9c430.0.tgz",
|
||||
"integrity": "sha512-FGmiaXawE9JxP2VTwVbFC8Qiafh8p1t4ahcDu6Rq0aWh5mNS0qVZj0vDCUMy3HjB9CtHO6+M6g/1u6FPYd9mXQ==",
|
||||
"version": "0.0.14-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.14-339e26e.0.tgz",
|
||||
"integrity": "sha512-MKknncK6gs/Y+g58tFxkifbOITLvy+RvLHn/eLnXQXLX0r+hs6j820wTd3HivsqlZ9xXjbxI5h/Mvus9Ycefrw==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@waku/core": "0.0.40-ff9c430.0",
|
||||
"@waku/enr": "0.0.34-ff9c430.0",
|
||||
"@waku/interfaces": "0.0.35-ff9c430.0",
|
||||
"@waku/proto": "0.0.15-ff9c430.0",
|
||||
"@waku/utils": "0.0.28-ff9c430.0",
|
||||
"@waku/core": "0.0.41-339e26e.0",
|
||||
"@waku/enr": "0.0.34-339e26e.0",
|
||||
"@waku/interfaces": "0.0.35-339e26e.0",
|
||||
"@waku/proto": "0.0.16-339e26e.0",
|
||||
"@waku/utils": "0.0.28-339e26e.0",
|
||||
"debug": "^4.3.4",
|
||||
"dns-over-http-resolver": "^3.0.8",
|
||||
"hi-base32": "^0.5.1",
|
||||
@ -8099,9 +8099,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/enr": {
|
||||
"version": "0.0.34-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.34-ff9c430.0.tgz",
|
||||
"integrity": "sha512-bJc3IJ9b17B4nAa8S6n4a66fvx2eblyaS0MtzGqEcTTtEr+c//GScH8Xm/2oZ4bdyf/dm6iP1/ZeApvD9Wo+XQ==",
|
||||
"version": "0.0.34-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.34-339e26e.0.tgz",
|
||||
"integrity": "sha512-AVo2Eg30mid1tb+ba+0ivrVLfZpuQvkDrR0gtKJhCCpDQ+8Dg7RFlqBZvD3FQyU7nj5jtwXVP+p0rMf94F2wwg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ethersproject/rlp": "^5.7.0",
|
||||
@ -8109,7 +8109,7 @@
|
||||
"@libp2p/peer-id": "5.1.7",
|
||||
"@multiformats/multiaddr": "^12.0.0",
|
||||
"@noble/secp256k1": "^1.7.1",
|
||||
"@waku/utils": "0.0.28-ff9c430.0",
|
||||
"@waku/utils": "0.0.28-339e26e.0",
|
||||
"debug": "^4.3.4",
|
||||
"js-sha3": "^0.9.2"
|
||||
},
|
||||
@ -8153,18 +8153,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/interfaces": {
|
||||
"version": "0.0.35-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.35-ff9c430.0.tgz",
|
||||
"integrity": "sha512-m6az/AVkCF7nSlZsxKtRgPmyPVEjKg6LREnOWNIyAA2uCvesx00JxEN7DhkmgST+wNrH5+1LgoyE6mXTcSDqTw==",
|
||||
"version": "0.0.35-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.35-339e26e.0.tgz",
|
||||
"integrity": "sha512-7if75dK/RFF13ey9v/1gnrvR/WHZ3JogCmhWGtFp3q34cA1cyfHu7l66eGarVVHbwdSgBSVSH6fM8YFMsoacDA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/proto": {
|
||||
"version": "0.0.15-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.15-ff9c430.0.tgz",
|
||||
"integrity": "sha512-JuPcioC2ry7do5Sa2TABjaJ4uQ4+jAbaZsjiir5OKmZI5krCv1BqKoZehY+BaX1lMerDfrGAXMpNQHjykfbthw==",
|
||||
"version": "0.0.16-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.16-339e26e.0.tgz",
|
||||
"integrity": "sha512-jPHKCBt1HkBHenXO2kbRADkwqYbgVDdGalTxkHwNrWFFVK8pRTgG5VAqtSmw6yiba87P5ErstuKrDnO8ycFRjA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"protons-runtime": "^5.4.0"
|
||||
@ -8174,9 +8174,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/sdk": {
|
||||
"version": "0.0.36-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.36-ff9c430.0.tgz",
|
||||
"integrity": "sha512-BO6svBNw1B+HeCioKDYeFsmsxhBHzbLkxPYqVMm8FDTngmZGj1dywli4YDMm8cgwWux+Kp9TcoDEkqvKgE0ctw==",
|
||||
"version": "0.0.37-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.37-339e26e.0.tgz",
|
||||
"integrity": "sha512-rIoJjoFYinY8u4OHl4jdoCD9mAhVLklLm9WxWELp8nry9mWCoST68MJ4wFvV6ZMJPXwALf/Fx5g3Pz2DiweK4g==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-noise": "16.1.3",
|
||||
@ -8187,12 +8187,12 @@
|
||||
"@libp2p/websockets": "9.2.16",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@waku/core": "0.0.40-ff9c430.0",
|
||||
"@waku/discovery": "0.0.13-ff9c430.0",
|
||||
"@waku/interfaces": "0.0.35-ff9c430.0",
|
||||
"@waku/proto": "0.0.15-ff9c430.0",
|
||||
"@waku/sds": "0.0.8-ff9c430.0",
|
||||
"@waku/utils": "0.0.28-ff9c430.0",
|
||||
"@waku/core": "0.0.41-339e26e.0",
|
||||
"@waku/discovery": "0.0.14-339e26e.0",
|
||||
"@waku/interfaces": "0.0.35-339e26e.0",
|
||||
"@waku/proto": "0.0.16-339e26e.0",
|
||||
"@waku/sds": "0.0.9-339e26e.0",
|
||||
"@waku/utils": "0.0.28-339e26e.0",
|
||||
"libp2p": "2.8.11",
|
||||
"lodash.debounce": "^4.0.8"
|
||||
},
|
||||
@ -8201,15 +8201,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/sds": {
|
||||
"version": "0.0.8-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/sds/-/sds-0.0.8-ff9c430.0.tgz",
|
||||
"integrity": "sha512-st2/2QeId6kFyK53L3aV4cNTyYCfrM7As77k21nlgo0PEHKHyPPFuoIkZUwRIWO6qM9ohXgiIU3RVTioEtZuTA==",
|
||||
"version": "0.0.9-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/sds/-/sds-0.0.9-339e26e.0.tgz",
|
||||
"integrity": "sha512-eEd8Co++8ayCid6XEQ2ex55SoMxiRJ6ZvWLz0GjhUKGhApjXZlU6cnQ8nnRpPnVCCuRvS2EU1oo8JCOHBduZHQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@libp2p/interface": "2.10.4",
|
||||
"@noble/hashes": "^1.7.1",
|
||||
"@waku/proto": "0.0.15-ff9c430.0",
|
||||
"@waku/utils": "0.0.28-ff9c430.0",
|
||||
"@waku/proto": "0.0.16-339e26e.0",
|
||||
"@waku/utils": "0.0.28-339e26e.0",
|
||||
"chai": "^5.1.2",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
@ -8233,13 +8233,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/utils": {
|
||||
"version": "0.0.28-ff9c430.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.28-ff9c430.0.tgz",
|
||||
"integrity": "sha512-6tBSQ/X+vzoLV3R573c80XLESZq4qWWSlyXtG4se/+RanQU1om9cSkAzrAlm9ZhO6IcrpLlNgeOsv/dIUIz+Fw==",
|
||||
"version": "0.0.28-339e26e.0",
|
||||
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.28-339e26e.0.tgz",
|
||||
"integrity": "sha512-lzFcCN8xj3IN6JwbUdH3zc9FLwS6UQu775zP+RM8PnR5bMNHED8dlKR1fovZXfoggCIU+KnFwgITH+HhBEcV9w==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.2",
|
||||
"@waku/interfaces": "0.0.35-ff9c430.0",
|
||||
"@waku/interfaces": "0.0.35-339e26e.0",
|
||||
"chai": "^4.3.10",
|
||||
"debug": "^4.3.4",
|
||||
"uint8arrays": "^5.0.1"
|
||||
@ -19470,7 +19470,7 @@
|
||||
"dependencies": {
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@waku/sdk": "0.0.36-ff9c430.0",
|
||||
"@waku/sdk": "0.0.37-339e26e.0",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"uuid": "^11.1.0",
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"dependencies": {
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@waku/sdk": "0.0.36-ff9c430.0",
|
||||
"@waku/sdk": "0.0.37-339e26e.0",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"uuid": "^11.1.0",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user