background

A new requirement in the company’s recent project is that the rich text editor should support file uploads and then display them as hyperlinks, like this:

React is used for the company project. Previously, react- draft-WYSIWYg was used as the rich text editor. If you go back to the editor then the input/output ratio is out of balance. So, the best way to do this is to modify the editor.

start

React draft-WYSIWYg is the most popular rich text editor for React. It is based on draft-JS. So long.

Draft-js is the React rich text editor framework. Note that it is a framework for developing your own editor. Draft-js provides a rich API for you to call.

Fork react-draft-WYSIWYg project & Clone to local

After the project is pulled down, let’s first look at package.json to see how it works:

"scripts": {
    "clean": "rimraf dist"."build:webpack": "cross-env NODE_ENV=production webpack --config config/webpack.config.js"."build": "npm run clean && npm run build:webpack"."test": "cross-env BABEL_ENV=test mocha --compilers js:config/test-compiler.js config/test-setup.js src/**/*Test.js"."lint": "eslint src"."lintdocs": "eslint docs/src"."flow": "flow; test $? -eq 0 -o $? -eq 2"."check": "npm run lint && npm run flow"."storybook": "start-storybook -p 6006"."build-storybook": "build-storybook"
 },
Copy the code

As you can see, it runs in storybook, so NPM install && NPM run storybook. After running, visit http://localhost:6006. We can see that it is a demo collection of editors, looking for it does not have file upload function.

/ / SRC document structure ├ ─ ─ components | ├ ─ ─ Dropdown can | ├ ─ ─ Option | └ ─ ─ Spinner ├ ─ ─ the config | └ ─ ─ defaultToolbar. Js / / function button configuration ├ ─ ─ Controls / / placed component directory | ├ ─ ─ BlockType | ├ ─ ─ the Clear | ├ ─ ─ ColorPicker | ├ ─ ─ Embedded | ├ ─ ─ Emoji | ├ ─ ─ FileUpload | ├ ─ ─ FontFamily | ├ ─ ─ FontSize | ├ ─ ─ the History | ├ ─ ─ Image | ├ ─ ─ index. The js | ├ ─ ─ the Inline | ├ ─ ─ the Link | ├ ─ ─ the List | ├ ─ ─ the Remove | └ ─ ─ TextAlign ├ ─ ─ Editor | ├ ─ ─ index. The js / / Editor entry | ├ ─ ─ styles. The CSS | └ ─ ─ __test__ ├ ─ ─ i18n / / international | ├ ─ ─ da. Js | ├ ─ ─ DE. Js | ├ ─ ─ en. Js | ├ ─ ─ es. Js | ├ ─ ─ fr. Js | ├ ─ ─ index. The js | ├ ─ ─ it. Js | ├ ─ ─ ja. Js | ├ ─ ─ ko. Js | ├ ─ ─ nl. Js | ├ ─ ─ pl. Js | ├ ─ ─ pt. Js | ├ ─ ─ ru. Js | ├ ─ ─ useful. Js | └ ─ ─ zh_tw. Js ├ ─ ─ index. The js ├ ─ ─ the renderer | ├ ─ ─ Embedded | ├ ─ ─ Image | └ ─ ─ index. The js └ ─ ─ utils ├── └ style.js ├─ common.js ├─ handlePaste.js ├─ tool.jsCopy the code

Add an attachment upload button to the first toolbar:

Check code can see configuration where the toolbar in the SRC/config/defaultToolbar js

export default {
    options: [// Configure visible buttons."image"."fileUpload".// we add a fileUpload here to represent fileUpload].// The following attributes are specific configurations for each tool, such as image:
    image: {
        icon: image, // Button icon
        className: undefined.component: undefined.popupClassName: undefined.urlEnabled: false.// Whether url import is supported
        uploadEnabled: true.// Whether upload is supported
        previewImage: false.// Whether to support preview images
        alignmentEnabled: true.// Whether to display alignment buttons (equivalent to text-align)
        uploadCallback: undefined.// Upload callback, this is the main point
        inputAccept: undefined.// File format (only suitable for image format)
        alt: { present: false.mandatory: false },
        defaultSize: {
          height: "auto".width: "auto"
        },
        title: undefined // HTML title attribute}...// It is similar to image upload. Our file upload has many similarities. So we can refer to the configuration
    fileUpload: {
        icon: fileUpload,
        className: undefined.component: undefined.popupClassName: undefined.urlEnabled: false.uploadEnabled: true.previewFileUpload: true.alignmentEnabled: true.uploadCallback: function() {},
        inputAccept: undefined.alt: { present: false.mandatory: false },
        title: undefined}},Copy the code

Here we need to find an icon, you can directly find an icon representing upload in Ali’s Iconfont, save it as SVG format, and reference it above.

Step 2 Add the fileUpload upload component

Add folder SRC/Controls /FileUpload, refer to other component format, New three files FileUpload/index, js, FileUpload/Component/index, js, FileUpload/Component/style.css. CSS. All three files refer to the image component. Let’s talk about differences: FileUpload/index.js

. addLink:Function = (linkTitle, linkTarget, linkTargetOption): void= > {
    const { editorState, onChange } = this.props;// editorState is the instance of the current editor
    const { currentEntity } = this.state;
    let selection = editorState.getSelection();
    if (currentEntity) {
      const entityRange = getEntityRange(editorState, currentEntity);
      selection = selection.merge({
        anchorOffset: entityRange.start,
        focusOffset: entityRange.end
      });
    }

    const entityKey = editorState
      .getCurrentContent()
      .createEntity("LINK"."MUTABLE", {
        url: linkTarget,
        targetOption: linkTargetOption
      })
      .getLastCreatedEntityKey();

    let contentState = Modifier.replaceText(
      editorState.getCurrentContent(),
      selection,
      `${linkTitle}`,
      editorState.getCurrentInlineStyle(),
      entityKey
    );
    let newEditorState = EditorState.push(
      editorState,
      contentState,
      "insert-characters"
    );

    selection = newEditorState.getSelection().merge({
      anchorOffset: selection.get("anchorOffset") + linkTitle.length,
      focusOffset: selection.get("anchorOffset") + linkTitle.length
    });
    newEditorState = EditorState.acceptSelection(newEditorState, selection);
    contentState = Modifier.insertText(
      newEditorState.getCurrentContent(),
      selection,
      "".// Insert a space to prevent subsequent edits from falling inside the A tag
      newEditorState.getCurrentInlineStyle(),
      undefined
    );
    onChange(
      EditorState.push(newEditorState, contentState, "insert-characters"));this.doCollapse(); }; .Copy the code

The steps before uploading can be the same as the image component, but the steps after uploading are different. We know that the image component generates an IMG tag that displays the image directly in the editor. What we need now is to insert a hypertext link which is the A tag. So we need to transform addImage into the addLink method.

Then we transform the preview picture, making it a clickable links will be displayed after upload: FileUpload/Component/index. Js

. uploadFileUpload: Function = (file: Object): void => { this.toggleShowFileUploadLoading(); const { uploadCallback } = this.props.config; uploadCallback(file) .then(({ data }) => { this.setState({ showFileUploadLoading: false, dragEnter: false, fileSrc: Data. The link | | data url, fileName: data. The name / / access to the file name}); this.fileUpload = false; }) .catch(() => { this.setState({ showFileUploadLoading: false, dragEnter: false }); }); }; . render () { ... <label htmlFor="file" className="rdw-file-modal-upload-option-label" > {previewFileUpload && fileSrc ? ( <a style={{ width: "100%", wordBreak: Href ={fileSrc} > {fileName} </a>) : ( translations["components.controls.image.dropFileText"] )} </label> ... }Copy the code

When referencing an image upload component or a file upload component, we can configure the uploadCallback method to use its own upload interface to customize the upload operation, as long as the function returns a Promise object with {data: {link: “,name: “}}.

The last step

Publish our code, reference it in the build, and change package.json

{
    "name": "react-mysoft-wysiwyg"."version": "1.12.16". }Copy the code

Change the name to ensure that the NPM platform does not have the same name, change the version number.

Run NPM publish. Publish to the NPM platform, and then go to your NPM home page to see if you have published successfully. After a successful release. Install components directly in your own project.

npm install react-mysoft-wysiwyg --save

The above. Full project address.

When using the editor, I found a small bug, that is, no matter how to delete, there will always be a P tag, and if the P tag has style attribute, it can not be cleared. This leads to the problem of how to tell if the editor is empty. Let’s just add an ’empty’ button. The adding steps are similar to the above. The general idea is to initialize editorState when we hit Clear using the API provided by draft-JS:

. removeInlineStyles:Function= () :void= > {
    const { onChange } = this.props;
    const newState = EditorState.createEmpty();/ / initializationonChange(newState); }; .Copy the code

The last

For convenience, I have directly adjusted the above for my own use, and I have not done the modification of international files, plus single test and document improvement. It’s best to recommend pr so that you can keep up with the release and practice your testing skills.