{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "post",
  "type": "registry:block",
  "title": "Lens Post",
  "description": "Displays a post from Lens Social Protocol, including the content, author, attachments, and engagement actions.",
  "dependencies": ["@lens-protocol/react@0.0.0-canary-20250820102520", "lucide-react", "moment@2.30.1"],
  "registryDependencies": [
    "avatar",
    "button",
    "dialog",
    "dropdown-menu",
    "skeleton",
    "https://lensblocks.com/r/audio-player.json",
    "https://lensblocks.com/r/collect-dialog.json",
    "https://lensblocks.com/r/lens-markdown.json",
    "https://lensblocks.com/r/link-preview.json",
    "https://lensblocks.com/r/quote-dialog.json",
    "https://lensblocks.com/r/tip-dialog.json",
    "https://lensblocks.com/r/use-post-context.json",
    "https://lensblocks.com/r/use-reaction-toggle.json",
    "https://lensblocks.com/r/utils.json",
    "https://lensblocks.com/r/video-player.json"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/feed/lens-post.tsx",
      "content": "\"use client\";\n\nimport { MouseEvent, useEffect, useRef, useState } from \"react\";\nimport { Copy, Flag, MoreHorizontal, UserCircle2 } from \"lucide-react\";\nimport { Account, Post, TxHash, URI } from \"@lens-protocol/react\";\nimport moment from \"moment/moment\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/registry/new-york/ui/avatar\";\nimport { Dialog, DialogContent } from \"@/registry/new-york/ui/dialog\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { RegEx } from \"@/registry/new-york/lib/regex\";\nimport { getUsernamePath, parseUri, toApiError, truncateAddress } from \"@/registry/new-york/lib/lens-utils\";\nimport { LensMarkdown } from \"@/registry/new-york/components/common/lens-markdown\";\nimport { LensAudioPlayer } from \"@/registry/new-york/components/common/lens-audio-player\";\nimport { LensVideoPlayer } from \"@/registry/new-york/components/common/lens-video-player\";\nimport { LikeButton } from \"@/registry/new-york/components/feed/likes/like-button\";\nimport { ReferenceButton } from \"@/registry/new-york/components/feed/references/reference-button\";\nimport { CommentButton } from \"@/registry/new-york/components/feed/comment/comment-button\";\nimport { CollectButton } from \"@/registry/new-york/components/feed/collects/collect-button\";\nimport { TipButton } from \"@/registry/new-york/components/feed/tips/tip-button\";\nimport { BookmarkButton } from \"@/registry/new-york/components/feed/bookmarks/bookmark-button\";\nimport { CollectDialogRef, LensCollectDialog } from \"@/registry/new-york/blocks/feed/lens-collect-dialog\";\nimport { LensQuoteDialog, QuoteDialogRef } from \"@/registry/new-york/blocks/feed/lens-quote-dialog\";\nimport { LensTipDialog, TipDialogRef } from \"@/registry/new-york/blocks/common/lens-tip-dialog\";\nimport { LensImage } from \"@/registry/new-york/components/feed/lens-image\";\nimport { LinkPreview } from \"@/registry/new-york/components/feed/link-preview\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\nimport { Skeleton } from \"@/registry/new-york/ui/skeleton\";\nimport { ResultAsync } from \"neverthrow\";\n\ntype LensPostProps = {\n  /**\n   * Callback function that is called when the user clicks on a post.\n   */\n  onPostClick?: (post: Post) => void;\n\n  /**\n   * Callback function that is called when the user clicks on an account.\n   */\n  onAccountClick?: (account: Account) => void;\n\n  /**\n   * Callback function that is called when the post URL is copied to clipboard.\n   */\n  onPostUrlCopied?: () => void;\n\n  /**\n   * The URL pattern to use for generating post links if onPostClick is not provided.\n   * It should include `{slug}` as a placeholder for the post slug.\n   * If not provided, a default pattern (/posts/{slug}) will be used.\n   * * Example: `/posts/{slug}` or `https://example.com/posts/{slug}`\n   */\n  postUrlPattern?: string;\n\n  /**\n   * Callback function that is called when a repost is successful.\n   * It receives the transaction hash as an argument.\n   */\n  onRepostSuccess?: (post: Post, txHash: TxHash) => void;\n\n  /**\n   * Callback function that is called when a tip is successfully created.\n   * It receives the transaction hash as an argument.\n   */\n  onTipCreated?: (post: Post, txHash: TxHash) => void;\n\n  /**\n   * Callback function that is called when there is an error creating a tip.\n   * It receives the error object as an argument.\n   */\n  onTipError?: (post: Post, error: Error) => void;\n\n  /**\n   * Callback function that is called when a post bookmark status is successfully updated.\n   */\n  onBookmarkToggle?: (post: Post, bookmarked: boolean) => void;\n\n  /**\n   * Optional additional class names to apply to the post container.\n   */\n  className?: string;\n\n  /**\n   * Optional additional class names to apply to the post content.\n   */\n  contentClassName?: string;\n\n  /**\n   * Whether to show action buttons (like, comment, repost, etc.). Default is true.\n   */\n  showActions?: boolean;\n};\n\nexport const LensPost = (props: LensPostProps) => {\n  const {\n    onPostClick,\n    onAccountClick,\n    postUrlPattern = \"/posts/{slug}\",\n    onRepostSuccess,\n    onPostUrlCopied,\n    onTipCreated,\n    onTipError,\n    onBookmarkToggle,\n    className,\n    contentClassName,\n    showActions = true,\n  } = props;\n\n  const collectDialog = useRef<CollectDialogRef>(null);\n  const quoteDialog = useRef<QuoteDialogRef>(null);\n  const tipDialog = useRef<TipDialogRef>(null);\n\n  const [lightboxUri, setLightboxUri] = useState<string | null>(null);\n  const [lightboxOpen, setLightboxOpen] = useState(false);\n  const [urlInContent, setUrlInContent] = useState<string | null>(null);\n\n  const { post, loading, tip, sessionClient, optimistic } = useLensPostContext();\n\n  useEffect(() => {\n    if (!lightboxOpen) {\n      setLightboxUri(null);\n    }\n  }, [lightboxOpen]);\n\n  useEffect(() => {\n    if (!post) return;\n\n    const isPost = post.__typename === \"Post\";\n    const basePost = isPost ? post : post.repostOf;\n    let postMetadata = basePost.metadata;\n    if (postMetadata.__typename === \"UnknownPostMetadata\") {\n      return;\n    }\n\n    const checkContentForUrls = async () => {\n      const urlRegex = new RegExp(RegEx.URL);\n\n      const urls = postMetadata.content.match(urlRegex);\n      if (!urls?.length) return;\n\n      setUrlInContent(urls[0]);\n    };\n\n    checkContentForUrls();\n  }, [post]);\n\n  if (!post) {\n    if (loading) {\n      return <PostSkeleton className={className} />;\n    }\n    return null;\n  }\n\n  const isPost = post.__typename === \"Post\";\n  const basePost = isPost ? post : post.repostOf;\n  const postMetadata = basePost.metadata;\n  if (postMetadata.__typename === \"UnknownPostMetadata\") {\n    return <>Unsupported post type</>;\n  }\n\n  const author = basePost.author;\n  const authorName = author.metadata?.name ?? author.username?.localName ?? \"[anonymous]\";\n  const image = postMetadata.__typename === \"ImageMetadata\" ? postMetadata.image : null;\n  const audio = postMetadata.__typename === \"AudioMetadata\" ? postMetadata.audio : null;\n  const video = postMetadata.__typename === \"VideoMetadata\" ? postMetadata.video : null;\n  const link: URI | null = postMetadata.__typename === \"LinkMetadata\" ? postMetadata.sharingLink : null;\n\n  // function isVideoPlatformUrl(url: string): boolean {\n  //   const patterns = [\n  //     /^(https?:\\/\\/)?(www\\.)?(youtube\\.com|youtu\\.be)\\/.+$/, // YouTube\n  //     /^(https?:\\/\\/)?(www\\.)?vimeo\\.com\\/.+$/, // Vimeo\n  //     /^(https?:\\/\\/)?(www\\.)?twitch\\.tv\\/.+$/, // Twitch\n  //     /^(https?:\\/\\/)?(www\\.)?tiktok\\.com\\/.+$/, // TikTok\n  //   ];\n  //\n  //   return patterns.some(pattern => pattern.test(url));\n  // }\n  //\n  // const isVideoEmbed = videoUri ? isVideoPlatformUrl(videoUri) : false;\n\n  const collectAction =\n    post && \"actions\" in post && post.actions?.find(action => action.__typename === \"SimpleCollectAction\");\n\n  const onReportClick = () => {};\n\n  const onCopyClick = () => {\n    const postUrl = postUrlPattern\n      ? postUrlPattern.replace(\"{slug}\", basePost.slug)\n      : `${window.location.origin}/posts/${basePost.slug}`;\n    navigator.clipboard\n      .writeText(postUrl)\n      .then(onPostUrlCopied)\n      .catch(err => {\n        console.error(\"Failed to copy post link:\", err);\n      });\n  };\n\n  const handleAccountClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.stopPropagation();\n    if (onAccountClick) {\n      onAccountClick(author);\n    } else if (author.username) {\n      const path = getUsernamePath(author.username.value, author.username.namespace);\n      if (event.metaKey || event.ctrlKey || event.button === 1) {\n        window.open(path, \"_blank\");\n      } else {\n        window.open(path, \"_self\");\n      }\n    }\n  };\n\n  const handlePostClick = (event: MouseEvent<HTMLButtonElement>) => {\n    // Allow text selection\n    if (window.getSelection()?.toString()) {\n      return;\n    }\n\n    event.preventDefault();\n\n    // If it's a repost, we assume the user wants to see the original post\n    const postUrl = postUrlPattern ? postUrlPattern.replace(\"{slug}\", basePost.slug) : `/posts/${basePost.slug}`;\n\n    if (onPostClick) {\n      onPostClick(basePost);\n    } else if (event.metaKey || event.ctrlKey || event.button === 1) {\n      window.open(postUrl, \"_blank\");\n    } else {\n      window.open(postUrl, \"_self\");\n    }\n  };\n\n  return (\n    <>\n      <article\n        onClick={handlePostClick}\n        className={cn(\"w-full px-3 md:px-4 pt-3 md:pt-4 pb-2 flex flex-col gap-3 text-start cursor-pointer\", className)}\n      >\n        <div className=\"flex-grow flex justify-between flex-none\">\n          <div className=\"flex gap-2 w-full min-w-0\">\n            <button type=\"button\" onClick={handleAccountClick} className=\"cursor-pointer\">\n              <Avatar className=\"flex-none w-10 h-10\">\n                <AvatarImage src={parseUri(author.metadata?.picture)} alt={`${authorName}'s avatar`} />\n                <AvatarFallback>\n                  <UserCircle2 className=\"w-10 h-10 opacity-45\" />\n                </AvatarFallback>\n              </Avatar>\n            </button>\n            <div className=\"flex-grow flex flex-col min-w-0\">\n              <button type=\"button\" onClick={handleAccountClick} className=\"flex gap-1 w-full min-w-0\">\n                <span className=\"text-sm md:text-base font-semibold truncate cursor-pointer hover:underline\">\n                  {authorName}\n                </span>\n                {author.username?.localName ? (\n                  <span className=\"text-sm md:text-base text-muted-foreground truncate cursor-pointer hover:underline\">\n                    @{author.username.localName}\n                  </span>\n                ) : (\n                  <span className=\"text-sm md:text-base text-muted-foreground truncate cursor-pointer hover:underline\">\n                    {truncateAddress(author.address)}\n                  </span>\n                )}\n              </button>\n              <a\n                href={postUrlPattern.replace(\"{slug}\", basePost.slug)}\n                onClick={e => e.stopPropagation()}\n                className=\"contents\"\n              >\n                <abbr\n                  title={new Date(post.timestamp).toLocaleString()}\n                  className=\"text-xs opacity-65 no-underline hover:underline m-0 p-0\"\n                >\n                  {moment(post.timestamp).fromNow(true)}\n                </abbr>\n              </a>\n            </div>\n          </div>\n          {showActions && (\n            <div className=\"flex gap-2\">\n              <BookmarkButton className=\"md:hidden\" />\n              <DropdownMenu>\n                <DropdownMenuTrigger asChild>\n                  <Button\n                    variant=\"ghost\"\n                    className=\"w-8 h-8 active:outline-none focus-visible:outline-none hover:opacity-75 cursor-pointer rounded-full -me-2.5 md:me-0\"\n                  >\n                    <MoreHorizontal className=\"w-4 h-4 opacity-75\" />\n                  </Button>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent className=\"min-w-48\" side=\"bottom\">\n                  <DropdownMenuItem className=\"focus:outline-none p-0\">\n                    <button className=\"flex gap-2 items-center w-full p-2\" onClick={onReportClick} disabled={loading}>\n                      <Flag />\n                      Report post\n                    </button>\n                  </DropdownMenuItem>\n                  <DropdownMenuItem className=\"focus:outline-none p-0\">\n                    <button className=\"flex gap-2 items-center w-full p-2\" onClick={onCopyClick} disabled={loading}>\n                      <Copy className=\"w-4 h-4 inline\" />\n                      Copy link\n                    </button>\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n          )}\n        </div>\n        {postMetadata.content && (\n          <div className=\"min-h-12 flex items-center\">\n            <LensMarkdown\n              content={postMetadata.content}\n              mentions={basePost.mentions}\n              className={cn(\"text-sm md:text-base\", contentClassName)}\n            />\n          </div>\n        )}\n        {image && (\n          <LensImage\n            image={image}\n            alt={image?.altTag ?? \"\"}\n            className=\"w-full mt-2 border rounded-xl object-contain cursor-pointer\"\n            width={600}\n            height={400}\n            loading=\"lazy\"\n            onClick={event => {\n              event.stopPropagation();\n              setLightboxUri(image.item);\n              setLightboxOpen(true);\n            }}\n          />\n        )}\n        {audio && (\n          <LensAudioPlayer\n            audio={audio}\n            postTitle={\"title\" in postMetadata ? postMetadata.title : undefined}\n            onCoverClick={imageUri => {\n              setLightboxUri(imageUri);\n              setLightboxOpen(true);\n            }}\n          />\n        )}\n        {video && <LensVideoPlayer video={video} preload=\"metadata\" />}\n        {link && <LinkPreview url={link} />}\n        {urlInContent && !image && !audio && !video && !link && <LinkPreview url={urlInContent} />}\n        {showActions && (\n          <div className=\"w-full flex gap-4 md:gap-8 items-center justify-between\">\n            <div className=\"w-full flex items-center justify-between md:justify-normal gap-4 md:gap-6 -mx-2\">\n              <CommentButton onClick={() => undefined} />\n              <LikeButton />\n              <ReferenceButton onQuoteClick={() => quoteDialog.current?.open()} onRepostSuccess={onRepostSuccess} />\n              {collectAction && <CollectButton onClick={() => collectDialog.current?.open()} />}\n              <TipButton onClick={() => tipDialog.current?.open()} />\n            </div>\n            <BookmarkButton\n              className=\"hidden md:flex\"\n              onSuccess={(post: Post, bookmarked: boolean) => onBookmarkToggle?.(post, bookmarked)}\n            />\n          </div>\n        )}\n      </article>\n      {collectAction && <LensCollectDialog ref={collectDialog} />}\n      <LensQuoteDialog ref={quoteDialog} />\n      <LensTipDialog\n        sessionClient={sessionClient}\n        ref={tipDialog}\n        createTip={(source, amount, tokenAddress) =>\n          ResultAsync.fromPromise(tip(source, amount, tokenAddress), toApiError)\n        }\n        onTipCreated={txHash => onTipCreated?.(basePost, txHash)}\n        onTipError={error => onTipError?.(basePost, error)}\n      />\n      <Dialog open={lightboxOpen} onOpenChange={setLightboxOpen}>\n        <DialogContent className=\"flex justify-center items-center max-h-full max-w-full bg-transparent border-none shadow-none\">\n          {lightboxOpen && lightboxUri && (\n            <img\n              src={lightboxUri}\n              alt={\"Image attached to post\"}\n              className=\"max-w-full max-h-full object-contain shadow-lg\"\n              loading=\"lazy\"\n            />\n          )}\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n};\n\nconst PostSkeleton = ({ className }: { className?: string }) => {\n  return (\n    <div className={cn(\"w-full px-3 md:px-4 pt-3 md:pt-4 pb-2 flex flex-col gap-3\", className)}>\n      <div className=\"flex-grow flex justify-between flex-none\">\n        <div className=\"flex gap-2 w-full min-w-0\">\n          <Skeleton className=\"w-10 h-10 rounded-full\" />\n          <div className=\"flex-grow flex flex-col min-w-0 gap-1 justify-center\">\n            <div className=\"flex gap-1 w-full min-w-0 items-center\">\n              <Skeleton className=\"h-4 w-32 rounded-full\" />\n              <Skeleton className=\"h-4 w-20 rounded-full\" />\n            </div>\n            <Skeleton className=\"h-3 w-16 rounded-full\" />\n          </div>\n        </div>\n      </div>\n      <div className=\"flex flex-col gap-2 pt-2\">\n        <Skeleton className=\"w-11/12 h-4 rounded-full\" />\n        <Skeleton className=\"w-full h-4 rounded-full\" />\n        <Skeleton className=\"w-2/3 h-4 rounded-full\" />\n      </div>\n      <div className=\"w-full flex gap-4 md:gap-8 items-center justify-between pt-2\">\n        <div className=\"w-full flex items-center justify-between md:justify-normal gap-4 md:gap-8\">\n          <Skeleton className=\"w-6 h-6 rounded-full\" />\n          <Skeleton className=\"w-6 h-6 rounded-full\" />\n          <Skeleton className=\"w-6 h-6 rounded-full\" />\n          <Skeleton className=\"w-6 h-6 rounded-full\" />\n          <Skeleton className=\"w-6 h-6 rounded-full\" />\n        </div>\n        <Skeleton className=\"w-6 h-6 rounded-full\" />\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/likes/like-button.tsx",
      "content": "\"use client\";\n\nimport { MouseEvent } from \"react\";\nimport { Heart } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\nimport { cn } from \"@/lib/utils\";\n\ntype LikeButtonProps = {\n  className?: string;\n  onSuccess?: () => void;\n  onError?: (error: Error) => void;\n  showCount?: boolean;\n};\n\nexport const LikeButton = ({ className, onSuccess, onError, showCount = true }: LikeButtonProps) => {\n  const { post, loading: postLoading, toggleLike, optimistic } = useLensPostContext();\n\n  const operations = post && \"operations\" in post ? post.operations : null;\n\n  const onClick = async (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n\n    if (post?.__typename !== \"Post\") return;\n\n    try {\n      await toggleLike();\n      onSuccess?.();\n    } catch (e) {\n      if (onError) {\n        onError(e instanceof Error ? e : new Error(\"An unexpected error occurred while toggling like.\"));\n      } else {\n        console.error(\"An unexpected error occurred while toggling like:\", e);\n      }\n    }\n  };\n\n  return (\n    <div className=\"flex items-center\">\n      <Button\n        onClick={onClick}\n        disabled={postLoading}\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn(\"w-8 h-8 active:outline-none focus-visible:outline-none cursor-pointer rounded-full\", className)}\n      >\n        {optimistic.liked || operations?.hasUpvoted ? (\n          <Heart className=\"text-primary\" fill=\"var(--primary)\" />\n        ) : (\n          <Heart className=\"opacity-85\" />\n        )}\n      </Button>\n      {showCount && <span>{new Intl.NumberFormat().format(optimistic.likeCount)}</span>}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/references/reference-button.tsx",
      "content": "import {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport { Post, TxHash } from \"@lens-protocol/react\";\nimport { MouseEvent, useState } from \"react\";\nimport { CheckCircle, Loader, MessageCircle, Repeat2 } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\n\ntype ReferenceButtonProps = {\n  onQuoteClick: (post: Post) => void;\n  onRepostSuccess?: (post: Post, txHash: TxHash) => void;\n  onError?: (error: Error) => void;\n  showCount?: boolean;\n};\n\nexport const ReferenceButton = ({ onQuoteClick, onRepostSuccess, onError, showCount = true }: ReferenceButtonProps) => {\n  const { post, repost, loading: postLoading, optimistic } = useLensPostContext();\n\n  const [isDropdownMenuOpen, setIsDropdownMenuOpen] = useState(false);\n  const [isPosting, setIsPosting] = useState(false);\n  const [showSuccess, setShowSuccess] = useState(false);\n\n  const postToReference: Post | null | undefined = post?.__typename === \"Repost\" ? post.repostOf : post;\n\n  const onClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n    setIsDropdownMenuOpen(true);\n  };\n\n  const onRepostClick = async (event: MouseEvent<HTMLButtonElement>) => {\n    event.stopPropagation();\n    setIsPosting(true);\n    try {\n      const txHash = await repost();\n      if (postToReference && txHash) {\n        onRepostSuccess?.(postToReference, txHash);\n        setShowSuccess(true);\n        setTimeout(() => {\n          setShowSuccess(false);\n        }, 3000);\n        setShowSuccess(false);\n      }\n    } catch (error) {\n      if (error instanceof Error) {\n        onError?.(error);\n      }\n    } finally {\n      setIsPosting(false);\n    }\n  };\n\n  const handleQuoteClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n    setIsDropdownMenuOpen(false);\n    if (!postToReference) return;\n    onQuoteClick(postToReference);\n  };\n\n  if (!post) return null;\n\n  return (\n    <div className=\"flex items-center\">\n      <DropdownMenu open={isDropdownMenuOpen} onOpenChange={setIsDropdownMenuOpen}>\n        <DropdownMenuTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            disabled={postLoading || isPosting || showSuccess}\n            onClick={onClick}\n            className=\"w-8 h-8 active:outline-none focus-visible:outline-none hover:opacity-75 cursor-pointer rounded-full\"\n          >\n            {isPosting ? (\n              <Loader className=\"animate-spin w-4 h-4 text-muted-foreground\" />\n            ) : showSuccess ? (\n              <CheckCircle className=\"size-2\" />\n            ) : postToReference?.operations?.hasReposted ||\n              postToReference?.operations?.hasQuoted.optimistic ||\n              postToReference?.operations?.hasQuoted.onChain ? (\n              <Repeat2 className=\"size-[1.125rem]\" strokeWidth={3} stroke=\"var(--primary)\" />\n            ) : (\n              <Repeat2 className=\"size-[1.125rem]\" />\n            )}\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent className=\"min-w-40\">\n          <DropdownMenuItem className=\"text-md focus:outline-none p-0\">\n            <button\n              className=\"flex gap-4 items-center w-full p-2 text-sm font-medium\"\n              onClick={onRepostClick}\n              disabled={postLoading || isPosting}\n            >\n              <Repeat2 className=\"w-4 h-4 inline\" />\n              Repost\n            </button>\n          </DropdownMenuItem>\n          <DropdownMenuItem className=\"text-md focus:outline-none p-0\">\n            <button\n              className=\"flex gap-4 items-center w-full p-2 text-sm font-medium\"\n              onClick={handleQuoteClick}\n              disabled={postLoading || isPosting}\n            >\n              <MessageCircle className=\"w-4 h-4 inline\" />\n              Quote\n            </button>\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n      {showCount && (\n        <span className=\"opacity-85\">{new Intl.NumberFormat().format(optimistic.repostAndQuoteCount)}</span>\n      )}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/bookmarks/bookmark-button.tsx",
      "content": "import { Button } from \"@/registry/new-york/ui/button\";\nimport { Bookmark } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\nimport { MouseEvent } from \"react\";\nimport { Post } from \"@lens-protocol/react\";\n\ntype BookmarkButtonProps = {\n  className?: string;\n  onSuccess?: (post: Post, bookmarked: boolean) => void;\n  onError?: (error: Error) => void;\n};\n\nexport const BookmarkButton = ({ className, onSuccess, onError }: BookmarkButtonProps) => {\n  const { post, loading: postLoading, toggleBookmark, optimistic } = useLensPostContext();\n\n  const basePost: Post | null | undefined = post?.__typename === \"Repost\" ? post.repostOf : post;\n\n  const onClick = async (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n\n    if (!basePost) return;\n\n    try {\n      const bookmarked = await toggleBookmark();\n      onSuccess?.(basePost, bookmarked);\n    } catch (error) {\n      if (error instanceof Error && onError) {\n        onError(error);\n      }\n    }\n  };\n\n  return (\n    <Button\n      onClick={onClick}\n      disabled={postLoading}\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\"w-8 h-8 active:outline-none focus-visible:outline-none cursor-pointer rounded-full\", className)}\n    >\n      {optimistic.bookmarked || basePost?.operations?.hasBookmarked ? (\n        <Bookmark className=\"text-primary\" fill=\"var(--primary)\" />\n      ) : (\n        <Bookmark className=\"opacity-85\" />\n      )}\n    </Button>\n  );\n};\n\nexport default BookmarkButton;\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/comment/comment-button.tsx",
      "content": "\"use client\";\n\nimport { MouseEvent } from \"react\";\nimport { MessageCircle } from \"lucide-react\";\nimport { AnyPost } from \"@lens-protocol/react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\n\ntype CommentButtonProps = {\n  onClick: (post: AnyPost) => void;\n  showCount?: boolean;\n};\n\nexport const CommentButton = ({ onClick, showCount = true }: CommentButtonProps) => {\n  const { post, loading: postLoading, optimistic } = useLensPostContext();\n\n  const operations = post && \"operations\" in post ? post.operations : null;\n\n  const onButtonClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n    if (!post) return;\n    onClick(post);\n  };\n\n  return (\n    <div className=\"flex items-center\">\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        onClick={onButtonClick}\n        disabled={postLoading}\n        className=\"w-8 h-8 active:outline-none focus-visible:outline-none cursor-pointer rounded-full\"\n      >\n        {operations?.hasCommented?.optimistic || operations?.hasCommented?.onChain ? (\n          <MessageCircle className=\"text-primary\" fill=\"var(--primary)\" />\n        ) : (\n          <MessageCircle className=\"opacity-85\" />\n        )}\n      </Button>\n      {showCount && <span className=\"opacity-85\">{new Intl.NumberFormat().format(optimistic.commentCount)}</span>}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/collects/collect-button.tsx",
      "content": "\"use client\";\n\nimport { AnyPost } from \"@lens-protocol/react\";\nimport { ShoppingBag } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\nimport { MouseEvent } from \"react\";\n\ntype CollectButtonProps = {\n  onClick: (post: AnyPost) => void;\n  showCount?: boolean;\n};\n\nexport const CollectButton = ({ onClick, showCount = true }: CollectButtonProps) => {\n  const { post, loading: postLoading, optimistic } = useLensPostContext();\n\n  const operations = post && \"operations\" in post ? post.operations : null;\n\n  const onButtonClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n    if (!post) return;\n    onClick(post);\n  };\n\n  return (\n    <div className=\"flex items-center\">\n      <Button\n        onClick={onButtonClick}\n        disabled={postLoading}\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"w-8 h-8 active:outline-none focus-visible:outline-none cursor-pointer rounded-full\"\n      >\n        {optimistic.collected || operations?.hasSimpleCollected ? (\n          <svg viewBox=\"0 0 20 22\" className=\"w-4 h-4\">\n            <g strokeWidth=\"1.5\" fill=\"none\" fillRule=\"evenodd\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n              <path\n                d=\"M3,0 L0,4 L0,18 C0,19.1045695 0.8954305,20 2,20 L16,20 C17.1045695,20 18,19.1045695 18,18 L18,4 L15,0 L3,0 Z\"\n                stroke=\"var(--primary)\"\n                fill=\"var(--primary)\"\n                transform=\"translate(1 1)\"\n              />\n              <path\n                stroke=\"var(--primary)\"\n                fill=\"var(--background)\"\n                d=\"M3 0 0 4 18 4 15 0z\"\n                transform=\"translate(1 1)\"\n              />\n              <path stroke=\"var(--primary)\" d=\"M0 4 18 4\" transform=\"translate(1 1)\" />\n              <path\n                d=\"M13,8 C13,10.209139 11.209139,12 9,12 C6.790861,12 5,10.209139 5,8\"\n                stroke=\"var(--background)\"\n                transform=\"translate(1 1)\"\n              />\n            </g>\n          </svg>\n        ) : (\n          <ShoppingBag className=\"h-4 h-4\" />\n        )}\n      </Button>\n      {showCount && <span className=\"opacity-85\">{new Intl.NumberFormat().format(optimistic.collectCount)}</span>}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/tips/tip-button.tsx",
      "content": "\"use client\";\n\nimport { AnyPost } from \"@lens-protocol/react\";\nimport { MouseEvent } from \"react\";\nimport { CircleDollarSign } from \"lucide-react\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\n\ninterface Props {\n  onClick: (post: AnyPost) => void;\n  showCount?: boolean;\n}\n\nexport const TipButton = ({ onClick, showCount = true }: Props) => {\n  const { post, loading: postLoading, optimistic } = useLensPostContext();\n\n  const operations = post && \"operations\" in post ? post.operations : null;\n\n  const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n    event.currentTarget.blur();\n    event.stopPropagation();\n    if (!post) return;\n    onClick(post);\n  };\n\n  if (!post) return null;\n\n  return (\n    <div className=\"flex items-center\">\n      <Button\n        variant=\"ghost\"\n        disabled={postLoading || !operations?.canTip}\n        onClick={handleClick}\n        className=\"w-8 h-8 active:outline-none focus-visible:outline-none cursor-pointer rounded-full\"\n      >\n        {optimistic.tipped ? (\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            viewBox=\"0 0 24 24\"\n            fill=\"var(--primary)\"\n            stroke=\"var(--background)\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"w-4 h-4\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"12\" />\n            <path d=\"M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8\" strokeWidth=\"2\" />\n            <path d=\"M12 18V6\" strokeWidth=\"2\" />\n          </svg>\n        ) : (\n          <CircleDollarSign className=\"w-4 h-4\" />\n        )}\n      </Button>\n      {showCount && <span className=\"opacity-85\">{new Intl.NumberFormat().format(optimistic.tipCount)}</span>}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/components/feed/lens-image.tsx",
      "content": "import { ComponentProps } from \"react\";\nimport { MediaImage } from \"@lens-protocol/react\";\nimport { parseUri } from \"@/registry/new-york/lib/lens-utils\";\nimport { cn } from \"@/lib/utils\";\n\ntype ButtonProps = Omit<ComponentProps<\"img\">, \"src\">;\n\ntype Props = {\n  image: MediaImage;\n};\n\nexport const LensImage = ({ image, ...props }: Props & ButtonProps) => {\n  const imageUri = image ? parseUri(image.item) : null;\n\n  if (!imageUri) {\n    return null;\n  }\n\n  return (\n    <img\n      src={imageUri}\n      {...props}\n      className={cn(\"w-full mt-2 border rounded-xl object-contain cursor-pointer\", props.className)}\n    />\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/lib/regex.ts",
      "content": "const regexLookbehindAvailable: boolean = ((): boolean => {\n  try {\n    return \"ab\".replace(/(?<=a)b/g, \"c\") === \"ac\";\n  } catch {\n    return false;\n  }\n})();\n\nconst MATCH_BEHIND = regexLookbehindAvailable ? \"(?<=^|\\\\s)\" : \"\";\n\nconst MENTION_NAMESPACE = \"\\\\w+\\\\/\";\nconst MENTION_BODY = \"([\\\\dA-Za-z]\\\\w{2,25})\";\n\nexport const RegEx = {\n  CASHTAG: /(\\$\\w*[A-Za-z]\\w*)/g,\n  HASHTAG: /(#\\w*[A-Za-z]\\w*)/g,\n  MENTION: new RegExp(`${MATCH_BEHIND}@${MENTION_NAMESPACE}${MENTION_BODY}`, \"g\"),\n  URL: /\\b(http|https):\\/\\/([\\p{L}\\p{N}_-]+(?:(?:\\.[\\p{L}\\p{N}_-]+)+))([\\p{L}\\p{N}_.,@?^=%&:\\/~+#-]*[\\p{L}\\p{N}_@?^=%&\\/~+#-])/gu,\n};\n",
      "type": "registry:lib"
    }
  ]
}
