Open DaisukeTujita opened 1 year ago
インターネット上の情報等によりメジャーなライブラリを特定。いずれも要件を満たしている。
名称 | 概要 | Githubスター数 |
---|---|---|
Draft.js | Meta製。2020年にアーカイブ化され開発されていない。 | 22.5k |
lexical | Meta製。Draftの後継。フレームワークなので作りこみが必要 | 15.2k |
tiptap | ヘッドレスUI。マンタインのベース。 | 20.7k |
quill | slackのWeb版で使われている | 36.7k |
ほぼ同様の選定を行っているサイトの情報。 2023年2月時点で9つのライブラリを比較しており、結果的にMantineを選定している。
画像アップロードメニューを追加
コード構成
src
├─app
| └─test_mantine
| └─page.tsx
├─components
| ├─InsertImageControl.tsx
| ├─MantineRichTextEditor.tsx
| └─MantineSaveButton.tsx
page.tsx
import { MantineRichTextEditor } from '@/components/MantineRichTextEditor';
import { MantineSaveButton } from '@/components/MantineSaveButton';
export default function Mantine() {
const content ='' //ここはサーバサイトでfetch
return (
<>
<MantineSaveButton />
<MantineRichTextEditor initailContent={content}/>
</>
)
}
MantineRichTextEditor.tsx
"use client"
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
import { InsertImageControl } from './InsertImageControl';
import { useState } from 'react';
import Image from '@tiptap/extension-image'
type MantineRichTextEditorProp = {
initailContent : string
}
export function MantineRichTextEditor(props : MantineRichTextEditorProp) {
const {initailContent} = props
const [content, setContent] = useState(initailContent);
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
Image,
],
content: content ?? '',
onUpdate: ({ editor }) => {
localStorage.setItem("content", editor.getHTML())
},
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
{/* 画像アップロード用メニューを追加 */}
<RichTextEditor.ControlsGroup>
<InsertImageControl />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
InsertImageControl.tsx
import { RichTextEditor, useRichTextEditorContext } from '@mantine/tiptap'
import { useRef } from 'react'
import {IoImageOutline} from 'react-icons/io5'
export const InsertImageControl = () => {
const { editor } = useRichTextEditorContext()
const inputRef = useRef<HTMLInputElement>(null)
const uploadImage = () => inputRef?.current?.click()
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// filesアップロード処理
const file = e.target.files && e.target.files[0]
if (!file) return
try{
let reader = new FileReader()
reader.onloadend = () => {
const imagePreviewUrl = reader.result?.toString()
// 本来はここでストレージアップロード
if (imagePreviewUrl) {
editor
.chain()
.focus()
.setImage({src: imagePreviewUrl})
.run()
localStorage.setItem("content", editor.getHTML())
}
}
reader.readAsDataURL(file)
}catch(err){
console.error(err)
}
}
return (
<>
<RichTextEditor.Control onClick={uploadImage} aria-label="画像" title="画像">
{/* アイコン */}
<IoImageOutline />
</RichTextEditor.Control>
<input
type="file"
accept="image/*"
aria-label="画像アップロード"
value=""
ref={inputRef}
style={{ display: `none` }}
onChange={handleFileChange}
/>
</>
)
}
MantineSaveButton.tsx
"use client"
export function MantineSaveButton() {
const handleSave = () => {
const content = localStorage.getItem("content")
console.log(content) // 本来はここでpostしてDBに登録
}
return (
<button onClick={handleSave}>Save</button>
);
}
シーケンス
リッチテキストエディタ選定
目的
下書きページでレシピ工程を作成する際に使用するリッチテキストエディタライブラリを選定する
前提
要件
候補の選び方と選定方法