{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editor",
  "type": "registry:block",
  "title": "Editor",
  "description": "A rich text editor for creating posts with support for images, video, emojis, and markdown.",
  "registryDependencies": [
    "editor-emoji-picker",
    "editor-markdown",
    "editor-upload"
  ],
  "files": [
    {
      "path": "src/components/deso/editor.tsx",
      "content": "'use client';\n\nimport React, { useState } from 'react';\nimport { Textarea } from '../ui/textarea';\nimport { Button } from '../ui/button';\nimport { cn } from '@/lib/utils/deso';\nimport { Profile } from '@/lib/schemas/deso';\nimport { EditorUpload, UploadedFile, UploadType } from './editor-upload';\nimport { PostImage } from './post-image';\nimport {\n  Image,\n  Video,\n  Mic,\n  Lock,\n  LucideGlobe,\n  LucideLock,\n  Smile,\n} from 'lucide-react';\nimport { Input } from '../ui/input';\nimport { Label } from '../ui/label';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '../ui/select';\nimport { EditorEmojiPicker } from './editor-emoji-picker';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport { UserInfo } from './user-info';\nimport { ProfilePicture } from './profile-picture';\nimport { EditorMarkdown } from './editor-markdown';\n\nconst EditorVisibility = ({\n  isExclusive,\n  onVisibilityChange,\n}: {\n  isExclusive: boolean;\n  onVisibilityChange: (value: string) => void;\n}) => (\n  <Select\n    defaultValue={isExclusive ? 'exclusive' : 'public'}\n    onValueChange={onVisibilityChange}\n  >\n    <SelectTrigger>\n      <SelectValue />\n    </SelectTrigger>\n    <SelectContent className=\"border border-border\">\n      <SelectItem value=\"public\">\n        <LucideGlobe className=\"w-4 h-4\" /> Post to Everyone\n      </SelectItem>\n      <SelectItem value=\"exclusive\">\n        <LucideLock className=\"w-4 h-4\" /> Post to Subscribers\n      </SelectItem>\n    </SelectContent>\n  </Select>\n);\n\nconst EditorActions = ({\n  showImageUpload,\n  showVideoUpload,\n  showAudioUpload,\n  showExclusiveContent,\n  showEmojiPicker,\n  onUploadClick,\n  onExclusiveClick,\n  onEmojiClick,\n}: {\n  showImageUpload: boolean;\n  showVideoUpload: boolean;\n  showAudioUpload: boolean;\n  showExclusiveContent: boolean;\n  showEmojiPicker: boolean;\n  onUploadClick: (type: UploadType) => void;\n  onExclusiveClick: () => void;\n  onEmojiClick: (emoji: string) => void;\n}) => {\n  const [isEmojiPickerOpen, setEmojiPickerOpen] = useState(false);\n\n  const handleEmojiSelect = (emoji: string) => {\n    onEmojiClick(emoji);\n    setEmojiPickerOpen(false);\n  };\n\n  const uploadButton = (\n    handleClick: () => void,\n    tooltipText: string,\n    icon: React.ReactNode\n  ) => (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          size=\"icon\"\n          onClick={handleClick}\n          variant=\"ghost\"\n          className=\"bg-transparent data-[state=active]:bg-accent\"\n        >\n          {icon}\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <p>{tooltipText}</p>\n      </TooltipContent>\n    </Tooltip>\n  );\n\n  return (\n    <div className=\"flex gap-1 -ml-2\">\n      {showImageUpload &&\n        uploadButton(\n          () => onUploadClick('image'),\n          'Upload Image',\n          <Image className=\"text-muted-foreground size-5\" />\n        )}\n      {showVideoUpload &&\n        uploadButton(\n          () => onUploadClick('video'),\n          'Upload Video',\n          <Video className=\"text-muted-foreground size-5\" />\n        )}\n      {showAudioUpload &&\n        uploadButton(\n          () => onUploadClick('audio'),\n          'Upload Audio',\n          <Mic className=\"text-muted-foreground size-5\" />\n        )}\n      {showExclusiveContent &&\n        uploadButton(\n          onExclusiveClick,\n          'Unlock Exclusive Content',\n          <Lock className=\"text-muted-foreground size-5\" />\n        )}\n      {showEmojiPicker && (\n        <Popover open={isEmojiPickerOpen} onOpenChange={setEmojiPickerOpen}>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <PopoverTrigger asChild>\n                <Button variant=\"ghost\" size=\"icon\">\n                  <Smile className=\"text-muted-foreground size-5\" />\n                </Button>\n              </PopoverTrigger>\n            </TooltipTrigger>\n            <TooltipContent>\n              <p>Add Emoji</p>\n            </TooltipContent>\n          </Tooltip>\n          <PopoverContent className=\"w-auto p-0 border-0\">\n            <EditorEmojiPicker onEmojiClick={handleEmojiSelect} />\n          </PopoverContent>\n        </Popover>\n      )}\n    </div>\n  );\n};\n\nconst EditorSubmit = ({\n  showCharacterCount,\n  remainingChars,\n  isPostDisabled,\n  onSubmit,\n  submitButtonText,\n}: {\n  showCharacterCount: boolean;\n  remainingChars: number;\n  isPostDisabled: boolean;\n  onSubmit: () => void;\n  submitButtonText: string;\n}) => (\n  <div className=\"flex items-center gap-4\">\n    {showCharacterCount && (\n      <span\n        className={cn('text-sm text-muted-foreground', {\n          'text-red-500': remainingChars < 0,\n        })}\n      >\n        {remainingChars}\n      </span>\n    )}\n    <Button onClick={onSubmit} disabled={isPostDisabled}>\n      {submitButtonText}\n    </Button>\n  </div>\n);\n\nexport interface EditorSubmitData {\n  postText: string;\n  files: File[];\n  isExclusive?: boolean;\n  priceNanos?: number;\n  previewText?: string;\n  showEmojiPicker?: boolean;\n  showCharacterCount?: boolean;\n  maxChars?: number;\n  layout?: 'default' | 'compact';\n  showVisibility?: boolean;\n  showUserInfo?: boolean;\n  submitButtonText?: string;\n}\n\nexport interface EditorProps {\n  currentUser: {\n    publicKey: string;\n    profile?: Profile;\n  };\n  onSubmit: (data: EditorSubmitData) => void;\n  className?: string;\n  placeholder?: string;\n  showImageUpload?: boolean;\n  showVideoUpload?: boolean;\n  showAudioUpload?: boolean;\n  showExclusiveContent?: boolean;\n  showEmojiPicker?: boolean;\n  showCharacterCount?: boolean;\n  maxChars?: number;\n  layout?: 'default' | 'compact';\n  showVisibility?: boolean;\n  showUserInfo?: boolean;\n  submitButtonText?: string;\n  submitOnEnter?: boolean;\n  useMarkdownEditor?: boolean;\n}\n\nexport function Editor({\n  currentUser,\n  onSubmit,\n  className,\n  placeholder = \"What's happening?\",\n  showImageUpload = true,\n  showVideoUpload = true,\n  showAudioUpload = true,\n  showExclusiveContent = true,\n  showEmojiPicker: showEmojiPickerProp = true,\n  showCharacterCount = true,\n  showVisibility = true,\n  showUserInfo = true,\n  maxChars = 600,\n  layout = 'default',\n  submitButtonText = 'Post',\n  submitOnEnter = false,\n  useMarkdownEditor = false,\n}: EditorProps) {\n  const [postText, setPostText] = useState('');\n  const [previewText, setPreviewText] = useState('');\n  const [price, setPrice] = useState('');\n  const [isExclusive, setIsExclusive] = useState(false);\n  const [showEmojiPicker, setShowEmojiPicker] = useState(false);\n  const [files, setFiles] = useState<UploadedFile[]>([]);\n  const [activeUploadType, setActiveUploadType] = useState<UploadType | null>(\n    null\n  );\n\n  const handleSubmit = () => {\n    if (postText.trim() || files.length > 0) {\n      onSubmit({\n        postText,\n        files: files.map((f) => f.file),\n        ...(isExclusive && {\n          isExclusive: true,\n          // NOTE: converting DESO to nanos. We may want to do this conversion\n          // in the submit handler instead of here.\n          priceNanos: parseFloat(price) * 1e9,\n          previewText,\n        }),\n      });\n      setPostText('');\n      setFiles([]);\n      setActiveUploadType(null);\n      setIsExclusive(false);\n      setPrice('');\n      setPreviewText('');\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (submitOnEnter && e.key === 'Enter' && !e.shiftKey) {\n      e.preventDefault();\n      handleSubmit();\n    }\n  };\n\n  const handleMarkdownChange = (value: string) => {\n    setPostText(value);\n  };\n\n  const handleFileUpload = (uploadedFiles: File[]) => {\n    const newFiles: UploadedFile[] = uploadedFiles.map((file) => ({\n      id: `${file.name}-${Date.now()}`,\n      file,\n      preview: URL.createObjectURL(file),\n      progress: 0,\n    }));\n\n    setFiles((prev) => [...prev, ...newFiles]);\n\n    newFiles.forEach((file) => {\n      let progress = 0;\n      const interval = setInterval(() => {\n        progress += 10;\n        if (progress >= 100) {\n          clearInterval(interval);\n          setFiles((prev) =>\n            prev.map((f) => (f.id === file.id ? { ...f, progress: 100 } : f))\n          );\n        } else {\n          setFiles((prev) =>\n            prev.map((f) => (f.id === file.id ? { ...f, progress } : f))\n          );\n        }\n      }, 200);\n    });\n  };\n\n  const handleUploadClick = (type: UploadType) => {\n    if (activeUploadType === type) {\n      setActiveUploadType(null);\n    } else {\n      setActiveUploadType(type);\n      setFiles([]);\n    }\n  };\n\n  const handleExclusiveClick = () => {\n    setIsExclusive((prev) => !prev);\n    setActiveUploadType(null);\n    setFiles([]);\n    setPrice('');\n    setPreviewText('');\n  };\n\n  const handleEmojiClick = (emoji: string) => {\n    if (isExclusive) {\n      setPreviewText((prev) => prev + emoji);\n    } else {\n      setPostText((prev) => prev + emoji);\n    }\n    setShowEmojiPicker(false);\n  };\n\n  const remainingChars = maxChars - postText.length;\n  const isPostDisabled =\n    (!postText.trim() && files.length === 0) || remainingChars < 0;\n\n  const handleVisibilityChange = (value: string) => {\n    setIsExclusive(value === 'exclusive');\n    setActiveUploadType(null);\n    setFiles([]);\n  };\n\n  const renderUploadUI = () => {\n    if (!activeUploadType) {\n      if (files.length > 0) {\n        // This part needs to be smarter based on file types\n        return (\n          <div\n            onClick={() => setActiveUploadType('image')}\n            className=\"cursor-pointer\"\n          >\n            <PostImage images={files.map((f) => f.preview)} />\n          </div>\n        );\n      }\n      return null;\n    }\n\n    return (\n      <EditorUpload\n        files={files}\n        onFilesChange={setFiles}\n        onFileUpload={handleFileUpload}\n        uploadType={activeUploadType}\n      />\n    );\n  };\n\n  if (layout === 'compact') {\n    return (\n      <div className={cn('flex items-end w-full gap-2', className)}>\n        <div className=\"flex items-end gap-2 w-full\">\n          <ProfilePicture\n            publicKey={currentUser.publicKey}\n            profile={currentUser.profile}\n            size=\"sm\"\n            className=\"relative -top-[2px]\"\n          />\n          <Textarea\n            value={postText}\n            onChange={(e) => setPostText(e.target.value)}\n            placeholder={placeholder}\n            className=\"bg-transparent border min-h-auto focus-visible:ring-0 focus-visible:ring-offset-0 p-2 text-md resize-none\"\n            rows={1}\n            onKeyDown={handleKeyDown}\n          />\n        </div>\n        <EditorSubmit\n          showCharacterCount={false}\n          remainingChars={remainingChars}\n          isPostDisabled={isPostDisabled}\n          onSubmit={handleSubmit}\n          submitButtonText={submitButtonText}\n        />\n      </div>\n    );\n  }\n\n  return (\n    <div className={cn('flex flex-col gap-4 p-6 border border-border rounded-xl', className)}>\n      <div className=\"flex flex-col gap-4\">\n        {(showUserInfo || showVisibility) && (\n          <div className=\"flex flex-row gap-2 justify-between items-center\">\n            {showUserInfo && (\n              <div>\n                <UserInfo\n                  publicKey={currentUser.publicKey}\n                  profile={currentUser.profile}\n                  pictureSize=\"sm\"\n                  />\n              </div>\n            )}\n            <div>\n              {showVisibility && (\n                <EditorVisibility\n                  isExclusive={isExclusive}\n                  onVisibilityChange={handleVisibilityChange}\n                />\n              )}\n            </div>\n          </div>\n        )}\n        <div className=\"flex-1\">\n          {isExclusive && (\n            <div className=\"flex flex-col gap-2 mb-4\">\n              <Label htmlFor=\"previewText\" className=\"text-sm font-semibold\">\n                Preview Content\n              </Label>\n              {useMarkdownEditor ? (\n              <EditorMarkdown\n                value={postText}\n                onChange={handleMarkdownChange}\n                placeholder={\n                  isExclusive ? 'Write your exclusive content here...' : placeholder\n                }\n                className=\"border dark:border-none min-h-auto focus-visible:ring-0 focus-visible:ring-offset-0 p-4 text-lg\"\n              />\n            ) : (\n              <Textarea\n                id=\"previewText\"\n                value={previewText}\n                onChange={(e) => setPreviewText(e.target.value)}\n                placeholder=\"Write a public preview for your post...\"\n                className=\"border dark:border-none min-h-auto focus-visible:ring-0 focus-visible:ring-offset-0 p-4 text-lg resize-none\"\n                rows={2}\n              />\n            )}\n            </div>\n          )}\n          <div className=\"flex flex-col gap-2\">\n            <Label\n              htmlFor=\"mainContent\"\n              className={cn('text-sm font-semibold', {\n                'sr-only': !isExclusive,\n              })}\n            >\n              {isExclusive ? 'Exclusive Content' : 'Post Content'}\n            </Label>\n            {useMarkdownEditor ? (\n              <EditorMarkdown\n                value={postText}\n                onChange={handleMarkdownChange}\n                placeholder={\n                  isExclusive ? 'Write your exclusive content here...' : placeholder\n                }\n                className=\"border dark:border-none min-h-auto focus-visible:ring-0 focus-visible:ring-offset-0 p-4 text-lg\"\n              />\n            ) : (\n              <Textarea\n                id=\"mainContent\"\n                value={postText}\n                onChange={(e) => setPostText(e.target.value)}\n                placeholder={\n                  isExclusive ? 'Write your exclusive content here...' : placeholder\n                }\n                className=\"border dark:border-none min-h-auto focus-visible:ring-0 focus-visible:ring-offset-0 p-4 text-lg resize-none\"\n                rows={3}\n                onKeyDown={handleKeyDown}\n              />\n            )}\n          </div>\n        </div>\n      </div>\n\n      {renderUploadUI()}\n\n      {isExclusive && (\n        <div className=\"flex flex-row gap-2\">\n          <Label htmlFor=\"price\" className=\"text-sm font-semibold flex-4\">\n            Unlock Price (in $DESO)\n          </Label>\n          <Input\n            id=\"price\"\n            type=\"number\"\n            value={price}\n            onChange={(e) => setPrice(e.target.value)}\n            placeholder=\"e.g. 1.0\"\n            className=\"bg-transparent flex-2\"\n          />\n        </div>\n      )}\n\n      <div className=\"flex justify-between items-center\">\n        <EditorActions\n          showImageUpload={showImageUpload}\n          showVideoUpload={showVideoUpload}\n          showAudioUpload={showAudioUpload}\n          showExclusiveContent={showExclusiveContent}\n          showEmojiPicker={showEmojiPickerProp}\n          onUploadClick={handleUploadClick}\n          onExclusiveClick={handleExclusiveClick}\n          onEmojiClick={handleEmojiClick}\n        />\n        <EditorSubmit\n          showCharacterCount={showCharacterCount}\n          remainingChars={remainingChars}\n          isPostDisabled={isPostDisabled}\n          onSubmit={handleSubmit}\n          submitButtonText={submitButtonText}\n        />\n      </div>\n    </div>\n  );\n} ",
      "type": "registry:component",
      "target": "src/components/deso-ui/editor.tsx"
    }
  ]
}