Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Registry #3786

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/www/public/r/styles/default/editor-ai.json
Original file line number Diff line number Diff line change
@@ -20,7 +20,8 @@
"@udecode/plate-mention",
"@udecode/plate-slash-command",
"@udecode/plate-table",
"@udecode/plate-toggle"
"@udecode/plate-toggle",
"sonner"
],
"description": "An AI editor",
"files": [
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
},
"files": [
{
"content": "'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { TPlaceholderElement } from '@udecode/plate-media';\n\nimport { cn } from '@udecode/cn';\nimport {\n insertNodes,\n removeNodes,\n withoutSavingHistory,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useEditorPlugin,\n withHOC,\n withRef,\n} from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n PlaceholderPlugin,\n PlaceholderProvider,\n VideoPlugin,\n updateUploadHistory,\n} from '@udecode/plate-media/react';\nimport { AudioLines, FileUp, Film, ImageIcon } from 'lucide-react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport { useUploadFile } from '../lib/uploadthing';\nimport { PlateElement } from './plate-element';\nimport { Spinner } from './spinner';\n\nconst CONTENT: Record<\n string,\n {\n accept: string[];\n content: ReactNode;\n icon: ReactNode;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n content: 'Add an audio file',\n icon: <AudioLines />,\n },\n [FilePlugin.key]: {\n accept: ['*'],\n content: 'Add a file',\n icon: <FileUp />,\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n content: 'Add an image',\n icon: <ImageIcon />,\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n content: 'Add a video',\n icon: <Film />,\n },\n};\n\nexport const MediaPlaceholderElement = withHOC(\n PlaceholderProvider,\n withRef<typeof PlateElement>(\n ({ children, className, editor, nodeProps, ...props }, ref) => {\n const element = props.element as TPlaceholderElement;\n\n const { api } = useEditorPlugin(PlaceholderPlugin);\n\n const { isUploading, progress, uploadFile, uploadedFile, uploadingFile } =\n useUploadFile();\n\n const loading = isUploading && uploadingFile;\n\n const currentContent = CONTENT[element.mediaType];\n\n const isImage = element.mediaType === ImagePlugin.key;\n\n const imageRef = useRef<HTMLImageElement>(null);\n\n const { openFilePicker } = useFilePicker({\n accept: currentContent.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n const firstFile = updatedFiles[0];\n const restFiles = updatedFiles.slice(1);\n\n replaceCurrentPlaceholder(firstFile);\n\n restFiles.length > 0 && (editor as any).tf.insert.media(restFiles);\n },\n });\n\n const replaceCurrentPlaceholder = useCallback(\n (file: File) => {\n void uploadFile(file);\n api.placeholder.addUploadingFile(element.id as string, file);\n },\n [api.placeholder, element.id, uploadFile]\n );\n\n useEffect(() => {\n if (!uploadedFile) return;\n\n const path = findNodePath(editor, element);\n\n withoutSavingHistory(editor, () => {\n removeNodes(editor, { at: path });\n\n const node = {\n children: [{ text: '' }],\n initialHeight: imageRef.current?.height,\n initialWidth: imageRef.current?.width,\n isUpload: true,\n name: element.mediaType === FilePlugin.key ? uploadedFile.name : '',\n placeholderId: element.id as string,\n type: element.mediaType!,\n url: uploadedFile.url,\n };\n\n insertNodes(editor, node, { at: path });\n\n updateUploadHistory(editor, node);\n });\n\n api.placeholder.removeUploadingFile(element.id as string);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uploadedFile, element.id]);\n\n // React dev mode will call useEffect twice\n const isReplaced = useRef(false);\n /** Paste and drop */\n useEffect(() => {\n if (isReplaced.current) return;\n\n isReplaced.current = true;\n const currentFiles = api.placeholder.getUploadingFile(\n element.id as string\n );\n\n if (!currentFiles) return;\n\n replaceCurrentPlaceholder(currentFiles);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isReplaced]);\n\n return (\n <PlateElement\n ref={ref}\n className={cn('relative my-1', className)}\n editor={editor}\n {...props}\n >\n {(!loading || !isImage) && (\n <div\n className={cn(\n 'flex cursor-pointer select-none items-center rounded-sm bg-muted p-3 pr-9 hover:bg-primary/10'\n )}\n onClick={() => !loading && openFilePicker()}\n contentEditable={false}\n >\n <div className=\"relative mr-3 flex text-muted-foreground/80 [&_svg]:size-6\">\n {currentContent.icon}\n </div>\n <div className=\"whitespace-nowrap text-sm text-muted-foreground\">\n <div>\n {loading ? uploadingFile?.name : currentContent.content}\n </div>\n\n {loading && !isImage && (\n <div className=\"mt-1 flex items-center gap-1.5\">\n <div>{formatBytes(uploadingFile?.size ?? 0)}</div>\n <div>–</div>\n <div className=\"flex items-center\">\n <Spinner className=\"mr-1 size-3.5\" />\n {progress ?? 0}%\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {isImage && loading && (\n <ImageProgress\n file={uploadingFile}\n imageRef={imageRef}\n progress={progress}\n />\n )}\n\n {children}\n </PlateElement>\n );\n }\n )\n);\n\nexport function ImageProgress({\n className,\n file,\n imageRef,\n progress = 0,\n}: {\n file: File;\n className?: string;\n imageRef?: React.RefObject<HTMLImageElement>;\n progress?: number;\n}) {\n const [objectUrl, setObjectUrl] = useState<string | null>(null);\n\n useEffect(() => {\n const url = URL.createObjectURL(file);\n setObjectUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [file]);\n\n if (!objectUrl) {\n return null;\n }\n\n return (\n <div className={cn('relative', className)} contentEditable={false}>\n <img\n ref={imageRef}\n className=\"h-auto w-full rounded-sm object-cover\"\n alt={file.name}\n src={objectUrl}\n />\n {progress < 100 && (\n <div className=\"absolute bottom-1 right-1 flex items-center space-x-2 rounded-full bg-black/50 px-1 py-0.5\">\n <Spinner />\n <span className=\"text-xs font-medium text-white\">\n {Math.round(progress)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n\nexport function formatBytes(\n bytes: number,\n opts: {\n decimals?: number;\n sizeType?: 'accurate' | 'normal';\n } = {}\n) {\n const { decimals = 0, sizeType = 'normal' } = opts;\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];\n\n if (bytes === 0) return '0 Byte';\n\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n\n return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${\n sizeType === 'accurate'\n ? (accurateSizes[i] ?? 'Bytest')\n : (sizes[i] ?? 'Bytes')\n }`;\n}\n",
"content": "'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { TPlaceholderElement } from '@udecode/plate-media';\n\nimport { cn } from '@udecode/cn';\nimport {\n insertNodes,\n removeNodes,\n withoutSavingHistory,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useEditorPlugin,\n withHOC,\n withRef,\n} from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n PlaceholderPlugin,\n PlaceholderProvider,\n VideoPlugin,\n updateUploadHistory,\n} from '@udecode/plate-media/react';\nimport { AudioLines, FileUp, Film, ImageIcon } from 'lucide-react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport { useUploadFile } from '../lib/uploadthing/uploadthing';\nimport { PlateElement } from './plate-element';\nimport { Spinner } from './spinner';\n\nconst CONTENT: Record<\n string,\n {\n accept: string[];\n content: ReactNode;\n icon: ReactNode;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n content: 'Add an audio file',\n icon: <AudioLines />,\n },\n [FilePlugin.key]: {\n accept: ['*'],\n content: 'Add a file',\n icon: <FileUp />,\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n content: 'Add an image',\n icon: <ImageIcon />,\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n content: 'Add a video',\n icon: <Film />,\n },\n};\n\nexport const MediaPlaceholderElement = withHOC(\n PlaceholderProvider,\n withRef<typeof PlateElement>(\n ({ children, className, editor, nodeProps, ...props }, ref) => {\n const element = props.element as TPlaceholderElement;\n\n const { api } = useEditorPlugin(PlaceholderPlugin);\n\n const { isUploading, progress, uploadFile, uploadedFile, uploadingFile } =\n useUploadFile();\n\n const loading = isUploading && uploadingFile;\n\n const currentContent = CONTENT[element.mediaType];\n\n const isImage = element.mediaType === ImagePlugin.key;\n\n const imageRef = useRef<HTMLImageElement>(null);\n\n const { openFilePicker } = useFilePicker({\n accept: currentContent.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n const firstFile = updatedFiles[0];\n const restFiles = updatedFiles.slice(1);\n\n replaceCurrentPlaceholder(firstFile);\n\n restFiles.length > 0 && (editor as any).tf.insert.media(restFiles);\n },\n });\n\n const replaceCurrentPlaceholder = useCallback(\n (file: File) => {\n void uploadFile(file);\n api.placeholder.addUploadingFile(element.id as string, file);\n },\n [api.placeholder, element.id, uploadFile]\n );\n\n useEffect(() => {\n if (!uploadedFile) return;\n\n const path = findNodePath(editor, element);\n\n withoutSavingHistory(editor, () => {\n removeNodes(editor, { at: path });\n\n const node = {\n children: [{ text: '' }],\n initialHeight: imageRef.current?.height,\n initialWidth: imageRef.current?.width,\n isUpload: true,\n name: element.mediaType === FilePlugin.key ? uploadedFile.name : '',\n placeholderId: element.id as string,\n type: element.mediaType!,\n url: uploadedFile.url,\n };\n\n insertNodes(editor, node, { at: path });\n\n updateUploadHistory(editor, node);\n });\n\n api.placeholder.removeUploadingFile(element.id as string);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uploadedFile, element.id]);\n\n // React dev mode will call useEffect twice\n const isReplaced = useRef(false);\n /** Paste and drop */\n useEffect(() => {\n if (isReplaced.current) return;\n\n isReplaced.current = true;\n const currentFiles = api.placeholder.getUploadingFile(\n element.id as string\n );\n\n if (!currentFiles) return;\n\n replaceCurrentPlaceholder(currentFiles);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isReplaced]);\n\n return (\n <PlateElement\n ref={ref}\n className={cn('relative my-1', className)}\n editor={editor}\n {...props}\n >\n {(!loading || !isImage) && (\n <div\n className={cn(\n 'flex cursor-pointer select-none items-center rounded-sm bg-muted p-3 pr-9 hover:bg-primary/10'\n )}\n onClick={() => !loading && openFilePicker()}\n contentEditable={false}\n >\n <div className=\"relative mr-3 flex text-muted-foreground/80 [&_svg]:size-6\">\n {currentContent.icon}\n </div>\n <div className=\"whitespace-nowrap text-sm text-muted-foreground\">\n <div>\n {loading ? uploadingFile?.name : currentContent.content}\n </div>\n\n {loading && !isImage && (\n <div className=\"mt-1 flex items-center gap-1.5\">\n <div>{formatBytes(uploadingFile?.size ?? 0)}</div>\n <div>–</div>\n <div className=\"flex items-center\">\n <Spinner className=\"mr-1 size-3.5\" />\n {progress ?? 0}%\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {isImage && loading && (\n <ImageProgress\n file={uploadingFile}\n imageRef={imageRef}\n progress={progress}\n />\n )}\n\n {children}\n </PlateElement>\n );\n }\n )\n);\n\nexport function ImageProgress({\n className,\n file,\n imageRef,\n progress = 0,\n}: {\n file: File;\n className?: string;\n imageRef?: React.RefObject<HTMLImageElement>;\n progress?: number;\n}) {\n const [objectUrl, setObjectUrl] = useState<string | null>(null);\n\n useEffect(() => {\n const url = URL.createObjectURL(file);\n setObjectUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [file]);\n\n if (!objectUrl) {\n return null;\n }\n\n return (\n <div className={cn('relative', className)} contentEditable={false}>\n <img\n ref={imageRef}\n className=\"h-auto w-full rounded-sm object-cover\"\n alt={file.name}\n src={objectUrl}\n />\n {progress < 100 && (\n <div className=\"absolute bottom-1 right-1 flex items-center space-x-2 rounded-full bg-black/50 px-1 py-0.5\">\n <Spinner />\n <span className=\"text-xs font-medium text-white\">\n {Math.round(progress)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n\nexport function formatBytes(\n bytes: number,\n opts: {\n decimals?: number;\n sizeType?: 'accurate' | 'normal';\n } = {}\n) {\n const { decimals = 0, sizeType = 'normal' } = opts;\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];\n\n if (bytes === 0) return '0 Byte';\n\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n\n return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${\n sizeType === 'accurate'\n ? (accurateSizes[i] ?? 'Bytest')\n : (sizes[i] ?? 'Bytes')\n }`;\n}\n",
"path": "plate-ui/media-placeholder-element.tsx",
"target": "components/plate-ui/media-placeholder-element.tsx",
"type": "registry:ui"