GithubHelp home page GithubHelp logo

88250 / lute Goto Github PK

View Code? Open in Web Editor NEW
988.0 988.0 118.0 110.39 MB

🎼 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。A structured Markdown engine that supports Go and JavaScript.

Home Page: https://ld246.com/tag/lute

License: Other

Go 99.82% Batchfile 0.04% Shell 0.03% HTML 0.07% Smarty 0.01% JavaScript 0.04%
gopherjs markdown protyle siyuan vditor

lute's Introduction

Lute

一款结构化的 Markdown 引擎,支持 Go 和 JavaScript

千呼万唤始出来 犹抱琵琶半遮面
转轴拨弦三两声 未成曲调先有情





        

English

💡 简介

Lute 是一款结构化的 Markdown 引擎,完整实现了最新的 GFM/CommonMark 规范,对中文语境支持更好。

欢迎到 Lute 官方讨论区了解更多。同时也欢迎关注 B3log 开源社区微信公众号 B3log开源

b3logos.jpg

📽️ 背景

之前我一直在使用其他 Markdown 引擎,它们或多或少都有些“瑕疵”:

  • 对标准规范的支持不一致
  • 对“怪异”文本处理非常耗时,甚至挂死
  • 对中文支持不够好

Lute 的目标是构建一个结构化的 Markdown 引擎,实现 GFM/CM 规范并对中文提供更好的支持。所谓的“结构化”指的是从输入的 MD 文本构建抽象语法树,通过操作树来进行 HTML 输出、原文格式化等。 实现规范是为了保证 Markdown 渲染不存在二义性,让同一份 Markdown 文本可以在实现规范的 Markdown 引擎处理后得到一样的结果,这一点非常重要。

实现规范的引擎并不多,我想试试看自己能不能写上一个,这也是 Lute 的动机之一。关于如何实现一个 Markdown 引擎,网上众说纷纭:

  • 有的人说 Markdown 适合用正则解析,因为文法规则太简单
  • 也有的人说 Markdown 可以用编译原理来处理,正则太难维护

我赞同后者,因为正则确实太难维护而且运行效率较低。最重要的原因是符合 GFM/CM 规范的 Markdown 引擎的核心解析算法不可能用正则写出来,因为规范定义的规则实在是太复杂了。

最后,还有一个很重要的动机就是 B3log 开源社区需要一款自己的 Markdown 引擎:

  • SoloPipeSym 需要效果统一的 Markdown 渲染,并且性能非常重要
  • Vditor 需要一款结构化的引擎作为支撑以实现下一代的 Markdown 编辑器

✨ 特性

  • 实现最新版 GFM/CM 规范
  • 零正则,非常快
  • 内置代码块语法高亮
  • 对中文语境支持更好
  • 术语拼写修正
  • Markdown 格式化
  • Emoji 解析
  • HTML 转换 Markdown
  • 自定义渲染函数
  • 支持 JavaScript 端使用

🗃 案例

🇨🇳 中文语境优化

  • 自动链接识别加强
  • 在中西文间自动插入空格

♍ 格式化

格式化功能可将“不整洁”的 Markdown 文本格式化为统一风格,在需要公共编辑的场景下,统一的排版风格能让大家更容易协作。

点此展开格式化示例。
Markdown 原文:
# ATX 标题也有可能需要格式化的 ##
一个简短的段落。

Setext 说实话我不喜欢 Setext 标题
----
0. 有序列表可以从 0 开始
0. 应该自增序号的
1.   对齐对齐对齐

我们再来看看另一个有序列表。
1. 没空行的情况下序号要从 1 开始才能打断段落开始一个新列表
3. 虽然乱序不影响渲染
2. 但是随意写序号容易引起误解

试下贴段代码:
```go
package main

import "fmt"

func main() {
  fmt.Println("Hello, 世界")
}
```
对了,缩进代码块建议换成围栏代码块:

    缩进代码块太隐晦了
    也没法指定编程语言,容易导致代码高亮失效
    所以建议大家用 ``` 围栏代码块
试下围栏代码块匹配场景:
````markdown
围栏代码块只要开头的 ` 和结束的 ` 数量匹配即可,这样可以实现在围栏代码块中显示围栏代码块:
```
这里只有 3 个 `,所以不会匹配markdown代码块结束
```
下面匹配到就真的结束了。
````
以上块级内容都挤在一坨了,插入合理的空行也很有必要。


但是过多的空行分段也不好啊,用来分段的话一个空行就够了。



接下来让我们试试稍微复杂点的场景,比如列表项包含多个段落的情况:
1. 列表项中的第一段

   这里是第二个段落,贴段代码:
   ```markdown
   要成为Markdown程序员并不容易,同理PPT架构师也是。
   注意代码块中的中西文间并没有插入空格。
   ```
   这里是最后一段了。
1. 整个有序列表是“松散”的:列表项内容要用 `<p>` 标签

最后,我们试下对 GFM 的格式化支持:

|col1|col2  |       col3   |
---           |---------------|--
col1 without left pipe      |   this is col2   | col3 without right pipe
                                 ||need align cell|

**以上就是为什么我们需要Markdown Format,而且是带中西文自动空格的格式化。**

格式化后:

# ATX 标题也有可能需要格式化的

一个简短的段落。

## Setext 说实话我不喜欢 Setext 标题

0. 有序列表可以从 0 开始
1. 应该自增序号的
2. 对齐对齐对齐

我们再来看看另一个有序列表。

1. 没空行的情况下序号要从 1 开始才能打断段落开始一个新列表
2. 虽然乱序不影响渲染
3. 但是随意写序号容易引起误解

试下贴段代码:

```go
package main

import "fmt"

func main() {
  fmt.Println("Hello, 世界")
}
```

对了,缩进代码块建议换成围栏代码块:

```
缩进代码块太隐晦了
也没法指定编程语言,容易导致代码高亮失效
所以建议大家用 ``` 围栏代码块
```

试下围栏代码块匹配场景:

````markdown
围栏代码块只要开头的 ` 和结束的 ` 数量匹配即可,这样可以实现在围栏代码块中显示围栏代码块:
```
这里只有 3 个 `,所以不会匹配markdown代码块结束
```
下面匹配到就真的结束了。
````

以上块级内容都挤在一坨了,插入合理的空行也很有必要。

但是过多的空行分段也不好啊,用来分段的话一个空行就够了。

接下来让我们试试稍微复杂点的场景,比如列表项包含多个段落的情况:

1. 列表项中的第一段

   这里是第二个段落,贴段代码:

   ```markdown
   要成为Markdown程序员并不容易,同理PPT架构师也是。
   注意代码块中的中西文间并没有插入空格。
   ```

   这里是最后一段了。
2. 整个有序列表是“松散”的:列表项内容要用 `<p>` 标签

最后,我们试下对 GFM 的格式化支持:

| col1                   | col2            | col3                    |
| ---------------------- | --------------- | ----------------------- |
| col1 without left pipe | this is col2    | col3 without right pipe |
|                        | need align cell |                         |

**以上就是为什么我们需要 Markdown Format,而且是带中西文自动空格的格式化。**

✍️ 术语修正

Markdown 原文:

在github上做开源项目是一件很开心的事情,请不要把Github拼写成`github`哦!

特别是简历中千万不要出现这样的情况:

> 熟练使用JAVA、Javascript、GIT,对android、ios开发有一定了解,熟练使用Mysql、postgresql数据库。

修正后:

在 GitHub 上做开源项目是一件很开心的事情,请不要把 GitHub 拼写成`github`哦!

特别是简历中千万不要出现这样的情况:

> 熟练使用 Java、JavaScript、Git,对 Android、iOS 开发有一定了解,熟练使用 MySQL、PostgreSQL 数据库。

⚡ 性能

请看 Golang markdown 引擎性能基准测试

💪 健壮性

Lute 承载了链滴上的所有 Markdown 处理,每天处理数百万解析渲染请求,运行表现稳定。

🔒 安全性

Lute 没有实现实现 GFM 中的 Disallowed Raw HTML (extension),因为该扩展还是存在一定漏洞(比如没有处理 <input> )。 建议通过其他库(比如 bluemonday)来进行 HTML 安全过滤,这样也能更好地适配应用场景。

🛠️ 使用

有三种方式使用 Lute:

  1. 后端:用 Go 语言的话引入 github.com/88250/lute
  2. 后端:将 Lute 启动为一个 HTTP 服务进程供其他进程调用,具体请参考这里
  3. 前端:引入 js 目录下的 lute.min.js,支持 Node.js

Go

引入 Lute 库:

go get -u github.com/88250/lute

最小化可工作示例:

package main

import (
	"fmt"

	"github.com/88250/lute"
)

func main() {
	luteEngine := lute.New() // 默认已经启用 GFM 支持以及中文语境优化
	html:= luteEngine.MarkdownStr("demo", "**Lute** - A structured markdown engine.")
	fmt.Println(html)
	// <p><strong>Lute</strong> - A structured Markdown engine.</p>
}

关于代码块语法高亮:

  • 默认使用外部样式表,主题为 github.css,可从 chroma-styles 目录下拷贝该样式文件到项目中引入
  • 可通过 lutenEngine.SetCodeSyntaxHighlightXXX() 来指定高亮相关参数,比如是否启用内联样式、行号以及主题

JavaScript

简单示例可参考 JavaScript 目录下的 demo,结合前端编辑器的完整用法请参考 Vditor 中的示例

Vditor

一些细节:

  1. lute.js 没有内置语法高亮特性
  2. lute.js 编译后大小为 ~3.5MB,GZip 压缩后大小 ~500KB

📜 文档

🏘️ 社区

📄 授权

Lute 使用 木兰宽松许可证, 第2版 开源协议。

🙏 鸣谢

Lute 的诞生离不开以下开源项目,在此对这些项目的贡献者们致敬!

  • commonmark.js:该项目是 CommonMark 官方参考实现的 JavaScript 版,Lute 参考了其解析器实现部分
  • goldmark:另一款用 golang 写的 Markdown 引擎,Lute 参考了其树遍历实现部分
  • golang-commonmark:另一款用 golang 写的 Markdown 引擎,Lute 参考了其 URL 编码以及 HTML 转义算法
  • Chroma:用 golang 写的语法高亮引擎
  • 中文文案排版指北:统一中文文案、排版的相关用法,降低团队成员之间的沟通成本,增强网站气质
  • GopherJS:将 Go 代码编译成 JavaScript 代码

lute's People

Contributors

88250 avatar bluishoul avatar fangjue avatar herberthe avatar iamqiz avatar myml avatar shuzijun avatar vanessa219 avatar zidoshare avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

lute's Issues

链接、图片相对路径支持

引入 option.LinkBase 参数:设置链接、图片的基础路径。如果用户在链接或者图片地址中使用相对路径(没有协议前缀且不以 / 开头)并且 LinkBase 不为空则会用该值作为前缀。

比如 LinkBase 设置为 http://domain.com/,对于 ![foo](bar.png) 则渲染为 <img src="http://domain.com/bar.png" alt="foo" />

支持脚注

https://www.markdownguide.org/extended-syntax

Here's a simple footnote,[^1] and here's a longer one.[^bignote]

[^1]: This is the first footnote.

[^bignote]: Here's one with multiple paragraphs and code.

    Indent paragraphs to include them in the footnote.

    `{ my code }`

    Add as many paragraphs as you like.

@jakekwak Thanks for the proposal

HTML2Md 没有 2 md

textHTML

<pre class="vditor-textarea" placeholder="placeholder" contenteditable="true" spellcheck="false" style="font-family: &quot;Helvetica Neue&quot;, &quot;Luxi Sans&quot;, &quot;DejaVu Sans&quot;, Tahoma, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Yahei&quot;, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Noto Color Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Android Emoji&quot;, EmojiSymbols; margin: 0px 1px 0px 0px; height: 462px; overflow: auto; width: 640px; -webkit-box-flex: 1; flex: 1 1 0%; border: 0px; resize: none; padding: 10px 9px 231px 10px; box-sizing: border-box; background-color: rgb(250, 251, 252); outline: none 0px; font-size: medium; line-height: 22px; color: var(--textarea-text-color); border-radius: 0px 0px 3px 3px; font-variant-ligatures: no-common-ligatures; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; display: block;"><span>`a`</span></pre>

vditor.lute.HTML2Md(textHTML)

<pre class="vditor-textarea" placeholder="placeholder" contenteditable="true" spellcheck="false" style="font-family: &#34;Helvetica Neue&#34;, &#34;Luxi Sans&#34;, &#34;DejaVu Sans&#34;, Tahoma, &#34;Hiragino Sans GB&#34;, &#34;Microsoft Yahei&#34;, sans-serif, &#34;Apple Color Emoji&#34;, &#34;Segoe UI Emoji&#34;, &#34;Noto Color Emoji&#34;, &#34;Segoe UI Symbol&#34;, &#34;Android Emoji&#34;, EmojiSymbols; margin: 0px 1px 0px 0px; height: 462px; overflow: auto; width: 640px; -webkit-box-flex: 1; flex: 1 1 0%; border: 0px; resize: none; padding: 10px 9px 231px 10px; box-sizing: border-box; background-color: rgb(250, 251, 252); outline: none 0px; font-size: medium; line-height: 22px; color: var(--textarea-text-color); border-radius: 0px 0px 3px 3px; font-variant-ligatures: no-common-ligatures; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; display: block;"><span>`a`</span></pre>

Vditor 支持列表 start 错误

SpinVditorDOM - argument: <ol data-tight="true" data-block="0"><li data-marker="2."><p>21<wbr></p></li></ol>
log.ts:6 SpinVditorDOM - result: <ol data-tight="true" start="2" data-block="0"><li data-marker="2.">21<wbr></li></ol>

ir 模式 toc 会产生空 p

SpinVditorIRDOM - argument: <div class="vditor-toc" data-block="0" data-type="toc-block" contenteditable="false">    <span data-type="toc-h">fdfs1312ff1111</span><br></div><h3 data-block="0" class="vditor-ir__node" data-marker="#"><span class="vditor-ir__marker vditor-ir__marker--heading">### </span>f1<wbr></h3>
log.ts:6 SpinVditorIRDOM - result: <div class="vditor-toc" data-block="0" data-type="toc-block" contenteditable="false">&emsp;&emsp;&emsp;&emsp;<span data-type="toc-h">f1<wbr></span><br></div><p data-block="0"></p><h3 data-block="0" class="vditor-ir__node vditor-ir__node--expand" data-marker="#"><span class="vditor-ir__marker vditor-ir__marker--heading">### </span>f1<wbr></h3>

增加一定程度的 XSS 安全过滤

原始需求:Vanessa219/vditor#226

对最终渲染的 HTML 进行一定程度的 XSS 安全过滤,参考 bluemonday 的 UGC 策略进行实现。

  • 加入配置开关 Sanitize,默认不开启 XSS 安全过滤
  • 对于 HTML 渲染器根据开关进行处理
  • 对于 Vditor 渲染器(WG、IR)固定开启处理

SpinVditorDOM bug

SpinVditorDOM 以下代码后会变为宽松列表

<ul data-tight="true" data-marker="*" data-block="0">
    <li data-marker="*"><p>1</p>
        <ul data-tight="true" data-marker="*" data-block="0">
            <li data-marker="*"><p>2</p></li>
        </ul>
    </li>
    <li data-marker="*">
        <p>4
            <wbr>
        </p>
        <ul data-tight="true" data-marker="*" data-block="0"></ul>
        <ul data-tight="true" data-marker="*" data-block="0">
            <li data-marker="*"><p>3</p></li>
        </ul>
    </li>
</ul>

分包

  • ast
  • parse
  • render
  • util

Vditor 支持列表切换问题

  • 无序列表切换为有序列表,再输入文字后会变为无序列表
  • 有序列表切换为无序列表,再输入文字后会变为有序列表

支持自定义标题 ID

### 这是一个标题 {#custom-id}

渲染的 HTML:

<h3 id="custom-id">这是一个标题</h3>

优化 ToC 生成

仅有顶级标题(直接挂在根上的)才纳入 ToC 生成,挂在其他元素下的标题不生成 ToC,比如:

{F2B7B132-DCA6-FC2C-05AE-D52F172C8341}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.