ckeditor5-react icon indicating copy to clipboard operation
ckeditor5-react copied to clipboard

How to trigger a popup model (A React Component) from CKEditor plugin button inside toolbar?

Open SuryaKavutarapu opened this issue 4 years ago • 4 comments

Requirement: Create a plugin that should have a button in the toolbar, On click should open a popup window which is a react component defined outside the plugin component.

export default class OpenPopup extends Plugin {
    init() {
        const editor = this.editor;
        editor.ui.componentFactory.add('openPopup', locale => {
            const view = new ButtonView(locale);
            view.set({
                label: 'Open Popup model',
                icon: popupImage,
                tooltip: true
            });

            // Callback executed once the button is clicked.
            view.on('execute', () => {
                
            });

            return view;
        });
    }
}

Popup code


import React, { useEffect, useState } from 'react';
import PropTypes from "prop-types";
import { Modal } from 'react-bootstrap';
const Popup= ({ show = false }) => {
    
    return (
            <Modal
                size="xl"
                show={show}
                dialogClassName="add-media-modal"
                onHide={()=> {show = false}}
            >
                <Modal.Header>
                    <Modal.Title id="contained-modal-title-vcenter">
                        Add Media
                    </Modal.Title>
                    <button type="button" className="btn btn-primary btn-sm" onClick={handleCloseModal}>X</button>
                </Modal.Header>
                <Modal.Body>
                      
                </Modal.Body>
                <Modal.Footer>
                   
                    <button
                        type="button" className="btn btn-primary btn-sm"
        
                    >
                        Add
                    </button>
                </Modal.Footer>
            </Modal>
    )
}

Popup.propTypes = {
    show: PropTypes.bool
};

export default Popup

I was looking for various options or solutions to the above requirement.

SuryaKavutarapu avatar Dec 28 '21 19:12 SuryaKavutarapu

Hello @pomek, Hope you are doing great! Can you please show some light here? It was a roadblock to me.

Thanks in advance!

SuryaKavutarapu avatar Dec 28 '21 20:12 SuryaKavutarapu

@SuryaKavutarapu I'm trying to achieve the same thing, did you find a satisfying solution ?

jcerri avatar Mar 29 '22 13:03 jcerri

I had the same problem, I solved it by adding a callback function to the config, the custom plugin just returns the state when you click the button

I'm not sure if this is the right way

// editor.tsx

import React, { FC, useState } from 'react';
import { CKEditor as Editor } from '@ckeditor/ckeditor5-react';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import editorConfiguration from './config';

......

import { Dialog } from '**dialog component**';

interface CKEditorProps {
  ...
}

const CKEditor: FC<CKEditorProps> = (props) => {
  // dialog visible state
  const [visible, setVisible] = useState(false);

  ......

  return (
    <div className='editor-content'>
      <Editor
        editor={DecoupledEditor}
        config={{
          ...editorConfiguration,
          importArticle: {
            handleVisibleDialog: bool => setVisible(bool),
          },
        }}
        ......
      />

      {/* my custom dialog */}
      <Dialog header='Basic Modal' visible={visible} confirmOnEnter onClose={() => setVisible(false)}>
        <p>This is a dialog</p>
      </Dialog>
    </div>
  );
};

export default CKEditor;

// config.ts

......
import ImportArticle from './utils/import-article';

const editorConfiguration = {
  plugins: [
    ......
    ImportArticle,
  ],

  toolbar: [
    ......
    'importArticle',
  ],

  ......
};

export default editorConfiguration;
// import-article.ts

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';

class ImportArticle extends Plugin {
  init() {
    const { editor } = this;

    editor.ui.componentFactory.add('importArticle', () => {
      const button = new ButtonView();

      button.set({
        label: 'label name',
        withText: true,
      });

      button.on('execute', () => {
        // get the custom callback function in config
        const { handleVisibleDialog } = editor.config.get('importArticle');
        handleVisibleDialog(true);
      });

      return button;
    });
  }
}

export default ImportArticle;

bemzhao avatar Nov 09 '22 07:11 bemzhao

It seems a better way to use Portals

// editor.tsx

import React, { FC, useState } from 'react';
import { CKEditor as Editor } from '@ckeditor/ckeditor5-react';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import editorConfiguration from './config';

......

import { DialogWithBtn } from '**dialog component**';

interface CKEditorProps {
  ...
}

const CKEditor: FC<CKEditorProps> = (props) => {
  const [importArticleWrap, setImportArticleWrap] = useState<Element | null>(null);

  const handleReady = async (editor: Editor) => {
    const importArticleWrapDom = document.querySelector('.toolbar-portals__import-article');
    setImportArticleWrap(importArticleWrapDom);
  };

  return (
    <div className='editor-content'>
      <Editor
        editor={DecoupledEditor}
        config={editorConfiguration}
        onReady={handleReady}
        ......
      />

      <ImportArticleDialog portalContainer={importArticleWrap} />
    </div>
  );
};

export default CKEditor;
// import-article-dialog.tsx

import React, { FC } from 'react';
import { createPortal } from 'react-dom';
import { Dialog } from '...';

interface ImportArticleDialogProps {
  portalContainer: Element | null;
}

const ImportArticleDialog: FC<ImportArticleDialogProps> = ({ portalContainer }) => {
  ......

  return (
    <>
      {portalContainer
        && createPortal(
            <button
              className='ck ck-button ck-off ck-button_with-text'
              onClick={() => do someing to show the dialog }
            >
              import article
            </button>
          portalContainer,
        )}

      <Dialog>
        ...
      </Dialog>
    </>
  );
};

export default ImportArticleDialog;

// ImportArticle plugin

import { DecoupledEditor } from '@ckeditor/ckeditor5-editor-decoupled';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import View from '@ckeditor/ckeditor5-ui/src/view';

class ImportArticle extends Plugin {
  init() {
    const { editor } = this;

    (editor as DecoupledEditor).ui.componentFactory.add('importArticle', () => {
      const view = new View();

      // Provide a container to insert custom react components by useing Portals
      view.setTemplate({
        tag: 'div',

        attributes: {
          class: 'toolbar-portals__import-article',
        },
      });

      return view;
    });
  }
}

export default ImportArticle;
// config.ts

......
import ImportArticle from './utils/import-article';

const editorConfiguration = {
  plugins: [
    ......
    ImportArticle,
  ],

  toolbar: [
    ......
    'importArticle',
  ],

  ......
};

export default editorConfiguration;

bemzhao avatar Nov 15 '22 06:11 bemzhao