'use client';
import { useEffect, useMemo, useRef } from 'react';
import { supabase } from '@/libs/supabase';
import { ImageActions } from '@xeger/quill-image-actions';
import { ImageFormats } from '@xeger/quill-image-formats';
import hljs from 'highlight.js';
import c from "highlight.js/lib/languages/c";
import javascript from "highlight.js/lib/languages/javascript";
import python from "highlight.js/lib/languages/python";
import typescript from "highlight.js/lib/languages/typescript";
import 'highlight.js/styles/github.css';
import ReactQuill, { Quill } from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import { v4 as uuidv4 } from 'uuid';
Quill.register('modules/imageActions', ImageActions);
Quill.register('modules/imageFormats', ImageFormats);
interface QuillEditorProps {
content: string;
setContent: (char: string) => void;
}
hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("python", python);
hljs.registerLanguage("typescript", typescript);
hljs.registerLanguage("c", c);
const formats = [
'font',
'size',
'header',
'color',
'background',
'bold',
'italic',
'align',
'underline',
'strike',
'blockquote',
'list',
'bullet',
'indent',
'link',
'image',
'video',
'formula',
'float',
'height',
'width',
'code-block'
];
const Size = Quill.import('formats/size');
Size.whitelist = ['small', 'medium', 'large', 'huge'];
Quill.register(Size, true);
const QuillEditor: React.FC<QuillEditorProps> = ({ content, setContent }) => {
const quillRef = useRef<ReactQuill>(null);
const imageHandler = async () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files?.[0];
if (!file) return;
const uniqueFileName = uuidv4();
const filePath = `uploads/images/${uniqueFileName}`; // 파일을 저장할 경로와 파일 이름
const contentType = file.type; // 파일의 MIME 타입
try {
const { error: uploadError, data } = await supabase.storage
.from('images') // 스토리지 버킷 이름
.upload(filePath, file, {
contentType,
upsert: false,
});
if (uploadError) throw uploadError;
const publicUrl = `https://rgvzlonuavmjvodmalpd.supabase.co/storage/v1/object/public/images/${data?.path}`;
console.log('publicUrl:', publicUrl);
// 에디터에 이미지 URL을 삽입합니다.
const editor = quillRef.current;
if (editor) {
const range = editor.getEditor().getSelection(true);
if (range) {
// insertEmbed의 첫 번째 인자는 커서 위치(index)입니다.
editor.getEditor().insertEmbed(range.index, 'image', publicUrl);
editor.getEditor().setSelection(range.index + 1, 0);
}
}
} catch (error: any) {
console.error('Error uploading image: ', error.message);
}
};
};
const modules = useMemo(() => {
return {
imageActions: {},
imageFormats: {},
syntax: {
highlight: (text: any) => hljs.highlightAuto(text).value,
},
toolbar: {
container: [
[{ font: [] }],
[{ header: '1' }, { header: '2' }],
[{ size: ['small', false, 'large', 'huge'] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[
{ align: '' },
{ align: 'center' },
{ align: 'right' },
{ align: 'justify' },
],
[
{ list: 'ordered' },
{ list: 'bullet' },
{ indent: '-1' },
{ indent: '+1' },
],
['code-block'],
[{ color: [] }, { background: [] }],
['link', 'image', 'video', 'formula'],
['clean'],
],
handlers: {
image: imageHandler,
},
},
clipboard: {
matchVisual: false,
},
};
}, []);
useEffect(() => {
document.querySelectorAll('pre code').forEach((block) => {
if (block instanceof HTMLElement) {
hljs.highlightBlock(block);
}
});
}, [content]);
return (
<div>
<ReactQuill
ref={quillRef}
style={{
width: '100%',
backgroundColor: '#FFF',
}}
theme="snow"
placeholder={'내용을 입력해주세요.'}
defaultValue={content}
value={content}
// onChange={(content, delta, source, editor) =>
// setContent(editor.getHTML())
// }
onChange={(value) => setContent(value)}
modules={modules}
formats={formats}
/>
</div>
);
};
export default QuillEditor;