Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difficulty Inserting HTML into Quill React Component #951

Open
kamruzzaman-gain opened this issue Feb 22, 2024 · 1 comment
Open

Difficulty Inserting HTML into Quill React Component #951

kamruzzaman-gain opened this issue Feb 22, 2024 · 1 comment

Comments

@kamruzzaman-gain
Copy link

kamruzzaman-gain commented Feb 22, 2024

Issue Summary:

Unable to insert html into Quill React Component.

Environment:

React version: "18.2.0"
react-quill: "2.0.0"
next:"14.1.0"

Code snippet:

My Component file:

'use client';

import { useLazyQuery } from '@apollo/client';
import dynamic from 'next/dynamic';
import React, { useRef, useEffect, useState } from 'react';

import { getUserFullName, toastAlert } from '@/utils';

import 'react-quill/dist/quill.snow.css';

import { GET_ORGANIZATION_USERS } from '@/components/user-and-roles/graphql/queries/getOrganizationUsers.gql';
import { useSelector } from 'react-redux';
import { size } from 'lodash';
import { useDebounce } from '@/helpers/hooks';

const loadReactQuill = async () => {
  const { default: RQ } = await import('react-quill');
  const reactQuillWithRef = ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;

  return reactQuillWithRef;
};

const ReactQuill = dynamic(loadReactQuill, {
  ssr: false
});

const AppEditor = ({
  readOnly = false,
  onChange,
  allowMention = false,
  value = '',
  placeholder = '',
  toolbarBottom = false,
  setMentionedIds = []
}) => {
  const editorRef = useRef(null);
  const quillContainerRef = useRef(null);
  const [showMentionList, setShowMentionList] = useState(false);
  const [mentionListPosition, setMentionListPosition] = useState({
    top: 0,
    left: 0
  });
  const [cursorIndex, setCursorIndex] = useState({});
  const userData = useSelector((state) => state.auth.user);

  const [agentsState, setAgentsState] = useState({
    data: [],
    metaData: {},
    loading: true,
    loaded: false,
    searchKeyword: '',
    optionData: {
      limit: 50,
      offset: 0,
      order: [['created_at', 'desc']]
    }
  });

  const [getAgents] = useLazyQuery(GET_ORGANIZATION_USERS, {
    errorPolicy: 'all',
    fetchPolicy: 'no-cache',
    variables: {
      queryData: { roleName: 'org_agent', search_keyword: agentsState.searchKeyword },
      optionData: agentsState.optionData
    },

    onCompleted: (data) => {
      const currentUserId = userData?.user_id;

      const preparedData =
        data?.getOrganizationUsers?.data?.map((org_user) => {
          let title =
            org_user?.user?.first_name || org_user?.user.last_name
              ? getUserFullName(org_user?.user?.first_name, org_user?.user?.last_name)
              : org_user?.user?.email;

          if (org_user?.user?.id === currentUserId) {
            title += ' (You)';
          }
          return { ...org_user, key: org_user?.id, title };
        }) || [];
      setAgentsState((prevState) => ({
        ...prevState,
        data:
          agentsState.optionData.offset === 0 ? preparedData : [...prevState.data, ...preparedData],
        metaData: data?.getOrganizationUsers?.meta_data || {},
        loading: false,
        loaded: true
      }));
    },
    onError: (error) => {
      console.error(error);
      setAgentsState((prevState) => ({ ...prevState, loading: false }));
    }
  });

  const updateQueryData = useDebounce((value) => {
    setAgentsState((prev) => ({
      ...prev,
      loading: true,
      searchKeyword: value,
      data: [],
      optionData: { ...prev.optionData, offset: 0 },
      metaData: {}
    }));
  }, 500);

  const handleContentChange = (content, delta, source, editor) => {
    if (allowMention && source === 'user') {
      const cursorPosition = editor.getSelection()?.index;
      if (cursorPosition) {
        const textBeforeCursor = editor.getText(0, cursorPosition);
        const atIndex = textBeforeCursor.lastIndexOf('@');
        const spaceIndex = textBeforeCursor.indexOf(' ', atIndex);
        if (atIndex > -1 && spaceIndex < 0) {
          const wordEnd = textBeforeCursor.length;
          const searchTerm = textBeforeCursor.substring(atIndex + 1, wordEnd);
          setCursorIndex({ startText: atIndex, endText: wordEnd });
          updateQueryData(searchTerm);
          setShowMentionList(true);
          const bounds = editor.getBounds(cursorPosition);
          setMentionListPosition({ top: bounds.bottom, left: bounds.left });
        } else {
          setShowMentionList(false);
        }
      }
    }
    onChange(content);
  };

  const handleSelectMention = (mention) => {
    setMentionedIds((prev) => [...prev, mention.id]);
    const editor = editorRef.current.getEditor();
    const range = editor.getSelection(true);
    if (range) {
      updateQueryData('');
      editor.deleteText(cursorIndex.startText, cursorIndex.endText);
      editor.clipboard.dangerouslyPasteHTML(
        cursorIndex.startText,
        `<p><strong id='${mention.id}'>@${mention.title}</strong></p>`
      );
      setShowMentionList(false);

      onChange(editor.root.innerHTML);
    }
  };
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (quillContainerRef.current && !quillContainerRef.current.contains(event.target)) {
        setShowMentionList(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  useEffect(() => {
    if (editorRef.current) {
      const editor = editorRef.current.getEditor();
      if (editor && editor.container) {
        quillContainerRef.current = editor.container;
      }
    }
  }, [editorRef]);

  useEffect(() => {
    getAgents();
  }, []);

  const modules = {
    toolbar: {
      container: [
        [{ header: [1, 2, false] }],
        ['bold', 'italic', 'underline', 'strike', 'blockquote'],
        [({ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' })],
        ['link', 'image', 'video']
      ]
    }
  };

  const formats = [
    'header',
    'bold',
    'italic',
    'underline',
    'strike',
    'blockquote',
    'list',
    'bullet',
    'indent',
    'link',
    'image',
    'video'
  ];

  const editorClassName = toolbarBottom ? 'editor-toolbar-bottom' : '';

  return (
    <div className={`react-quill-editor ${editorClassName} relative`}>
      <ReactQuill
        forwardedRef={editorRef}
        theme="snow"
        value={value}
        onChange={handleContentChange}
        readOnly={readOnly}
        modules={modules}
        formats={formats}
        placeholder={placeholder}
      />
      {showMentionList && (
        <div
          className="absolute z-10 mt-1 bg-white rounded border border-gray-300 shadow-lg"
          style={{
            top: mentionListPosition.top,
            left: mentionListPosition.left
          }}
        >
          {size(agentsState.data) > 0 &&
            agentsState.data.map((mention) => (
              <div
                key={mention.id}
                className="px-4 py-2 cursor-pointer hover:bg-blue-100"
                onMouseDown={(e) => {
                  e.preventDefault();
                  handleSelectMention(mention);
                }}
              >
                {mention.title}
              </div>
            ))}
        </div>
      )}
    </div>
  );
};

export default AppEditor;

I have alos tried with parchment package but this also not work.

Here the Code sinppet

const loadReactQuill = async () => {
  const { default: RQ } = await import('react-quill');

  const Parchment = RQ.Quill.import('parchment');
  const Block = Parchment.query('block');

  class ButtonBlot extends Block {
    static create(value) {
      const node = super.create(value);

      node.setAttribute('class', 'button-class');
      node.setAttribute('style', 'button-style');

      return node;
    }
  }

  ButtonBlot.blotName = 'button';
  ButtonBlot.tagName = 'button';

  RQ.Quill.register(ButtonBlot);

  const reactQuillWithRef = ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;

  return reactQuillWithRef;
};
@goodafteryoon
Copy link

Did you solve this problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants