2.0不再提供左右栏的结构,而是尝试将输入的markdown文本直接转化为html,用户点击渲染的html元素时,显示原始的markdown文本以供编辑
如果你对此感兴趣,可以到 https://md.qyh.me/new.html 尝试,原型版本提供以下操作:
markdown编辑器,特性如下:
https://github.com/mhlx/heather
从 https://github.com/mhlx/heather 下载最新的文件
<link rel="stylesheet" href="codemirror/lib/codemirror.css" media="screen">
<link rel="stylesheet"
href="codemirror/addon/scroll/simplescrollbars.css" media="screen">
<link rel="stylesheet" href="css/markdown.css" media="all">
<link href="fontawesome-free/css/all.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="css/style.css" media="screen">
<link rel="stylesheet" href="css/print.css" media="print">
<script src="jquery/jquery.min.js"></script>
<script src="js/htmlparser.js"></script>
<script src="js/morphdom-umd.min.js"></script>
<script src="codemirror/lib/codemirror.js"></script>
<script src="codemirror/addon/mode/overlay.js"></script>
<script src="codemirror/mode/javascript/javascript.js"></script>
<script src="codemirror/mode/xml/xml.js"></script>
<script src="codemirror/mode/markdown/markdown.js"></script>
<script src="codemirror/mode/gfm/gfm.js"></script>
<script src="codemirror/addon/selection/mark-selection.js"></script>
<script src="codemirror/addon/search/searchcursor.js"></script>
<script src="codemirror/addon/search/search.js"></script>
<script src="codemirror/addon/edit/continuelist.js"></script>
<script src="codemirror/addon/scroll/simplescrollbars.js"></script>
<script src="sweet2alert/dist/sweetalert2.all.min.js"></script>
<script src="js/turndown.js"></script>
<script src="js/turndown-plugin-gfm.js"></script>
<script src="highlight/highlight.pack.js"></script>
<script src="js/jquery.touchwipe.min.js"></script>
<script src="js/fullscreen.js"></script>
<script src="js/md.js"></script>
<script src="js/all.js"></script>
var config = {};
var wrapper = EditorWrapper.create(config);
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="description" content="Heather is a online Markdown Editor">
<meta name="keywords" content="Markdown, Heather, Editor, CodeMirror, Github, Open Source">
<link rel="stylesheet" href="codemirror/lib/codemirror.css" media="screen">
<link rel="stylesheet"
href="codemirror/addon/scroll/simplescrollbars.css" media="screen">
<link rel="stylesheet" href="css/markdown.css" media="all">
<link href="fontawesome-free/css/all.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="css/style.css" media="screen">
<link rel="stylesheet" href="css/print.css" media="print">
</head>
<body>
<script src="jquery/jquery.min.js"></script>
<script src="js/htmlparser.js"></script>
<script src="js/morphdom-umd.min.js"></script>
<script src="codemirror/lib/codemirror.js"></script>
<script src="codemirror/addon/mode/overlay.js"></script>
<script src="codemirror/mode/javascript/javascript.js"></script>
<script src="codemirror/mode/xml/xml.js"></script>
<script src="codemirror/mode/markdown/markdown.js"></script>
<script src="codemirror/mode/gfm/gfm.js"></script>
<script src="codemirror/addon/selection/mark-selection.js"></script>
<script src="codemirror/addon/search/searchcursor.js"></script>
<script src="codemirror/addon/search/search.js"></script>
<script src="codemirror/addon/edit/continuelist.js"></script>
<script src="codemirror/addon/scroll/simplescrollbars.js"></script>
<script src="sweet2alert/dist/sweetalert2.all.min.js"></script>
<script src="js/turndown.js"></script>
<script src="js/turndown-plugin-gfm.js"></script>
<script src="highlight/highlight.pack.js"></script>
<script src="js/jquery.touchwipe.min.js"></script>
<script src="js/fullscreen.js"></script>
<script src="js/md.js"></script>
<script src="js/all.js"></script>
<script>
var config = {};
var wrapper = EditorWrapper.create(config);
//https://www.qyh.me/space/web/article/598
var userAgent = navigator.userAgent;
if(userAgent.indexOf('Windows NT 10.0') != -1 && userAgent.indexOf('Firefox') != -1){
var textarea = wrapper.editor.getWrapperElement().querySelector('textarea');
textarea.style.width = "1000px";
}
</script>
</body>
</html>
codemirror.js
codemirror核心文件 本地处理过,请勿使用cdn
md.js
markdown-it 本地处理过,请勿使用cdn
EditorWrapper
实例,创建另一个EditorWrapper
实例时会自动销毁已经存在的实例EditorWrapper
依赖以下html元素(初始化实例时自动创建),请保证ID的唯一性<div id="editor_wrapper">
<div id="editor_toc"></div>
<div id="editor_in">
<div id="editor_toolbar"></div>
<textarea style="width: 100%; height: 100%"></textarea>
<div id="editor_stat"></div>
<div id="editor_innerBar"></div>
</div>
<div class="markdown-body " id="editor_out" ></div>
</div>
配置项 | 说明 | 默认值 |
---|---|---|
toolbar_icons |
配置顶部工具条图标 | [ 'toc','innerBar','backup','new','search','config' ] |
backupEnable |
是否开启备份 | |
backup_autoSaveMs |
当配置backupEnable为true时生效,当编辑器内容发生变更时,多少毫秒后自动保存编辑器内容 | 500 |
innerBar_icons |
配置辅助工具条的图标 | ['heading','bold','italic','quote','strikethrough','link','code','code-block','uncheck','check','table','undo','redo','close'] |
render_allowHtml |
是否允许html标签 | false |
render_plugins |
配置markdown渲染支持的插件 | ['footnote','katex','mermaid','anchor','task-lists','sup','sub','abbr'] |
render_beforeRender |
配置在渲染前元素处理器 | |
render_ms |
当编辑器内容发生变更时,多少毫秒后开始渲染 | 500 |
stat_enable |
是否启用状态条 | true |
stat_formatter |
当启用状态条时,用于获取状态条渲染内容的方法 | |
sync_animateMs |
配置同步滚动时滚动动画时间 | 0 |
sync_enable |
是否启用同步滚动 | |
swipe_animateMs |
预览、编辑、toc切换时动画时间 | 500 |
res_mermaid_js |
指定mermaid文件的加载路径 | js/mermaid.min.js |
res_katex_css |
指定katex css文件的加载路径 | katex/katex.min.css |
res_katex_js |
指定katex js文件的加载路径 | katex/katex.min.js |
res_hljsTheme |
获取highlightjs主题的路径 | function(theme){return themeCssUrl} |
res_editorTheme |
获取CodeMirror主题的路径 | function(theme){return themeCssUrl} |
res_colorpickerCss |
指定colorpicker css文件路径 | colorpicker/dist/css/bootstrap-colorpicker.min.css |
res_colorpickerJs |
指定colorpicker js文件路径 | colorpicker/dist/js/bootstrap-colorpicker.min.js |
renderAllDocEnable |
代码高亮的同步预览中,由于codemirror只渲染当前视窗,因此会出现不同步的现象,开启这个选项在载入文档时可以消除这个现象,但是在文本量比较大的时候,加载非常缓慢,可以选择关闭 | true |
wrapper.editor
wrapper.getValue()
wrapper.setValue(newValue)
wrapper.getHtml()
wrapper.doRender(patch)
patch:是否patch更新
wrapper.doSync()
wrapper.enableSync()
wrapper.disableSync()
wrapper.toEditor()
wrapper.toToc()
wrapper.toPreview()
只在手机端或者全屏模式下有效
wrapper.toolbar
wrapper.toolbar.addIcon(clazz,hander,callback)
clazz为fontawesome图标的样式,例如fa fa-file icon
,handler为图标被点击时触发的方法,callback则为图标元素的回调,例如为添加的图标加上id属性:
wrapper.toolbar.addIcon(clazz,hander,function(icon){
icon.setAttribute(id,'icon-id');
})
mobile-hide
在手机端隐藏pc-hide
在pc端隐藏nofullscreen
在全屏模式下隐藏onfullscreen
只在全屏模式下出现wrapper.toolbar.removeIcon(function(icon){return bool})
wrapper.toolbar.hide()
wrapper.toolbar.show()
wrapper.toolbar.hide();
wrapper.innerBar
方法同顶部工具条
wrapper.theme
theme.setEditorTheme(wrapper.editor,'abcdef',function(){
theme.render();
})
theme.hljs.theme = 'a11y-light'
theme.render();
theme.toolbar.color = '#fff'
theme.render();
theme.bar.color = '#fff'
theme.render();
theme.stat.color = '#fff'
theme.render();
wrapper.theme.customCss = css;
wrapper.theme.render();
wrapper.saveTheme();
wrapper.resetTheme();
wrapper.execCommand(commandName)
系统内置指令
名称 | 说明 | 快捷键_mac | 快捷键_pc |
---|---|---|---|
search | 开启|关闭一个搜索框 | Ctrl S | Alt S |
emoji | 打开emoji选择框 | ||
heading | 打开heading选择框 | Ctrl H | Ctrl H |
bold | 加粗文本 | Ctrl B | Ctrl B |
italic | 倾斜文本 | Ctrl I | Ctrl I |
quote | 引用文本 | Ctrl Q | Ctrl Q |
strikethrough | 为文本添加删除线 | ||
link | 插入超链接 | Ctrl L | Ctrl L |
codeBlock | 插入代码块 | Shift Cmd B | Alt B |
code | 插入行内代码 | ||
uncheck | 插入待办事项 | Shift Cmd U | Alt U |
check | 插入已完成待办事项 | Shift Cmd I | Alt I |
undo | 撤回 | Cmd Z | Ctrl Z |
redo | 取消撤回 | Cmd Y | Ctrl Y |
table | 插入表格 | Shift Cmd T | Alt T |
EditorWrapper.commands[commandName] = commandHandler
commandHandler接受一个参数wrapper
,例如:
新增一个插入图片的指令,并且为该指令绑定Ctrl+G
的快捷键:
EditorWrapper.commands['image'] = function(wrapper){
var editor = wrapper.editor;
var text = editor.getSelection();
if (text == '') {
editor.replaceRange("![]()", editor.getCursor());
editor.focus();
var start_cursor = editor.getCursor();
var cursorLine = start_cursor.line;
var cursorCh = start_cursor.ch;
editor.setCursor({
line: cursorLine,
ch: cursorCh - 1
});
} else {
editor.replaceSelection("![" + text + "]()");
}
}
wrapper.bindKey({'Ctrl-G':'image'});
wrapper.bindKey(keyMap)
keyMap对象属性为快捷键名称,例如Ctrl-A
,属性值可以为string或者一个方法,如果为string类型,那么则会绑定对应名称的命令,如果为方法,则会直接绑定该方法
wrapper.unbindKey(keyArray)
keyArray为快捷键数组,例如['Ctrl-A']
wrapper.requestFullScreen()
只在PC端有效
wrapper.exitFullScreen()
只在PC端有效
wrapper.remove()
wrapper.onRemove(fun)
wrapper.offRemove(fun)
没有直接导出PDF的方法,但是可以通过以下步骤,让chrome浏览器的打印功能来实现
<link rel="stylesheet" href="codemirror/lib/codemirror.css" media="screen">
<link rel="stylesheet"
href="codemirror/addon/scroll/simplescrollbars.css" media="screen">
<link rel="stylesheet" href="css/markdown.css" media="all">
<link href="fontawesome-free/css/all.min.css" media="all" rel="stylesheet">
<link rel="stylesheet" href="css/style.css" media="screen">
<link rel="stylesheet" href="css/print.css" media="print">
上述html中,media="print"
的只会在打印时使用,media="screen"
的则只会在页面渲染时使用,而media="all"
则在所有情况下都会使用,请参考 https://www.w3schools.com/tags/att_link_media.asp
mermaid渲染出来的元素大小会根据视窗大小自动调整,由于左右预览的原因,打印出来的大小会跟预期的大小不一致,此时可以通过监听打印事件使得在打印前再次渲染。
var wrapper = EditorWrapper.create({});
var beforePrintHandler = function(mql) {
if (mql.matches) {
wrapper.doRender(false);
}
}
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener(beforePrintHandler);
}
wrapper.onRemove(function(){
mediaQueryList.removeListener(beforePrintHandler);
});
通过添加以下代码可以让pdf文件强制分页
<div style="page-break-after: always;"></div>
通过配置upload_url
和upload_finish
即可开启文件上传,例如:
var config = {
upload_url:'https://putsreq.com/aPamE6UIaFogo0JwhL6N',
upload_finish:function(resp){
swal('仅供测试上传所用,固定返回同一地址')
return {
type : 'image',
url : 'https://www.qyh.me/image/news/8BE085FBC2F48482047C510EE0A36C4F.jpeg/600'
};
}
};
var wrapper = EditorWrapper.create(config);
其中upload_url
为上传的地址,upload_finish
为上传成功后的回调函数,接受一个参数,该参数内容为服务器响应的内容
,同时返回地址信息,地址信息分为三种:图片|视频|一般文件。
{type:'image',url:'图片地址'}
{type:'video',url:'视频地址'}
,同时可以通过设置poster属性设定一个封面,例如:{type:'video',url:'视频地址','poster':'封面图片地址'}
,如果需要设置多个source,可以设置sources属性,source属性应该为一个数组,单个数组元素内容格式为{'type':'video/mp4|video/ogg等','src':'视频地址'}
{type:'file',url:'文件地址'}
通过配置upload_before
可以在上传前增加额外的参数,例如
config.upload_before = function(formData,file){
formData.append("key", file.name);
}
通过配置upload_fileName
可以设定文件上传名称,默认为file
一个简单的七牛云文件上传的例子:
前端:
var config = {
upload_url:'http://upload.qiniu.com/',
upload_before:function(formData,file){
$.ajax({
async:false,//这里一定要同步获取token
url : 'http://localhost:8081/test/test',
success:function(token){
formData.append("token", token);
formData.append("key", file.name);
}
})
},
upload_finish:function(resp){
resp = $.parseJSON(resp);
if(resp.error){
swal(resp.error);
return ;
}
return {
type : 'image',
url : 'http://img.qyh.me/'+resp.key
};
}
};
后端:
package test;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.qiniu.util.Auth;
public class TestServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
private final String ak = "";
private final String sk = "";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Auth auth = Auth.create(ak, sk);
String upToken = auth.uploadToken("mhlx");
resp.addHeader("Access-Control-Allow-Origin", "*");
resp.getWriter().write(upToken);
}
}
只在chrome上做了测试,但应该支持一些其他的现代化浏览器
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。