给Markdown编辑器HyperMD添加新功能
HyperMD是一个独特的Markdown编辑器。
和其他的编辑器不同,它的特色在于编辑和预览一体,在输入Markdown语法后自动在原位置渲染,当编辑指针指向/鼠标点击被渲染后的元素时,将自动展开Markdown编码以供编辑。
在这里可以在线尝试。
可是,虽然这是个很棒的idea,但这是一个已经被抛弃的项目,它的作者laobubu已经不再维护它,转而维护他的新作品MarkdownIME。后者我认为只能胜任简单的书写,而不是频繁地用Markdown编辑较长的内容——无法修改已经渲染的标签,而且想要修改格式简直是噩梦一般。做个评论框和普通的WYSIWYG编辑器配合还是可以的。
而HyperMD已经较难使用了。原因在于它有一些bug。我着手修复了其中的一部分。
基本功能修复
开始从github上pull的版本根本不能用。很多bug比如
控制台频频弹出错误(每次点击都会)、
公式不能正常显示/折叠、
代码区字体不是等宽的。
然后搞了一个东西可以按目录结构完整地保存远端网页。
用到的东西:
- phantomjs,访问一下网页并延时一段时间,记录所有请求。
- nodejs,将上一步的请求按照目录全部抓取。
这样就能用了。
不过因为这样模块不全所以可能会出bug。试试安个codemirror结果发现问题重现。
怀疑是不再支持新版的codemirror.开始尝试codemirror的各个版本。
如何列出一个包的所有版本?
cnpm view codemirror versions
然后二分尝试。
5.3x
是最新版但是有上述问题。
3.11.x
的无法使用,缺少某个函数。版本过低
5.10
的可以用,结果图片被识别成链接。。而且缩进有问题
5.20.0
没问题。但是会爆出一些overlay的错误。我魔改了一下。
于是选定 5.20.0
。
还依赖marked和mathjax。这些没问题最新版就可以了。
拖拽和粘贴
完工。
粘贴
原来是有的,但是并不能用。稍微改了一下。
相关代码在 /hypermd/addon/paste-image.js
Paste.prototype.pasteHandle = function (cm, ev) {
var cd = ev.clipboardData || window.clipboardData;
if (!cd || !cd.items || 1 != cd.items.length || cd.types[0] != 'Files') return;
this.doInsert(cd.items[0].getAsFile());
ev.preventDefault();
}
在chrome下不知为何,粘贴文件是无法从 clipboardData.files
里拿到file的。但是如果粘贴的是剪贴板里的图片数据却可以通过 clipboardData.items[0].getAsFile()
拿到值。所以文件是无法粘贴的,但是可以粘贴个截图什么的还是比较好用。
测试:
拖拽
原来是不支持的。我也加在 paste-image.js
里了。
首先添加挂载\卸载的事件。
Paste.prototype.bind = function () {
this.cm.on('paste', this._pasteHandle);
this.cm.on('drop', this._dropHandle); //add
}
Paste.prototype.unbind = function () {
this.cm.off('paste', this._pasteHandle);
this.cm.off('drop', this._dropHandle); //add
}
function Paste(cm) {
this.cm = cm;
this.enabled = false;
this.uploadTo = 'sm.ms';
this._pasteHandle = this.pasteHandle.bind(this);
this._dropHandle = this.dropHandle.bind(this); //add
this.updateUploader(this.uploadTo);
}
然后是正文:
/**
* 'drop' event handler
*
* @param ???
*/
Paste.prototype.dropHandle = function (cm, ev) {
//console.log('dropped');
if(!ev || !ev.dataTransfer || !ev.dataTransfer.files.length) return;
var self = this, file = ev.dataTransfer.files[0];
setTimeout(function() {
self.doInsert(file)
}, 300);
//ev.preventDefault();
}
可以看到,拖拽的文件是通过 event.dataTransfer.files
传入的。
这里有个有意思的地方。注册的drop事件是在光标移动之前进行的。所以如果直接 doInsert
或者最后来个 ev.preventDefault();
,倒是能插入图片,但是插入的位置就错了——插入到了拖拽前光标所在的位置(而不是拖拽时指向的位置)。
所以选择一个很笨但是很有效的办法,就是延时解决。还不能延时0ms,那样好像不够。300ms根本觉察不出来。
测试:
好像动态gif会有显示问题。原因或许是多次的重复渲染导致频繁从头开始。
匹配bug和输入跳动bug
甚至直接用demo文章就能体现问题。
***Note**: This complex approach is temporary. but don't worry,*
找到这一行,鼠标点点,发现什么了...
恐怕深在markdown解析器内部——三个 ***
同时出现,则分两种情况,得看strong的标签在前还是在后。可是这个解释器直接按照在前处理了。所以弃。
但是还是改得好看了一点——
- 原来是往左往右搜到第一个具有classList包含关系的标签停止。现在我加了一个栈,预先走一遍看文本所在的最深的标签类型,然后搜的时候只盯着那个类型跑。
- 原来的方案中,在一行的末尾用输入法输入文字时,这一行里的标签会忽闪忽闪的,十分不友好。于是加了一个挺长的判断,大意是如果是
**
*
~~
以及反引号这些特殊的标签,必须满足text具备它们所包含的属性。
这些改动都在 hide-token.js
里面的函数 patchFormattingSpan
里。比较长。
其他
原来的加载过程是先拉一个index.js
再用require.js
异步加载模块。而模块。。估计有好几十个js小文件。于是本地加载都很慢。
所以写了一个合并js并自动压缩的程序。按照官方说明的加载顺序直接把所有js拼成一个文件,md.js
,直接引用就可以加载所有HyperMD所需的脚本。直接拼起来有1.51M,自动调用下uglify.js
压缩一下,然后大概是772K。基本满足要求了。
所以呢?
现在还有些光标不对应的奇怪bug。
虽然还是很喜欢它的latex公式功能...
如果有机会就写一个用它当编辑器的blog把它嵌进ghost博客里吧。