0

Xây dựng trình soạn thảo văn bản RichText mạnh mẽ với TipTap và React (kèm tính năng Mentions)

Bạn muốn ứng dụng React của mình sở hữu một trình soạn thảo RichText vừa mạnh mẽ vừa dễ tùy chỉnh? TipTap chính là lựa chọn tuyệt vời dành cho bạn. Hãy cùng tìm hiểu cách tích hợp TipTap, thêm tính năng mentions, và xử lý một số vấn đề phát sinh thường gặp khi xây dựng trình soạn thảo này.

Bước 1: Cài đặt các phụ thuộc

Trước khi bắt đầu, hãy cài đặt các thư viện cần thiết:

npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-mention

Bước 2: Tạo một trình soạn thảo RichText cơ bản

Bắt đầu bằng cách tạo một thành phần RichTextEditor. Sau đây là một triển khai đơn giản:

import { useEditor, EditorContent } from '@tiptap/react';  

import StarterKit from '@tiptap/starter-kit';  

export const RichTextEditor = ({ content, onChange }) => {  
  const editor = useEditor({  
    extensions: [StarterKit],  
    content: content,  
    onUpdate: ({ editor }) => {  
      onChange(editor.getHTML());  
    },  
  });  
  return <EditorContent editor={editor} />;  
};

Bước 3: Thêm Mention

Tính năng mentions giúp tăng cường tương tác người dùng, đặc biệt trong các ứng dụng chat hoặc cộng tác. Để thêm mentions, chúng ta cần cài đặt và cấu hình Mention extension từ @tiptap/extension-mention.

import Mention from '@tiptap/extension-mention';  

export const RichTextEditor = ({ content, onChange, mentions }) => {  
  const editor = useEditor({  
    extensions: [  
      StarterKit,  
      Mention.configure({  
        HTMLAttributes: { class: 'mention' },  
        suggestion: {  
          items: ({ query }) =>  
            mentions.filter(item => item.display.toLowerCase().includes(query.toLowerCase())).slice(0, 5),  
          render: () => {  
            let component;  
            let popup;  
            return {  
              onStart: (props) => {  
                popup = document.createElement('div');  
                popup.className = 'mention-popup';  
                document.body.appendChild(popup);  
                component = {  
                  updateProps: () => {  
                    popup.innerHTML = `  
                      <div class="items">  
                        ${props.items.map(item => `  
                          <button class="item ${item.selected ? 'is-selected' : ''}">${item.display}</button>  
                        `).join('')}  
                      </div>  
                    `;  
                  },  
                  destroy: () => popup.remove(),  
                };  
                popup.addEventListener('click', (e) => {  
                  const button = e.target.closest('button');  
                  if (button) {  
                    const index = Array.from(popup.querySelectorAll('.item')).indexOf(button);  
                    props.command({ id: props.items[index].id, label: props.items[index].display });  
                  }  
                });  
                component.updateProps();  
              },  
              onExit: () => component?.destroy(),  
            };  
          },  
        },  
      }),  
    ],  
    content,  
    onUpdate: ({ editor }) => onChange(editor.getHTML()),  
  });  
  return <EditorContent editor={editor} />;  
};

Bước 4: Định dạng Popup Mention

Các mentions phải khác biệt về mặt hình ảnh. Thêm các kiểu sau để tăng khả năng sử dụng:

.mention-popup {  
  background: white;  
  border-radius: 8px;  
  box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);  
  padding: 8px;  
  position: absolute;  
  z-index: 1000;  
}  
.mention-popup .items {  
  display: flex;  
  flex-direction: column;  
}  
.mention-popup .item {  
  padding: 8px;  
  cursor: pointer;  
  border-radius: 4px;  
}  

.mention-popup .item:hover,  
.mention-popup .item.is-selected {  
  background: #f0f0f0;  
}

Bước 5: Các trường hợp ngoại lệ sẽ gặp phải trong quá trình triển khai

1. Con trỏ nhảy: Để tránh con trỏ nhảy khi đang gõ, hãy đảm bảo các bản cập nhật nội dung giữ nguyên vị trí của con trỏ:

const editor = useEditor({  
  extensions: [StarterKit],  
  content,  
  onUpdate: ({ editor }) => {  
    const selection = editor.state.selection;  
    onChange(editor.getHTML());  
    editor.commands.setTextSelection(selection);  
  },  
});

2. Không hiển thị Placeholder: Sử dụng tiện ích mở rộng Placeholder để hiển thị gợi ý khi trình soạn thảo trống:

import Placeholder from '@tiptap/extension-placeholder';  
const editor = useEditor({  
  extensions: [  
    StarterKit,  
    Placeholder.configure({ placeholder: 'Type something...' }),  
  ],  
});

Cuối cùng, nếu danh sách gợi ý mention không hiển thị, hãy kiểm tra lại phương thức suggestion.items để đảm bảo nó lọc và trả về danh sách đúng như mong đợi.

Bước 6: Tích hợp vào ứng dụng của bạn

Để tích hợp trình soạn thảo vào ứng dụng, bạn có thể đặt nó trong một modal hoặc form component, ví dụ như form tạo thông báo. Form này sẽ chứa component RichTextEditor, nhận nội dung từ editor và gửi đi khi người dùng submit.

import React from 'react';  

const NotificationForm = ({ mentions, onSubmit }) => {  
  const [content, setContent] = React.useState('');  
  return (  
    <form onSubmit={() => onSubmit(content)}>  
      <RichTextEditor content={content} onChange={setContent} mentions={mentions} />  
      <button type="submit">Send</button>  
    </form> 
  );  
};

Kết luận

Với TipTap, việc xây dựng trình soạn thảo RichText mạnh mẽ và thân thiện với người dùng trở nên dễ dàng hơn bao giờ hết. Tính năng mentions giúp tăng cường tính tương tác, làm cho ứng dụng của bạn hấp dẫn hơn đối với người dùng. Để tìm hiểu thêm, hãy truy cập trang web chính thức của TipTap.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí