Skip to content

Commit

Permalink
Merge pull request #73 from hunghg255/update-extension-image
Browse files Browse the repository at this point in the history
  • Loading branch information
hunghg255 authored Oct 16, 2024
2 parents 03c8cbc + 6809a96 commit 41bf380
Show file tree
Hide file tree
Showing 20 changed files with 243 additions and 15 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"prosemirror-docx": "^0.2.0",
"re-resizable": "^6.10.0",
"react-colorful": "^5.6.1",
"react-image-crop": "^11.0.7",
"react-visibility-sensor": "^5.1.1",
"scroll-into-view-if-needed": "^3.1.0",
"shiki": "^1.22.0",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable react/no-duplicate-key */
import React, { useEffect, useMemo, useState } from 'react'

import { Plus } from 'lucide-react'
Expand Down Expand Up @@ -74,7 +73,7 @@ function ColorPicker(props: ColorPickerProps) {
}

return (
<Popover>
<Popover modal>
<PopoverTrigger className="!richtext-p-0" disabled={disabled} asChild>{props?.children}</PopoverTrigger>

<PopoverContent hideWhenDetached className="richtext-w-full richtext-h-full richtext-p-2" align="start" side="bottom">
Expand Down
2 changes: 1 addition & 1 deletion src/components/SizeSetter/SizeSetter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const SizeSetter: React.FC<IProps> = ({ width, maxWidth, height, onOk, ch
}

return (
<Popover>
<Popover modal>
<PopoverTrigger asChild>
{children}
</PopoverTrigger>
Expand Down
2 changes: 2 additions & 0 deletions src/components/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Columns3,
Columns4,
Copy,
CropIcon,
Eraser,
Eye,
Frame,
Expand Down Expand Up @@ -195,4 +196,5 @@ export const icons = {
Attachment: Paperclip,
GifIcon,
ChevronUp,
Crop: CropIcon,
} as any
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function EmojiPickerWrap({ onSelectEmoji, children }: IProps) {
}, [])

return (
<Popover>
<Popover modal>
<PopoverTrigger asChild>{children}</PopoverTrigger>

<PopoverContent hideWhenDetached className="richtext-w-full richtext-h-full richtext-p-2" align="start" side="bottom">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function ImageGifWrap({ selectImage, giphyApiKey, children }: IProps) {
)

return (
<Popover>
<Popover modal>
<PopoverTrigger asChild>{children}</PopoverTrigger>

<PopoverContent hideWhenDetached className="richtext-w-full richtext-h-full richtext-p-2" align="start" side="bottom">
Expand Down
184 changes: 184 additions & 0 deletions src/extensions/ImageUpload/components/ImageCropper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/* eslint-disable no-console */
import React, { useRef, useState } from 'react'

import ReactCrop, {
type Crop,
type PixelCrop,
} from 'react-image-crop'

import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogFooter,
DialogTrigger,
} from '@/components/ui/dialog'

import { useLocale } from '@/locales'
import { dataURLtoFile, readImageAsBase64 } from '@/utils/file'
import { createImageUpload } from '@/plugins/image-upload'

import 'react-image-crop/dist/ReactCrop.css'
import { IconComponent } from '@/components'

export function ImageCropper({ editor, getPos }: any) {
const { t } = useLocale()

const [dialogOpen, setDialogOpen] = useState(false)

const imgRef = React.useRef<HTMLImageElement | null>(null)

const [crop, setCrop] = React.useState<Crop>()
const [croppedImageUrl, setCroppedImageUrl] = React.useState<string>('')
const fileInput = useRef<HTMLInputElement>(null)
const [urlUpload, setUrlUpload] = useState<any>({
src: '',
file: null,
})

function onCropComplete(crop: PixelCrop) {
if (imgRef.current && crop.width && crop.height) {
const croppedImageUrl = getCroppedImg(imgRef.current, crop)
setCroppedImageUrl(croppedImageUrl)
}
}

function getCroppedImg(image: HTMLImageElement, crop: PixelCrop): string {
const canvas = document.createElement('canvas')
const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height

canvas.width = crop.width * scaleX
canvas.height = crop.height * scaleY

const ctx = canvas.getContext('2d')

if (ctx) {
ctx.imageSmoothingEnabled = false

ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width * scaleX,
crop.height * scaleY,
)
}

return canvas.toDataURL('image/png', 1.0)
}

async function onCrop() {
try {
const fileCrop = await dataURLtoFile(croppedImageUrl, urlUpload?.file?.name || 'image.png')

const uploadOptions = editor.extensionManager.extensions.find(
(extension: any) => extension.name === 'imageUpload',
)?.options

const uploadFn = createImageUpload({
validateFn: () => {
return true
},
onUpload: uploadOptions.upload,
postUpload: uploadOptions.postUpload,
})
uploadFn([fileCrop], editor.view, getPos())

setDialogOpen(false)

setUrlUpload({
src: '',
file: null,
})
}
catch (error) {
console.log('Error cropping image', error)
}
}

function handleClick(e: any) {
e.preventDefault()
fileInput.current?.click()
}

const handleFile = async (event: any) => {
const files = event?.target?.files
if (!editor || editor.isDestroyed || files.length === 0) {
return
}
const file = files[0]

const base64 = await readImageAsBase64(file)

setDialogOpen(true)
setUrlUpload({
src: base64.src,
file,
})
}

return (
<>
<Button className="richtext-w-full richtext-mt-1" size="sm" onClick={handleClick}>
{t('editor.image.dialog.tab.uploadCrop')}
</Button>

<Dialog open={dialogOpen}>
<DialogTrigger />

<DialogContent className="[&>button]:richtext-hidden">
<div>
{urlUpload.src && (
<ReactCrop
crop={crop}
onChange={c => setCrop(c)}
onComplete={c => onCropComplete(c)}
className="richtext-w-full"
>
<img
ref={imgRef}
alt="Crop me"
src={urlUpload.src}
/>
</ReactCrop>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setDialogOpen(false)
setUrlUpload({
src: '',
file: null,
})
}}
>
{t('editor.imageUpload.cancel')}
<IconComponent name="Trash2" className="richtext-ml-[4px]" />
</Button>
<Button className="richtext-w-fit" onClick={onCrop}>
{t('editor.imageUpload.crop')}
<IconComponent name="Crop" className="richtext-ml-[4px]" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<input
type="file"
accept="image/*"
ref={fileInput}
multiple
style={{
display: 'none',
}}
onChange={handleFile}
/>
</>
)
}
10 changes: 7 additions & 3 deletions src/extensions/ImageUpload/components/ImageUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NodeViewWrapper } from '@tiptap/react'
import { Button, IconComponent, Input, Popover, PopoverContent, PopoverTrigger, Tabs, TabsContent, TabsList, TabsTrigger } from '@/components'
import { useLocale } from '@/locales'
import { createImageUpload } from '@/plugins/image-upload'
import { ImageCropper } from '@/extensions/ImageUpload/components/ImageCropper'

function ImageUploader(props: any) {
const { t } = useLocale()
Expand Down Expand Up @@ -78,9 +79,12 @@ function ImageUploader(props: any) {
</TabsTrigger>
</TabsList>
<TabsContent value="upload">
<Button className="richtext-w-full richtext-mt-1" size="sm" onClick={handleClick}>
{t('editor.image.dialog.tab.upload')}
</Button>
<div className="richtext-flex richtext-items-center richtext-gap-[10px]">
<Button className="richtext-w-full richtext-mt-1" size="sm" onClick={handleClick}>
{t('editor.image.dialog.tab.upload')}
</Button>
<ImageCropper editor={props.editor} getPos={props.getPos} />
</div>
<input
type="file"
accept="image/*"
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/Katex/components/KatexActiveButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function KatexActiveButton({ editor, ...props }: any) {
)

return (
<Popover>
<Popover modal>
<PopoverTrigger asChild>
<ActionButton
tooltip={props?.tooltip}
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/Link/components/LinkEditPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function LinkEditPopover(props: IPropsLinkEditPopover) {
}

return (
<Popover>
<Popover modal>
<PopoverTrigger disabled={props?.disabled} asChild>
<ActionButton
tooltip={props?.tooltip}
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/Table/components/CreateTablePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function CreateTablePopover(props: IPropsCreateTablePopover) {
}

return (
<Popover>
<Popover modal>
<PopoverTrigger asChild>
{props?.children}
</PopoverTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function TextAlignMenuButton(props: IPropsTextAlignMenuButton) {
}, [props])

return (
<Popover>
<Popover modal>
<PopoverTrigger disabled={props?.disabled} asChild>
<ActionButton
customClass="!richtext-w-12 richtext-h-12"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function TextDirectionButton(props: IPropsTextDirectionButton) {
}, [props])

return (
<Popover>
<Popover modal>
<PopoverTrigger disabled={props?.disabled} asChild>
<ActionButton
customClass="!richtext-w-12 richtext-h-12"
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const locale = {
'editor.image.dialog.title': 'Add an image',
'editor.image.dialog.tab.url': 'Url',
'editor.image.dialog.tab.upload': 'Upload',
'editor.image.dialog.tab.uploadCrop': 'Upload & Crop',
'editor.image.dialog.uploading': 'Uploading',
'editor.image.dialog.form.link': 'Link',
'editor.image.dialog.placeholder': 'Link',
Expand Down Expand Up @@ -113,6 +114,8 @@ const locale = {
'editor.redo.tooltip': 'Redo',
'editor.fullscreen.tooltip.fullscreen': 'Fullscreen',
'editor.fullscreen.tooltip.exit': 'Fullscreen Exit',
'editor.imageUpload.cancel': 'Cancel',
'editor.imageUpload.crop': 'Crop',
'editor.imageUpload.fileTypeNotSupported': 'File type not supported',
'editor.imageUpload.fileSizeTooBig': 'File size too big, Maximum size is',
'editor.table.menu.insertColumnBefore': 'Insert Column Before',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const locale = {
'editor.image.dialog.title': 'Adicionar uma imagem',
'editor.image.dialog.tab.url': 'Url',
'editor.image.dialog.tab.upload': 'Enviar',
'editor.image.dialog.tab.uploadCrop': 'Enviar e cortar',
'editor.image.dialog.uploading': 'Enviando',
'editor.image.dialog.form.link': 'Link',
'editor.image.dialog.placeholder': 'Link',
Expand Down Expand Up @@ -113,6 +114,8 @@ const locale = {
'editor.redo.tooltip': 'Refazer',
'editor.fullscreen.tooltip.fullscreen': 'Tela cheia',
'editor.fullscreen.tooltip.exit': 'Sair da tela cheia',
'editor.imageUpload.cancel': 'Cancelar',
'editor.imageUpload.crop': 'Cortar',
'editor.imageUpload.fileTypeNotSupported': 'Tipo de arquivo não suportado',
'editor.imageUpload.fileSizeTooBig': 'Tamanho do arquivo muito grande, tamanho máximo é',
'editor.table.menu.insertColumnBefore': 'Inserir coluna antes',
Expand Down
Loading

0 comments on commit 41bf380

Please sign in to comment.