百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

2021年的一点工作总结(二)富文本编辑器

connygpt 2024-10-05 14:50 6 浏览

邮件项目的核心功能就是编辑邮件了,所以文本的编辑特别容易被用户吐槽了。用户报障的时候一个万能的吐槽点“没有xxx功能,不支持xxx,没有Outlook好用”。 其实作为一个web产品,如果需要更加公平的对比的话,应该远程web版网页邮箱对比(如outlook网页版),而不是客服端软件(如Outlook),普通用户不知道也不会这么对比,所以你也只能受着。。。 从原来的wangEditor替换成CKEditor也不是说就万事大吉了,官方什么插件都有,什么功能都不用自己开发了,那你就大错特错了。说去不负责任的话,支持功能越多,开放给用户的就越多,然后被吐槽的就越多。

2021年,因为富文本的缘故我做过几次相关的改造:

第一次改造:Vue -> React

项目从Vue迁移到React,虽然当时并未“动”编辑器,仍用的是老的wangEditor,但是为了邮件的编辑器“插件”签名和快捷回复功能(以下主要以“签名”为例),能跟原来一致的使用体验。

Vue时代

就是图中绿框中的部分。

这两个功能一直是用Vue写的,然后集中在一个名为editorChildren的div中,然后该div作为整体被wangEditor编辑器append到指定的dom结构中使用,大致用法如下:

  • 用Vue中编辑签名相应的功能
  • 在wangEditor的源码中将其整体插入

这里提到的iframe就是在上图中rich-editor.vue给预留的id为editorIframe的iframe,wangEditor之前已经被改造成在iframe中使用了。

按照这种模式可以将签名对应的功能直接用Vue来编写,不用费劲的用原生JS实现了,开发效率更高。

React时代

迁移到React后,发现不能原来这种模式了,这样会导致签名上面绑定的点击事件没有反应,这个应该是跟React对点击事件的响应机制有关。

所以不得不对此做些改动,将上述的editorChildren的那部分直接写成一个React组件ToolBar

然后用render方法生成对应的html,再有wangEditor将其插入对应的dom中

import ReactDom from 'react-dom';
import Toolbar from "../components/editor-widgets/Toolbar";

const children = document.querySelector('#editorChildren');
ReactDom.render(<Toolbar/>, tools);
iframe.contentDocument.body.append(children);

至此原来的签名和快捷回复功能算是能在React项目中正常使用了。

第二次改造 wangEditor -> CKEditor

这次算是最大的一次改造了,就是要把wangEditor替换成CKEditor。

CKEditor可不支持之前像wangEditor那样的骚操作了,它是有自己的插件编写规则的,我个人也不想像对带wangEditor那样改源码了。

CKEditor的插件都是用原生JavaScript编写的。

(function () {
    CKEDITOR.plugins.add( 'mailshortcutreply', {
        requires: ['mailcontent', 'mailshortcutreplyrichcombo'],
        lang: 'en,ja,zh-cn,zh,shark', // %REMOVE_LINE_CORE%
        template: '<div class="shortcutreply_container">{1}</div>',
        init: function( editor ) {
            var lang = editor.lang.mailshortcutreply;
            editor.addCommand( 'mailShortcutReplyTree', {
                exec: function( editor, tree ) {
                    CKEDITOR.mail.shortcutReplyTree = tree;
                    editor.ui.instances.ShortcutReply.reset();
                },
                editorFocus: false
            });
            var config = editor.config;

            var panelTitle = lang.label;
            var styles = {};

            editor.ui.addShortcutReplyRichCombo( 'ShortcutReply', {
                label: panelTitle,
                title: panelTitle,
                toolbar: 'styles,20',
                command: 'shortcutReply',
                // allowedContent: allowedContent,

                panel: {
                    css: [ CKEDITOR.skin.getPath( 'editor' ) ].concat( CKEDITOR.skin.getPath( 'wangeditor' ) ),
                    level: 'mailshortcutreply',
                    multiSelect: false,
                    attributes: { 'aria-label': panelTitle }
                },

                init: function() {
                    this.startGroup( panelTitle );
                    var that = this;
                    CKEDITOR.mail.shortcutReplyTree.forEach(function (item) {
                        that.add( item.key, item.title, item.title);
                    });
                    if (CKEDITOR.mail.shortcutReplyTree.length === 0) {
                        // 鉴于length === 0不会调用add方法,故需要手动设置标志位started
                        this._.list._.started = 1;
                    }
                    // 默认选中第一个,先缓存值
                    var firstCategory = CKEDITOR.mail.shortcutReplyTree[0];
                    if (firstCategory) {
                        this.storeInitValue(firstCategory.key);
                    }
                },

                selectedList: function(key, searchText) {
                    var category = CKEDITOR.mail.shortcutReplyTree.find(function (item) {
                        return item.key === key
                    });
                    if (category && category.children && category.children.length > 0) {
                        return category.children.filter(function(item) {
                            var tempDiv = document.createElement('div');
                            tempDiv.innerHTML = item.content;
                            var plainTextContent = tempDiv.innerText;
                            // 根据内容(里面可能有html代码)的纯文字来检索
                            return item.title.includes(searchText) || plainTextContent.includes(searchText);
                        });
                    }
                    return [];
                },
                fireSearch: function() {
                    var inputValue = this.getStoreInputValue();
                    var value = this.getStoreValue();
                    var list = this.selectedList(value, inputValue);
                    var that = this;
                    list.forEach(function (item) {
                        that.addResult( item.key, item.text, item.title);
                    });
                    this.setStoreValue(value);
                    this.commitResult();
                },
                storeInitValue: function(value) {
                    this.setStoreValue(value || '');
                },
                onClick: function( value ) {
                    this.setStoreValue(value);
                    this.fireSearch();
                },
                onSettingClick: function() {
                    $MailMessageCenter.publish('editor.goToSetting', ['shortcutReply']);
                },
                onChange: function(inputValue) {
                    this.setStoreInputValue(inputValue);
                    this.fireSearch();
                },
                onSelect: function(value) {
                    editor.focus();
                    editor.fire( 'saveSnapshot' );
                    var categoryKey = this.getStoreValue();
                    var category = CKEDITOR.mail.shortcutReplyTree.find(function (item) {
                        return item.key === categoryKey;
                    });
                    if (category) {
                        var shortcutReply = (category.children || []).find(function (item) {
                            return item.key === value;
                        });
                        CKEDITOR.mail.insertHtml(shortcutReply.text);
                    }
                },
                onOpen: function() {
                    this.showAll();
                },

                reset: function() {
                    if (this._.committed) { // 已经初始化过的,则需要进行重置
                        this.destroy();
                        this._.panel = void 0;
                        if (this._.list && this._.list.element) {
                            this._.list.element.$.remove();
                        }
                        this._.committed = 0;
                        this.createPanel(editor);
                    }
                },

                refresh: function() {
                    var elementPath = editor.elementPath();

                    if ( !elementPath )
                        return;
                }
            } );
        }
    });
})();

里面的过滤、搜索、选择什么写起来都没有以前那么爽了,这大概也是Vue、React这类框架的意义之一吧。

第三次改造 默认样式dom结构调整

因为CKEditor是支持格式刷功能,虽然部分场景有bug,但是还是决定开放给用户使用,原来的默认样式功能跟格式刷不兼容,导致这部分内容使用格式刷存在bug,所以只好改造了默认样式的dom结构。具体可以参考之前写过的 CKEditor系列(六)改造原编辑器默认样式dom结构效果对比

对于编辑器,有太多的坑,要想满足用户一切省事的需求,有太多的地方需要改造了,只看你觉得值不值了。

【这不是结束,这甚至不是结束的开始。但,这可能是开始的结束。】

相关推荐

自学Python,写一个挨打的游戏代码来初识While循环

自学Python的第11天。旋转~跳跃~,我~闭着眼!学完循环,沐浴着while的光芒,闲来无事和同事一起扯皮,我说:“编程语言好神奇,一个小小的循环,竟然在生活中也可以找到原理和例子”,同事也...

常用的 Python 工具与资源,你知道几个?

最近几年你会发现,越来越多的人开始学习Python,工欲善其事必先利其器,今天纬软小编就跟大家分享一些常用的Python工具与资源,记得收藏哦!不然下次就找不到我了。1、PycharmPychar...

一张思维导图概括Python的基本语法, 一周的学习成果都在里面了

一周总结不知不觉已经自学Python一周的时间了,这一周,从认识Python到安装Python,再到基本语法和基本数据类型,对于小白的我来说无比艰辛的,充满坎坷。最主要的是每天学习时间有限。只...

三日速成python?打工人,小心钱包,别当韭菜

随着人工智能的热度越来越高,许多非计算机专业的同学们也都纷纷投入到学习编程的道路上来。而Python,作为一种相对比较容易上手的语言,也越来越受欢迎。网络上各类网课层出不穷,各式广告令人眼花缭乱。某些...

Python自动化软件测试怎么学?路线和方法都在这里了

Python自动化测试是指使用Python编程语言和相关工具,对软件系统进行自动化测试的过程。学习Python自动化测试需要掌握以下技术:Python编程语言:学习Python自动化测试需要先掌握Py...

Python从放弃到入门:公众号历史文章爬取为例谈快速学习技能

这篇文章不谈江流所专研的营销与运营,而聊一聊技能学习之路,聊一聊Python这门最简单的编程语言该如何学习,我完成的第一个Python项目,将任意公众号的所有历史文章导出成PDF电子书。或许我这个Py...

【黑客必会】python学习计划

阅读Python文档从Python官方网站上下载并阅读Python最新版本的文档(中文版),这是学习Python的最好方式。对于每个新概念和想法,请尝试运行一些代码片段,并检查生成的输出。这将帮助您更...

公布了!2025CDA考试安排

CDA数据分析师报考流程数据分析师是指在不同行业中专门从事行业数据搜集、整理、分析依据数据作出行业研究评估的专业人员CDA证书分为1-3级,中英文双证就业面广,含金量高!!?报考条件:满18...

一文搞懂全排列、组合、子集问题(经典回溯递归)

原创公众号:【bigsai】头条号:程序员bigsai前言Hello,大家好,我是bigsai,longtimenosee!在刷题和面试过程中,我们经常遇到一些排列组合类的问题,而全排列、组合...

「西法带你学算法」一次搞定前缀和

我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。467.环绕字符串中唯一的子字符串[1](中等)795.区...

平均数的5种方法,你用过几种方法?

平均数,看似很简单的东西,其实里面包含着很多学问。今天,分享5种经常会用到的平均数方法。1.算术平均法用到最多的莫过于算术平均法,考试平均分、平均工资等等,都是用到这个。=AVERAGE(B2:B11...

【干货收藏】如何最简单、通俗地理解决策树分类算法?

决策树(Decisiontree)是基于已知各种情况(特征取值)的基础上,通过构建树型决策结构来进行分析的一种方式,是常用的有监督的分类算法。决策树算法是机器学习中的一种经典算法,它通过一系列的规则...

面试必备:回溯算法详解

我们刷leetcode的时候,经常会遇到回溯算法类型题目。回溯算法是五大基本算法之一,一般大厂也喜欢问。今天跟大家一起来学习回溯算法的套路,文章如果有不正确的地方,欢迎大家指出哈,感谢感谢~什么是回溯...

「机器学习」决策树——ID3、C4.5、CART(非常详细)

决策树是一个非常常见并且优秀的机器学习算法,它易于理解、可解释性强,其可作为分类算法,也可用于回归模型。本文将分三篇介绍决策树,第一篇介绍基本树(包括ID3、C4.5、CART),第二篇介绍Ran...

大话AI算法: 决策树

所谓的决策树算法,通俗的说就是建立一个树形的结构,通过这个结构去一层一层的筛选判断问题是否好坏的算法。比如判断一个西瓜是否好瓜,有20条西瓜的样本提供给你,让你根据这20条(通过机器学习)建立起...