首页 > 解决方案 > 如何将自定义图像选择器连接到 CKeditor5 在反应 js 中插入图像

问题描述

我正在尝试将 CKeditor 集成到自定义文档编辑器中,使用 ckeditor5-react 模块很容易将状态集成到数据中,但是在我的特定用例中,插入图像的默认行为并不理想。我的应用程序中内置了一个图像库,因此我希望 CKeditor 按钮打开我的图像库组件,以便用户可以从库中选择图像,然后将该图像插入光标所在的位置。在尝试使用常规的、开箱即用的 ClassicEditor 构建来实现这一点后,我意识到这是不可能的,所以我创建了一个自定义插件来实现我想要的:

import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Image from '@ckeditor/ckeditor5-image/src/image';
import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
import Link from '@ckeditor/ckeditor5-link/src/link';
import List from '@ckeditor/ckeditor5-list/src/list';
import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';

import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import mix from '@ckeditor/ckeditor5-utils/src/mix';

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

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

import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';

export default class ClassicEditor extends ClassicEditorBase {}

class InsertImage extends Plugin {
    init() {
    const editor = this.editor;

    editor.ui.componentFactory.add( 'insertImage', locale => {
      const view = new ButtonView( locale );

      view.set( {
        label: 'Insert image',
                icon: imageIcon,
        tooltip: true,
      } );

      // set observables on editor
      editor.set( { 
        insertImageRequested: false,
        imageFileRequested: null
      } );


        // Callback executed once the image button is clicked.
      view.on( 'execute', () => {
        // set observable to indicate a request to insert image has been made...
        editor.set( { insertImageRequested: true } );
      });

      // Now this waits for the user to have selected a file URL which should show up 
      // in the imageFileRequested observable
      editor.on( 'change:imageFileRequested', (evt, newShizz, oldShizz) => {
        // // Which then injects the image into the body...
        const imageUrl = editor.imageFileRequested;
        editor.model.change( writer => {
          const imageElement = writer.createElement( 'image', {
            src: imageUrl
          } );
          // Insert the image in the current selection location.
          editor.model.insertContent( imageElement, editor.model.document.selection );
        });

        // and unsets our observables back to their original state
        editor.set( { 
          insertImageRequested: false,
          imageFileRequested: null
        } );
      } )

            return view;
        } );
    }
}
mix( InsertImage, ObservableMixin );

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
    Essentials,
    Autoformat,
    Bold,
    Italic,
    BlockQuote,
    EasyImage,
    Heading,
    Image,
    ImageStyle,
    ImageToolbar,
    ImageUpload,
    Link,
    List,
    MediaEmbed,
    Paragraph,
    PasteFromOffice,
    Table,
    TableToolbar,
    InsertImage
];

// Editor configuration.
ClassicEditor.defaultConfig = {
    toolbar: {
        items: [
            'heading',
            '|',
            'bold',
            'italic',
            'link',
            'bulletedList',
            'numberedList',
            'blockQuote',
            'insertTable',
            'mediaEmbed',
            'undo',
            'redo',
            '|',
            'InsertImage'
        ]
    },
    image: {
        toolbar: [
            'imageStyle:full',
            'imageStyle:side',
            '|',
            'imageTextAlternative'
        ]
    },
    table: {
        contentToolbar: [
            'tableColumn',
            'tableRow',
            'mergeTableCells'
        ]
    },
    // This value must be kept in sync with the language defined in webpack.config.js.
    language: 'en'
};

从上面的代码可以看出,它使用了CKeditor5的Observable events特性来设置2个observable:insertImageRequested(bool)或者imageFileRequested(null或者string)。现在在我的反应组件中,我收到“insertImageRequested”更改事件并更新我的状态,以便库应该打开并且我的组件可以正常工作(但是光标确实移动到了文档的开头,这是我不想要的)。问题是我似乎无法让编辑器允许我从编辑器外部使用所选图像 URL 设置第二个 Observable“imageFileRequested”,因此我无法将 fileURL 字符串发送回自定义编辑器以设置 Observable

import React from 'react';
import PropTypes from 'prop-types';

...

import CKEditor from '@ckeditor/ckeditor5-react';
//import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import CustomEditor from './customCKeditor/ckeditor.js';

import styles from './articleEditor.scss'; 

class ArticleEditor extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      insertImageRequested: false,
      imageFileRequested: null,
      eventLogData: null
    }

    ...

    this.imageClick = this.imageClick.bind(this);
  }

  ...

  imageClick(){
// Temp Data to be passed to custom Ckeditor
    let tempURL = "https://via.placeholder.com/350x150";
    this.setState({
      insertImageRequested: false,
      imageFileRequested: tempURL
    })

  // HOW DO I PASS 'tempURL' variable to editor so that I can set it as the value for the imageFileRequested observable (so CKeditor on change event is triggered as shown above)?

  }

  render() {
    return(
        <div className={styles.container}>
          <CKEditor
            editor={ CustomEditor }
            data={this.state.articleBody}
            name={'articleBody'}

            config={{
                toolbar: [ 'InsertImage', '|', 'heading', '|', 'bold', 'italic', '|', 'link', 'bulletedList', 'numberedList', 'blockQuote', '|', "mediaEmbed", '|', 'undo', 'redo', '|', "insertTable", "tableColumn", "tableRow", "mergeTableCells", '|' ],
                heading: {
                    options: [
                        { model: 'paragraph', title: 'Paragraph' },
                        { model: 'heading1', view: 'h1', title: 'Heading 1'},
                        { model: 'heading2', view: 'h2', title: 'Heading 2'},
                        { model: 'heading3', view: 'h3', title: 'Heading 3'},
                        { model: 'heading4', view: 'h4', title: 'Heading 4'},
                        { model: 'heading5', view: 'h5', title: 'Heading 5'},
                        { model: 'heading6', view: 'h6', title: 'Heading 6'}
                    ]
                }
            }}

            onInit={ editor => {
              // A request for a new image has been made
              editor.on( 'change:insertImageRequested', (evt, newShizz, oldShizz) => {
                //reflect that in the react app state
                this.setState({
                  insertImageRequested: true
                })
              } )
            } }

            onChange={ ( event, editor ) => {
                console.log(event);
                console.log(editor);
                //console.log(editor.insertImageRequested);
                const data = editor.getData();
                console.log( { event, editor, data } );
                this.updateArticleBodyState(data);
                this.eventLogger(editor);

                // THIS DOES NOT WORK
                // //if(editor.imageFileRequested != null){
                //     console.log("imagefilerequested is defined!")
                //     editor.set( { imageFileRequested: this.state.imageFileRequested } );
                // //}

            } }
            onBlur={ editor => {
                //console.log( 'Blur.', editor );
            } }
            onFocus={ editor => {
                //console.log( 'Focus.', editor );
            } }
          />
        </div>
    );
  }
}


...

经过 2 天的努力,解决方案从相对简单到不必要的复杂,我想到我这样做的方式很可能是不正确的,我的整个方法都是错误的,所以我想我的问题是两个 -折叠。首先,上述方法是我正在做的正确方法吗?如果是,你能帮我将 tempUrl 变量设置为 ckeditor 中的 observable 吗?其次,如果这完全是错误的方法,请您帮我找到正确的方法来实现它。如果需要,我很乐意提供更多信息,但是这篇文章已经变得很长,所以我会结束,提前感谢您提供的任何帮助。

标签: javascriptreactjsecmascript-6ckeditor5

解决方案


最后我选择了 Quill 和 React-quill。它不需要对 webpack 进行更改,并且具有适合我的适度需求的 API。


推荐阅读