发布博客文章编辑器用的是KindEditor,这编辑器的排版功能很弱,字体、字号、行距、首行缩进,想调齐了得一段一段手动点。我都是先在 Word 里排好版,再整段复制进编辑器。
很麻烦,Word 里排得整整齐齐,粘到网页上往往变样,右边参差不齐,回头还得手动补一遍;有时候隔几天再发一篇,版式又跟前一篇对不上。后来想想,这事的规则其实是死的,字体多大、缩进多少、行距几倍,翻来覆去就那几个数,每回手动点一遍实在没必要。那不如做成一个按钮:选中正文,点一下,参数一次套齐。
按钮怎么挂上去
工具栏得先认得这个名字。在 editor/kindeditor.js 的 items 数组末尾加上 'wordfmt':
KE.lang['wordfmt'] = '格式排版(先选中文字再点)';
KE.plugin['wordfmt'] = {
click : function(id) {
if (window.wordFmt) window.wordFmt.apply(id);
}
};再回 Plugin.php,给它配提示文字和点击动作:
KE.lang 那句会变成鼠标停在按钮上的提示。我特意写清"先选中文字再点",因为这按钮不选中就不干活,不提醒一句,点完没反应的人会以为坏了。
图标没什么玄机,给 .ke-icon-wordfmt 挂一张背景图就行。我用了四条长短不一的横线,看着像文字排整齐的样子:
KE.lang['wordfmt'] = '格式排版(先选中文字再点)';
KE.plugin['wordfmt'] = {
click : function(id) {
if (window.wordFmt) window.wordFmt.apply(id);
}
};最后把新文件引进来,末尾的版本号别忘,文件以后改了要跟着往上加,不然浏览器和 CDN 还在读缓存的旧文件:
<script type="text/javascript" charset="utf-8" src="{$editor_path}/wordfmt.js?v=4"></script>
点下去之后做了什么
逻辑都在 wordfmt.js。
编辑器是 iframe 结构,选区要从 iframe 自己的 window 里拿,用外层的拿不到。先看有没有选中东西,光标点一下没选中的要拦住,不然一按整篇都会被改:
var doc = g.iframeDoc;
var sel = g.iframeWin.getSelection ? g.iframeWin.getSelection() : null;
if (!sel || sel.rangeCount === 0) {
alert('请先选中要排版的文字,再点"格式"。');
return;
}
var range = sel.getRangeAt(0);
if (range.collapsed) { // 光标点了一下、没选中任何内容
alert('请先选中要排版的文字,再点"格式"。');
return;
}然后把跟选区相交的块级元素挑出来。这里只处理"叶子块",也就是里面不再套段落的那些。不这么筛的话,外层一个 div 套着三个 p,div 和里面三个 p 全进名单,同一段文字要被处理两遍:
function pickBlocks(doc, range) {
var candidates = doc.body.querySelectorAll('p,div,li,blockquote,h1,h2,h3,h4,h5,h6,figure,section,article,td,th,pre');
var picked = [];
for (var i = 0; i < candidates.length; i++) {
var el = candidates[i];
if (!intersects(range, el)) continue;
if (tag(el) === 'PRE') continue; // 代码块不参与
// 里面还套着块级元素的,说明不是叶子块,交给它里面的元素处理
if (el.querySelector('p,div,li,blockquote,h1,h2,h3,h4,h5,h6,figure,section,article,td,th,pre,table,ul,ol')) continue;
if (el.closest && el.closest('pre')) continue; // 代码块里的内容一概不碰
picked.push(el);
}
return picked;
}样式没什么花活,就是把参数拼成字符串写进内联 style:
var P_STYLE = [
'font-family:Microsoft YaHei,微软雅黑,sans-serif',
'font-size:11pt',
'color:#000000',
'line-height:2.5',
'text-indent:26.5pt',
'margin:0 0 14.4pt',
'text-align:justify',
'text-shadow:1px 1px 3px rgba(0,0,0,0.25)'
].join(';');列表和引用块不给缩进和段距,那两样套上去看着别扭,只留字体、行距和阴影:
var PLAIN_STYLE = [
'font-family:Microsoft YaHei,微软雅黑,sans-serif',
'font-size:11pt',
'color:#000000',
'line-height:2.5',
'text-shadow:1px 1px 3px rgba(0,0,0,0.25)'
].join(';');标题、代码块、表格整个跳过。它们本来就有自己的样式,标题该有标题的字号,代码块里是它自己的高亮配色,硬套正文字体只会变难看:
var SKIP_TAGS = {
H1:1, H2:1, H3:1, H4:1, H5:1, H6:1,
PRE:1, TABLE:1, THEAD:1, TBODY:1, TR:1, TD:1, TH:1,
CODE:1, SCRIPT:1, STYLE:1, TEXTAREA:1
};最后把这个动作记进撤销栈,不然点错了退不回去:
try { KE.toolbar.updateState(id); } catch (e) {}
try { KE.history.add(id, false); } catch (e) {}
前台兜底
有一点得先说:这个编辑器落库的段落是 <div style="...">,不是 <p>。我是点完按钮去前台看,才发现正文里全是 div。所以兜底 CSS 两种都要管上:
#article-body p,
#article-body > div:not([class]) {
font-family: Microsoft YaHei, 微软雅黑, sans-serif !important;
font-size: 11pt !important;
color: #000 !important;
text-indent: 26.5pt !important;
line-height: 2.5 !important;
margin: 0 0 14.4pt !important;
text-align: justify !important;
}> div:not([class]) 这个限制是有意加的,只认没有类名的直接子 div,代码卡片、插件容器那些都带类名,这么写不会误伤到它们。
这套 CSS 是给没点过按钮的老文章兜底的,让新旧文章看起来一致。有一点容易想错:内联样式的优先级比 CSS 高,加 !important 也压不过。所以想让某篇文章的样式变,只能动内联样式,光改 CSS 没用。
平常怎么用
后台写文章,正文粘进来,鼠标选中,点工具栏那个"格式"按钮,就完了。选中的部分套上统一排版,标题和代码块原样不动。不选东西直接点会弹提示,不会乱改内容。手快误点了,Ctrl+Z 退回来。
效果预览









