网站首页 > 技术文章 正文
邮件项目的核心功能就是编辑邮件了,所以文本的编辑特别容易被用户吐槽了。用户报障的时候一个万能的吐槽点“没有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结构效果对比
对于编辑器,有太多的坑,要想满足用户一切省事的需求,有太多的地方需要改造了,只看你觉得值不值了。
【这不是结束,这甚至不是结束的开始。但,这可能是开始的结束。】
猜你喜欢
- 2024-11-24 无障碍优化之如何制作Google Chrome插件
- 2024-11-24 「asp.net core 系列」5 布局页和静态资源
- 2024-11-24 16.python学习笔记-页面样式
- 2024-11-24 Vue的干货分享-Vue3中的弹窗(模态框)的使用
- 2024-11-24 如何衡量一个人的 JavaScript 水平?
- 2024-11-24 高性能轻量级零依赖的轮播图组件——Glider.js
- 2024-11-24 Python数据分析之爬虫第五练:如何得到我们需要的日期数据?
- 2024-11-24 SSM整合_年轻人的第一个增删改查
- 2024-11-24 DevTools小技巧-让你不止会console.log()
- 2024-11-24 评价打分组件,SVG 半颗星的解决方案
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-