65.9K
CodeProject 正在变化。 阅读更多。
Home

Griffin Editor – 用 TypeScript 编写的 Markdown 编辑器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015 年 5 月 29 日

LGPL3

4分钟阅读

viewsIcon

9915

Griffin Editor - 一个用 TypeScript 编写的 Markdown 编辑器

Griffin Editor 是一个 Markdown 编辑器,支持键盘快捷键、语法高亮、主题、预览等功能。

我上次提交 Griffin Editor 的代码大约是三年前。当我写最后一个版本时,我几乎不知道如何编写模块化的 JavaScript 库。我现在仍然不知道。这就是为什么 TypeScript 如此好用。 :) 与旧版本相比,编辑器更容易扩展和定制。

用户界面

screenshot

工具栏按钮看起来有点过时了,它们确实是。 :) 欢迎贡献更具现代感的图标。但最棒的是它们易于定制,因为布局不是内置在库中的。

这是一个示例布局

<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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&times;</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">&times;</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 上找到。

© . All rights reserved.