近期有这样的一个需求,需要把kanboard进行改进,需要在添加任务的时候,可以添加图片。由于kanboard使用的是markddown进行文本编辑,那么问题来了,怎样进行markddown的图片上传呢,解决方案是有的,比如简书,已经实现了markddown图片粘贴上传。但是呢,我是个比较懒的人,那么还有一个解决方案,使用富文本编辑器。下文介绍如何将kanboard中的markddown编辑器修改为富文本编辑器。
选材
现今富文本编辑器已经有很多了,而且都很完善,那么选择哪个富文本编辑器的问题就来了。需求有两个:
需求可以直接粘贴图片到富文本编辑器
改动代码越少越好
入选
经过一系列的筛选,现在有两个富文本编辑器入选
百度的UEditor
wangEditor
选择
由于百度UEditor已经在2016年的5月就不再维护,并且需要引入资源略多,虽说只有js资源和css资源,但是还是略多。
最后选择使用wangEditor富文本编辑器,为啥呢,因为只需要引入js文件即可。
改版
选材已经搞完,那么就开始动工了。
引入JS
将wangEditor的JS文件,引入kanboard
将JS粘贴到assets/js目录下
打开模板的app/Template/layout.php页面,在所有js资源加载的最后,引入wangEditor的js,如下图:
错误解决
之前引入JS之后,刷新一下页面试试,应该会报下面的错误,如下图:
该错误是由于kanboard的CSP机制导致的,什么是CSP?点击查看什么是CSP。当然解决办法也很简单。
找到kanboard中csp的配置,位置在app/ServiceProvider/ClassProvider.php中,找到最下面的register方法,将cspRules数组中的设置,改为下面的代码
1
2
3
4
5'default-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline'",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
'font-src' => '* data:',
'img-src' => '* data:',
再次刷新,报错没有了。继续解决下面的问题。
显示
有人会问了,显示还不简单,直接复制进去就完事儿了。
其实吧,显示这个事情真的还是比较费劲的。请继续往下看。
尝试
由于富文本编辑器需要使用JS进行渲染,那么首先要解决的就是,在添加任务的面板上,让kanboard执行js。是的,让面板执行js,因为kanboard不执行页面的js
首先找到添加任务的面板位置,app/Template/task_creation/show.php文件。找到class为task-form-main-column的div,然后在里面随便添加点什么,看看是不是该面板。代码如下:
1
2
3
4
5
6
7
8
= $this->task->renderTitleField($values, $errors) ?>
= $this->task->renderDescriptionField($values, $errors) ?>
= $this->task->renderDescriptionTemplateDropdown($project['id']) ?>
= $this->task->renderTagField($project) ?>
测试
= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?>
其中,测试是我自己添加的,显示效果如下图
下面添加js执行,看看是否执行了。添加代码如下
1
2
3
4
5
6
7
8
9
10
11
= $this->task->renderTitleField($values, $errors) ?>
= $this->task->renderDescriptionField($values, $errors) ?>
= $this->task->renderDescriptionTemplateDropdown($project['id']) ?>
= $this->task->renderTagField($project) ?>
console.log('这是测试的输出');
alert('测试一个');
= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?>
那么这样的执行结果如何?请看下图
空空如也的控制台输出,这说明了什么?kanboard压根就没有执行这段代码。是因为kanboard不执行吗?不是的,因为这是添加任务的面板是ajax调用出来的。证明是什么呢?如下代码:
1
2
3
4
console.log('这是测试的输出');
alert('测试一个');
将这段代码复制到app/Template/layout.php中,也就是咱们前面添加wangEditor的下面,看看是否执行,如下图
解决
根据上面的运行结果,得出结论,常规的JS加载是不可用的,那么我们能不能学学插件里的JS是怎么写的,插件的JS是如何加载的。如下图,日历组件的js加载
可以看到,组件是通过KB.component进行的注册,然后再方法内的this.render中进行的方法调用。
根据上面的做法,我们需要自己写一个加载富文本编辑器的JS,并且我们需要直接粘贴图片,我现在又不希望再做一个图片上传的方法,那么,我们就可以将富文本编辑器的图片直接转成base64进行存储及显示,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26KB.component('wang-edit', function (){
this.render = function (){
var E = window.wangEditor
var editor = new E('#wang-editor');
editor.customConfig.menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'image', // 插入图片
'table' // 表格
]
editor.customConfig.uploadImgShowBase64 = true
editor.create();
};
});
注意,记住咱们的js注册名,wang-edit然后将这个JS引入,位置放到之前引入富文本编辑器下面。
富文本编辑器显示
富文本编辑器加载
文本编辑器加载,位置在app/Template/task_creation/show.php文件。找到class为task-form-main-column的div,代码如下:
1
2
3
4
5
6
7
= $this->task->renderTitleField($values, $errors) ?>
= $this->task->renderDescriptionField($values, $errors) ?>
= $this->task->renderDescriptionTemplateDropdown($project['id']) ?>
= $this->task->renderTagField($project) ?>
= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?>
其中的= $this->task->renderDescriptionField($values, $errors) ?>是文本编辑器加载的地方.
我们根据这个加载的方法,找到php中加载的地方。位置是app/Helper/TaskHelper.php,再搜索renderDescriptionField方法,即可找到,会发现,该方法中是直接返回的另外一个方法发加载,代码为return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2));
那么我们继续找,方法为textEditor的,位置为app/Helper/FormHelper.php,然后找到textEditorEE方法,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public function textEditor($name, $values = array(), array $errors = array(), array $attributes = array()){
$params = array(
'name' => $name,
'text' => isset($values[$name]) ? $values[$name] : '',
'css' => $this->errorClass($errors, $name),
'required' => isset($attributes['required']) && $attributes['required'],
'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1',
'labelPreview' => t('Preview'),
'labelWrite' => t('Write'),
'placeholder' => t('Write your text in Markdown'),
'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'],
'suggestOptions' => array(
'triggers' => array(
'#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')),
)
),
);
if (isset($values['project_id'])) {
$params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM'));
}
$html = '
$html .= $this->errorList($errors, $name);
return $html;
}
我们需要做的是,只需要改一行代码,
将
1$html = '
改为富文本编辑器加载的div,
1$html = '
这样就可以了,我们再运行页面看看。注意,class为js-wang-edit因为咱们之前的js注册名为wang-edit
这里class引入的时候,在前面加上js-后面写上js的注册名,这些配置好之后,运行图如下:
关键任务测试
富文本编辑器加载成,那么试试这次的关键任务,截图直接粘贴到富文本编辑器,效果如下图
问题
然后点击保存按钮,你会发现,之前的功夫都白费了。因为内容根本就没有添加到数据库。
解决
这个问题是肯定会出现的,如果你仔细的话,会发现,提交的数据中,description字段为空,然后再看之前的编辑器,内容其实是一个textarea容器,那么解决办法来了
在渲染富文本编辑器的下面添加上一个隐藏的textarea,id为description,name为description,另外将$params['text']加载进去,这个参数取到的是之前的内容,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public function textEditor($name, $values = array(), array $errors = array(), array $attributes = array()){
$params = array(
'name' => $name,
'text' => isset($values[$name]) ? $values[$name] : '',
'css' => $this->errorClass($errors, $name),
'required' => isset($attributes['required']) && $attributes['required'],
'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1',
'labelPreview' => t('Preview'),
'labelWrite' => t('Write'),
'placeholder' => t('Write your text in Markdown'),
'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'],
'suggestOptions' => array(
'triggers' => array(
'#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')),
)
),
);
if (isset($values['project_id'])) {
$params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM'));
}
$html = '
'.$params['text'].'
$html .= ''.$params['text'].'';
$html .= $this->errorList($errors, $name);
return $html;
}
数据库出问题
这时候我们再试试,没问题,文字可以保存,图片可以保存进去。但是,如果你幸运的话,会看到这么一个错误,Internal Error: SQL error: SQLSTATE[22001]: String data, right truncated: 1046 Data too long for column 'description' as row 1,报错很明显,数据库的字段不够长。
解决数据库问题修改tasks表的报错,sql代码如下
1
2ALTER TABLE `kanboard`.`tasks`
MODIFY COLUMN `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL AFTER `title`;
修改user_has_unread_notifications表的报错,sql代码如下
1
2ALTER TABLE `kanboard`.`user_has_unread_notifications`
MODIFY COLUMN `event_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL AFTER `event_name`;
修改project_activities表的报错,sql代码如下
1
2ALTER TABLE `kanboard`.`project_activities`
MODIFY COLUMN `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL AFTER `task_id`;
再次运行,会有一个惊喜的发现,问题解决了。
邮件内容问题
邮件提醒的内容可能会让你更惊喜,如下图
是的,邮件提醒中是这样的,因为html没有解析
邮件内容解决
这个解决办法是临时的,也是最暴力的,就是在发送邮件的时候,将内容描述去掉,代码目录在app/Model/UserUnreadNotificationModel.php中的create方法,我们使用unset($event_data['description']);将description去掉即可,这个会在下个版本进行改进
任务展示问题
当前面这些都解决了,图片也添加进去了,你开开心心的点开任务的时候,会发现如下图
任务展示问题解决找到任务展示文件app/Template/task/show.php文件
找到empty($task['description'])这个的判断,代码如下
1
2
3
4<?php if (!empty($task['description'])): ?>
= $this->hook->render('template:task:show:before-description', array('task' => $task, 'project' => $project)) ?>
= $this->render('task/description', array('task' => $task)) ?>
上面代码是进行的markdown的解析,由于富文本编辑器存入数据库的就是html所以我们就不用解析,直接展示即可,代码如下
1
2
3<?php if (!empty($task['description'])): ?>
= $task['description'] ?>
全都乱了
等上面的事情都好了之后,你的心情的美美的,当你打开评论的时候会发现,全乱了,评论的面板也变成了富文本编辑器,所有的编辑地方都变成了富文本编辑器。这个时候我们需要将之前改的textEditor方法进行一个封装,因为renderDescriptionField方法只是任务创建和修改的方法,不会影响其他的,将renderDescriptionField中的方法改成我们自己封装的方法即可。
总结
上面的解决方法只是临时的,需要改进的地方是,图片上传使用的是base64,会对数据库造成大批量的字节写入,拖垮数据库,需要将图片上传改用cdn存储
已经有了解决方法,点击链接跳转解决Kanboard修改富文本编辑器遗留问题
结束1E-mail:blog@meaoo.cn