Griffin Editor – 用 TypeScript 编写的 Markdown 编辑器
Griffin Editor - 一个用 TypeScript 编写的 Markdown 编辑器
Griffin Editor 是一个 Markdown 编辑器,支持键盘快捷键、语法高亮、主题、预览等功能。
我上次提交 Griffin Editor 的代码大约是三年前。当我写最后一个版本时,我几乎不知道如何编写模块化的 JavaScript 库。我现在仍然不知道。这就是为什么 TypeScript 如此好用。 与旧版本相比,编辑器更容易扩展和定制。
用户界面
工具栏按钮看起来有点过时了,它们确实是。 欢迎贡献更具现代感的图标。但最棒的是它们易于定制,因为布局不是内置在库中的。
这是一个示例布局
<div id="editor">
<!-- TOOLBAR layout -->
<div class="toolbar">
<span class="button-h1" accesskey="1"
title="Heading 1"><img src="images/h1.png" /></span>
<span class="button-h2" accesskey="2"
title="Heading 2"><img src="images/h2.png" /></span>
<span class="button-h3" accesskey="3"
title="Heading 3"><img src="images/h3.png" /></span>
<span class="button-bold" accesskey="b"
title="Bold text"><img src="images/bold.png" /></span>
<span class="button-italic" accesskey="i"
title="Italic text"><img src="images/italic.png" /></span>
<span class="divider"> </span>
<span class="button-bullets" accesskey="l"
itle="Bullet List"><img src="images/bullets.png" /></span>
<span class="button-numbers" accesskey="n"
title="Ordered list"><img src="images/numbers.png" /></span>
<span class="divider"> </span>
<span class="button-sourcecode" accesskey="k"
title="Source code"><img src="images/source_code.png" /></span>
<span class="button-quote" accesskey="q"
title="Qoutation"><img src="images/document_quote.png" /></span>
<span class="divider"> </span>
<span class="button-link" accesskey="l"
title="Insert link"><img src="images/link.png" /></span>
<span class="button-image" accesskey="p"
title="Insert picture/image"><img src="images/picture.png" /></span>
</div>
<!-- The actual text area -->
<textarea class="area"># Hello World!</textarea>
</div>
按键映射
每个工具栏按钮都有一个以 button-
开头的 CSS 类。这些用于将操作绑定到每个工具栏按钮。
热键
您还可以看到每个按钮都定义了一个 accesskey
。这些也会被库拾取,并在您处于 textarea
内时映射到 CONTROL 键。例如,您可以按 CTRL+1 来创建一个新的 H1
标题。
预览
要启用预览,您需要在页面上添加另一个 DIV
元素。它应该命名为容器 div 的 ID
,但后缀为 -preview
。在上面的示例中,容器 div 被命名为 editor
,因此我们需要添加以下 div
<div id="editor-preview">
</div>
(您还需要配置一个 markdown 解析器库,稍后会详细介绍)。
主题
由于没有内置 HTML,只要您遵循上述约定,就可以随心所欲地自定义 UI。
- 给容器 div 添加一个 ID
- 为每个工具栏按钮添加
button-actionName
作为类名 - 在每个工具栏按钮上使用
accesskey
来启用键盘快捷键 - 将
editorID-preview
作为预览窗口的名称
库
该库仍然依赖 jQuery,这些依赖将在接下来的几个月内移除(如果有人贡献更改,速度会更快)。除此之外,该库没有内置的依赖项。
要开始使用,请包含 JavaScript 文件并加载编辑器。
<script src="Scripts/jquery-2.1.4.min.js"></script>
<script src="Scripts/GriffinEditor.js"></script>
<script type="text/javascript">
new Griffin.Editor('editor');
</script>
就是这样!
预览
为了能够预览结果,您需要选择一个 markdown 解析器库。我推荐 marked,因为它支持 github 表格和带分隔符的代码块等。不过,使用哪个库并不重要,因为您需要编写一个小的适配器来允许编辑器使用它。
以下代码演示了如何使用 marked
var markedAdapter = {
parse: function (text) {
return marked(text);
}
}
var editor = new Griffin.Editor('editor', markedAdapter);
editor.preview();
如果您想激活 GFM 支持,您需要配置 marked
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
可以使用相同的技术来处理您喜欢的 markdown 解析器。
热键
热键不需要任何特殊配置。但是,要让它们在纯 JavaScript 中正常工作有点棘手。本节只是为了让您了解如何实现(如果您想在自己的代码中添加支持)。
要使用的事件是 keyup
,因为它相对于 keydown
(每次按下键时会触发多次)而言,每个按键组合只触发一次。
棘手的部分是,当您只使用 keyup
时,浏览器会拦截一些按键。例如,在 Chrome 中,CTRL+1 用于选择第一个标签页。要解决这个问题,您需要使用 keydown
。但如果您喜欢简单,那就不那么有趣了,因为您需要开始管理按键状态(以过滤掉所有已引发事件中唯一的按键)。
我的方法是简单地拦截所有用于我的按键绑定的 keydown
事件。代码大致如下(为便于演示,已简化):
document.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.target !== self.element)
return;
if (isEventForMyKeyBindings(e) {
e.cancelBubble = true;
e.stopPropagation();
e.preventDefault();
}
});
this.element.addEventListener('keyup', (e: KeyboardEvent) => {
if (!e.ctrlKey)
return;
var key = String.fromCharCode(e.which);
if (!key || key.length === 0)
return;
var actionName = this.keyMap[key];
if (actionName) {
this.invokeAction(actionName);
self.preview();
}
});
这样,对于所有按键绑定,一切都能正常工作。
语法高亮
语法高亮用于美化预览窗口中的源代码。语法高亮与 markdown 解析器采用相同的方法。您需要编写一个小的适配器。
我更喜欢使用 prismjs 库。
var markedAdapter = {
parse: function (text) {
return marked(text);
}
}
var prismAdapter = {
highlight: function (blockElements, inlineElements) {
blockElements.forEach(function(item) {
Prism.highlightElement(item);
});
}
};
var editor = new Griffin.Editor('editor', markedAdapter );
editor.syntaxHighlighter = prismAdapter ;
editor.preview();
对话框
当工具栏中的链接或图片按钮被按下时,我们需要能够向用户询问图片或链接。默认情况下,有两种选择。
如果加载了 Bootstrap,您可以使用它来显示对话框。只需添加(或自定义)以下 HTML
<div class="modal fade" id="editor-imageDialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
<h4 class="modal-title">Image selection</h4>
</div>
<div class="modal-body">
<div>
Image URL<br />
<input type="text" name="imageUrl" /><br />
</div>
<div>
Caption<br />
<input type="text" name="imageCaption" /><br />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary"
data-success="true">Add</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="editor-linkDialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
<h4 class="modal-title">Link dialog</h4>
</div>
<div class="modal-body">
<div>
URL<br />
<input type="text" name="linkUrl" /><br />
</div>
<div>
Link text<br />
<input type="text" name="linkText" /><br />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary"
data-success="true">Add</button>
</div>
</div>
</div>
</div>
库会找到它们,因为它们使用容器 div ID(“editor
”)作为其 ID 的前缀。唯一不能更改的是表单元素的名称以及“添加”按钮是否具有 data-success="true"
属性。
要完全自定义对话框,您可以像这样指定自己的对话框提供程序:
var customDialogs = {
image: function (context, callback) {
//show your image dialog here.
//once the dialog closes (and an image have been selected).
callback({
href: url,
title: "Enter title here"
});
},
link: function (context, callback) {
//show your link dialog here.
//once the dialog closes (and an image have been selected).
callback({
url: url,
text: "Enter title here"
});
}
};
var editor = new Griffin.Editor('editor', markedAdapter );
//assign our custom provider
editor.dialogProvider = customDialogs;
摘要
所有内容都记录在 TypeScript 文件中。阅读它以了解每个适配器接收到的信息等。
一些功能尚未迁移,例如自动增长(而不是显示滚动条),并且工具栏目前在 Internet Explorer 中不起作用(将尽快尝试修复)。
该项目可以在 github 上找到。