├── packages
├── docs
│ ├── Demo.md
│ ├── Example.md
│ ├── zh-hans
│ │ ├── 示例.md
│ │ ├── 插件.md
│ │ └── README.md
│ ├── .vuepress
│ │ ├── nav
│ │ │ ├── zh.js
│ │ │ └── en.js
│ │ ├── enhanceApp.js
│ │ ├── components
│ │ │ ├── Examples.vue
│ │ │ ├── Example.vue
│ │ │ └── Demo.vue
│ │ ├── config.js
│ │ └── util
│ │ │ └── highlight.js
│ ├── package.json
│ └── Plugin.md
├── core
│ ├── test
│ │ ├── globals.d.ts
│ │ ├── spec
│ │ │ ├── markdownPaper
│ │ │ │ ├── table-no-header.md
│ │ │ │ ├── table-no-header.html
│ │ │ │ ├── paper2.md
│ │ │ │ ├── paper2-csdn.md
│ │ │ │ └── paper2-csdn.html
│ │ │ └── md-it-plugin-taskList.js
│ │ ├── table-no-header.test.ts
│ │ ├── csdn.test.ts
│ │ └── gfm.test.ts
│ ├── src
│ │ ├── util
│ │ │ ├── repeat.ts
│ │ │ ├── replacement
│ │ │ │ ├── index.ts
│ │ │ │ ├── keep.ts
│ │ │ │ ├── list
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── listNode.ts
│ │ │ │ ├── fence.ts
│ │ │ │ └── blank.ts
│ │ │ ├── escape.ts
│ │ │ ├── isFence.ts
│ │ │ ├── isCode.ts
│ │ │ ├── indentCodeIsListfirstChild.ts
│ │ │ ├── findParentNumber.ts
│ │ │ ├── isKeep.ts
│ │ │ ├── isVoid.ts
│ │ │ ├── index.ts
│ │ │ ├── findOrderListIndentNumber.ts
│ │ │ ├── isBlock.ts
│ │ │ ├── join.ts
│ │ │ └── collapse-whitespace.ts
│ │ ├── plugins
│ │ │ ├── hr.ts
│ │ │ ├── br.ts
│ │ │ ├── del.ts
│ │ │ ├── fencedCodeBlock.ts
│ │ │ ├── strong.ts
│ │ │ ├── image.ts
│ │ │ ├── taskListItems.ts
│ │ │ ├── blockquote.ts
│ │ │ ├── em.ts
│ │ │ ├── paragraph.ts
│ │ │ ├── list.ts
│ │ │ ├── heading.ts
│ │ │ ├── code.ts
│ │ │ ├── link.ts
│ │ │ ├── indentedCodeBlock.ts
│ │ │ ├── index.ts
│ │ │ ├── referenceLinks.ts
│ │ │ └── table.ts
│ │ ├── service
│ │ │ ├── Node
│ │ │ │ ├── isBlank.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── flankingWhitespace.ts
│ │ │ ├── Rules
│ │ │ │ ├── findRule.ts
│ │ │ │ └── index.ts
│ │ │ ├── HTMLParser.ts
│ │ │ ├── RootNode.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── types
│ │ │ └── index.ts
│ ├── jest.config.js
│ ├── tsconfig.json
│ └── package.json
└── @sitdown
│ ├── juejin
│ ├── test
│ │ ├── globals.d.ts
│ │ └── juejin.test.ts
│ ├── jest.config.js
│ ├── README.md
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── package.json
│ ├── wechat
│ ├── test
│ │ ├── globals.d.ts
│ │ ├── spec
│ │ │ └── markdownPaper
│ │ │ │ ├── fence.md
│ │ │ │ ├── formula.md
│ │ │ │ ├── fence.html
│ │ │ │ ├── formula.html
│ │ │ │ ├── paper4.md
│ │ │ │ ├── paper2.md
│ │ │ │ ├── paper5.md
│ │ │ │ ├── paper3.md
│ │ │ │ └── paper1.md
│ │ └── wechat.test.ts
│ ├── jest.config.js
│ ├── README.md
│ ├── tsconfig.json
│ ├── package.json
│ └── src
│ │ ├── fence.ts
│ │ └── index.ts
│ ├── zhihu
│ ├── test
│ │ ├── globals.d.ts
│ │ ├── spec
│ │ │ └── markdownPaper
│ │ │ │ ├── formula.md
│ │ │ │ ├── formula.html
│ │ │ │ ├── paper2-zhihu.md
│ │ │ │ └── paper3-zhihu.md
│ │ └── zhihu.test.ts
│ ├── jest.config.js
│ ├── README.md
│ ├── src
│ │ ├── p.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── package.json
│ └── javascriptweekly
│ ├── test
│ ├── globals.d.ts
│ ├── spec
│ │ └── markdownPaper
│ │ │ ├── paper1.md
│ │ │ └── paper1.html
│ └── javascriptweekly.test.ts
│ ├── jest.config.js
│ ├── README.md
│ ├── tsdx.config.js
│ ├── tsconfig.json
│ ├── src
│ └── index.ts
│ └── package.json
├── lerna.json
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── main.yml
│ └── dev.yml
├── LICENSE
├── package.json
├── .gitignore
└── README.md
/packages/docs/Demo.md:
--------------------------------------------------------------------------------
1 |
14 |pre code {display: -webkit-box !important}
|
3 |
4 | TypeScript 3.9 Beta Released — 3.9’s focus is on “performance, polish, and stability” with the most significant change you’re likely to notice being faster compile times. 5 |Daniel Rosenwasser (Microsoft) 6 | |
${code}`
13 | }
14 |
15 | export default (str, lang) => {
16 | if (!lang) {
17 | return wrap(str, 'text')
18 | }
19 | lang = lang.toLowerCase()
20 | const rawLang = lang
21 | if (lang === 'vue' || lang === 'html') {
22 | lang = 'markup'
23 | }
24 | if (lang === 'md') {
25 | lang = 'markdown'
26 | }
27 | if (lang === 'rb') {
28 | lang = 'ruby'
29 | }
30 | if (lang === 'ts') {
31 | lang = 'typescript'
32 | }
33 | if (lang === 'py') {
34 | lang = 'python'
35 | }
36 | if (lang === 'sh') {
37 | lang = 'bash'
38 | }
39 | if (lang === 'yml') {
40 | lang = 'yaml'
41 | }
42 | if (lang === 'styl') {
43 | lang = 'stylus'
44 | }
45 |
46 | if (!prism.languages[lang]) {
47 | // console.log('不存在的语言');
48 | }
49 | if (prism.languages[lang]) {
50 | const code = prism.highlight(str, prism.languages[lang], lang)
51 | return wrap(code, rawLang)
52 | }
53 | return wrap(str, 'text')
54 | }
55 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sitdown",
3 | "version": "1.1.7",
4 | "description": "Convert HTML into Markdown with JavaScript.Support GitHub Flavored Markdown Spec.Also support almost wechat/zhihu/csdn/juejin HTML.",
5 | "author": "LinFeng1997 <244732635@qq.com>",
6 | "module": "dist/src.esm.js",
7 | "homepage": "https://github.com/mdnice/sitdown#readme",
8 | "license": "MIT",
9 | "main": "dist/index.js",
10 | "typings": "dist/index.d.ts",
11 | "files": [
12 | "dist"
13 | ],
14 | "publishConfig": {
15 | "registry": "https://registry.npmjs.org/"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/mdnice/sitdown.git"
20 | },
21 | "scripts": {
22 | "start": "tsdx watch --name src",
23 | "build": "tsdx build --name src",
24 | "test": "tsdx test",
25 | "test:debug": "node --inspect ../../node_modules/.bin/tsdx test --runInBand",
26 | "lint": "tsdx lint src --fix",
27 | "patch": "npm run build && npm version patch && npm publish",
28 | "minor": "npm run build && npm version minor && npm publish",
29 | "major": "npm run build && npm version major && npm publish"
30 | },
31 | "bugs": {
32 | "url": "https://github.com/mdnice/sitdown/issues"
33 | },
34 | "browser": {
35 | "jsdom": false
36 | },
37 | "dependencies": {
38 | "jsdom": "^16.5.0"
39 | },
40 | "gitHead": "ca200d2940d38e15d809a4a879982027f520b4ab"
41 | }
42 |
--------------------------------------------------------------------------------
/packages/@sitdown/zhihu/test/zhihu.test.ts:
--------------------------------------------------------------------------------
1 | import { Sitdown } from 'sitdown';
2 | import { applyZhihuRule } from '../src';
3 | import md from './spec/markdownPaper/paper1-zhihu.md';
4 | import html from './spec/markdownPaper/paper1-zhihu.html';
5 | import md2 from './spec/markdownPaper/paper2-zhihu.md';
6 | import html2 from './spec/markdownPaper/paper2-zhihu.html';
7 | import md3 from './spec/markdownPaper/paper3-zhihu.md';
8 | import html3 from './spec/markdownPaper/paper3-zhihu.html';
9 | import md4 from './spec/markdownPaper/formula.md';
10 | import html4 from './spec/markdownPaper/formula.html';
11 |
12 | /*
13 | 知乎 html 转 md 存在的不一致处:
14 | 1. 知乎进行了中文排版美化,如数字与中文、中文与英文间有空格
15 | 2. 图片转储了,并在 noscript 里有一份备份
16 | 3. 图片、链接描述(alt)没了
17 | 4. 知乎将公式转成图片了
18 | 5. 知乎把强调的链接的强调给滤掉了
19 | */
20 | describe('知乎', () => {
21 | let sitdown = new Sitdown({
22 | keepFilter: ['style'],
23 | codeBlockStyle: 'fenced',
24 | bulletListMarker: '-',
25 | hr: '---',
26 | });
27 | sitdown.use(applyZhihuRule);
28 | it('paper1 works', () => {
29 | const expected = sitdown.HTMLToMD(html);
30 | expect(expected).toEqual(md);
31 | });
32 |
33 | it('paper2 works', () => {
34 | const expected = sitdown.HTMLToMD(html2);
35 | expect(expected).toEqual(md2);
36 | });
37 |
38 | it('paper3 works', () => {
39 | const expected = sitdown.HTMLToMD(html3);
40 | expect(expected).toEqual(md3);
41 | });
42 |
43 | it('formula', () => {
44 | const expected = sitdown.HTMLToMD(html4);
45 | expect(expected).toEqual(md4);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/packages/docs/Plugin.md:
--------------------------------------------------------------------------------
1 | ## `@sitdown/juejin`
2 |
3 | ```ts
4 | import { Sitdown } from 'sitdown';
5 | import { applyJuejinRule } from '@sitdown/juejin';
6 |
7 | let sitdown = new Sitdown({
8 | keepFilter: ['style'],
9 | codeBlockStyle: 'fenced',
10 | bulletListMarker: '-',
11 | hr: '---',
12 | });
13 | sitdown.use(applyJuejinRule);
14 | ```
15 |
16 | ## `@sitdown/wechat`
17 |
18 | ```ts
19 | import { Sitdown,RootNode } from 'sitdown';
20 | import { applyWechatRule, extraFootLinks } from '@sitdown/wechat';
21 |
22 | let sitdown = new Sitdown({
23 | keepFilter: ['style'],
24 | codeBlockStyle: 'fenced',
25 | bulletListMarker: '-',
26 | hr: '---',
27 | });
28 | sitdown.use(applyWechatRule);
29 | ```
30 |
31 | support mdnice wechat footlink:
32 | ```ts
33 | import { extraFootLinks } from '@sitdown/wechat';
34 |
35 | const wechatToMD = (html: string) => {
36 | const root = new sitdown.RootNode(html);
37 | const footLinks = extraFootLinks(root);
38 | return sitdown.HTMLToMD(html, { footLinks });
39 | };
40 | ```
41 |
42 | ## `@sitdown/zhihu`
43 |
44 | ```ts
45 | import { Sitdown } from 'sitdown';
46 | import { applyZhihuRule } from '@sitdown/zhihu';
47 | let sitdown = new Sitdown({
48 | keepFilter: ['style'],
49 | codeBlockStyle: 'fenced',
50 | bulletListMarker: '-',
51 | hr: '---',
52 | });
53 | sitdown.use(applyZhihuRule);
54 |
55 | ```
56 |
57 | ## csdn
58 |
59 | ```ts
60 | import { Sitdown } from 'sitdown';
61 |
62 | let sitdown = new Sitdown({
63 | keepFilter: ['style'],
64 | codeBlockStyle: 'fenced',
65 | bulletListMarker: '-',
66 | hr: '---',
67 | });
68 | ```
69 |
--------------------------------------------------------------------------------
/packages/@sitdown/javascriptweekly/test/spec/markdownPaper/paper1.html:
--------------------------------------------------------------------------------
1 | |
2 |
3 | ECMAScript 2020: The Final Feature Set — TC39 has just approved the ECMAScript 2020 spec (a full weekend's bedtime reading right there!) with Ecma GA approval due in a few months, but what’s new? Dr. Axel rounds it up with links to the included stage 4 proposals. If you prefer something more code-driven, Pawel Grzybek has a similar roundup. 4 |Dr. Axel Rauschmayer 5 | |
6 |
11 |19 | 矩阵:
20 |{{isZh ? '示例' : 'Example'}} {{example.index}}
4 || {{isZh ? '效果' : 'Demo'}} | 8 |HTML | 9 |Markdown | 10 |
|---|---|---|
| 15 | 16 | | 17 |18 | 19 | | 20 |21 | 22 | | 23 |
option:{{JSON.stringify(example.option,null,2)}}}
4 | 谭淞宸
5 |Vol. 15
8 |📝 2.1 千字
9 |🕒 5.2 分钟
10 |本文结束
178 |关注这里,获取更多精彩内容:
179 |
180 | Hello worldWorld
Hello worldWorld
` 元素的规则如下所示: 137 | 138 | ```js 139 | { 140 | filter: 'p', 141 | replacement: function (content) { 142 | return '\n\n' + content + '\n\n' 143 | } 144 | } 145 | ``` 146 | 147 | 筛选器选择 `
` 元素,替换函数返回由两个换行分隔的 `
` 内容。 148 | 149 | ### [](#filter-stringarrayfunction)`filter` String|Array|Function 150 | 151 | 筛选器属性确定是否应将元素替换为规则的 `replacement` 函数。只需使用标签名称或标签名称数组即可选择 DOM 节点: 152 | 153 | * `filter: 'p'` 将选择 `
` 元素
154 | * `filter: ['em', 'i']` 将选择 `` 或 `` 元素
155 |
156 | 或者,筛选器可以是一个函数,根据是否应替换给定节点来返回布尔值。该函数传递一个 DOM 节点以及 `Sitdown` 选项。例如,当 `linkStyle` 选项为 `inlined` 时,以下规则选择 `` 元素(带 `href`):
157 |
158 | ```js
159 | filter: function (node, options) {
160 | return (
161 | options.linkStyle === 'inlined' &&
162 | node.nodeName === 'A' &&
163 | node.getAttribute('href')
164 | )
165 | }
166 | ```
167 |
168 | ### [](#replacement-function)`replacement` Function
169 |
170 | 替换函数确定元素的转换方式。它应返回给定节点的 Markdown 字符串。该函数传递节点的内容、节点本身和 `Sitdown` 选项。
171 |
172 | 以下规则显示了如何转换 `` 元素:
173 |
174 | ```js
175 | rules.emphasis = {
176 | filter: ['em', 'i'],
177 |
178 | replacement: function (content, node, options) {
179 | return options.emDelimiter + content + options.emDelimiter
180 | }
181 | }
182 | ```
183 |
184 | ### [](#special-rules)特殊的规则
185 |
186 | **空白规则** 确定如何处理空白元素。它覆盖每个规则 (即使那些通过 `addRule` 添加的规则)。如果节点仅包含空格,并且不是 ``,` 笔者是CSDN的长期用户,也见到了很多不错的CSDN博客 优点 缺点 博客园是除了CSDN外,另一个比较早专做技术博客的网站 优点 缺点 简书也是笔者曾经考虑并且实际去操作过的博客网站 知乎专栏笔者虽然没有自己发过,但是也读了很多,身边也有人在发,所以也有一定的发言权 笔者也是个github page的使用者,也尝试着写过自己的主题,最近在用github issues来做博客,在这里推销一波,博客地址,欢迎交流star 这个方法在最开始的时候笔者本人也尝试过,后来由于成本问题不再做了 优点 缺点 近两年新兴起的比如说segmentFault、稀土掘金,如果喜欢哪个网站大胆的去写吧 想了解更多开源信息和计算机知识么,欢迎扫码关注公众号:牧码咯 Hello Hello ` elements is as follows:
138 |
139 | ```js
140 | {
141 | filter: 'p',
142 | replacement: function (content) {
143 | return '\n\n' + content + '\n\n'
144 | }
145 | }
146 | ```
147 |
148 | The filter selects ` ` elements, and the replacement function returns the ` ` contents separated by two new lines.
149 |
150 | ### [](#filter-stringarrayfunction)`filter` String|Array|Function
151 |
152 | The filter property determines whether or not an element should be replaced with the rule's `replacement`. DOM nodes can be selected simply using a tag name or an array of tag names:
153 |
154 | * `filter: 'p'` will select ` ` elements
155 | * `filter: ['em', 'i']` will select `` or `` elements
156 |
157 | Alternatively, the filter can be a function that returns a boolean depending on whether a given node should be replaced. The function is passed a DOM node as well as the `Service` options. For example, the following rule selects `` elements \(with an `href`\) when the `linkStyle` option is `inlined`:
158 |
159 | ```js
160 | filter: function (node, options) {
161 | return (
162 | options.linkStyle === 'inlined' &&
163 | node.nodeName === 'A' &&
164 | node.getAttribute('href')
165 | )
166 | }
167 | ```
168 |
169 | ### [](#replacement-function)`replacement` Function
170 |
171 | The replacement function determines how an element should be converted. It should return the Markdown string for a given node. The function is passed the node's content, the node itself, and the `Service` options.
172 |
173 | The following rule shows how `` elements are converted:
174 |
175 | ```js
176 | rules.emphasis = {
177 | filter: ['em', 'i'],
178 |
179 | replacement: function (content, node, options) {
180 | return options.emDelimiter + content + options.emDelimiter
181 | }
182 | }
183 | ```
184 |
185 | ### [](#special-rules)Special Rules
186 |
187 | **Blank rule** determines how to handle blank elements. It overrides every rule \(even those added via `addRule`\). A node is blank if it only contains whitespace, and it's not an ``, ``,` `或 void 元素,则该节点为空。可以使用 `blankReplacement` 选项自定义其行为。
187 |
188 | **保留规则** 确定如何处理不应转换的元素,即在 Markdown 输出中渲染为 HTML。默认情况下,不会保留任何元素。块级元素将通过空白行与周围内容分隔。其行为可以使用 `keepReplacement` 选项进行自定义。
189 |
190 | **删除规则** 确定要完全删除哪些元素。默认情况下,不会删除任何元素。
191 |
192 | **默认规则** 处理任意其他规则无法识别的节点。默认情况下,它输出节点的文本内容(如果是块级元素,则用空白行分隔)。其行为可以通过 `defaultReplacement` 选项进行自定义。
193 |
194 | ### [](#rule-precedence)规则优先级
195 |
196 | Sitdown 会遍放规则集,并选取与 `filter` 匹配的第一个规则。下面的列表描述了优先级的顺序:
197 |
198 | 1. 空白规则
199 | 2. 添加的规则(可选的)
200 | 3. Commonmark 规则
201 | 4. 保留规则
202 | 5. 删除规则
203 | 6. 默认规则
204 |
205 | ## [](#plugins)插件
206 |
207 | 插件 API 为开发人员提供了一种应用多个扩展的便捷方法。插件只是使用 `sitdown.service` 调用的函数。
208 |
209 | ## 转义 Markdown 字符
210 | Sitdown 使用后斜杠 (`\`\) 来转义 HTML 输入中的 Markdown 字符。这可确保在输出编译回 HTML 时,这些字符不会解释为 Markdown。例如, ` 1. Hello world
` 需要被转义成 `1\. Hello world`,否则它将被解释为一个列表项,而不是一个标题。
211 |
212 | 为了避免将每个 HTML 元素的内容解析为 Markdown 的复杂性和性能影响,Sitdown 使用一组正则表达式来转义潜在的 Markdown 语法。因此,转义规则可能相当具有入侵性。
213 |
214 | ## 本地开发
215 |
216 | 下面是你可能会发现有用的命令列表。
217 |
218 | ### `npm start` 或 `yarn start`
219 |
220 | 以开发/监视模式运行项目。你的项目将在更改时重新构建。TSDX 有一个特殊的记录器,方便你打印错误消息和格式化的兼容 VSC 的问题选项卡。
221 |
222 |
223 |
224 | 如果进行编辑,将重新构建你的库。
225 |
226 | ### `npm run build` 或 `yarn build`
227 |
228 | 将包打包到 `dist` 文件夹。
229 | 该软件包经过优化,并可输出成多种格式(Common JS、UMD 和 ES 模块)。
230 |
231 |
232 |
233 | ### `npm test` 或 `yarn test`
234 |
235 | 在监视模式下运行测试程序(Jest)。
236 | 默认情况下,运行自上次提交以来更改的文件。
237 |
238 | ## [](#license)License
239 |
240 | Sitdown 版规归 © 2020+ mdnice 所有,并用 MIT license 发布。
--------------------------------------------------------------------------------
/packages/core/test/spec/markdownPaper/paper2-csdn.html:
--------------------------------------------------------------------------------
1 |
7 |
10 | CSDN、博客园、简书、知乎专栏、Github Page、个人建站和其他CSDN
11 |
13 |
36 |
16 |
22 |
26 |
34 |
30 |
32 | 博客园
38 |
40 |
64 |
43 |
49 |
53 |
62 |
57 |
60 | 简书
68 |
70 |
81 |
72 |
79 |
75 |
77 |
83 |
93 |
85 |
91 | 知乎专栏
94 |
96 |
109 |
98 |
107 |
111 |
119 |
113 |
117 | Github Page
120 |
122 |
130 |
124 |
128 |
132 |
140 |
134 |
138 | 个人建站
141 |
143 |
160 |
146 |
150 |
154 |
158 | 其他
162 |
164 |
168 | 关注
169 | 
Hello world!
')
21 | ```
22 |
23 | ```js
24 | // For Browser
25 | import { Sitdown } from 'sitdown/src.esm'
26 |
27 | var sitdown = new Sitdown()
28 | var markdown = sitdown.HTMLToMD('Hello world!
')
29 | ```
30 |
31 | Sitdown also accepts DOM nodes as input \(either element nodes, document nodes, or document fragment nodes\):
32 |
33 | ```js
34 | var markdown = sitdown.HTMLToMD(document.getElementById('content'))
35 | ```
36 |
37 | ## Options
38 | Options can be passed in to the constructor on instantiation. For example:
39 |
40 | ```js
41 | var sitdown = new Sitdown({ option: 'value' })
42 | ```
43 |
44 | | Option | Valid values | Default |
45 | | :-- | :-- | :-- |
46 | | `headingStyle` | `setext` or `atx` | `atx` |
47 | | `hr` | Any [Thematic break](http://spec.commonmark.org/0.27/#thematic-breaks) | `* * *` |
48 | | `bulletListMarker` | `-`, `+`, or `*` | `*` |
49 | | `codeBlockStyle` | `indented` or `fenced` | `indented` |
50 | | `fence` | ` ``` ` or `~~~` | ` ``` ` |
51 | | `emDelimiter` | `_` or `*` | `_` |
52 | | `strongDelimiter` | `**` or `__` | `**` |
53 | | `linkStyle` | `inlined` or `referenced` | `inlined` |
54 | | `linkReferenceStyle` | `full`, `collapsed`, or `shortcut` | `full` |
55 | | `keepFilter` | `style`, `['style','div']`, or a function | `['style','div']` |
56 | | `env` | `{}` | |
57 |
58 | ## Advanced Options
59 | | Option | Valid values | Default |
60 | | :-- | :-- | :-- |
61 | | `blankReplacement` | rule replacement function | See **Special Rules** below |
62 | | `keepReplacement` | rule replacement function | See **Special Rules** below |
63 | | `defaultReplacement` | rule replacement function | See **Special Rules** below |
64 |
65 | ## Methods
66 |
67 | ### [](#addrulekey-rule)`addRule(key, rule)`
68 |
69 | The `key` parameter is a unique name for the rule for easy reference. Example:
70 |
71 | ```js
72 | sitdown.service.addRule('strikethrough', {
73 | filter: ['del', 's', 'strike'],
74 | replacement: function (content) {
75 | return '~' + content + '~'
76 | }
77 | })
78 | ```
79 |
80 | `addRule` returns the `service` instance for chaining.
81 |
82 | See **Extending with Rules** below.
83 |
84 | ### `keep(filter)`
85 |
86 | Determines which elements are to be kept and rendered as HTML. By default, Sitdown does not keep any elements. The filter parameter works like a rule filter \(see section on filters belows\). Example:
87 |
88 | ```js
89 | sitdown.service.keep(['del', 'ins'])
90 | sitdown.HTMLToMD('worldWorldworldWorld'
91 | ```
92 |
93 | This will render `` and `` elements as HTML when converted.
94 |
95 | `keep` can be called multiple times, with the newly added keep filters taking precedence over older ones. Keep filters will be overridden by the standard CommonMark rules and any added rules. To keep elements that are normally handled by those rules, add a rule with the desired behaviour.
96 |
97 | `keep` returns the `service` instance for chaining.
98 |
99 | ### [](#removefilter)`remove(filter)`
100 |
101 | Determines which elements are to be removed altogether i.e. converted to an empty string. By default, Sitdown does not remove any elements. The filter parameter works like a rule filter \(see section on filters belows\). Example:
102 |
103 | ```js
104 | sitdown.service.remove('del')
105 | sitdown.HTMLToMD('worldWorld` elements \(and contents\).
109 |
110 | `remove` can be called multiple times, with the newly added remove filters taking precedence over older ones. Remove filters will be overridden by the keep filters, standard CommonMark rules, and any added rules. To remove elements that are normally handled by those rules, add a rule with the desired behaviour.
111 |
112 | `remove` returns the `service` instance for chaining.
113 |
114 | ### [](#usepluginarray)`use(plugin|array)`
115 |
116 | Use a plugin, or an array of plugins. Example:
117 |
118 | ```js
119 | import { Sitdown } from 'sitdown';
120 | import { applyJuejinRule } from '@sitdown/juejin';
121 |
122 | let sitdown = new Sitdown({
123 | keepFilter: ['style'],
124 | codeBlockStyle: 'fenced',
125 | bulletListMarker: '-',
126 | hr: '---',
127 | });
128 | sitdown.use(applyJuejinRule);
129 | ```
130 |
131 | `use` returns the `service` instance for chaining.
132 |
133 | See **Plugins** below.
134 |
135 | ## [](#extending-with-rules)Extending with Rules
136 |
137 | Sitdown can be extended by adding **rules**. A rule is a plain JavaScript object with `filter` and `replacement` properties. For example, the rule for converting ``,` ` or a void element. Its behaviour can be customised using the `blankReplacement` option.
188 |
189 | **Keep rules** determine how to handle the elements that should not be converted, i.e. rendered as HTML in the Markdown output. By default, no elements are kept. Block-level elements will be separated from surrounding content by blank lines. Its behaviour can be customised using the `keepReplacement` option.
190 |
191 | **Remove rules** determine which elements to remove altogether. By default, no elements are removed.
192 |
193 | **Default rule** handles nodes which are not recognised by any other rule. By default, it outputs the node's text content \(separated by blank lines if it is a block-level element\). Its behaviour can be customised with the `defaultReplacement` option.
194 |
195 | ### [](#rule-precedence)Rule Precedence
196 |
197 | Sitdown iterates over the set of rules, and picks the first one that matches the `filter`. The following list describes the order of precedence:
198 |
199 | 1. Blank rule
200 | 2. Added rules \(optional\)
201 | 3. Commonmark rules
202 | 4. Keep rules
203 | 5. Remove rules
204 | 6. Default rule
205 |
206 | ## [](#plugins)Plugins
207 |
208 | The plugin API provides a convenient way for developers to apply multiple extensions. A plugin is just a function that is called with the `service` instance.
209 |
210 | ## Escaping Markdown Characters
211 | Sitdown uses backslashes \(`\`\) to escape Markdown characters in the HTML input. This ensures that these characters are not interpreted as Markdown when the output is compiled back to HTML. For example, the contents of ` 1. Hello world
` needs to be escaped to `1\. Hello world`, otherwise it will be interpreted as a list item rather than a heading.
212 |
213 | To avoid the complexity and the performance implications of parsing the content of every HTML element as Markdown, Sitdown uses a group of regular expressions to escape potential Markdown syntax. As a result, the escaping rules can be quite aggressive.
--------------------------------------------------------------------------------
/packages/@sitdown/wechat/test/spec/markdownPaper/paper1.md:
--------------------------------------------------------------------------------
1 | # 和微信公众号编辑器战斗的日子
2 |
3 | > 本文提到的 Markdown Nice 体验地址:**https://mdnice.com**
4 |
5 | 公元2019年,微信公众号排版能力孱弱,始终为运营者所诟病,秀米、135编辑器等工具割据一方。
6 |
7 | 但无论是微信原生工具,还是其他编辑器,都让创作者不得不将有限的创作经历分散到排版设计上。
8 |
9 | > **Markdown**,解决排版的灵丹妙药,应运而生。
10 |
11 | ## 引子
12 |
13 | ### Markdown是什么?
14 |
15 | Markdown 是一种排版语法,拥有极简的输入方式和极低的学习成本。
16 |
17 | 富含了标题、引用、加粗、链接、图片、代码段、公式等一切在文字创作中需要的排版格式。
18 |
19 | > 拥有它,让人专注于内容本身,而不被格式所打扰。
20 |
21 | 
22 |
23 | ### 怎么和微信公众号结合?
24 |
25 | 这么优秀的排版语法,可是微信公众号也不支持呀。
26 |
27 | > **别急!Markdown Nice来帮你解决问题!**
28 |
29 | ### Markdown Nice是什么?
30 |
31 | 一款开源 Markdown 编辑器,写完后即排版成功,复制即可粘贴到微信公众号。
32 |
33 | **So what? 只有这点么?**
34 |
35 | **当然不是!Nice宝宝你还有什么特性呀?**
36 |
37 | 
38 |
39 | - 支持图床、脚注、代码、公式
40 | - 支持 8 种排版主题和 7 种代码主题
41 | - 支持自定义样式,可提交主题供人瞻仰
42 | - 除了公众号外,还支持知乎、掘金、博客园和CSDN等平台
43 | - **我颜值高呀**
44 |
45 | 
46 |
47 | 这么多优秀的特性摆在你面前,还在等什么?
48 |
49 | 地址:`https://mdnice.com`
50 |
51 | **快让你的微信排版 Nice 起来!**
52 |
53 | ## 正文
54 |
55 | ~~没想到你还在看~~
56 |
57 | 咳咳、、、
58 |
59 | 不要小看Nice宝宝我,为了能够使用 Markdown 进行公众号排版,我可是和微信公众号编辑器做了半年的斗争,才赢得了现在的战果!
60 |
61 | 
62 |
63 | 想听听我是如何战斗的?下面且听我娓娓道来!
64 |
65 | ### 战斗序章
66 |
67 | 微信公众号编辑器源于百度 FEX 前端团队的开源的 [ueditor](http://ueditor.baidu.com "ueditor") 项目,这可是宝宝我扒取了网页代码发现的,如图所示:
68 |
69 | 
70 |
71 | 因为 ueditor 是富文本编辑器,所以我即将面临的是 `markdown->富文本` 的转换战役,在开源界或者软件界这种转换战役有了相当多的优秀实现。比如:
72 |
73 | - Web端:[editor.md](https://github.com/pandao/editor.md "editor.md")、[mavonEditor](https://github.com/hinesboy/mavonEditor "mavonEditor")
74 | - PC端:[typora](https://typora.io/ "typora")、[MWeb](https://zh.mweb.im/ "MWeb")
75 |
76 | 但是上述工具都存在一个问题,没有很好地将 **CSS 样式**融入富文本中,进而适配微信编辑器,以至于国内其他各大平台的富文本编辑器。
77 |
78 | 那么有没有尝试弥补这一问题的工具呢?其实是有的:
79 |
80 | - Web端:[md2all](http://md.aclickall.com/ "md2all")、[wechat-format](https://github.com/lyricat/wechat-format "wechat-format")
81 | - 浏览器插件:[markdown-here](https://markdown-here.com/ "markdown-here")
82 |
83 | > 但是上述工具也各自有不完美的地方,于是Nice宝宝我发现了这个契机,把自己生产了出来,解决一切不完美!
84 |
85 | Markdown Nice 是一个开源项目,由很多开源技术合体而成,其中主要包括:
86 |
87 | - [React](https://github.com/facebook/react "React"):facebook 开源的 js 视图层框架
88 | - [markdown-it](https://github.com/markdown-it/markdown-it "markdown-it"):markdown 转换富文本解析器
89 | - [juice](https://github.com/Automattic/juice "juice"):将 CSS 类选择器转换为行内样式的工具
90 | - [codemirror](https://github.com/codemirror/codemirror "codemirror"):网页代码编辑器
91 | - [ant-design](https://github.com/ant-design/ant-design "ant-design"):React UI组件库
92 | - [mobx](https://github.com/mobxjs/mobx "mobx"):状态管理库
93 | - [highlight.js](https://github.com/highlightjs/highlight.js "highlight.js"):代码高亮库
94 | - [MathJax-node](https://github.com/mathjax/MathJax-node "MathJax-node"):公式转图片库
95 | - axios、ali-oss、qiniu-js等
96 |
97 | 注:下文会提到上述某些开源库,开源库具体作用请参考此处。
98 |
99 | 在拥有上述技术傍身之后,我向微信编辑器宣战,打响了战斗第一枪!
100 |
101 | 
102 |
103 | ### 战斗第一枪:代码主题
104 |
105 | 微信公众号在2018年以前,是完全不支持代码块的,目前的支持也很单一,并且存在代码字体较大的问题。
106 |
107 | > 说来很气,微信公众号编辑器的开发者,就木有想过**代码块对程序员群体是多么重要么**??
108 |
109 | 
110 |
111 | 为此我找来了`highlight.js`代码高亮神器,帮助解决代码主题单一的问题。
112 |
113 | 经过一定的筛选后,最终选定了 atom-one-dark, atom-one-light, monikai, github, vs2015, xcode 和微信代码主题共 7 个代码样式供大家选择。
114 |
115 | 其中微信代码主题由于其不属于`highlight.js`的归属范畴,故而其结构需要从微信公众号编辑器源码中获取,下面两张图展示了如何获取:
116 |
117 | 
118 |
119 | 
120 |
121 | 而 `highlight.js` 与 `markdown-it` 解析器是关联使用的,故而工具中存在 2 个 markdown 解析器,分别用于解析微信代码主题和其他代码主题,[源码参考](https://github.com/zhning12/markdown-nice/blob/master/src/utils/helper.js#L55-L133 "源码参考")。
122 |
123 | 除了上述问题外,很多技术类公众号代码中会存在:**一行代码过长导致的多行显示问题**。
124 |
125 | 该问题使用以下 CSS 代码即可解决:
126 |
127 | ```css
128 | pre code {
129 | display: -webkit-box !important
130 | }
131 | ```
132 |
133 | 对比效果如图所示:
134 |
135 | 
136 |
137 | 从此以后使用 Markdown Nice 的同志们代码更美观啦!
138 |
139 | ### 战斗第二枪:图片上传
140 |
141 | 一篇好的文章怎么可以没有图片?
142 |
143 | (某些技术人员说:我的就没有)
144 |
145 | 
146 |
147 | > 图片管理是每一个成熟编辑器都会遇到的问题。
148 |
149 | Nice宝宝最开始使用 [SM.MS](https://sm.ms/ "SM.MS") 图床,该图床由一位大佬在运维,非常感谢!
150 |
151 | 使用该图床虽然能够上传图片,但是粘贴到微信编辑器时,失败率极高(**想踩死微信编辑器**),如图所示:
152 |
153 | 
154 |
155 | 真是{喜闻乐见|hē hē hē hē}呀!
156 |
157 | 为了解决上述问题,Markdown Nice 先后支持了自定义阿里云和七牛云图床,通过购买阿里云和七牛云的服务使用自建图床。
158 |
159 | **但是!让使用者自己配置,尽管有配置文档,但是整个配置过程复杂无比,简直惨绝人寰!**
160 |
161 | 
162 |
163 | 于是,Nice宝宝又使用自己的账号,自建图床,设定保存时间为一天,提供临时排版使用的 mdnice 图床。至此,工具中的图片上传支持情况如下:
164 |
165 | |图床|费用|有效期|失败率|
166 | |---|---|---|---|
167 | |mdnice|免费|1天|低|
168 | |SM.MS|免费|长期|高|
169 | |阿里云|[参考](https://cn.aliyun.com/product/oss "参考")|自定义|低|
170 | |七牛云|10G免费|自定义|低|
171 |
172 | 
173 |
174 | 这场图床的战役中,最大的难度在于需要阅读阿里云 OSS 和七牛云 KODO 的文档,并且使用其开源出来的工具包 ali-oss 和 qiniu-js 。
175 |
176 | 这其中又涉及到了 FormData、file对象、base64 和 blob 之间的神奇转换,[源码参考](https://github.com/zhning12/markdown-nice/blob/master/src/component/Dialog/ImageDialog.js "源码参考")。
177 |
178 | 总之,这场战斗打的不亦乐乎。
179 |
180 | ### 战斗第三枪:数学公式
181 |
182 | **微信公众号排版中,数学公式是一个噩梦!**
183 |
184 | 因为微信编辑器做了以下三件事情:
185 |
186 | - 不支持公式编辑
187 | - 不支持 html 和 css 生成的公式,因为字体无法导入
188 | - 不支持svg,放入后提示失败
189 |
190 | > 这三件事情简直像魔鬼的步伐,把公式需求者放在光滑的地板上摩擦、摩擦....
191 |
192 | 
193 |
194 | 目前唯一可行的方案就是将公式转换成图片,再将图片直接贴到公众号里,Nice宝宝觉得自己这个想法简直是完美!
195 |
196 | 但是,怎么做呢......
197 |
198 | 
199 |
200 | 忽然灵机一动,想到了朋友曾经推荐的一个转换公式网站 [codecogs](https://www.codecogs.com/latex/eqneditor.php "codecogs"),这个网站能够做到将任意公式转换成png图片并给出可访问链接。
201 |
202 | 
203 |
204 | 但是由于图片稳定性,无法直接使用该链接,会存在和第二场战役一样,图片粘贴失败的情况,让人苦不堪言。
205 |
206 | - 这个问题该怎么解决呢?**如果能够自建公式转png图片服务就好了。**
207 | - 有没有这样的开源库?**有!MathJax-node 就可以!**
208 |
209 | 于是Nice宝宝自建后台服务,封装 RESTful 接口供前端调用,实现了公式转图片的功能!经过测试,完全可以使用,粘贴后再根据排版情况调整图片大小即可。
210 |
211 | 其中对于大量公式的转换,前端合理使用了异步请求,并非一个个转换而是并行执行,**性能上达到了10个公式也能2秒转换完毕的效果**,完全可用于公式排版。
212 |
213 | 
214 |
215 | ### 战斗第四枪:微信外链转脚注
216 |
217 | 众所周知(不知道也得知道),微信不支持外链,除了域名为`https://mp.weixin.qq.com/`的合法链接外,其他的链接出现后都会被自动删除。
218 |
219 | 而添加外链的唯一官方方式就是在阅读全文处,当然,直接将链接本身放到文中或者制作二维码图片也是可以的选择。
220 |
221 | **而Nice宝宝则提供了将微信外链转为脚注的方式解决该问题,是不是很优雅呢?**
222 |
223 | 
224 |
225 |
226 | 其中链接和脚注的使用区别如下:
227 |
228 | ```markdown
229 | 链接:[文字](链接 "文字")
230 | 脚注:[文字](脚注解释 "脚注名字")
231 | ```
232 |
233 | 这里又涉及到了一个常见的问题,就是很多公众号作者的文章中,原来在其他平台发布时都是链接,而到这里排版时需要进行挨个修改,实在是让人头大。
234 |
235 | 
236 |
237 | 于是Nice宝宝我又做了一个小改进,就是在粘贴文章的时候会自动监测是否存在外链,并提示作者是否一键转成脚注,这样就不必手工修改了,赞不赞!
238 |
239 | 
240 |
241 | ### 战役总结
242 |
243 | 和微信公众号编辑器对抗,是个极其有意思的过程。除了上述提到的问题之外,还有很多的细节点需要注意,在此就不一一讨论了,感兴趣欢迎阅读源码。
244 |
245 | > Markdown Nice 的战斗之旅还远远没有结束,官网制作、浏览器插件、本地工具和排版纠正等功能蓄势待发。
246 |
247 | ## 谈点和战斗无关的
248 |
249 | ### 设计理念
250 |
251 | **大多数人而言,内容重于排版,排版重于设计。**
252 |
253 | 内容是吸引读者的核心,所以最重要。
254 |
255 | 而排版与设计之间的比较,作为一个曾经的微信美术编辑,随着排版经验的增多,发现文章的效果并不在于额外的花边、点缀。
256 |
257 | > **整齐、舒服、简单是硬道理!**
258 |
259 | ### 内容、排版与设计
260 |
261 | **设计 = 排版 + 创意**
262 |
263 | ---
264 |
265 | 因为:大多数人不会获取创意,或认为创意成本过高。
266 |
267 | 所以:大多数人不做设计,富文本设计不适合单纯的内容编辑者。
268 |
269 | ---
270 |
271 | 虽然:大多数人也不会排版,或认为排版成本过高。
272 |
273 | 但是:**Markdown Nice 将用户从排版中释放出来,只关注内容本身。**
274 |
275 | ### 关于开源
276 |
277 | 开源是个既简单又困难的过程:
278 |
279 | - **说简单是因为**:笔记、书单、工具、平台所有有价值的东西都可以在 GitHub 中输出,做起来很简单
280 | - **说困难是因为**:努力做到对别人有价值,始终坚持输出,做起来很困难
281 |
282 | 正如上面描述的那样,做开源其实是在做一款产品,有可能是技术产品(比如 redis、ant design),也有可能是业务产品(比如 Markdown Nice),只有做好了才可能对别人产生价值。
283 |
284 | 做产品的过程曲折而漫长,对照 Markdown Nice 开发过程,可以看到以下的步骤:
285 |
286 | 1. 要有一个 idea,并且验证其可行性和必要性,去和同类产品比较,做到心中有数
287 | 2. 抓住痛点,掌握核心价值,站在用户角度思考,多听反馈意见
288 | 3. 螺旋上升,不断迭代,产出精品
289 | 4. 最最重要一点,**做产品不只要写代码,还要宣传呀!!酒香也怕巷子深!!**
290 |
291 | 参与开源,一路走来,甚是不易,望君珍惜。
292 |
293 | > 最后,感谢每一位开源参与者,欢迎更多人参与到开源中来,还有好多代码等着有人来写呢!
294 |
295 | 
296 |
297 |
--------------------------------------------------------------------------------