Feature/paste image from buffer (#251)

Editor: Paste images from clipboard
This commit is contained in:
Ilya Y 2023-10-09 08:14:58 +03:00 committed by GitHub
parent f85a3e84fe
commit f593d95358
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 3 deletions

View File

@ -212,7 +212,6 @@
"Nothing here yet": "There's nothing here yet", "Nothing here yet": "There's nothing here yet",
"Nothing is here": "There is nothing here", "Nothing is here": "There is nothing here",
"Notifications": "Notifications", "Notifications": "Notifications",
"Registered since {{date}}": "Registered since {{date}}",
"Or continue with social network": "Or continue with social network", "Or continue with social network": "Or continue with social network",
"Or paste a link to an image": "Or paste a link to an image", "Or paste a link to an image": "Or paste a link to an image",
"Ordered list": "Ordered list", "Ordered list": "Ordered list",
@ -250,6 +249,7 @@
"Quotes": "Quotes", "Quotes": "Quotes",
"Reason uknown": "Reason unknown", "Reason uknown": "Reason unknown",
"Recent": "Fresh", "Recent": "Fresh",
"Registered since {{date}}": "Registered since {{date}}",
"Remove link": "Remove link", "Remove link": "Remove link",
"Reply": "Reply", "Reply": "Reply",
"Report": "Complain", "Report": "Complain",
@ -327,6 +327,7 @@
"Upload": "Upload", "Upload": "Upload",
"Upload error": "Upload error", "Upload error": "Upload error",
"Upload video": "Upload video", "Upload video": "Upload video",
"Uploading image": "Uploading image",
"Username": "Username", "Username": "Username",
"Userpic": "Userpic", "Userpic": "Userpic",
"Users": "Users", "Users": "Users",

View File

@ -222,7 +222,6 @@
"Nothing here yet": "Здесь пока ничего нет", "Nothing here yet": "Здесь пока ничего нет",
"Nothing is here": "Здесь ничего нет", "Nothing is here": "Здесь ничего нет",
"Notifications": "Уведомления", "Notifications": "Уведомления",
"Registered since {{date}}": "На сайте c {{date}}",
"Or continue with social network": "Или войдите через соцсеть", "Or continue with social network": "Или войдите через соцсеть",
"Or paste a link to an image": "Или вставьте ссылку на изображение", "Or paste a link to an image": "Или вставьте ссылку на изображение",
"Ordered list": "Нумерованный список", "Ordered list": "Нумерованный список",
@ -264,6 +263,7 @@
"Quotes": "Цитаты", "Quotes": "Цитаты",
"Reason uknown": "Причина неизвестна", "Reason uknown": "Причина неизвестна",
"Recent": "Свежее", "Recent": "Свежее",
"Registered since {{date}}": "На сайте c {{date}}",
"Release date...": "Дата выхода...", "Release date...": "Дата выхода...",
"Remove link": "Убрать ссылку", "Remove link": "Убрать ссылку",
"Reply": "Ответить", "Reply": "Ответить",
@ -345,6 +345,7 @@
"Upload": "Загрузить", "Upload": "Загрузить",
"Upload error": "Ошибка загрузки", "Upload error": "Ошибка загрузки",
"Upload video": "Загрузить видео", "Upload video": "Загрузить видео",
"Uploading image": "Загружаем изображение",
"Username": "Имя пользователя", "Username": "Имя пользователя",
"Userpic": "Аватар", "Userpic": "Аватар",
"Users": "Пользователи", "Users": "Пользователи",

View File

@ -44,6 +44,9 @@ import { EditorFloatingMenu } from './EditorFloatingMenu'
import './Prosemirror.scss' import './Prosemirror.scss'
import { Image } from '@tiptap/extension-image' import { Image } from '@tiptap/extension-image'
import { Footnote } from './extensions/Footnote' import { Footnote } from './extensions/Footnote'
import { handleFileUpload } from '../../utils/handleFileUpload'
import { imageProxy } from '../../utils/imageProxy'
import { useSnackbar } from '../../context/snackbar'
type Props = { type Props = {
shoutId: number shoutId: number
@ -51,6 +54,17 @@ type Props = {
onChange: (text: string) => void onChange: (text: string) => void
} }
const allowedImageTypes = new Set([
'image/bmp',
'image/gif',
'image/jpeg',
'image/jpg',
'image/png',
'image/tiff',
'image/webp',
'image/x-icon'
])
const yDocs: Record<string, Doc> = {} const yDocs: Record<string, Doc> = {}
const providers: Record<string, HocuspocusProvider> = {} const providers: Record<string, HocuspocusProvider> = {}
@ -61,6 +75,10 @@ export const Editor = (props: Props) => {
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false) const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false) const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
const {
actions: { showSnackbar }
} = useSnackbar()
const docName = `shout-${props.shoutId}` const docName = `shout-${props.shoutId}`
if (!yDocs[docName]) { if (!yDocs[docName]) {
@ -114,12 +132,73 @@ export const Editor = (props: Props) => {
content: 'figcaption image' content: 'figcaption image'
}) })
const handleClipboardPaste = async () => {
try {
const clipboardItems = await navigator.clipboard.read()
if (clipboardItems.length === 0) return
const [clipboardItem] = clipboardItems
const { types } = clipboardItem
const imageType = types.find((type) => allowedImageTypes.has(type))
if (!imageType) return
const blob = await clipboardItem.getType(imageType)
const extension = imageType.split('/')[1]
const file = new File([blob], `clipboardImage.${extension}`)
const uplFile = {
source: blob.toString(),
name: file.name,
size: file.size,
file
}
showSnackbar({ body: t('Uploading image') })
const result = await handleFileUpload(uplFile)
editor()
.chain()
.focus()
.insertContent({
type: 'capturedImage',
content: [
{
type: 'figcaption',
content: [
{
type: 'text',
text: result.originalFilename
}
]
},
{
type: 'image',
attrs: {
src: imageProxy(result.url)
}
}
]
})
.run()
} catch (error) {
console.log('!!! Paste image Error:', error)
}
}
const { initialContent } = props const { initialContent } = props
const editor = createTiptapEditor(() => ({ const editor = createTiptapEditor(() => ({
element: editorElRef.current, element: editorElRef.current,
editorProps: { editorProps: {
attributes: { attributes: {
class: 'articleEditor' class: 'articleEditor'
},
transformPastedHTML(html) {
return html.replaceAll(/<img.*?>/g, '')
},
handlePaste: () => {
handleClipboardPaste()
return false
} }
}, },
extensions: [ extensions: [
@ -246,6 +325,7 @@ export const Editor = (props: Props) => {
TrailingNode, TrailingNode,
Article Article
], ],
enablePasteRules: [Link],
content: initialContent ?? null content: initialContent ?? null
})) }))

View File

@ -15,7 +15,7 @@ export const renderUploadedImage = (editor: Editor, image: UploadedFile) => {
content: [ content: [
{ {
type: 'text', type: 'text',
text: image.originalFilename text: image.originalFilename ?? ''
} }
] ]
}, },