富文本编辑器不符合需求?改!

背景

最近公司项目有个新需求,就是富文本编辑器要支持文件上传功能,然后上传之后以超链接的形式显示,像这样:

公司项目用的react,之前一直用的是react-draft-wysiwyg作为富文本编辑器。如果重新再找编辑器那投入和产出比就失衡了。so,最好的方式就是改造编辑器了。

开始

先说说react-draft-wysiwyg,它算是目前最火的react的富文本编辑器,它基于draft-js来开发。长这样。

draft-js是react 富文本编辑器框架,注意,它是一个框架,感兴趣的朋友可以研究下,然后开发属于自己的编辑器,它提供丰富的api供你调用。

fork react-draft-wysiwyg项目&clone 到本地

项目拉下来之后,我们首先看看package.json,看它怎么运行:

"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"
 },
复制代码

可以看到它是通过storybook来运行的,so npm install && npm run storybook。运行之后访问http://localhost:6006。我们可以看到它是编辑器的demo集合,找了一通确实没有文件上传功能。

// src 文档结构
├── components
|  ├── Dropdown
|  ├── Option
|  └── Spinner
├── config
|  └── defaultToolbar.js // 功能按钮配置
├── controls             // 放置组件的目录
|  ├── BlockType
|  ├── Clear
|  ├── ColorPicker
|  ├── Embedded
|  ├── Emoji
|  ├── FileUpload
|  ├── FontFamily
|  ├── FontSize
|  ├── History
|  ├── Image
|  ├── index.js
|  ├── Inline
|  ├── Link
|  ├── List
|  ├── Remove
|  └── TextAlign
├── Editor
|  ├── index.js           // 编辑器入口
|  ├── styles.css
|  └── __test__
├── i18n                  // 国际化
|  ├── da.js
|  ├── de.js
|  ├── en.js
|  ├── es.js
|  ├── fr.js
|  ├── index.js
|  ├── it.js
|  ├── ja.js
|  ├── ko.js
|  ├── nl.js
|  ├── pl.js
|  ├── pt.js
|  ├── ru.js
|  ├── zh.js
|  └── zh_tw.js
├── index.js
├── renderer
|  ├── Embedded
|  ├── Image
|  └── index.js
└── utils
   ├── BlockStyle.js
   ├── common.js
   ├── handlePaste.js
   └── toolbar.js
复制代码

第一步 toolbar加个附件上传按钮:

查看代码可以看到配置toolbar的地方在src/config/defaultToolbar.js

export default {
    options: [// 配置可见的按钮
        
        ...
        
        "image",
        "fileUpload",//我们在这里加一个fileUpload,代表文件上传
    ],
    // 下面的属性都是每个工具的具体的具体配置,如image:
    image: {
        icon: image, // 按钮图标
        className: undefined, 
        component: undefined,
        popupClassName: undefined,
        urlEnabled: false,// 是否支持url形式引入
        uploadEnabled: true,// 是否支持上传
        previewImage: false,// 是否支持预览图片
        alignmentEnabled: true,// 是否显示排列按钮(相当于配置text-align)
        uploadCallback: undefined,// 上传的回调,这个是重点
        inputAccept: undefined,// 文件格式(这里只适合写图片格式)
        alt: { present: false, mandatory: false },
        defaultSize: {
          height: "auto",
          width: "auto"
        },
        title: undefined // html title属性
    }
    ...
    
    // 相当于图片上传,我们的文件上传有诸多类似。所以我们可以参照着配置一下
    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
    },
}
复制代码

这里我们需要找一个icon,可以直接在阿里的iconfont里面找个代表上传的图标,存为svg格式,在上面引用即可。

第二步 新增fileUpload上传组件

新增文件夹src/controls/FileUpload,参照其他组件的格式,新增三个文件FileUpload/index.js, FileUpload/Component/index.js,FileUpload/Component/style.css。三个文件皆可参照image组件。 咱们来说说不同点: FileUpload/index.js

...
addLink: Function = (linkTitle, linkTarget, linkTargetOption): void => {
    const { editorState, onChange } = this.props;// editorState就是当前编辑器的实例
    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,
      " ",// 插入一个空格,避免后面编辑的内容落在a标签内
      newEditorState.getCurrentInlineStyle(),
      undefined
    );
    onChange(
      EditorState.push(newEditorState, contentState, "insert-characters")
    );
    this.doCollapse();
  };
...
复制代码

上传之前的步骤我们都可以和image组件保持一致,但是上传之后的操作就不一样了。我们知道image组件是生成了一个img标签,直接在编辑器内显示图片。我们现在的需要的是插入一个超文本链接也就是a标签。所以我们需要改造addImage为addLink方法。

然后我们改造下图片预览,使它上传之后显示一个可点击的链接: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.link || data.url,
          fileName: data.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: "break-all" }} // 加上wordBreak强制换行,不然英文文件名会溢出
                href={fileSrc}
              >
                {fileName}
              </a>
            ) : (
              translations["components.controls.image.dropFileText"]
            )}
        </label>
   ...
}
复制代码

当引用图片上传组件或者文件上传组件时,我们可以配置uploadCallback方法,用自己的上传接口自定义上传操作,只需保证此函数返回promise对象且返回结果包含{data: {link:'',name: ''}}即可。

最后一步

发布我们的代码,在组建中引用,改下package.json

{
    "name": "react-mysoft-wysiwyg",
    "version": "1.12.16",
    ...
}
复制代码

改下名字,保证npm平台没有重名,改下版本号。

运行 npm publish。发布到npm平台,然后到自己的npm主页,看是否发布成功。 发布成功后。直接在自己的项目安装组件即可。

npm install react-mysoft-wysiwyg --save

以上。完整项目地址

使用编辑器的时候发现,有一个小bug,就是不管怎么删,最终总会遗留一个p标签,而且如果这个p标签带了样式属性,它是清除不了的。这就产生了如何判断编辑器为空的麻烦。索性咱们就加一个‘清空’按钮。增加步骤和上面类似。大致思路就是当我们点击清空的时候,利用draft-js提供的api,初始化editorState:

...
removeInlineStyles: Function = (): void => {
    const { onChange } = this.props;
    const newState = EditorState.createEmpty();// 初始化
    onChange(newState);
};
...
复制代码

最后

上面我也是为了方便所以直接随便整了一下自己用,还有国际化文件的修改、加上单测、以及文档的完善工作没做。最好还是建议大家提pr,这样可以紧跟版本,而且可以锻炼下单测技能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue中,富文本编辑器通常用于编辑和展示HTML格式的文本。虽然Vue富文本编辑器可以处理和生成HTML格式的内容,但是直接导入Word文件并进行编辑是一个复杂的任务。这是因为Word文件和HTML之间存在着很大的差异,包括样式、布局、图像等方面。 如果你需要在Vue中导入Word文件并进行编辑,可以考虑以下步骤: 1. 将Word文件转换为HTML格式:使用第三方库(例如`mammoth.js`)将Word文件转换为HTML格式。这样你就可以将HTML内容加载到Vue的富文本编辑器中进行编辑。 2. 在Vue中使用富文本编辑器:使用Vue富文本编辑器(例如`vue-quill-editor`)将转换后的HTML内容加载到编辑器中。用户可以对内容进行编辑、样式调整等操作。 3. 导出编辑后的内容:当用户完成编辑后,你可以将编辑后的HTML内容导出为Word文件。使用第三方库(例如`html-docx-js`)将HTML转换为可下载的Word文件。 需要注意的是,由于Word和HTML之间的差异较大,转换和导出过程可能会引入一些格式方面的不一致。因此,在实现导入和导出功能时,可能需要进行一些额外的处理和调整,以确保最终结果符合预期。 总之,实现在Vue中导入Word文件并进行编辑是一个相对复杂的任务,需要借助一些第三方库来完成。以上提到的库只是其中的一些示例,你可以根据自己的需求选择适合的库来实现该功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值